#include #include #include #include #include #include #include #include #include #include namespace websockets { // Will be caught in the callback and program will continue to run (but connection will be closed) struct runtime_error : public std::runtime_error { int return_value; runtime_error(const char * message, int return_value = -1) : std::runtime_error(message) , return_value(return_value) {} }; // Will never be caught (by us), and hence things will terminate struct fatal_error : public std::runtime_error { fatal_error(const char * message) : std::runtime_error(message) {} }; struct Log { Log(std::string const & name, int syslog_options, int debug_level){ setlogmask(LOG_UPTO (LOG_DEBUG)); openlog(name.c_str(), syslog_options, LOG_DAEMON); lws_set_log_level(debug_level, lwsl_emit_syslog); } #define log_function(nice_name, lwsl_name)\ template \ void nice_name(std::string const & format, T... args){\ lwsl_name(format.c_str(), args...);\ } log_function(notice, lwsl_notice) log_function(error, lwsl_err) log_function(warning, lwsl_warn) log_function(debug, lwsl_debug) #undef log_function ~Log(){ closelog(); } }; // Basic wrapper for the context pointer struct Context { std::unique_ptr context; Context(lws_context_creation_info& info) : context(libwebsocket_create_context(&info), &libwebsocket_context_destroy) { if(context == nullptr){ lwsl_err("libwebsocket init failed\n"); throw std::runtime_error("libwebsocket init failed"); } } libwebsocket_context const * get_raw() const { return context.get(); } libwebsocket_context * get_raw() { return context.get(); } }; // Basic protocol to extend, already casts the void* to the actual type template struct Protocol { typedef T user_type; int callback(libwebsocket_context *context, libwebsocket *wsi, libwebsocket_callback_reasons reason, void *user, void *in, size_t len){ return call(*context, *wsi, reason, *static_cast(user), in, len); } virtual int call(libwebsocket_context& context, libwebsocket& wsi, libwebsocket_callback_reasons reason, T& user, void *in, size_t len){ return 0; }; }; struct basic_websocket_info{ libwebsocket_context* context{nullptr}; libwebsocket* wsi{nullptr}; basic_websocket_info(libwebsocket_context* context, libwebsocket* wsi) : context(context) , wsi(wsi) {} }; // 4-function Protocol, provide functions to handle establishment, closing, receiving and writing (follows receiving) // To let the callback return something non-zero (in case of error), use websockets::runtime_error // Only types with no-arg ctor are allowed (uses placement new, because libwebsockets allocates for us) template struct TestProtocol { typedef T user_type; std::function establish_func; std::function close_func; std::function write_func; std::function receive_func; bool verbose{false}; TestProtocol(decltype(establish_func) establish_func, decltype(close_func) close_func, decltype(write_func) write_func, decltype(receive_func) receive_func) : establish_func(establish_func) , close_func(close_func) , write_func(write_func) , receive_func(receive_func) {} int callback(libwebsocket_context* context, libwebsocket* wsi, libwebsocket_callback_reasons reason, void* user_ptr, void *in, size_t len){ user_type& user = *static_cast(user_ptr); basic_websocket_info binfo{context, wsi}; try{ switch (reason) { case LWS_CALLBACK_ESTABLISHED: lwsl_notice("Connection established (%p, %p)\n", this, user_ptr); new (user_ptr) user_type(); establish_func(user, binfo); break; case LWS_CALLBACK_CLOSED: lwsl_notice("Connection closed (%p, %p)\n", this, user_ptr); user.~user_type(); close_func(user, binfo); break; case LWS_CALLBACK_SERVER_WRITEABLE:{ std::string string_to_send = write_func(user, binfo); if(verbose) std::cout << string_to_send << std::endl; // we need the extra bytes padding on both sides :( unsigned char * buf = new unsigned char [LWS_SEND_BUFFER_PRE_PADDING + string_to_send.size() + LWS_SEND_BUFFER_POST_PADDING]; memcpy(&buf[LWS_SEND_BUFFER_PRE_PADDING], string_to_send.c_str(), string_to_send.size()); int n = libwebsocket_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING], string_to_send.size(), LWS_WRITE_TEXT); if (n < 0) throw runtime_error("Could not write", 1); if (n < string_to_send.size()) throw runtime_error("Partial write", -1); break; } case LWS_CALLBACK_RECEIVE: receive_func(user, std::string((char*)in, len), binfo); libwebsocket_callback_on_writable(context, wsi); break; default: break; } } catch(runtime_error& e){ lwsl_err("Exception thrown: %s", e.what()); return e.return_value; } return 0; } }; // Some wrappers to easily create the list of protocols from structs of above kind. #define WSprotocol_callback(protocol)\ [](libwebsocket_context *context, libwebsocket *wsi, libwebsocket_callback_reasons reason, void *user, void *in, size_t len)\ {return protocol.callback(context, wsi, reason, user, in, len);} #define WSstandard_protocol(name, protocol)\ { name, WSprotocol_callback(protocol), sizeof(decltype(protocol)::user_type) } int default_main(int argc, char **argv, libwebsocket_protocols* protocols, std::function runloop_callback = nullptr); } // namespace websockets