diff --git a/CMakeLists.txt b/CMakeLists.txt index 34694028..66126012 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,13 +95,14 @@ option(LWS_WITH_HTTP_PROXY "Support for rewriting HTTP proxying" OFF) option(LWS_WITH_LWSWS "Libwebsockets Webserver" OFF) option(LWS_WITH_PLUGINS "Support plugins for protocols and extensions" OFF) option(LWS_WITH_ACCESS_LOG "Support generating Apache-compatible access logs" OFF) - +option(LWS_WITH_SERVER_STATUS "Support json + jscript server monitoring" 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) set(LWS_WITH_ACCESS_LOG 1) + set(LWS_WITH_SERVER_STATUS 1) endif() if (LWS_WITH_PLUGINS AND NOT LWS_WITH_LIBUV) @@ -1199,6 +1200,10 @@ if (NOT LWS_WITHOUT_TESTAPPS) "plugins/protocol_lws_mirror.c") create_plugin(protocol_lws_status "plugins/protocol_lws_status.c") +if (LWS_WITH_SERVER_STATUS) + create_plugin(protocol_lws_server_status + "plugins/protocol_lws_server_status.c") +endif() endif(LWS_WITH_PLUGINS AND LWS_WITH_SHARED) # @@ -1399,6 +1404,11 @@ if (LWS_WITH_PLUGINS) PERMISSIONS OWNER_WRITE OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_READ GROUP_READ WORLD_READ DESTINATION share/libwebsockets-test-server/plugins COMPONENT plugins) +if (LWS_WITH_SERVER_STATUS) + install(FILES plugins/server-status.html + DESTINATION share/libwebsockets-test-server/server-status + COMPONENT examples) +endif() endif() # Install the LibwebsocketsConfig.cmake and LibwebsocketsConfigVersion.cmake @@ -1466,6 +1476,7 @@ message(" LWS_WITH_HTTP_PROXY = ${LWS_WITH_HTTP_PROXY}") message(" LIBHUBBUB_LIBRARIES = ${LIBHUBBUB_LIBRARIES}") message(" PLUGINS = ${PLUGINS_LIST}") message(" LWS_WITH_ACCESS_LOG = ${LWS_WITH_ACCESS_LOG}") +message(" LWS_WITH_SERVER_STATUS = ${LWS_WITH_SERVER_STATUS}") message("---------------------------------------------------------------------") # These will be available to parent projects including libwebsockets using add_subdirectory() diff --git a/README.lwsws.md b/README.lwsws.md index 6b821271..15a8bd31 100644 --- a/README.lwsws.md +++ b/README.lwsws.md @@ -153,6 +153,11 @@ Vhosts can select which plugins they want to offer and give them per-vhost setti ``` +The "x":"y" parameters like "status":"ok" are made available to the protocol during its per-vhost +LWS_CALLBACK_PROTOCOL_INIT (@in is a pointer to a linked list of struct lws_protocol_vhost_options +containing the name and value pointers). + + Other vhost options ------------------- @@ -265,3 +270,28 @@ dumb increment, mirror and status protocol plugins are provided as examples. +lws-server-status plugin +------------------------ + +One provided protocol can be used to monitor the server status. + +Enable the protocol like this on a vhost's ws-protocols section + + "lws-server-status": { + "status": "ok", + "update-ms": "5000" + } + +"update-ms" is used to control how often updated JSON is sent on a ws link. + +And map the provided HTML into the vhost in the mounts section + + { + "mountpoint": "/server-status", + "origin": "file:///usr/local/share/libwebsockets-test-server/server-status", + "default": "server-status.html" + } + +You might choose to put it on its own vhost which has "interface": "lo", so it's not +externally visible. + diff --git a/lib/context.c b/lib/context.c index e746e8cb..8ee5ffa7 100644 --- a/lib/context.c +++ b/lib/context.c @@ -440,7 +440,6 @@ lws_create_context(struct lws_context_creation_info *info) struct rlimit rt; #endif - lwsl_notice("Initial logging level %d\n", log_level); lwsl_notice("Libwebsockets version: %s\n", library_version); #if LWS_POSIX @@ -471,6 +470,8 @@ lws_create_context(struct lws_context_creation_info *info) lwsl_err("No memory for websocket context\n"); return NULL; } + + context->time_up = time(NULL); #ifndef LWS_NO_DAEMONIZE if (pid_daemon) { context->started_with_parent = pid_daemon; diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 22368a22..58d00064 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -2234,6 +2234,8 @@ lws_access_log(struct lws *wsi) } #endif +#ifdef LWS_WITH_SERVER_STATUS + LWS_EXTERN int lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) { @@ -2254,10 +2256,15 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) buf += snprintf(buf, end - buf, "{\n \"name\":\"%s\",\n" " \"port\":\"%d\",\n" - " \"use-ssl\":\"%d\",\n" + " \"use_ssl\":\"%d\",\n" " \"sts\":\"%d\",\n" " \"rx\":\"%lu\",\n" - " \"tx\":\"%lu\",\n", + " \"tx\":\"%lu\",\n" + " \"conn\":\"%lu\",\n" + " \"trans\":\"%lu\",\n" + " \"ws_upg\":\"%lu\",\n" + " \"http2_upg\":\"%lu\"" + , vh->name, vh->listen_port, #ifdef LWS_OPENSSL_SUPPORT vh->use_ssl, @@ -2265,7 +2272,8 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) 0, #endif !!(vh->options & LWS_SERVER_OPTION_STS), - vh->rx, vh->tx + vh->rx, vh->tx, vh->conn, vh->trans, vh->ws_upgrades, + vh->http2_upgrades ); if (vh->mount_list) { @@ -2277,7 +2285,7 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) buf += snprintf(buf, end - buf, ","); buf += snprintf(buf, end - buf, "\n {\n \"mountpoint\":\"%s\",\n" - " \"origin\":\"%s%s\"" + " \"origin\":\"%s%s\"\n" , m->mountpoint, prots[m->origin_protocol], @@ -2316,3 +2324,34 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) return buf - orig; } + + +LWS_EXTERN LWS_VISIBLE int +lws_json_dump_context(const struct lws_context *context, char *buf, int len) +{ + char *orig = buf, *end = buf + len - 1, first = 1; + struct lws_vhost *vh = context->vhost_list; + time_t t = time(NULL); + + buf += snprintf(buf, end - buf, "{ " + "\"uptime\":\"%ld\",\n" + "\"wsi_alive\":\"%d\",\n" + "\"vhosts\":[\n ", + (unsigned long)(t - context->time_up), + context->count_wsi_allocated); + + while (vh) { + if (!first) + if(buf != end) + *buf++ = ','; + buf += lws_json_dump_vhost(vh, buf, end - buf); + first = 0; + vh = vh->vhost_next; + } + + buf += snprintf(buf, end - buf, "]}\n "); + + return buf - orig; +} + +#endif diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 0f6687dd..60bbe4f2 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -1566,6 +1566,9 @@ lws_write_http_mount(struct lws_http_mount *next, struct lws_http_mount **res, LWS_EXTERN int lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len); +LWS_EXTERN int +lws_json_dump_context(const struct lws_context *context, char *buf, int len); + LWS_VISIBLE LWS_EXTERN void lws_set_log_level(int level, void (*log_emit_function)(int level, const char *line)); diff --git a/lib/output.c b/lib/output.c index c3fc2c84..451ca0df 100644 --- a/lib/output.c +++ b/lib/output.c @@ -251,6 +251,8 @@ LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, size_t len, #ifdef LWS_WITH_ACCESS_LOG wsi->access_log.sent += len; #endif + if (wsi->vhost) + wsi->vhost->tx += len; if (wsi->state == LWSS_ESTABLISHED && wsi->u.ws.tx_draining_ext) { /* remove us from the list */ @@ -634,8 +636,11 @@ lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len) int n; n = recv(wsi->sock, (char *)buf, len, 0); - if (n > 0) + if (n > 0) { + if (wsi->vhost) + wsi->vhost->rx += n; return n; + } #if LWS_POSIX if (LWS_ERRNO == LWS_EAGAIN || LWS_ERRNO == LWS_EWOULDBLOCK || diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index f91a5c13..60c038ca 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -668,7 +668,7 @@ struct lws_vhost { #ifndef LWS_NO_EXTENSIONS const struct lws_extension *extensions; #endif - unsigned long rx, tx; + unsigned long rx, tx, conn, trans, ws_upgrades, http2_upgrades; int listen_port; unsigned int http_proxy_port; @@ -698,6 +698,7 @@ struct lws_vhost { struct lws_context { time_t last_timeout_check_s; + time_t time_up; struct lws_plat_file_ops fops; struct lws_context_per_thread pt[LWS_MAX_SMP]; #ifdef _WIN32 @@ -1255,6 +1256,7 @@ struct lws { unsigned int socket_is_permanently_unusable:1; unsigned int rxflow_change_to:2; unsigned int more_rx_waiting:1; /* has to live here since ah may stick to end */ + unsigned int conn_stat_done:1; #ifdef LWS_WITH_ACCESS_LOG unsigned int access_log_pending:1; #endif diff --git a/lib/server.c b/lib/server.c index 76159080..41e6339a 100644 --- a/lib/server.c +++ b/lib/server.c @@ -700,6 +700,23 @@ lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) lwsl_debug("%s: wsi->more_rx_waiting=%d\n", __func__, wsi->more_rx_waiting); + /* select vhost */ + + if (lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { + struct lws_vhost *vhost = lws_select_vhost( + context, wsi->vhost->listen_port, + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)); + + if (vhost) + wsi->vhost = vhost; + } + + wsi->vhost->trans++; + if (!wsi->conn_stat_done) { + wsi->vhost->conn++; + wsi->conn_stat_done = 1; + } + wsi->mode = LWSCM_PRE_WS_SERVING_ACCEPT; lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); @@ -708,12 +725,14 @@ lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) { if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), "websocket")) { + wsi->vhost->ws_upgrades++; lwsl_info("Upgrade to ws\n"); goto upgrade_ws; } #ifdef LWS_USE_HTTP2 if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), "h2c")) { + wsi->vhost->http2_upgrades++; lwsl_info("Upgrade to h2c\n"); goto upgrade_h2c; } @@ -728,17 +747,6 @@ lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) lwsl_info("No upgrade\n"); ah = wsi->u.hdr.ah; - /* select vhost */ - - if (lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { - struct lws_vhost *vhost = lws_select_vhost( - context, wsi->vhost->listen_port, - lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)); - - if (vhost) - wsi->vhost = vhost; - } - lws_union_transition(wsi, LWSCM_HTTP_SERVING_ACCEPTED); wsi->state = LWSS_HTTP; wsi->u.http.fd = LWS_INVALID_FILE; diff --git a/lws_config.h.in b/lws_config.h.in index a9298ff4..87a0c6d2 100644 --- a/lws_config.h.in +++ b/lws_config.h.in @@ -94,6 +94,7 @@ /* Http access log support */ #cmakedefine LWS_WITH_ACCESS_LOG +#cmakedefine LWS_WITH_SERVER_STATUS /* Maximum supported service threads */ #define LWS_MAX_SMP ${LWS_MAX_SMP} diff --git a/plugins/protocol_dumb_increment.c b/plugins/protocol_dumb_increment.c index fd4eb299..5c1a343d 100644 --- a/plugins/protocol_dumb_increment.c +++ b/plugins/protocol_dumb_increment.c @@ -59,7 +59,6 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, switch (reason) { case LWS_CALLBACK_PROTOCOL_INIT: - lwsl_notice("%s: pvo %p\n", __func__, in); vhd = lws_protocol_vh_priv_zalloc(lws_vhost_get(wsi), lws_protocol_get(wsi), sizeof(struct per_vhost_data__dumb_increment)); @@ -73,6 +72,8 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_PROTOCOL_DESTROY: + if (!vhd) + break; uv_timer_stop(&vhd->timeout_watcher); break; diff --git a/plugins/protocol_lws_mirror.c b/plugins/protocol_lws_mirror.c index 05614db9..4b9319c4 100644 --- a/plugins/protocol_lws_mirror.c +++ b/plugins/protocol_lws_mirror.c @@ -66,6 +66,8 @@ callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason, break; case LWS_CALLBACK_PROTOCOL_DESTROY: /* per vhost */ + if (!v) + break; 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) { diff --git a/plugins/protocol_lws_server_status.c b/plugins/protocol_lws_server_status.c new file mode 100644 index 00000000..588ada55 --- /dev/null +++ b/plugins/protocol_lws_server_status.c @@ -0,0 +1,147 @@ +/* + * 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 + +#define LWS_SS_VERSIONS 3 + +struct lws_ss_dumps { + char buf[32768]; + int length; +}; + +static struct lws_ss_dumps d[LWS_SS_VERSIONS]; +static int last_dump; +static uv_timer_t timeout_watcher; +static struct lws_context *context; +static int tow_flag; + +struct per_session_data__server_status { + int ver; + int pos; +}; + +static const struct lws_protocols protocols[]; + +static void +uv_timeout_cb_server_status(uv_timer_t *w +#if UV_VERSION_MAJOR == 0 + , int status +#endif +) +{ + int n; + + last_dump = (last_dump + 1) % LWS_SS_VERSIONS; + n = lws_json_dump_context(context, d[last_dump].buf + LWS_PRE, + sizeof(d[0].buf) - LWS_PRE); + d[last_dump].length = n; + + lws_callback_on_writable_all_protocol(context, &protocols[0]); +} + +static int +callback_lws_server_status(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct lws_protocol_vhost_options *pvo = + (struct lws_protocol_vhost_options *)in; + int m, period = 1000; + + switch (reason) { + + case LWS_CALLBACK_ESTABLISHED: + lwsl_info("%s: LWS_CALLBACK_ESTABLISHED\n", __func__); + lws_callback_on_writable(wsi); + break; + + case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */ + if (tow_flag) + break; + while (pvo) { + if (!strcmp(pvo->name, "update-ms")) + period = atoi(pvo->value); + pvo = pvo->next; + } + context = lws_get_context(wsi); + uv_timer_init(lws_uv_getloop(context, 0), &timeout_watcher); + uv_timer_start(&timeout_watcher, + uv_timeout_cb_server_status, 2000, period); + tow_flag = 1; + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: /* per vhost */ + if (!tow_flag) + break; + uv_timer_stop(&timeout_watcher); + tow_flag = 0; + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + m = lws_write(wsi, (unsigned char *) + d[last_dump].buf + LWS_PRE, d[last_dump].length, + LWS_WRITE_TEXT); + if (m < 0) + return -1; + break; + + case LWS_CALLBACK_RECEIVE: + break; + + default: + break; + } + + return 0; +} + +static const struct lws_protocols protocols[] = { + { + "lws-server-status", + callback_lws_server_status, + sizeof(struct per_session_data__server_status), + 1024, + }, +}; + +LWS_VISIBLE int +init_protocol_lws_server_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_server_status(struct lws_context *context) +{ + return 0; +} + diff --git a/plugins/protocol_lws_status.c b/plugins/protocol_lws_status.c index 76d2563e..38281b76 100644 --- a/plugins/protocol_lws_status.c +++ b/plugins/protocol_lws_status.c @@ -102,8 +102,7 @@ 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; + (struct per_session_data__lws_status *)user, **pp; char name[128], rip[128]; int m; diff --git a/plugins/server-status.html b/plugins/server-status.html new file mode 100644 index 00000000..0ecebb4b --- /dev/null +++ b/plugins/server-status.html @@ -0,0 +1,219 @@ + + + + + LWS Server Status + + + + +
+
+ + + + +
+
...
+
+ +
+ + + + +