plugin lws server status

Signed-off-by: Andy Green <andy@warmcat.com>
This commit is contained in:
Andy Green 2016-04-15 14:01:29 +08:00
parent 4714cf02f4
commit 980614035f
14 changed files with 490 additions and 22 deletions

View file

@ -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()

View file

@ -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.

View file

@ -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;

View file

@ -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

View file

@ -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));

View file

@ -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 ||

View file

@ -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

View file

@ -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;

View file

@ -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}

View file

@ -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;

View file

@ -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) {

View file

@ -0,0 +1,147 @@
/*
* 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;
}

View file

@ -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;

219
plugins/server-status.html Normal file
View file

@ -0,0 +1,219 @@
<!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>