This allows mounts to define the caching policy of the files inside them.
Support is added in lwsws for controlling it from the config files.
The api for serializing a mount struct opaquely is removed and lws_http_mount struct
made public... it was getting out of control trying to hide the options.
Signed-off-by: Andy Green <andy@warmcat.com>
DESTINATION share/libwebsockets-test-server/plugins
COMPONENT plugins)
if (LWS_WITH_SERVER_STATUS)
- install(FILES plugins/server-status.html
+ install(FILES plugins/server-status.html;plugins/lwsws-logo.png
DESTINATION share/libwebsockets-test-server/server-status
COMPONENT examples)
endif()
would cause the url /git/myrepo to pass "myrepo" to the cgi /var/www/cgi-bin/cgit and send the results to the client.
- When using a cgi:// protcol origin at a mountpoint, you may also give cgi environment variables specific to the mountpoint like this
+Note: currently only a fixed set of mimetypes are supported.
+
+
+Other mount options
+-------------------
+
+1) When using a cgi:// protcol origin at a mountpoint, you may also give cgi environment variables specific to the mountpoint like this
```
{
This allows you to customize one cgi depending on the mountpoint (and / or vhost).
- It's also possible to set the cgi timeout (in secs) per cgi:// mount, like this
+2) It's also possible to set the cgi timeout (in secs) per cgi:// mount, like this
```
"cgi-timeout": "30"
```
+3) Cache policy of the files in the mount can also be set. If no
+options are given, the content is marked uncacheable.
-Note: currently only a fixed set of mimetypes are supported.
+ {
+ "mountpoint": "/",
+ "origin": "file:///var/www/mysite.com",
+ "cache-max-age": "60", # seconds
+ "cache-reuse": "1", # allow reuse at client at all
+ "cache-revalidate": "1", # check it with server each time
+ "cache-intermediaries": "1" # allow intermediary caches to hold
+ }
Plugins
">https://",
};
-LWS_VISIBLE LWS_EXTERN int
-lws_write_http_mount(struct lws_http_mount *next, struct lws_http_mount **res,
- void *store, const char *mountpoint, const char *origin,
- const char *def, struct lws_protocol_vhost_options *cgienv,
- int cgi_timeout)
-{
- struct lws_http_mount *m;
- void *orig = store;
- unsigned long l = (unsigned long)store;
- int n;
-
- if (l & 15)
- l += 16 - (l & 15);
-
- store = (void *)l;
- m = (struct lws_http_mount *)store;
- *res = m;
-
- m->def = def;
- m->mountpoint = mountpoint;
- m->mountpoint_len = (unsigned char)strlen(mountpoint);
- m->mount_next = NULL;
- m->cgienv = cgienv;
- m->cgi_timeout = cgi_timeout;
-
- if (next)
- next->mount_next = m;
-
- for (n = 0; n < ARRAY_SIZE(mount_protocols); n++)
- if (!strncmp(origin, mount_protocols[n],
- strlen(mount_protocols[n]))) {
- m->origin_protocol = n;
- m->origin = origin + strlen(mount_protocols[n]);
- break;
- }
-
- if (n == ARRAY_SIZE(mount_protocols)) {
- lwsl_err("unsupported protocol:// %s\n", origin);
- return 0; /* ie, fail */
- }
-
- 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)
if (code == 200)
description = "OK";
- if (code >= 300 && code < 400)
- description = "Redirect";
+ if (code == 304)
+ description = "Not Modified";
+ else
+ if (code >= 300 && code < 400)
+ description = "Redirect";
if (wsi->u.http.request_version < ARRAY_SIZE(hver))
p1 = hver[wsi->u.http.request_version];
buf += snprintf(buf, end - buf, ",");
buf += snprintf(buf, end - buf,
"\n {\n \"mountpoint\":\"%s\",\n"
- " \"origin\":\"%s%s\"\n"
+ " \"origin\":\"%s%s\",\n"
+ " \"cache_max_age\":\"%d\",\n"
+ " \"cache_reuse\":\"%d\",\n"
+ " \"cache_revalidate\":\"%d\",\n"
+ " \"cache_intermediaries\":\"%d\"\n"
,
m->mountpoint,
prots[m->origin_protocol],
- m->origin);
+ m->origin,
+ m->cache_max_age,
+ m->cache_reusable,
+ m->cache_revalidate,
+ m->cache_intermediaries);
if (m->def)
buf += snprintf(buf, end - buf,
",\n \"default\":\"%s\"",
const char *value;
};
+struct lws_http_mount {
+ struct lws_http_mount *mount_next;
+ const char *mountpoint; /* mountpoint in http pathspace, eg, "/" */
+ const char *origin; /* path to be mounted, eg, "/var/www/warmcat.com" */
+ const char *def; /* default target, eg, "index.html" */
+
+ struct lws_protocol_vhost_options *cgienv;
+
+ int cgi_timeout;
+ int cache_max_age;
+
+ unsigned int cache_reusable:1;
+ unsigned int cache_revalidate:1;
+ unsigned int cache_intermediaries:1;
+
+ unsigned char origin_protocol;
+ unsigned char mountpoint_len;
+};
+
/**
* struct lws_context_creation_info - parameters to create context with
*
void *_unused[4];
};
-struct lws_http_mount;
-
enum {
LWSMPRO_HTTP,
LWSMPRO_HTTPS,
LWSMPRO_REDIR_HTTPS,
};
-LWS_VISIBLE LWS_EXTERN int
-lws_write_http_mount(struct lws_http_mount *next, struct lws_http_mount **res,
- void *store, const char *mountpoint, const char *origin,
- const char *def,
- struct lws_protocol_vhost_options *cgienv,
- int cgi_timeout);
-
LWS_EXTERN int
lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len);
unsigned char tid;
};
-struct lws_http_mount {
- struct lws_http_mount *mount_next;
- const char *mountpoint; /* mountpoint in http pathspace, eg, "/" */
- const char *origin; /* path to be mounted, eg, "/var/www/warmcat.com" */
- const char *def; /* default target, eg, "index.html" */
-
- struct lws_protocol_vhost_options *cgienv;
-
- int cgi_timeout;
-
- unsigned char origin_protocol;
- unsigned char mountpoint_len;
-};
-
/*
* virtual host -related context information
* vhostwide SSL context
#ifndef LWS_NO_CLIENT
int chunk_remaining;
#endif
+ unsigned int cache_secs;
unsigned int hdr_parsing_completed:1;
unsigned int http2_substream: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;
+ unsigned int cache_reuse:1;
+ unsigned int cache_revalidate:1;
+ unsigned int cache_intermediaries:1;
#ifdef LWS_WITH_ACCESS_LOG
unsigned int access_log_pending:1;
#endif
#if LWS_POSIX
listen(wsi->sock, LWS_SOMAXCONN);
- } /* for each thread able to independently lister */
+ } /* for each thread able to independently listen */
#else
mbed3_tcp_stream_bind(wsi->sock, info->port, wsi);
#endif
const char *mimetype;
struct stat st;
char path[256], sym[256];
+ unsigned char *p = (unsigned char *)sym + 32 + LWS_PRE, *start = p;
+ unsigned char *end = p + sizeof(sym) - 32 - LWS_PRE;
int n, spin = 0;
- lwsl_notice("%s: %s %s\n", __func__, uri, origin);
snprintf(path, sizeof(path) - 1, "%s/%s", origin, uri);
do {
} while ((S_IFMT & st.st_mode) != S_IFREG && spin < 5);
- if (spin == 5) {
+ if (spin == 5)
lwsl_err("symlink loop %s \n", path);
+
+ n = sprintf(sym, "%08lX%08lX", (unsigned long)st.st_size,
+ (unsigned long)st.st_mtime);
+
+ if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_IF_NONE_MATCH)) {
+ /*
+ * he thinks he has some version of it already,
+ * check if the tag matches
+ */
+ if (!strcmp(sym, lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_IF_NONE_MATCH))) {
+
+ lwsl_notice("%s: ETAG match %s %s\n", __func__,
+ uri, origin);
+
+ /* we don't need to send the payload */
+ if (lws_add_http_header_status(wsi, 304, &p, end))
+ return -1;
+ if (lws_add_http_header_by_token(wsi,
+ WSI_TOKEN_HTTP_ETAG,
+ (unsigned char *)sym, n, &p, end))
+ return -1;
+ if (lws_finalize_http_header(wsi, &p, end))
+ return -1;
+
+ n = lws_write(wsi, start, p - start,
+ LWS_WRITE_HTTP_HEADERS);
+ if (n != (p - start)) {
+ lwsl_err("_write returned %d from %d\n", n, p - start);
+ return -1;
+ }
+
+ return lws_http_transaction_completed(wsi);
+ }
}
+ if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_ETAG,
+ (unsigned char *)sym, n, &p, end))
+ return -1;
+
mimetype = get_mimetype(path);
if (!mimetype) {
lwsl_err("unknown mimetype for %s", path);
goto bail;
}
- n = lws_serve_http_file(wsi, path, mimetype, NULL, 0);
+ n = lws_serve_http_file(wsi, path, mimetype, (char *)start, p - start);
if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi)))
return -1; /* error or can't reuse connection: close the socket */
n = strlen(s);
if (s[0] == '\0' || (n == 1 && s[n - 1] == '/'))
s = (char *)hit->def;
-
if (!s)
s = "index.html";
- // lwsl_err("okok\n");
+ wsi->cache_secs = hit->cache_max_age;
+ wsi->cache_reuse = hit->cache_reusable;
+ wsi->cache_revalidate = hit->cache_revalidate;
+ wsi->cache_intermediaries = hit->cache_intermediaries;
n = lws_http_serve(wsi, s, hit->origin);
} else
lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type,
const char *other_headers, int other_headers_len)
{
+ static const char * const intermediates[] = { "private", "public" };
struct lws_context *context = lws_get_context(wsi);
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
+ char cache_control[50], *cc = "no-store";
unsigned char *response = pt->serv_buf + LWS_PRE;
unsigned char *p = response;
unsigned char *end = p + LWS_MAX_SOCKET_IO_BUF - LWS_PRE;
- int ret = 0;
+ int ret = 0, cclen = 8;
wsi->u.http.fd = lws_plat_file_open(wsi, file, &wsi->u.http.filelen,
O_RDONLY);
if (lws_add_http_header_status(wsi, 200, &p, end))
return -1;
- if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER,
- (unsigned char *)"libwebsockets", 13,
- &p, end))
- return -1;
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
(unsigned char *)content_type,
strlen(content_type), &p, end))
if (lws_add_http_header_content_length(wsi, wsi->u.http.filelen, &p, end))
return -1;
+ if (wsi->cache_secs && wsi->cache_reuse) {
+ if (wsi->cache_revalidate) {
+ cc = cache_control;
+ cclen = sprintf(cache_control, "%s max-age: %u",
+ intermediates[wsi->cache_intermediaries],
+ wsi->cache_secs);
+ } else {
+ cc = "no-cache";
+ cclen = 8;
+ }
+ }
+
+ if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CACHE_CONTROL,
+ (unsigned char *)cc, cclen, &p, end))
+ return -1;
+
if (other_headers) {
if ((end - p) < other_headers_len)
return -1;
"vhosts[].mounts[].default",
"vhosts[].mounts[].cgi-timeout",
"vhosts[].mounts[].cgi-env[].*",
+ "vhosts[].mounts[].cache-max-age",
+ "vhosts[].mounts[].cache-reuse",
+ "vhosts[].mounts[].cache-revalidate",
+ "vhosts[].mounts[].cache-intermediaries",
"vhosts[].ws-protocols[].*.*",
"vhosts[].ws-protocols[].*",
"vhosts[].ws-protocols[]",
LEJPVP_DEFAULT,
LEJPVP_CGI_TIMEOUT,
LEJPVP_CGI_ENV,
+ LEJPVP_MOUNT_CACHE_MAX_AGE,
+ LEJPVP_MOUNT_CACHE_REUSE,
+ LEJPVP_MOUNT_CACHE_REVALIDATE,
+ LEJPVP_MOUNT_CACHE_INTERMEDIARIES,
LEJPVP_PROTOCOL_NAME_OPT,
LEJPVP_PROTOCOL_NAME,
LEJPVP_PROTOCOL,
const struct lws_extension *extensions;
char *p, *end, valid;
struct lws_http_mount *head, *last;
- char *mountpoint, *origin, *def;
+
struct lws_protocol_vhost_options *pvo;
- struct lws_protocol_vhost_options *mp_cgienv;
- int cgi_timeout;
+ struct lws_http_mount m;
};
static void *
}
if (reason == LEJPCB_OBJECT_START &&
- ctx->path_match == LEJPVP_MOUNTS + 1) {
- a->mountpoint = NULL;
- a->origin = NULL;
- a->def = NULL;
- a->mp_cgienv = NULL;
- a->cgi_timeout = 0;
- }
+ ctx->path_match == LEJPVP_MOUNTS + 1)
+ memset(&a->m, 0, sizeof(a->m));
/* this catches, eg, vhosts[].ws-protocols[].xxx-protocol */
if (reason == LEJPCB_OBJECT_START &&
if (reason == LEJPCB_OBJECT_END &&
ctx->path_match == LEJPVP_MOUNTS + 1) {
- if (!a->mountpoint || !a->origin) {
+ static const char * const mount_protocols[] = {
+ "http://",
+ "https://",
+ "file://",
+ "cgi://",
+ ">http://",
+ ">https://",
+ };
+
+ if (!a->m.mountpoint || !a->m.origin) {
lwsl_err("mountpoint and origin required\n");
return 1;
}
-
- n = lws_write_http_mount(a->last, &m, a->p, a->mountpoint,
- a->origin, a->def, a->mp_cgienv,
- a->cgi_timeout);
- if (!n)
+ m = lwsws_align(a);
+ memcpy(m, &a->m, sizeof(*m));
+ if (a->last)
+ a->last->mount_next = m;
+
+ for (n = 0; n < ARRAY_SIZE(mount_protocols); n++)
+ if (!strncmp(a->m.origin, mount_protocols[n],
+ strlen(mount_protocols[n]))) {
+ m->origin_protocol = n;
+ m->origin = a->m.origin + strlen(mount_protocols[n]);
+ break;
+ }
+
+ if (n == ARRAY_SIZE(mount_protocols)) {
+ lwsl_err("unsupported protocol:// %s\n", a->m.origin);
return 1;
- a->p += n;
+ }
+
+ a->p += sizeof(*m);
if (!a->head)
a->head = m;
a->info->log_filepath = a->p;
break;
case LEJPVP_MOUNTPOINT:
- a->mountpoint = a->p;
+ a->m.mountpoint = a->p;
+ a->m.mountpoint_len = strlen(ctx->buf);
break;
case LEJPVP_ORIGIN:
- a->origin = a->p;
+ a->m.origin = a->p;
break;
case LEJPVP_DEFAULT:
- a->def = a->p;
+ a->m.def = a->p;
break;
+ case LEJPVP_MOUNT_CACHE_MAX_AGE:
+ a->m.cache_max_age = atoi(ctx->buf);
+ return 0;
+ case LEJPVP_MOUNT_CACHE_REUSE:
+ a->m.cache_reusable = arg_to_bool(ctx->buf);
+ return 0;
+ case LEJPVP_MOUNT_CACHE_REVALIDATE:
+ a->m.cache_revalidate = arg_to_bool(ctx->buf);
+ return 0;
+ case LEJPVP_MOUNT_CACHE_INTERMEDIARIES:
+ a->m.cache_intermediaries = arg_to_bool(ctx->buf);;
+ return 0;
case LEJPVP_CGI_TIMEOUT:
- a->cgi_timeout = atoi(ctx->buf);
+ a->m.cgi_timeout = atoi(ctx->buf);
return 0;
case LEJPVP_KEEPALIVE_TIMEOUT:
a->info->keepalive_timeout = atoi(ctx->buf);
return 0;
case LEJPVP_CGI_ENV:
mp_cgienv = lwsws_align(a);
- a->p += sizeof(*a->mp_cgienv);
+ a->p += sizeof(*a->m.cgienv);
- mp_cgienv->next = a->mp_cgienv;
- a->mp_cgienv = mp_cgienv;
+ mp_cgienv->next = a->m.cgienv;
+ a->m.cgienv = mp_cgienv;
n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
mp_cgienv->name = a->p;
"gid": "48",
"interface": "eth0",
"count-threads": "1",
+ "server-string": "lwsws",
"init-ssl": "yes"
}
}
+# comment
+
{
"vhosts": [ {
- "name": "localhost",
- "port": "80",
+ "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",
+ "access-log": "/var/log/httpd/lws-access-log",
+ "sts": "on",
"mounts": [{
"mountpoint": "/",
"origin": "file:///var/www/libwebsockets.org",
- "default": "index.html"
+ "default": "index.html",
+ "cache-max-age": "60",
+ "cache-reuse": "1",
+ "cache-revalidate": "1",
+ "cache-intermediaries": "0"
}, {
+ # this hooks us up to cgit cgi part
"mountpoint": "/git",
"origin": "cgi:///var/www/cgi-bin/cgit",
"default": "/",
- "cgi-env": [{
- "CGIT_CONFIG": "/etc/cgitrc/libwebsockets.org"
- }]
- }, {
- "mountpoint": "/cgit-data",
+ "cgi-env": [{
+ "CGIT_CONFIG": "/etc/cgitrc/git.libwebsockets.org"
+ }],
+ # we can also set up per-cgi process timeout
+ "cgi-timeout": "30"
+ }, {
+ # this hooks us up to cgit static assets
+ "mountpoint": "/git/cgit-data",
"origin": "file:///usr/share/cgit",
"default": "/"
}, {
- "mountpoint": "/testcgi",
- "origin": "cgi:///usr/local/share/libwebsockets-test-server/lws-cgi-test.sh"
- }, {
"mountpoint": "/mailman",
- "origin": ">http://localhost/mailman/listinfo"
+ "origin": ">https://libwebsockets.org/mailman/listinfo"
}, {
"mountpoint": "/mailman/listinfo",
"origin": "cgi:///usr/lib/mailman/cgi-bin/listinfo"
}, {
"mountpoint": "/pipermail",
"origin": "file:///var/lib/mailman/archives/public",
- "default": "index.html"
+ "default": "index.html"
+ }, {
+ # we used to have a trac, redirect anyone using it to github
+ "mountpoint": "/trac",
+ "origin": ">https://github.com/warmcat/libwebsockets"
+ }, {
+ "mountpoint": "/server-status",
+ "origin": "file:///usr/local/share/libwebsockets-test-server/server-status",
+ "default": "server-status.html"
}, {
"mountpoint": "/testserver",
"origin": "file:///usr/local/share/libwebsockets-test-server",
"default": "test.html"
- }],
+ }
+ ],
# which protocols are enabled for this vhost, and optional
# vhost-specific config options for the protocol
#
"ws-protocols": [{
- "warmcat,timezoom": {
+ "dumb-increment-protocol": {
+ "status": "ok"
+ },
+ "lws-mirror-protocol": {
"status": "ok"
+ },
+ "lws-status": {
+ "status": "ok"
+ },
+ "lws-server-status": {
+ "status": "ok",
+ "update-ms": "5000"
}
+ }],
+ "ws-extensions": [{
+ "extension": "permessage-deflate"
+ }]
+ },
+
+ # redirect any guys coming in on http to https
+ {
+ "name": "libwebsockets.org",
+ "port": "80",
+ "sts": "on",
+ "mounts": [{
+ "mountpoint": "/",
+ "origin": ">https://libwebsockets.org"
}]
- },
+ },
{
- "name": "localhost",
+ # the old test server ran this on :7681, put a redirect
+ # there to take us to the new location
+ "name": "libwebsockets.org",
"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",
"mounts": [{
"mountpoint": "/",
- "origin": ">https://localhost"
+ "origin": ">https://libwebsockets.org/testserver/"
+ }]
+ },
+
+ # old site for mailing list redirect to new one
+ {
+ "name": "ml.libwebsockets.org",
+ "port": "80",
+ "mounts": [{
+ "mountpoint": "/",
+ "origin": ">https://libwebsockets.org/mailman"
}]
},
+ # old site for mailing list redirect to new one
{
- "name": "localhostx",
+ "name": "ml.libwebsockets.org",
+ "port": "443",
+ "mounts": [{
+ "mountpoint": "/",
+ "origin": ">https://libwebsockets.org/mailman"
+ }]
+ },
+
+
+ # redirect any guys coming in on http to https
+ {
+ "name": "git.libwebsockets.org",
"port": "80",
"mounts": [{
"mountpoint": "/",
- "origin": ">https://localhost"
+ "origin": ">https://libwebsockets.org/git"
+ }]
+ },
+ {
+ # the old test server ran this on :7681, put a redirect
+ # there to take us to the new location
+ "name": "git.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": ">https://libwebsockets.org/git"
}]
}
-
- ]
+ }]
}
<header></header>
<article>
-<table><tr><td align=center>
+<table>
+<tr><td><img src="./lwsws-logo.png"></td><td><span id=title class=title>Server status</span></td></tr>
+<tr><td align=center colspan=2>
<div id="conninfo">...</div>
</td></tr>
try {
socket_status.onopen = function() {
- }
+ document.getElementById("title").innerHTML = "Server Status (Active)";
+ }
socket_status.onmessage =function got_packet(msg) {
document.getElementById("conninfo").innerHTML = "<pre>"+msg.data+"</pre>";
"total http transactions " + san(jso.vhosts[n].trans) + "<br>" +
"Upgrades to ws: " + san(jso.vhosts[n].ws_upg) + ", " +
"to http/2: " + san(jso.vhosts[n].http2_upg) + "<br>" +
- "<table><tr><td class=t colspan=2>Mounts</td></tr>";
+ "<table><tr><td class=t>Mountpoint</td><td class=t>Origin</td><td class=t>Cache Policy</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);
+ san(jso.vhosts[n].mounts[m].origin) +
+ "</td><td>";
+ if (parseInt(san(jso.vhosts[n].mounts[m].cache_max_age)))
+ s = s + "max-age: " +
+ san(jso.vhosts[n].mounts[m].cache_max_age) +
+ ", reuse: " +
+ san(jso.vhosts[n].mounts[m].cache_reuse) +
+ ", reval: " +
+ san(jso.vhosts[n].mounts[m].cache_revalidate) +
+ ", inter: " +
+ san(jso.vhosts[n].mounts[m].cache_intermediaries);
s = s + "</td></tr>"
}
s = s + "</table>";
}
socket_status.onclose = function(){
- document.getElementById("s_statustd").style.backgroundColor = "#ff4040";
- document.getElementById("s_status").textContent = " websocket connection CLOSED ";
+ document.getElementById("title").innerHTML = "Server Status (Disconnected)";
}
} catch(exception) {
alert('<p>Error' + exception);