diff --git a/.travis.yml b/.travis.yml index b61a7650..56dd54fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ env: global: - secure: "KhAdQ9ja+LBObWNQTYO7Df5J4DyOih6S+eerDMu8UPSO+CoWV2pWoQzbOfocjyOscGOwC+2PrrHDNZyGfqkCLDXg1BxynXPCFerHC1yc2IajvKpGXmAAygNIvp4KACDfGv/dkXrViqIzr/CdcNaU4vIMHSVb5xkeLi0W1dPnQOI=" matrix: + - LWS_METHOD=lwsws CMAKE_ARGS="-DLWS_WITH_LWSWS=ON" - LWS_METHOD=default - LWS_METHOD=noserver CMAKE_ARGS="-DLWS_WITHOUT_SERVER=ON" - LWS_METHOD=noclient CMAKE_ARGS="-DLWS_WITHOUT_CLIENT=ON" @@ -18,16 +19,18 @@ env: os: - linux - osx -language: c +language: generic install: - ./travis_install.sh script: - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then mkdir build && cd build && cmake $CMAKE_ARGS .. && cmake --build .; fi +sudo: required +dist: trusty addons: coverity_scan: project: name: "warmcat/libwebsockets" - notification_email: andy.green@linaro.org + notification_email: andy@warmcat.com build_command_prepend: "mkdir build && cd build && cmake .." build_command: "cmake --build ." branch_pattern: coverity_scan diff --git a/CMakeLists.txt b/CMakeLists.txt index f45a70e7..d12af4d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,7 +91,14 @@ option(LWS_MBED3 "Platform is MBED3" OFF) option(LWS_SSL_SERVER_WITH_ECDH_CERT "Include SSL server use ECDH certificate" OFF) option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out/err) APIs" OFF) option(LWS_WITH_HTTP_PROXY "Support for rewriting HTTP proxying" OFF) -option(LWS_WITH_LWSWS "Libwebsockets Webserver" ON) +option(LWS_WITH_LWSWS "Libwebsockets Webserver" OFF) +option(LWS_WITH_PLUGINS "Support plugins for protocols and extensions" OFF) + +if (LWS_WITH_LWSWS) + message(STATUS "LWS_WITH_LWSWS --> Enabling LWS_WITH_PLUGINS and LWS_WITH_LIBUV") + set(LWS_WITH_PLUGINS 1) + set(LWS_WITH_LIBUV 1) +endif() if (DEFINED YOTTA_WEBSOCKETS_VERSION_STRING) @@ -101,6 +108,7 @@ set(LWS_WITH_ZLIB OFF) set(LWS_WITHOUT_CLIENT ON) set(LWS_WITHOUT_TESTAPPS ON) set(LWS_WITHOUT_EXTENSIONS ON) +set(LWS_WITH_PLUGINS OFF) set(LWS_MBED3 ON) # this implies no pthreads in the lib set(LWS_MAX_SMP 1) @@ -110,6 +118,9 @@ endif() if (WIN32) # this implies no pthreads in the lib set(LWS_MAX_SMP 1) + +# plugin stuff not implemented in win32 plat +set (LWS_WITH_PLUGINS OFF) endif() @@ -926,6 +937,10 @@ if (NOT LWS_WITHOUT_TESTAPPS) message("OpenSSL executable: ${OPENSSL_EXECUTABLE}") endif() + if (UNIX AND LWS_WITH_PLUGINS) + set(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}") + target_link_libraries(websockets dl) + endif() if (NOT LWS_WITHOUT_SERVER) # @@ -1115,6 +1130,54 @@ if (NOT LWS_WITHOUT_TESTAPPS) endif() endif(NOT LWS_WITHOUT_CLIENT) + + + if (LWS_WITH_PLUGINS AND LWS_WITH_SHARED) + macro(create_plugin PLUGIN_NAME MAIN_SRC) + + set(PLUGIN_SRCS ${MAIN_SRC}) + + if (WIN32) + list(APPEND PLUGIN_SRCSset_property + ${WIN32_HELPERS_PATH}/getopt.c + ${WIN32_HELPERS_PATH}/getopt_long.c + ${WIN32_HELPERS_PATH}/gettimeofday.c + ) + + list(APPEND PLUGIN_HDR + ${WIN32_HELPERS_PATH}/getopt.h + ${WIN32_HELPERS_PATH}/gettimeofday.h + ) + endif(WIN32) + + source_group("Headers Private" FILES ${PLUGIN_HDR}) + source_group("Sources" FILES ${PLUGIN_SRCS}) + add_library(${PLUGIN_NAME} SHARED ${PLUGIN_SRCS} ${PLUGIN_HDR}) + + target_link_libraries(${PLUGIN_NAME} websockets) + add_dependencies(${PLUGIN_NAME} websockets) + + # Set test app specific defines. + set_property(TARGET ${PLUGIN_NAME} + PROPERTY COMPILE_DEFINITIONS + INSTALL_DATADIR="${CMAKE_INSTALL_PREFIX}/plugins" + ) + +# set_target_properties(${PLUGIN_NAME} +# PROPERTIES +# OUTPUT_NAME ${PLUGIN_NAME}) + + list(APPEND PLUGINS_LIST ${PLUGIN_NAME}) + endmacro() + + + create_plugin(protocol_dumb_increment + "plugins/protocol_dumb_increment.c") + create_plugin(protocol_lws_mirror + "plugins/protocol_lws_mirror.c") + create_plugin(protocol_lws_status + "plugins/protocol_lws_status.c") + endif(LWS_WITH_PLUGINS AND LWS_WITH_SHARED) # # Copy OpenSSL dlls to the output directory on Windows. @@ -1318,6 +1381,15 @@ if (LWS_WITH_CGI) endif() endif() +# plugins + +if (LWS_WITH_PLUGINS) + install(TARGETS ${PLUGINS_LIST} + PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_READ GROUP_READ WORLD_READ + DESTINATION share/libwebsockets-test-server/plugins + COMPONENT plugins) +endif() + # Install the LibwebsocketsConfig.cmake and LibwebsocketsConfigVersion.cmake install(FILES "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/LibwebsocketsConfig.cmake" @@ -1379,6 +1451,7 @@ message(" LWS_WITH_CGI = ${LWS_WITH_CGI}") message(" LWS_HAVE_OPENSSL_ECDH_H = ${LWS_HAVE_OPENSSL_ECDH_H}") message(" LWS_WITH_HTTP_PROXY = ${LWS_WITH_HTTP_PROXY}") message(" LIBHUBBUB_LIBRARIES = ${LIBHUBBUB_LIBRARIES}") +message(" PLUGINS = ${PLUGINS_LIST}") message("---------------------------------------------------------------------") # These will be available to parent projects including libwebsockets using add_subdirectory() diff --git a/README.lwsws.md b/README.lwsws.md index 460e0e56..ff559b9e 100644 --- a/README.lwsws.md +++ b/README.lwsws.md @@ -4,6 +4,14 @@ Libwebsockets Web Server lwsws is an implementation of a very lightweight, ws-capable generic web server, which uses libwebsockets to implement everything underneath. +Build +----- + +Just enable -DLWS_WITH_LWSWS=1 at cmake-time. + +It enables libuv and plugin support automatically. + + Configuration ------------- @@ -94,10 +102,12 @@ The vhost name field is used to match on incoming SNI or Host: header, so it must always be the host name used to reach the vhost externally. Vhosts may have the same name and different ports, these will each create a -listening socket on the appropriate port, and they may have the same port and -different name: these will be treated as true vhosts on one listening socket -and the active vhost decided at SSL negotiation time (via SNI) or if no SSL, -then after the Host: header from the client has been parsed. +listening socket on the appropriate port. + +They may also have the same port and different name: these will be treated as +true vhosts on one listening socket and the active vhost decided at SSL +negotiation time (via SNI) or if no SSL, then after the Host: header from +the client has been parsed. Mounts @@ -107,4 +117,34 @@ Where mounts are given in the vhost definition, then directory contents may be auto-served if it matches the mountpoint. Currently only file:// mount protocol and a fixed set of mimetypes are -supported. \ No newline at end of file +supported. + + +Plugins +------- + +Protcols and extensions may also be provided from "plugins", these are +lightweight dynamic libraries. They are scanned for at init time, and +any protocols and extensions found are added to the list given at context +creation time. + +Protocols receive init (LWS_CALLBACK_PROTOCOL_INIT) and destruction +(LWS_CALLBACK_PROTOCOL_DESTROY) callbacks per-vhost, and there are arrangements +they can make per-vhost allocations and get hold of the correct pointer from +the wsi at the callback. + +This allows a protocol to choose to strictly segregate data on a per-vhost +basis, and also allows the plugin to handle its own initialization and +context storage. + +To help that happen conveniently, there are some new apis + + - lws_vhost_get(wsi) + - lws_protocol_get(wsi) + - lws_callback_on_writable_all_protocol_vhost(vhost, protocol) + - lws_protocol_vh_priv_zalloc(vhost, protocol, size) + - lws_protocol_vh_priv_get(vhost, protocol) + +dumb increment, mirror and status protocol plugins are provided as examples. + + diff --git a/lib/context.c b/lib/context.c index b8d031b0..46ff5050 100644 --- a/lib/context.c +++ b/lib/context.c @@ -86,6 +86,86 @@ lws_write_http_mount(struct lws_http_mount *next, struct lws_http_mount **res, return ((char *)store + sizeof(*m)) - (char *)orig; } +LWS_VISIBLE void * +lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost, const struct lws_protocols *prot, + int size) +{ + int n = 0; + + /* allocate the vh priv array only on demand */ + if (!vhost->protocol_vh_privs) { + vhost->protocol_vh_privs = (void **)lws_zalloc( + vhost->count_protocols * sizeof(void *)); + if (!vhost->protocol_vh_privs) + return NULL; + } + + while (n < vhost->count_protocols && &vhost->protocols[n] != prot) + n++; + + if (n == vhost->count_protocols) + return NULL; + + vhost->protocol_vh_privs[n] = lws_zalloc(size); + return vhost->protocol_vh_privs[n]; +} + +LWS_VISIBLE void * +lws_protocol_vh_priv_get(struct lws_vhost *vhost, const struct lws_protocols *prot) +{ + int n = 0; + + if (!vhost->protocol_vh_privs) + return NULL; + + while (n < vhost->count_protocols && &vhost->protocols[n] != prot) + n++; + + if (n == vhost->count_protocols) { + lwsl_err("%s: unknown protocol %p\n", __func__, prot); + return NULL; + } + + return vhost->protocol_vh_privs[n]; +} + +int +lws_protocol_init(struct lws_context *context) +{ + struct lws_vhost *vh = context->vhost_list; + struct lws wsi; + int n; + + memset(&wsi, 0, sizeof(wsi)); + wsi.context = context; + + while (vh) { + wsi.vhost = vh; + + /* initialize supported protocols on this vhost */ + + for (n = 0; n < vh->count_protocols; n++) { + wsi.protocol = &vh->protocols[n]; + + /* + * inform all the protocols that they are doing their one-time + * initialization if they want to. + * + * NOTE the wsi is all zeros except for the context, vh and + * protocol ptrs so lws_get_context(wsi) etc can work + */ + vh->protocols[n].callback(&wsi, + LWS_CALLBACK_PROTOCOL_INIT, NULL, NULL, 0); + } + + vh = vh->vhost_next; + } + + context->protocol_init_done = 1; + + return 0; +} + LWS_VISIBLE struct lws_vhost * lws_create_vhost(struct lws_context *context, struct lws_context_creation_info *info, @@ -93,9 +173,12 @@ lws_create_vhost(struct lws_context *context, { struct lws_vhost *vh = lws_zalloc(sizeof(*vh)), **vh1 = &context->vhost_list; - struct lws wsi; +#ifdef LWS_WITH_PLUGINS + struct lws_plugin *plugin = context->plugin_list; + struct lws_protocols *lwsp; + int m; +#endif char *p; - int n; if (!vh) return NULL; @@ -107,11 +190,38 @@ lws_create_vhost(struct lws_context *context, vh->name = info->vhost_name; vh->iface = info->iface; - vh->protocols = info->protocols; for (vh->count_protocols = 0; info->protocols[vh->count_protocols].callback; vh->count_protocols++) ; +#ifdef LWS_WITH_PLUGINS + if (plugin) { + /* + * give the vhost a unified list of protocols including the + * ones that came from plugins + */ + lwsp = lws_zalloc(sizeof(struct lws_protocols) * + (vh->count_protocols + + context->plugin_protocol_count + 1)); + if (!lwsp) + return NULL; + + m = vh->count_protocols; + memcpy(lwsp, info->protocols, + sizeof(struct lws_protocols) * m); + while (plugin) { + memcpy(&lwsp[m], plugin->caps.protocols, + sizeof(struct lws_protocols) * + plugin->caps.count_protocols); + m += plugin->caps.count_protocols; + vh->count_protocols += plugin->caps.count_protocols; + plugin = plugin->list; + } + vh->protocols = lwsp; + } else +#endif + vh->protocols = info->protocols; + vh->mount_list = mounts; @@ -126,7 +236,35 @@ lws_create_vhost(struct lws_context *context, } #ifndef LWS_NO_EXTENSIONS - vh->extensions = info->extensions; +#ifdef LWS_WITH_PLUGINS + if (context->plugin_extension_count) { + + m = 0; + while (info->extensions && info->extensions[m].callback) + m++; + + /* + * give the vhost a unified list of extensions including the + * ones that came from plugins + */ + vh->extensions = lws_zalloc(sizeof(struct lws_extension) * + (m + + context->plugin_extension_count + 1)); + if (!vh->extensions) + return NULL; + + memcpy((struct lws_extension *)vh->extensions, info->extensions, + sizeof(struct lws_extension) * m); + while (plugin) { + memcpy((struct lws_extension *)&vh->extensions[m], plugin->caps.extensions, + sizeof(struct lws_extension) * + plugin->caps.count_extensions); + m += plugin->caps.count_extensions; + plugin = plugin->list; + } + } else +#endif + vh->extensions = info->extensions; #endif vh->listen_port = info->port; @@ -148,23 +286,6 @@ lws_create_vhost(struct lws_context *context, #endif } - memset(&wsi, 0, sizeof(wsi)); - wsi.context = context; - wsi.vhost = vh; - - /* initialize supported protocols */ - - for (n = 0; n < vh->count_protocols; n++) - /* - * inform all the protocols that they are doing their one-time - * initialization if they want to. - * - * NOTE the wsi is all zeros except for the context & vh ptrs - * so lws_get_context(wsi) can work in the callback. - */ - info->protocols[n].callback(&wsi, - LWS_CALLBACK_PROTOCOL_INIT, NULL, NULL, 0); - vh->ka_time = info->ka_time; vh->ka_interval = info->ka_interval; vh->ka_probes = info->ka_probes; @@ -467,7 +588,7 @@ lws_context_destroy(struct lws_context *context) { const struct lws_protocols *protocol = NULL; struct lws_context_per_thread *pt; - struct lws_vhost *vh; + struct lws_vhost *vh, *vh1; struct lws wsi; int n, m; @@ -514,18 +635,25 @@ lws_context_destroy(struct lws_context *context) /* * inform all the protocols that they are done and will have no more - * callbacks + * callbacks. + * + * We can't free things until after the event loop shuts down. */ vh = context->vhost_list; while (vh) { + wsi.vhost = vh; protocol = vh->protocols; - if (protocol) - while (protocol->callback) { + if (protocol) { + n = 0; + while (n < vh->count_protocols) { + wsi.protocol = protocol; protocol->callback(&wsi, LWS_CALLBACK_PROTOCOL_DESTROY, NULL, NULL, 0); protocol++; + n++; } - lws_ssl_SSL_CTX_destroy(vh); + } + vh = vh->vhost_next; } @@ -547,6 +675,39 @@ lws_context_destroy(struct lws_context *context) if (context->pt[0].fds) lws_free_set_NULL(context->pt[0].fds); + /* free all the vhost allocations */ + + vh = context->vhost_list; + while (vh) { + protocol = vh->protocols; + if (protocol) { + n = 0; + while (n < vh->count_protocols) { + if (vh->protocol_vh_privs && + vh->protocol_vh_privs[n]) { + lws_free(vh->protocol_vh_privs[n]); + vh->protocol_vh_privs[n] = NULL; + } + protocol++; + n++; + } + } + if (vh->protocol_vh_privs) + lws_free(vh->protocol_vh_privs); + lws_ssl_SSL_CTX_destroy(vh); +#ifdef LWS_WITH_PLUGINS + if (context->plugin_list) + lws_free((void *)vh->protocols); +#ifndef LWS_NO_EXTENSIONS + if (context->plugin_extension_count) + lws_free((void *)vh->extensions); +#endif +#endif + vh1 = vh->vhost_next; + lws_free(vh); + vh = vh1; + } + lws_plat_context_late_destroy(context); lws_free(context); diff --git a/lib/libev.c b/lib/libev.c index cb8c3c51..39242bb2 100644 --- a/lib/libev.c +++ b/lib/libev.c @@ -32,7 +32,7 @@ void lws_feature_status_libev(struct lws_context_creation_info *info) static void lws_accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents) { - struct lws_io_watcher *lws_io = container_of(watcher, + struct lws_io_watcher *lws_io = lws_container_of(watcher, struct lws_io_watcher, ev_watcher); struct lws_context *context = lws_io->context; struct lws_pollfd eventfd; diff --git a/lib/libuv.c b/lib/libuv.c index 14d3f18b..3cf4a94a 100644 --- a/lib/libuv.c +++ b/lib/libuv.c @@ -31,9 +31,13 @@ lws_feature_status_libuv(struct lws_context_creation_info *info) } static void -lws_uv_idle(uv_idle_t *handle) +lws_uv_idle(uv_idle_t *handle +#if UV_VERSION_MAJOR == 0 + , int status +#endif +) { - struct lws_context_per_thread *pt = container_of(handle, + struct lws_context_per_thread *pt = lws_container_of(handle, struct lws_context_per_thread, uv_idle); lwsl_debug("%s\n", __func__); @@ -57,9 +61,9 @@ lws_uv_idle(uv_idle_t *handle) static void lws_io_cb(uv_poll_t *watcher, int status, int revents) { - struct lws_io_watcher *lws_io = container_of(watcher, + struct lws_io_watcher *lws_io = lws_container_of(watcher, struct lws_io_watcher, uv_watcher); - struct lws *wsi = container_of(lws_io, struct lws, w_read); + struct lws *wsi = lws_container_of(lws_io, struct lws, w_read); struct lws_context *context = lws_io->context; struct lws_pollfd eventfd; @@ -117,9 +121,13 @@ lws_uv_sigint_cfg(struct lws_context *context, int use_uv_sigint, } static void -lws_uv_timeout_cb(uv_timer_t *timer) +lws_uv_timeout_cb(uv_timer_t *timer +#if UV_VERSION_MAJOR == 0 + , int status +#endif +) { - struct lws_context_per_thread *pt = container_of(timer, + struct lws_context_per_thread *pt = lws_container_of(timer, struct lws_context_per_thread, uv_timeout_watcher); lwsl_debug("%s\n", __func__); @@ -138,7 +146,12 @@ lws_uv_initloop(struct lws_context *context, uv_loop_t *loop, int tsi) if (!loop) { loop = lws_malloc(sizeof(*loop)); +#if UV_VERSION_MAJOR > 0 uv_loop_init(loop); +#else + lwsl_err("This libuv is too old to work...\n"); + return 1; +#endif pt->ev_loop_foreign = 0; } else pt->ev_loop_foreign = 1; @@ -217,9 +230,11 @@ lws_libuv_destroyloop(struct lws_context *context, int tsi) uv_stop(pt->io_loop_uv); uv_walk(pt->io_loop_uv, lws_uv_walk_cb, NULL); while (uv_run(pt->io_loop_uv, UV_RUN_NOWAIT)); +#if UV_VERSION_MAJOR > 0 m = uv_loop_close(pt->io_loop_uv); if (m == UV_EBUSY) lwsl_debug("%s: uv_loop_close: UV_EBUSY\n", __func__); +#endif lws_free(pt->io_loop_uv); } } @@ -298,9 +313,8 @@ lws_libuv_init_fd_table(struct lws_context *context) if (!LWS_LIBUV_ENABLED(context)) return 0; - for (n = 0; n < context->count_threads; n++) { + for (n = 0; n < context->count_threads; n++) context->pt[n].w_sigint.context = context; - } return 1; } diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index b83b3143..99588164 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -707,6 +707,18 @@ lws_context_user(struct lws_context *context) return context->user_space; } +LWS_VISIBLE struct lws_vhost * +lws_vhost_get(struct lws *wsi) +{ + return wsi->vhost; +} + +LWS_VISIBLE const struct lws_protocols * +lws_protocol_get(struct lws *wsi) +{ + return wsi->protocol; +} + /** * lws_callback_all_protocol() - Callback all connections using @@ -739,6 +751,39 @@ lws_callback_all_protocol(struct lws_context *context, return 0; } +/** + * lws_callback_all_protocol_vhost() - Callback all connections using + * the given protocol with the given reason + * + * @vh: Vhost whose connections will get callbacks + * @protocol: Which protocol to match + * @reason: Callback reason index + */ + +LWS_VISIBLE int +lws_callback_all_protocol_vhost(struct lws_vhost *vh, + const struct lws_protocols *protocol, int reason) +{ + struct lws_context *context = vh->context; + struct lws_context_per_thread *pt = &context->pt[0]; + unsigned int n, m = context->count_threads; + struct lws *wsi; + + while (m--) { + for (n = 0; n < pt->fds_count; n++) { + wsi = wsi_from_fd(context, pt->fds[n].fd); + if (!wsi) + continue; + if (wsi->vhost == vh && wsi->protocol == protocol) + protocol->callback(wsi, reason, wsi->user_space, + NULL, 0); + } + pt++; + } + + return 0; +} + #if LWS_POSIX /** diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 9479fce6..5cbaaadc 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -280,6 +280,14 @@ LWS_VISIBLE LWS_EXTERN void lwsl_hexdump(void *buf, size_t len); #define lwsl_hexdump(a, b) #endif + +#include + +#ifndef lws_container_of +#define lws_container_of(P,T,M) ((T *)((char *)(P) - offsetof(T, M))) +#endif + + struct lws; #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) @@ -1293,6 +1301,26 @@ struct lws_extension { * This is part of the ABI, don't needlessly break compatibility */ }; +#define LWS_PLUGIN_API_MAGIC 180 + +struct lws_plugin_capability { + unsigned int api_magic; /* caller fills this in, plugin fills rest */ + const struct lws_protocols *protocols; + int count_protocols; + const struct lws_extension *extensions; + int count_extensions; +}; + +typedef int (*lws_plugin_init_func)(struct lws_context *, + struct lws_plugin_capability *); +typedef int (*lws_plugin_destroy_func)(struct lws_context *); +struct lws_plugin { + struct lws_plugin *list; + void *l; + char name[64]; + struct lws_plugin_capability caps; +}; + /* * The internal exts are part of the public abi * If we add more extensions, publish the callback here ------v @@ -1414,6 +1442,7 @@ struct lws_context_creation_info { unsigned int timeout_secs; /* VH */ const char *ecdh_curve; /* VH */ const char *vhost_name; /* VH */ + const char *plugins_dir; /* context */ /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility @@ -1505,6 +1534,18 @@ lws_create_vhost(struct lws_context *context, struct lws_context_creation_info *info, struct lws_http_mount *mounts); +LWS_VISIBLE struct lws_vhost * +lws_vhost_get(struct lws *wsi); + +LWS_VISIBLE const struct lws_protocols * +lws_protocol_get(struct lws *wsi); + +LWS_VISIBLE void * +lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost, const struct lws_protocols *prot, + int size); +LWS_VISIBLE void * +lws_protocol_vh_priv_get(struct lws_vhost *vhost, const struct lws_protocols *prot); + LWS_VISIBLE LWS_EXTERN int lws_finalize_startup(struct lws_context *context); @@ -1740,10 +1781,18 @@ LWS_VISIBLE LWS_EXTERN int lws_callback_on_writable_all_protocol(const struct lws_context *context, const struct lws_protocols *protocol); +LWS_VISIBLE int +lws_callback_on_writable_all_protocol_vhost(const struct lws_vhost *vhost, + const struct lws_protocols *protocol); + LWS_VISIBLE LWS_EXTERN int lws_callback_all_protocol(struct lws_context *context, const struct lws_protocols *protocol, int reason); +LWS_VISIBLE int +lws_callback_all_protocol_vhost(struct lws_vhost *vh, + const struct lws_protocols *protocol, int reason); + LWS_VISIBLE LWS_EXTERN int lws_get_socket_fd(struct lws *wsi); diff --git a/lib/lws-plat-unix.c b/lib/lws-plat-unix.c index 2b89e22b..c726e9e8 100644 --- a/lib/lws-plat-unix.c +++ b/lib/lws-plat-unix.c @@ -3,6 +3,10 @@ #include #include +#include +#include + + /* * included from libwebsockets.c for unix builds */ @@ -294,6 +298,141 @@ lws_plat_drop_app_privileges(struct lws_context_creation_info *info) } } +#ifdef LWS_WITH_PLUGINS + +static int filter(const struct dirent *ent) +{ + if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) + return 0; + + return 1; +} + +LWS_VISIBLE int +lws_plat_plugins_init(struct lws_context * context, const char *d) +{ + struct lws_plugin_capability lcaps; + struct lws_plugin *plugin; + lws_plugin_init_func initfunc; + struct dirent **namelist; + int n, i, m, ret = 0; + char path[256]; + void *l; + + + n = scandir(d, &namelist, filter, alphasort); + if (n < 0) { + lwsl_err("Scandir on %d failed\n", d); + return 1; + } + + lwsl_notice(" Plugins:\n"); + + for (i = 0; i < n; i++) { + if (strlen(namelist[i]->d_name) < 7) + goto inval; + + lwsl_notice(" %s\n", namelist[i]->d_name); + + snprintf(path, sizeof(path) - 1, "%s/%s", d, + namelist[i]->d_name); + l = dlopen(path, RTLD_NOW); + if (!l) { + lwsl_err("Error loading DSO: %s\n", dlerror()); + while (i++ < n) + free(namelist[i]); + goto bail; + } + /* we could open it, can we get his init function? */ + m = snprintf(path, sizeof(path) - 1, "init_%s", + namelist[i]->d_name + 3 /* snip lib... */); + path[m - 3] = '\0'; /* snip the .so */ + initfunc = dlsym(l, path); + if (!initfunc) { + lwsl_err("Failed to get init on %s: %s", + namelist[i]->d_name, dlerror()); + dlclose(l); + } + lcaps.api_magic = LWS_PLUGIN_API_MAGIC; + m = initfunc(context, &lcaps); + if (m) { + lwsl_err("Initializing %s failed %d\n", + namelist[i]->d_name, m); + dlclose(l); + goto skip; + } + + plugin = lws_malloc(sizeof(*plugin)); + if (!plugin) { + lwsl_err("OOM\n"); + goto bail; + } + plugin->list = context->plugin_list; + context->plugin_list = plugin; + strncpy(plugin->name, namelist[i]->d_name, sizeof(plugin->name) - 1); + plugin->name[sizeof(plugin->name) - 1] = '\0'; + plugin->l = l; + plugin->caps = lcaps; + context->plugin_protocol_count += lcaps.count_protocols; + context->plugin_extension_count += lcaps.count_extensions; + + free(namelist[i]); + continue; + +skip: + dlclose(l); +inval: + free(namelist[i]); + } + +bail: + free(namelist); + + return ret; +} + +LWS_VISIBLE int +lws_plat_plugins_destroy(struct lws_context * context) +{ + struct lws_plugin *plugin = context->plugin_list, *p; + lws_plugin_destroy_func func; + char path[256]; + int m; + + if (!plugin) + return 0; + + lwsl_notice("%s\n", __func__); + + while (plugin) { + p = plugin; + m = snprintf(path, sizeof(path) - 1, "destroy_%s", plugin->name + 3); + path[m - 3] = '\0'; + func = dlsym(plugin->l, path); + if (!func) { + lwsl_err("Failed to get destroy on %s: %s", + plugin->name, dlerror()); + goto next; + } + m = func(context); + if (m) + lwsl_err("Initializing %s failed %d\n", + plugin->name, m); +next: + dlclose(p->l); + plugin = p->list; + p->list = NULL; + free(p); + } + + context->plugin_list = NULL; + + return 0; +} + +#endif + + static void sigpipe_handler(int x) { @@ -326,6 +465,11 @@ lws_plat_context_late_destroy(struct lws_context *context) struct lws_context_per_thread *pt = &context->pt[0]; int m = context->count_threads; +#ifdef LWS_WITH_PLUGINS + if (context->plugin_list) + lws_plat_plugins_destroy(context); +#endif + if (context->lws_lookup) lws_free(context->lws_lookup); @@ -513,6 +657,7 @@ _lws_plat_file_write(struct lws *wsi, lws_filefd_type fd, unsigned long *amount, return 0; } + LWS_VISIBLE int lws_plat_init(struct lws_context *context, struct lws_context_creation_info *info) @@ -565,5 +710,10 @@ lws_plat_init(struct lws_context *context, context->fops.read = _lws_plat_file_read; context->fops.write = _lws_plat_file_write; +#ifdef LWS_WITH_PLUGINS + if (info->plugins_dir) + lws_plat_plugins_init(context, info->plugins_dir); +#endif + return 0; } diff --git a/lib/pollfd.c b/lib/pollfd.c index 1d359bda..7a4f17e4 100644 --- a/lib/pollfd.c +++ b/lib/pollfd.c @@ -374,3 +374,37 @@ lws_callback_on_writable_all_protocol(const struct lws_context *context, return 0; } + + +/** + * lws_callback_on_writable_all_protocol_vhost() - Request a callback for + * all connections using the given protocol when it + * becomes possible to write to each socket without + * blocking in turn. + * + * @vhost: Only consider connections on this lws_vhost + * @protocol: Protocol whose connections will get callbacks + */ + +LWS_VISIBLE int +lws_callback_on_writable_all_protocol_vhost(const struct lws_vhost *vhost, + const struct lws_protocols *protocol) +{ + const struct lws_context *context = vhost->context; + const struct lws_context_per_thread *pt = &context->pt[0]; + unsigned int n, m = context->count_threads; + struct lws *wsi; + + while (m--) { + for (n = 0; n < pt->fds_count; n++) { + wsi = wsi_from_fd(context, pt->fds[n].fd); + if (!wsi) + continue; + if (wsi->vhost == vhost && wsi->protocol == protocol) + lws_callback_on_writable(wsi); + } + pt++; + } + + return 0; +} diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 5ebb9743..839eab54 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -261,12 +261,6 @@ typedef unsigned __int64 u_int64_t; #endif #endif -#include - -#ifndef container_of -#define container_of(P,T,M) ((T *)((char *)(P) - offsetof(T, M))) -#endif - #else #include @@ -283,11 +277,6 @@ typedef unsigned __int64 u_int64_t; #ifdef __cplusplus extern "C" { #endif -#include - -#ifndef container_of -#define container_of(P,T,M) ((T *)((char *)(P) - offsetof(T, M))) -#endif #if defined(__QNX__) #include @@ -664,6 +653,7 @@ struct lws_vhost { const char *name; const char *iface; const struct lws_protocols *protocols; + void **protocol_vh_privs; #ifdef LWS_OPENSSL_SUPPORT SSL_CTX *ssl_ctx; SSL_CTX *ssl_client_ctx; @@ -704,6 +694,7 @@ struct lws_context { struct lws **lws_lookup; /* fd to wsi */ #endif struct lws_vhost *vhost_list; + struct lws_plugin *plugin_list; const struct lws_token_limits *token_limits; void *user_space; @@ -755,9 +746,12 @@ struct lws_context { short max_http_header_data; short max_http_header_pool; short count_threads; + short plugin_protocol_count; + short plugin_extension_count; unsigned int being_destroyed:1; unsigned int requested_kill:1; + unsigned int protocol_init_done:1; }; #define lws_get_context_protocol(ctx, x) ctx->vhost_list->protocols[x] @@ -1671,6 +1665,9 @@ lws_get_addresses(struct lws_context *context, void *ads, char *name, LWS_EXTERN int lws_cgi_kill_terminated(struct lws_context_per_thread *pt); +int +lws_protocol_init(struct lws_context *context); + /* * custom allocator */ diff --git a/lib/server.c b/lib/server.c index 51dd427d..3103256e 100644 --- a/lib/server.c +++ b/lib/server.c @@ -194,6 +194,9 @@ static const char * get_mimetype(const char *file) if (!strcmp(&file[n - 4], ".png")) return "image/png"; + if (!strcmp(&file[n - 4], ".jpg")) + return "image/jpeg"; + if (!strcmp(&file[n - 5], ".html")) return "text/html"; @@ -220,7 +223,7 @@ int lws_http_serve(struct lws *wsi, char *uri, const char *origin) } n = lws_serve_http_file(wsi, path, mimetype, NULL, 0); - if (n < 0) + if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) return -1; /* error or can't reuse connection: close the socket */ diff --git a/lib/service.c b/lib/service.c index 80e513c5..2e66812b 100644 --- a/lib/service.c +++ b/lib/service.c @@ -637,6 +637,9 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t int n = 0, m; int more; + if (!context->protocol_init_done) + lws_protocol_init(context); + /* * you can call us with pollfd = NULL to just allow the once-per-second * global timeout checks; if less than a second since the last check diff --git a/lws_config.h.in b/lws_config.h.in index f90abeb9..452e9693 100644 --- a/lws_config.h.in +++ b/lws_config.h.in @@ -17,6 +17,8 @@ #cmakedefine LWS_USE_MBEDTLS #cmakedefine LWS_USE_POLARSSL +#cmakedefine LWS_WITH_PLUGINS + /* The Libwebsocket version */ #cmakedefine LWS_LIBRARY_VERSION "${LWS_LIBRARY_VERSION}" diff --git a/lwsws/etc-lwsws-conf-EXAMPLE b/lwsws/etc-lwsws-conf-EXAMPLE new file mode 100644 index 00000000..ac2c9ac5 --- /dev/null +++ b/lwsws/etc-lwsws-conf-EXAMPLE @@ -0,0 +1,14 @@ +# these are the server global settings +# stuff related to vhosts should go in one +# file per vhost in ../conf.d/ + +{ + "global": { + "uid": "99", + "gid": "99", + "interface": "eth0", + "count-threads": "1", + "init-ssl": "yes" + } +} + diff --git a/lwsws/etc-lwsws-conf.d-libwebsockets.org-EXAMPLE b/lwsws/etc-lwsws-conf.d-libwebsockets.org-EXAMPLE new file mode 100644 index 00000000..b4e307db --- /dev/null +++ b/lwsws/etc-lwsws-conf.d-libwebsockets.org-EXAMPLE @@ -0,0 +1,50 @@ +# comment + +{ + "vhosts": [ { + "name": "libwebsockets.org", + "port": "443", + "host-ssl-key": "/etc/pki/tls/private/libwebsockets.org.key", + "host-ssl-cert": "/etc/pki/tls/certs/libwebsockets.org.crt", + "host-ssl-ca": "/etc/pki/tls/certs/libwebsockets.org.cer", + "mounts": [{ + "mountpoint": "/", + "origin": "file:///var/www/libwebsockets.org", + "default": "index.html" + }, { + "mountpoint": "/git", + "origin": "http://git.warmcat.com", + "default": "/" + }, { + "mountpoint": "/mailman", + "origin": "cgi://usr/lib/mailman/cgi-bin/", + "default": "/list-info" + }] + }, + { + "name": "libwebsockets.org", # disambiguated by port, must be same for SNI + "port": "7681", + "host-ssl-key": "/etc/pki/tls/private/libwebsockets.org.key", + "host-ssl-cert": "/etc/pki/tls/certs/libwebsockets.org.crt", + "host-ssl-ca": "/etc/pki/tls/certs/libwebsockets.org.cer", + "ws-protocols": [{ + "wsprotocol": "dumb-increment-protocol", + "wsprotocol": "lws-mirror-protocol", + "wsprotocol": "lws-status" + }], + "ws-extensions": [{ + "extension": "permessage-deflate" + }], + "mounts": [{ + "mountpoint": "/", + "origin": "file:///usr/local/share/libwebsockets-test-server", + "default": "test.html" + }] + }, + { + "name": "libwebsockets.org", + "port": "80", + "global-redirect": "https://libwebsockets.org" + }] +} + diff --git a/lwsws/http.c b/lwsws/http.c index 1b27a7d2..66f398f9 100644 --- a/lwsws/http.c +++ b/lwsws/http.c @@ -47,6 +47,9 @@ const char * get_mimetype(const char *file) if (!strcmp(&file[n - 4], ".png")) return "image/png"; + if (!strcmp(&file[n - 4], ".jpg")) + return "image/jpeg"; + if (!strcmp(&file[n - 5], ".html")) return "text/html"; @@ -190,7 +193,7 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, return 0; /* check for the "send a big file by hand" example case */ - + lwsl_notice("%s\n", in); if (!strcmp((const char *)in, "/leaf.jpg")) { if (strlen(resource_path) > sizeof(leaf_path) - 10) return -1; diff --git a/lwsws/main.c b/lwsws/main.c index 741fe7a3..24fc31d1 100644 --- a/lwsws/main.c +++ b/lwsws/main.c @@ -62,6 +62,7 @@ static struct lws_protocols protocols[] = { sizeof (struct per_session_data__http), /* per_session_data_size */ 0, /* max frame size / rx buffer */ }, + { } }; void sighandler(int sig) @@ -177,17 +178,16 @@ int main(int argc, char **argv) info.max_http_header_pool = 16; info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8 | - LWS_SERVER_OPTION_EXPLICIT_VHOSTS; -#ifdef LWS_USE_LIBUV - info.options |= LWS_SERVER_OPTION_LIBUV; -#endif + LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_LIBUV; + + info.plugins_dir = INSTALL_DATADIR"/libwebsockets-test-server/plugins/"; lwsl_notice("Using config dir: \"%s\"\n", config_dir); /* * first go through the config for creating the outer context */ - if (lwsws_get_config_globals(&info, config_dir, &cs, &cs_len)) goto bail; @@ -214,21 +214,14 @@ int main(int argc, char **argv) if (lwsws_get_config_vhosts(context, &info, config_dir, &cs, &cs_len)) goto bail; -#ifdef LWS_USE_LIBUV lws_uv_sigint_cfg(context, 1, signal_cb); lws_uv_initloop(context, NULL, 0); - lws_libuv_run(context, 0); -#else - n = 0; - while (n >= 0 && !force_exit) { - n = lws_service(context, 50); - } -#endif + lws_libuv_run(context, 0); bail: lws_context_destroy(context); - lwsl_notice("lwsws exited cleanly\n"); + fprintf(stderr, "lwsws exited cleanly\n"); #ifndef _WIN32 closelog(); diff --git a/plugins/protocol_dumb_increment.c b/plugins/protocol_dumb_increment.c new file mode 100644 index 00000000..499073dd --- /dev/null +++ b/plugins/protocol_dumb_increment.c @@ -0,0 +1,141 @@ +/* + * ws protocol handler plugin for "dumb increment" + * + * Copyright (C) 2010-2016 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * The person who associated a work with this deed has dedicated + * the work to the public domain by waiving all of his or her rights + * to the work worldwide under copyright law, including all related + * and neighboring rights, to the extent allowed by law. You can copy, + * modify, distribute and perform the work, even for commercial purposes, + * all without asking permission. + * + * These test plugins are intended to be adapted for use in your code, which + * may be proprietary. So unlike the library itself, they are licensed + * Public Domain. + */ +#include "../lib/libwebsockets.h" + +struct per_vhost_data__dumb_increment { + uv_timer_t timeout_watcher; + struct lws_context *context; + struct lws_vhost *vhost; + const struct lws_protocols *protocol; +}; + +struct per_session_data__dumb_increment { + int number; +}; + +static void +uv_timeout_cb_dumb_increment(uv_timer_t *w +#if UV_VERSION_MAJOR == 0 + , int status +#endif +) +{ + struct per_vhost_data__dumb_increment *vhd = lws_container_of(w, + struct per_vhost_data__dumb_increment, timeout_watcher); + lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol); +} + +static int +callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct per_session_data__dumb_increment *pss = + (struct per_session_data__dumb_increment *)user; + struct per_vhost_data__dumb_increment *vhd = + (struct per_vhost_data__dumb_increment *) + lws_protocol_vh_priv_get(lws_vhost_get(wsi), + lws_protocol_get(wsi)); + unsigned char buf[LWS_PRE + 512]; + unsigned char *p = &buf[LWS_PRE]; + int n, m; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + vhd = lws_protocol_vh_priv_zalloc(lws_vhost_get(wsi), + lws_protocol_get(wsi), + sizeof(struct per_vhost_data__dumb_increment)); + vhd->context = lws_get_context(wsi); + vhd->protocol = lws_protocol_get(wsi); + vhd->vhost = lws_vhost_get(wsi); + uv_timer_init(lws_uv_getloop(vhd->context, 0), + &vhd->timeout_watcher); + uv_timer_start(&vhd->timeout_watcher, + uv_timeout_cb_dumb_increment, 50, 50); + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: + uv_timer_stop(&vhd->timeout_watcher); + break; + + case LWS_CALLBACK_ESTABLISHED: + pss->number = 0; + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + n = sprintf((char *)p, "%d", pss->number++); + m = lws_write(wsi, p, n, LWS_WRITE_TEXT); + if (m < n) { + lwsl_err("ERROR %d writing to di socket\n", n); + return -1; + } + break; + + case LWS_CALLBACK_RECEIVE: + if (len < 6) + break; + if (strcmp((const char *)in, "reset\n") == 0) + pss->number = 0; + if (strcmp((const char *)in, "closeme\n") == 0) { + lwsl_notice("dumb_inc: closing as requested\n"); + lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY, + (unsigned char *)"seeya", 5); + return -1; + } + break; + + default: + break; + } + + return 0; +} + +static const struct lws_protocols protocols[] = { + { + "dumb-increment-protocol", + callback_dumb_increment, + sizeof(struct per_session_data__dumb_increment), + 10, /* rx buf size must be >= permessage-deflate rx size */ + }, +}; + +LWS_VISIBLE int +init_protocol_dumb_increment(struct lws_context *context, + struct lws_plugin_capability *c) +{ + if (c->api_magic != LWS_PLUGIN_API_MAGIC) { + lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC, + c->api_magic); + return 1; + } + + c->protocols = protocols; + c->count_protocols = ARRAY_SIZE(protocols); + c->extensions = NULL; + c->count_extensions = 0; + + return 0; +} + +LWS_VISIBLE int +destroy_protocol_dumb_increment(struct lws_context *context) +{ + return 0; +} diff --git a/plugins/protocol_lws_mirror.c b/plugins/protocol_lws_mirror.c new file mode 100644 index 00000000..ba656603 --- /dev/null +++ b/plugins/protocol_lws_mirror.c @@ -0,0 +1,177 @@ +/* + * libwebsockets-test-server - libwebsockets test implementation + * + * Copyright (C) 2010-2016 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * The person who associated a work with this deed has dedicated + * the work to the public domain by waiving all of his or her rights + * to the work worldwide under copyright law, including all related + * and neighboring rights, to the extent allowed by law. You can copy, + * modify, distribute and perform the work, even for commercial purposes, + * all without asking permission. + * + * The test apps are intended to be adapted for use in your code, which + * may be proprietary. So unlike the library itself, they are licensed + * Public Domain. + */ +#include "../lib/libwebsockets.h" + +/* lws-mirror_protocol */ + +#define MAX_MESSAGE_QUEUE 512 + +struct per_session_data__lws_mirror { + struct lws *wsi; + int ringbuffer_tail; +}; + +struct a_message { + void *payload; + size_t len; +}; + +struct per_vhost_data__lws_mirror { + struct a_message ringbuffer[MAX_MESSAGE_QUEUE]; + int ringbuffer_head; +}; + +static int +callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct per_session_data__lws_mirror *pss = + (struct per_session_data__lws_mirror *)user; + struct per_vhost_data__lws_mirror *v = + (struct per_vhost_data__lws_mirror *) + lws_protocol_vh_priv_get(lws_vhost_get(wsi), + lws_protocol_get(wsi)); + int n, m; + + switch (reason) { + + case LWS_CALLBACK_ESTABLISHED: + lwsl_info("%s: LWS_CALLBACK_ESTABLISHED\n", __func__); + pss->ringbuffer_tail = v->ringbuffer_head; + pss->wsi = wsi; + break; + + case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */ + lws_protocol_vh_priv_zalloc(lws_vhost_get(wsi), + lws_protocol_get(wsi), + sizeof(struct per_vhost_data__lws_mirror)); + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: /* per vhost */ + lwsl_info("%s: mirror protocol cleaning up %p\n", __func__, v); + for (n = 0; n < ARRAY_SIZE(v->ringbuffer); n++) + if (v->ringbuffer[n].payload) { + free(v->ringbuffer[n].payload); + v->ringbuffer[n].payload = NULL; + } + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + while (pss->ringbuffer_tail != v->ringbuffer_head) { + m = v->ringbuffer[pss->ringbuffer_tail].len; + n = lws_write(wsi, (unsigned char *) + v->ringbuffer[pss->ringbuffer_tail].payload + + LWS_PRE, m, LWS_WRITE_TEXT); + if (n < 0) { + lwsl_err("ERROR %d writing to mirror socket\n", n); + return -1; + } + if (n < m) + lwsl_err("mirror partial write %d vs %d\n", n, m); + + if (pss->ringbuffer_tail == (MAX_MESSAGE_QUEUE - 1)) + pss->ringbuffer_tail = 0; + else + pss->ringbuffer_tail++; + + if (((v->ringbuffer_head - pss->ringbuffer_tail) & + (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 15)) + lws_rx_flow_allow_all_protocol(lws_get_context(wsi), + lws_get_protocol(wsi)); + + if (lws_send_pipe_choked(wsi)) { + lws_callback_on_writable(wsi); + break; + } + } + break; + + case LWS_CALLBACK_RECEIVE: + if (((v->ringbuffer_head - pss->ringbuffer_tail) & + (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 1)) { + lwsl_err("dropping!\n"); + goto choke; + } + + if (v->ringbuffer[v->ringbuffer_head].payload) + free(v->ringbuffer[v->ringbuffer_head].payload); + + v->ringbuffer[v->ringbuffer_head].payload = malloc(LWS_PRE + len); + v->ringbuffer[v->ringbuffer_head].len = len; + memcpy((char *)v->ringbuffer[v->ringbuffer_head].payload + + LWS_PRE, in, len); + if (v->ringbuffer_head == (MAX_MESSAGE_QUEUE - 1)) + v->ringbuffer_head = 0; + else + v->ringbuffer_head++; + + if (((v->ringbuffer_head - pss->ringbuffer_tail) & + (MAX_MESSAGE_QUEUE - 1)) != (MAX_MESSAGE_QUEUE - 2)) + goto done; + +choke: + lwsl_debug("LWS_CALLBACK_RECEIVE: throttling %p\n", wsi); + lws_rx_flow_control(wsi, 0); + +done: + lws_callback_on_writable_all_protocol(lws_get_context(wsi), + lws_get_protocol(wsi)); + break; + + default: + break; + } + + return 0; +} + +static const struct lws_protocols protocols[] = { + { + "lws-mirror-protocol", + callback_lws_mirror, + sizeof(struct per_session_data__lws_mirror), + 128, /* rx buf size must be >= permessage-deflate rx size */ + }, +}; + +LWS_VISIBLE int +init_protocol_lws_mirror(struct lws_context *context, + struct lws_plugin_capability *c) +{ + if (c->api_magic != LWS_PLUGIN_API_MAGIC) { + lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC, + c->api_magic); + return 1; + } + + c->protocols = protocols; + c->count_protocols = ARRAY_SIZE(protocols); + c->extensions = NULL; + c->count_extensions = 0; + + return 0; +} + +LWS_VISIBLE int +destroy_protocol_lws_mirror(struct lws_context *context) +{ + return 0; +} + diff --git a/plugins/protocol_lws_status.c b/plugins/protocol_lws_status.c new file mode 100644 index 00000000..fabaf6d0 --- /dev/null +++ b/plugins/protocol_lws_status.c @@ -0,0 +1,208 @@ +/* + * libwebsockets-test-server - libwebsockets test implementation + * + * Copyright (C) 2010-2016 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * The person who associated a work with this deed has dedicated + * the work to the public domain by waiving all of his or her rights + * to the work worldwide under copyright law, including all related + * and neighboring rights, to the extent allowed by law. You can copy, + * modify, distribute and perform the work, even for commercial purposes, + * all without asking permission. + * + * The test apps are intended to be adapted for use in your code, which + * may be proprietary. So unlike the library itself, they are licensed + * Public Domain. + */ +#include "../lib/libwebsockets.h" +#include + +struct per_session_data__lws_status { + struct per_session_data__lws_status *list; + struct timeval tv_established; + int last; + char ip[270]; + char user_agent[512]; + const char *pos; + int len; +}; + + +static unsigned char server_info[1024]; +static int server_info_len; +static int current; +static char cache[16384]; +static int cache_len; +static struct per_session_data__lws_status *list; +static int live_wsi; + + +static void +update_status(struct lws *wsi, struct per_session_data__lws_status *pss) +{ + struct per_session_data__lws_status **pp = &list; + int subsequent = 0; + char *p = cache + LWS_PRE, *start = p; + char date[128]; + time_t t; + struct tm *ptm; +#ifndef WIN32 + struct tm tm; +#endif + + p += snprintf(p, 512, " { %s, \"wsi\":\"%d\", \"conns\":[", + server_info, live_wsi); + + /* render the list */ + while (*pp) { + t = (*pp)->tv_established.tv_sec; +#ifdef WIN32 + ptm = localtime(&t); + if (!ptm) +#else + ptm = &tm; + if (!localtime_r(&t, &tm)) +#endif + strcpy(date, "unknown"); + else + strftime(date, sizeof(date), "%F %H:%M %Z", ptm); + if ((p - start) > (sizeof(cache) - 512)) + break; + if (subsequent) + *p++ = ','; + subsequent = 1; + p += snprintf(p, sizeof(cache) - (p - start) - 1, + "{\"peer\":\"%s\",\"time\":\"%s\"," + "\"ua\":\"%s\"}", + (*pp)->ip, date, (*pp)->user_agent); + pp = &((*pp)->list); + } + + p += sprintf(p, "]}"); + cache_len = p - start; + lwsl_err("cache_len %d\n", cache_len); + *p = '\0'; + + /* since we changed the list, increment the 'version' */ + current++; + /* update everyone */ + lws_callback_on_writable_all_protocol(lws_get_context(wsi), + lws_get_protocol(wsi)); +} + + +/* lws-status protocol */ + +int +callback_lws_status(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct per_session_data__lws_status *pss = + (struct per_session_data__lws_status *)user, + **pp; + char name[128], rip[128]; + int m; + + switch (reason) { + + case LWS_CALLBACK_PROTOCOL_INIT: + /* + * Prepare the static server info + */ + server_info_len = sprintf((char *)server_info, + "\"version\":\"%s\"," + " \"hostname\":\"%s\"", + lws_get_library_version(), + lws_canonical_hostname( + lws_get_context(wsi))); + break; + + case LWS_CALLBACK_ESTABLISHED: + /* + * we keep a linked list of live pss, so we can walk it + */ + pss->last = 0; + pss->list = list; + list = pss; + live_wsi++; + lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, + sizeof(name), rip, sizeof(rip)); + sprintf(pss->ip, "%s (%s)", name, rip); + gettimeofday(&pss->tv_established, NULL); + strcpy(pss->user_agent, "unknown"); + lws_hdr_copy(wsi, pss->user_agent, sizeof(pss->user_agent), + WSI_TOKEN_HTTP_USER_AGENT); + update_status(wsi, pss); + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + m = lws_write(wsi, (unsigned char *)cache + LWS_PRE, cache_len, + LWS_WRITE_TEXT); + if (m < server_info_len) { + lwsl_err("ERROR %d writing to di socket\n", m); + return -1; + } + break; + + case LWS_CALLBACK_CLOSED: + /* + * remove ourselves from live pss list + */ + lwsl_err("CLOSING pss %p ********\n", pss); + + pp = &list; + while (*pp) { + if (*pp == pss) { + *pp = pss->list; + pss->list = NULL; + live_wsi--; + break; + } + pp = &((*pp)->list); + } + + update_status(wsi, pss); + break; + + default: + break; + } + + return 0; +} + +static const struct lws_protocols protocols[] = { + { + "lws-status", + callback_lws_status, + sizeof(struct per_session_data__lws_status), + 128, /* rx buf size must be >= permessage-deflate rx size */ + }, +}; + +LWS_VISIBLE int +init_protocol_lws_status(struct lws_context *context, + struct lws_plugin_capability *c) +{ + if (c->api_magic != LWS_PLUGIN_API_MAGIC) { + lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC, + c->api_magic); + return 1; + } + + c->protocols = protocols; + c->count_protocols = ARRAY_SIZE(protocols); + c->extensions = NULL; + c->count_extensions = 0; + + return 0; +} + +LWS_VISIBLE int +destroy_protocol_lws_status(struct lws_context *context) +{ + return 0; +} diff --git a/test-server/test-server-libuv.c b/test-server/test-server-libuv.c index fb95f7fd..183c2b75 100644 --- a/test-server/test-server-libuv.c +++ b/test-server/test-server-libuv.c @@ -128,7 +128,11 @@ void signal_cb(uv_signal_t *watcher, int signum) } static void -uv_timeout_cb_dumb_increment(uv_timer_t *w) +uv_timeout_cb_dumb_increment(uv_timer_t *w +#if UV_VERSION_MAJOR == 0 + , int status +#endif +) { lws_callback_on_writable_all_protocol(context, &protocols[PROTOCOL_DUMB_INCREMENT]); diff --git a/travis_install.sh b/travis_install.sh index 96ac72d9..f51f087e 100755 --- a/travis_install.sh +++ b/travis_install.sh @@ -10,6 +10,12 @@ then then sudo apt-get install -y -qq libev-dev; fi + + if [ "$LWS_METHOD" == "libuv" -o "$LWS_METHOD" == "lwsws" ]; + then + sudo apt-get install -y -qq libuv-dev; + fi + fi if [ "$TRAVIS_OS_NAME" == "osx" ]; @@ -18,6 +24,12 @@ then then brew install libev; fi + + if [ "$LWS_METHOD" == "libuv" -o "$LWS_METHOD" == "lwsws" ]; + then + brew install libuv; + fi + fi