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)
"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)
#
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
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()
```
+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
-------------------
+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.
+
struct rlimit rt;
#endif
-
lwsl_notice("Initial logging level %d\n", log_level);
lwsl_notice("Libwebsockets version: %s\n", library_version);
#if LWS_POSIX
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;
}
#endif
+#ifdef LWS_WITH_SERVER_STATUS
+
LWS_EXTERN int
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,
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) {
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],
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
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));
#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 */
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 ||
#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;
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
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
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);
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;
}
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;
/* 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}
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));
break;
case LWS_CALLBACK_PROTOCOL_DESTROY:
+ if (!vhd)
+ break;
uv_timer_stop(&vhd->timeout_watcher);
break;
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) {
--- /dev/null
+/*
+ * libwebsockets-test-server - libwebsockets test implementation
+ *
+ * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ *
+ * 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 <string.h>
+
+#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;
+}
+
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;
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
+ <title>LWS Server Status</title>
+<style type="text/css">
+ span.title { font-size:18pt; font: Arial; font-weight:normal;
+ text-align:center; color:#000000; }
+ span.mount { font-size:10pt; font: Arial; font-weight:normal;
+ text-align:center; color:#000000; }
+ .browser { font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#ffff00; vertical-align:middle; text-align:center; background:#d0b070; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px;}
+ .group2 { vertical-align:middle;
+ text-align:center;
+ background:#f0f0e0;
+ padding:12px;
+ -webkit-border-radius:10px;
+ -moz-border-radius:10px;
+ border-radius:10px; }
+ .explain { vertical-align:middle;
+ text-align:center;
+ background:#f0f0c0; padding:12px;
+ -webkit-border-radius:10px;
+ -moz-border-radius:10px;
+ border-radius:10px;
+ color:#404000; }
+ td.wsstatus { vertical-align:middle; width:200px; height:50px;
+ text-align:center;
+ background:#f0f0c0; padding:6px;
+ -webkit-border-radius:8px;
+ -moz-border-radius:8px;
+ border-radius:8px;
+ color:#404000; }
+ td.l { vertical-align:middle;
+ text-align:center;
+ background:#d0d0b0;
+ padding:3px;
+ -webkit-border-radius:3px;
+ -moz-border-radius:3px;
+ border-radius:3px; }
+ td.c { vertical-align:middle;
+ text-align:center;
+ background:#c0c0a0;
+ padding:3px;
+ -webkit-border-radius:3px;
+ -moz-border-radius:3px;
+ border-radius:3px; }
+ td.t { vertical-align:middle;
+ text-align:center;
+ background:#e0e0c0;
+ padding:3px;
+ -webkit-border-radius:3px;
+ -moz-border-radius:3px;
+ border-radius:3px; }
+ .content { vertical-align:top; text-align:center; background:#fffff0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; }
+ .canvas { vertical-align:top; text-align:center; background:#efefd0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; }
+.tabs {
+ position: relative;
+ min-height: 750px; /* This part sucks */
+ clear: both;
+ margin: 25px 0;
+}
+.tab {
+ float: left;
+}
+.tab label {
+ background: #eee;
+ padding: 10px;
+ border: 1px solid #ccc;
+ margin-left: -1px;
+ position: relative;
+ left: 1px;
+}
+.tab [type=radio] {
+ display: none;
+}
+.content {
+ position: absolute;
+ top: 28px;
+ left: 0;
+ background: white;
+ right: 0;
+ bottom: 0;
+ padding: 20px;
+ border: 1px solid #ccc;
+}
+[type=radio]:checked ~ label {
+ background: white;
+ border-bottom: 1px solid white;
+ z-index: 2;
+}
+[type=radio]:checked ~ label ~ .content {
+ z-index: 1;
+}
+</style>
+</head>
+
+<body>
+<header></header>
+<article>
+
+<table><tr><td align=center>
+<div id="conninfo">...</div>
+</td></tr>
+
+
+</table>
+
+</article>
+
+<script>
+
+/*
+ * We display untrusted stuff in html context... reject anything
+ * that has HTML stuff in it
+ */
+
+function san(s)
+{
+ if (s.search("<") != -1)
+ return "invalid string";
+
+ return s;
+}
+
+ var pos = 0;
+
+function get_appropriate_ws_url()
+{
+ var pcol;
+ var u = document.URL;
+
+ /*
+ * We open the websocket encrypted if this page came on an
+ * https:// url itself, otherwise unencrypted
+ */
+
+ if (u.substring(0, 5) == "https") {
+ pcol = "wss://";
+ u = u.substr(8);
+ } else {
+ pcol = "ws://";
+ if (u.substring(0, 4) == "http")
+ u = u.substr(7);
+ }
+
+ u = u.split('/');
+
+ /* + "/xxx" bit is for IE10 workaround */
+
+ return pcol + u[0] + "/xxx";
+}
+
+
+ var socket_status, jso, s;
+
+ if (typeof MozWebSocket != "undefined") {
+ socket_status = new MozWebSocket(get_appropriate_ws_url(),
+ "lws-server-status");
+ } else {
+ socket_status = new WebSocket(get_appropriate_ws_url(),
+ "lws-server-status");
+ }
+
+
+ try {
+ socket_status.onopen = function() {
+ }
+
+ socket_status.onmessage =function got_packet(msg) {
+ document.getElementById("conninfo").innerHTML = "<pre>"+msg.data+"</pre>";
+ jso = JSON.parse(msg.data);
+
+ s="<table><tr><td class=\"c\">" +
+ "Context</td><td>Uptime: " + san(jso.uptime) + "<br>" +
+ "Current wsi alive: " + san(jso.wsi_alive) +
+ "</td></tr>";
+ var n;
+ for (n = 0; n < jso.vhosts.length; n++) {
+ s = s + "<tr><td class=\"l\">vhost " + (n + 1) +
+ "</td><td><b>" + san(jso.vhosts[n].name) + ":" +
+ san(jso.vhosts[n].port) +
+ "</b><br>" +
+ "ssl " + san(jso.vhosts[n].use_ssl) + ", " +
+ "sts " + san(jso.vhosts[n].sts) + "<br>" +
+ "rx " + san(jso.vhosts[n].rx) + ", " +
+ "tx " + san(jso.vhosts[n].tx) + "<br>" +
+ "total connections " + san(jso.vhosts[n].conn) + ", " +
+ "total http transactions " + san(jso.vhosts[n].trans) + "<br>" +
+ "Upgrades to ws " + san(jso.vhosts[n].ws_upg) + ", " +
+ "to http2 " + san(jso.vhosts[n].http2_upg) + "<br>" +
+ "<table><tr><td class=t colspan=2>Mounts</td></tr>";
+
+ var m;
+ for (m = 0; m < jso.vhosts[n].mounts.length; m++) {
+ s = s + "<tr><td>";
+ s = s + san(jso.vhosts[n].mounts[m].mountpoint) +
+ "</td><td>" +
+ san(jso.vhosts[n].mounts[m].origin);
+ s = s + "</td></tr>"
+ }
+ s = s + "</table>";
+ s = s + "</td></tr>";
+ }
+ s = s + "</table>";
+
+ document.getElementById("conninfo").innerHTML = s;
+ }
+
+ socket_status.onclose = function(){
+ document.getElementById("s_statustd").style.backgroundColor = "#ff4040";
+ document.getElementById("s_status").textContent = " websocket connection CLOSED ";
+ }
+ } catch(exception) {
+ alert('<p>Error' + exception);
+ }
+</script>
+
+</body>
+</html>