From: Jaroslav Kysela Date: Sun, 30 Jul 2017 12:48:37 +0000 (+0200) Subject: http server: implement websockets for instant messages X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f9df9b7401e1838767c0e55126fae7d39640db9a;p=thirdparty%2Ftvheadend.git http server: implement websockets for instant messages --- diff --git a/src/http.c b/src/http.c index 2921f3e63..120637f0b 100644 --- a/src/http.c +++ b/src/http.c @@ -39,6 +39,7 @@ #include "notify.h" #include "channels.h" #include "config.h" +#include "htsmsg_json.h" #if ENABLE_ANDROID #include @@ -199,6 +200,7 @@ static const char * http_rc2str(int code) { switch(code) { + case HTTP_STATUS_PSWITCH: /* 101 */ return "Switching Protocols"; case HTTP_STATUS_OK: /* 200 */ return "OK"; case HTTP_STATUS_PARTIAL_CONTENT: /* 206 */ return "Partial Content"; case HTTP_STATUS_FOUND: /* 302 */ return "Found"; @@ -436,6 +438,46 @@ http_send_header(http_connection_t *hc, int rc, const char *content, tcp_write_queue(hc->hc_fd, &hdrs); } +/* + * Transmit a websocket upgrade reply + */ +int +http_send_header_websocket(http_connection_t *hc, const char *protocol) +{ + htsbuf_queue_t hdrs; + const int rc = HTTP_STATUS_PSWITCH; + uint8_t hash[20]; + const char *s; + const char *guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + char encoded[512]; + + s = http_arg_get(&hc->hc_args, "sec-websocket-key"); + if (s == NULL || strlen(s) < 10) { + http_error(hc, HTTP_STATUS_UNSUPPORTED); + return -1; + } + + htsbuf_queue_init(&hdrs, 0); + + htsbuf_qprintf(&hdrs, "%s %d %s\r\n", + http_ver2str(hc->hc_version), rc, http_rc2str(rc)); + + sha1_calc(hash, (uint8_t *)s, strlen(s), (uint8_t *)guid, strlen(guid)); + base64_encode(encoded, sizeof(encoded), hash, sizeof(hash)); + + htsbuf_qprintf(&hdrs, "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: %s\r\n" + "Sec-WebSocket-Protocol: %s\r\n" + "\r\n", + encoded, protocol); + + pthread_mutex_lock(&hc->hc_fd_lock); + tcp_write_queue(hc->hc_fd, &hdrs); + pthread_mutex_unlock(&hc->hc_fd_lock); + return 0; +} + /* * */ @@ -471,6 +513,39 @@ http_encoding_valid(http_connection_t *hc, const char *encoding) return 0; } +/** + * + */ +int +http_header_match(http_connection_t *hc, const char *name, const char *value) +{ + char *tokbuf, *tok, *saveptr = NULL, *q, *s; + + s = http_arg_get(&hc->hc_args, name); + if (s == NULL) + return 0; + tokbuf = tvh_strdupa(s); + tok = strtok_r(tokbuf, ",", &saveptr); + while (tok) { + while (*tok == ' ') + tok++; + s = tok; + if (*s == '"') { + q = strchr(++s, '"'); + if (q) + *q = '\0'; + } else { + q = strchr(s, ' '); + if (q) + *q = '\0'; + } + if (strcasecmp(s, value) == 0) + return 1; + tok = strtok_r(NULL, ",", &saveptr); + } + return 0; +} + /** * */ @@ -915,6 +990,18 @@ http_access_verify_channel(http_connection_t *hc, int mask, return res; } +/** + * + */ +static int +http_websocket_valid(http_connection_t *hc) +{ + if (!http_header_match(hc, "connection", "upgrade") || + http_arg_get(&hc->hc_args, "sec-websocket-key") == NULL) + return HTTP_STATUS_UNSUPPORTED; + return 0; +} + /** * Execute url callback * @@ -928,11 +1015,16 @@ http_exec(http_connection_t *hc, http_path_t *hp, char *remain) if ((hc->hc_username && hc->hc_username[0] == '\0') || http_access_verify(hc, hp->hp_accessmask)) { - if (!hp->hp_no_verification) { + if ((hp->hp_flags & HTTP_PATH_NO_VERIFICATION) == 0) { err = HTTP_STATUS_UNAUTHORIZED; goto destroy; } } + if (hp->hp_flags & HTTP_PATH_WEBSOCKET) { + err = http_websocket_valid(hc); + if (err) + goto destroy; + } err = hp->hp_callback(hc, remain, hp->hp_opaque); destroy: access_destroy(hc->hc_access); @@ -1351,7 +1443,7 @@ http_path_add_modify(const char *path, void *opaque, http_callback_t *callback, hp->hp_callback = callback; hp->hp_accessmask = accessmask; hp->hp_path_modify = path_modify; - hp->hp_no_verification = 0; + hp->hp_flags = 0; pthread_mutex_lock(&http_paths_mutex); LIST_INSERT_HEAD(&http_paths, hp, hp_link); pthread_mutex_unlock(&http_paths_mutex); @@ -1368,6 +1460,160 @@ http_path_add(const char *path, void *opaque, http_callback_t *callback, return http_path_add_modify(path, opaque, callback, accessmask, NULL); } +/** + * + */ +int +http_websocket_send(http_connection_t *hc, uint8_t *buf, uint64_t buflen, + int opcode) +{ + int op, r = 0; + uint8_t b[10]; + uint64_t bsize; + + switch (opcode) { + case HTTP_WSOP_TEXT: op = 0x01; break; + case HTTP_WSOP_BINARY: op = 0x02; break; + case HTTP_WSOP_PING: op = 0x09; break; + case HTTP_WSOP_PONG: op = 0x0a; break; + default: return -1; + } + + b[0] = 0x80 | op; /* FIN + opcode */ + if (buflen <= 125) { + b[1] = buflen; + bsize = 2; + } else if (buflen <= 65535) { + b[1] = 126; + b[2] = (buflen >> 8) & 0xff; + b[3] = buflen & 0xff; + bsize = 4; + } else { + b[1] = 127; + b[2] = (buflen >> 56) & 0xff; + b[3] = (buflen >> 48) & 0xff; + b[4] = (buflen >> 40) & 0xff; + b[5] = (buflen >> 32) & 0xff; + b[6] = (buflen >> 24) & 0xff; + b[7] = (buflen >> 16) & 0xff; + b[8] = (buflen >> 8) & 0xff; + b[9] = (buflen >> 0) & 0xff; + bsize = 10; + } + pthread_mutex_lock(&hc->hc_fd_lock); + if (tvh_write(hc->hc_fd, b, bsize)) + r = -1; + if (r == 0 && tvh_write(hc->hc_fd, buf, buflen)) + r = -1; + pthread_mutex_unlock(&hc->hc_fd_lock); + return r; +} + +/** + * + */ +int +http_websocket_send_json(http_connection_t *hc, htsmsg_t *msg) +{ + htsbuf_queue_t q; + char *s; + int r; + + htsbuf_queue_init(&q, 0); + htsmsg_json_serialize(msg, &q, 0); + s = htsbuf_to_string(&q); + htsbuf_queue_flush(&q); + r = http_websocket_send(hc, (uint8_t *)s, strlen(s), HTTP_WSOP_TEXT); + free(s); + return r; +} + +/** + * + */ +int +http_websocket_read(http_connection_t *hc, htsmsg_t **_res, int timeout) +{ + htsmsg_t *msg, *msg1; + uint8_t b[2], bl[8], *p; + int op, r; + uint64_t plen, pi; + + *_res = NULL; +again: + r = tcp_read_timeout(hc->hc_fd, b, 2, timeout); + if (r == ETIMEDOUT) + return 0; + if (r) /* closed connection */ + return -1; + if ((b[1] & 0x80) == 0) /* accept only masked messages */ + return -1; + switch (b[0] & 0x0f) { /* opcode */ + case 0x0: /* continuation */ + goto again; + case 0x1: /* text */ + op = HTTP_WSOP_TEXT; + break; + case 0x2: /* binary */ + op = HTTP_WSOP_BINARY; + break; + case 0x8: /* close connection */ + return -1; + case 0x9: /* ping */ + op = HTTP_WSOP_PING; + break; + case 0xa: /* pong */ + op = HTTP_WSOP_PONG; + break; + default: + return -1; + } + plen = b[1] & 0x7f; + if (plen == 126) { + if (tcp_read(hc->hc_fd, bl, 2)) + return -1; + plen = (bl[0] << 8) | bl[1]; + } else if (plen == 127) { + if (tcp_read(hc->hc_fd, bl, 8)) + return -1; + plen = ((uint64_t)bl[0] << 56) | ((uint64_t)bl[1] << 48) | + ((uint64_t)bl[2] << 40) | ((uint64_t)bl[3] << 32) | + ((uint64_t)bl[4] << 24) | ((uint64_t)bl[5] << 16) | + ((uint64_t)bl[6] << 8 ) | ((uint64_t)bl[7] << 0); + } + if (plen > 5*1024*1024) + return -1; + p = malloc(plen + 1); + if (p == NULL) + return -1; + if (tcp_read(hc->hc_fd, bl, 4)) + return -1; + if (tcp_read(hc->hc_fd, p, plen)) + return -1; + /* apply mask descrambling */ + for (pi = 0; pi < plen; pi++) + p[pi] ^= bl[pi & 3]; + if (op == HTTP_WSOP_PING) { + http_websocket_send(hc, p, plen, HTTP_WSOP_PONG); + goto again; + } + msg = htsmsg_create_map(); + htsmsg_add_s32(msg, "op", op); + if (op == HTTP_WSOP_TEXT) { + p[plen] = '\0'; + msg1 = p[0] == '{' || p[0] == '[' ? + htsmsg_json_deserialize((char *)p) : NULL; + if (msg1) + htsmsg_add_msg(msg, "json", msg1); + else + htsmsg_add_str(msg, "text", (char *)p); + } else { + htsmsg_add_bin(msg, "bin", p, plen); + } + *_res = msg; + return 0; +} + /** * Parse arguments of a HTTP GET url, not perfect, but works for us */ diff --git a/src/http.h b/src/http.h index 36c8e97bf..f26fc8f8f 100644 --- a/src/http.h +++ b/src/http.h @@ -20,9 +20,10 @@ #define HTTP_H_ #include "htsbuf.h" +#include "htsmsg.h" #include "url.h" #include "tvhpoll.h" - #include "access.h" +#include "access.h" struct channel; struct http_path; @@ -121,6 +122,13 @@ typedef enum http_ver { RTSP_VERSION_1_0, } http_ver_t; +typedef enum http_wsop { + HTTP_WSOP_TEXT = 0, + HTTP_WSOP_BINARY = 1, + HTTP_WSOP_PING = 2, + HTTP_WSOP_PONG = 3 +} http_wsop_t; + typedef struct http_connection { pthread_mutex_t hc_fd_lock; int hc_fd; @@ -195,6 +203,8 @@ void http_error(http_connection_t *hc, int error); int http_encoding_valid(http_connection_t *hc, const char *encoding); +int http_header_match(http_connection_t *hc, const char *name, const char *value); + void http_output_html(http_connection_t *hc); void http_output_content(http_connection_t *hc, const char *content); @@ -209,6 +219,14 @@ void http_send_header(http_connection_t *hc, int rc, const char *content, const char *location, int maxage, const char *range, const char *disposition, http_arg_list_t *args); +int http_send_header_websocket(http_connection_t *hc, const char *protocol); + +int http_websocket_send(http_connection_t *hc, uint8_t *buf, uint64_t buflen, int opcode); + +int http_websocket_send_json(http_connection_t *hc, htsmsg_t *msg); + +int http_websocket_read(http_connection_t *hc, htsmsg_t **_res, int timeout); + void http_serve_requests(http_connection_t *hc); void http_cancel(void *opaque); @@ -219,6 +237,8 @@ typedef int (http_callback_t)(http_connection_t *hc, typedef char * (http_path_modify_t)(http_connection_t *hc, const char * path, int *cut); +#define HTTP_PATH_NO_VERIFICATION (1 << 0) +#define HTTP_PATH_WEBSOCKET (1 << 1) typedef struct http_path { LIST_ENTRY(http_path) hp_link; @@ -226,7 +246,7 @@ typedef struct http_path { void *hp_opaque; http_callback_t *hp_callback; int hp_len; - int hp_no_verification; + uint32_t hp_flags; uint32_t hp_accessmask; http_path_modify_t *hp_path_modify; } http_path_t; diff --git a/src/webui/comet.c b/src/webui/comet.c index 8ae35f432..1da5a917d 100644 --- a/src/webui/comet.c +++ b/src/webui/comet.c @@ -57,6 +57,7 @@ int comet_running; typedef struct comet_mailbox { char *cmb_boxid; /* SHA-1 hash */ char *cmb_lang; /* UI language */ + int cmb_refcount; int cmb_restricted; /* !admin */ htsmsg_t *cmb_messages; /* A vector */ int64_t cmb_last_used; @@ -83,7 +84,6 @@ cmb_destroy(comet_mailbox_t *cmb) free(cmb); } - /** * */ @@ -97,13 +97,13 @@ comet_flush(void) for(cmb = LIST_FIRST(&mailboxes); cmb != NULL; cmb = next) { next = LIST_NEXT(cmb, cmb_link); - if(cmb->cmb_last_used && cmb->cmb_last_used + sec2mono(60) < mclk()) + if(cmb->cmb_refcount == 1 && + cmb->cmb_last_used && cmb->cmb_last_used + sec2mono(60) < mclk()) cmb_destroy(cmb); } pthread_mutex_unlock(&comet_mutex); } - /** * */ @@ -133,6 +133,7 @@ comet_mailbox_create(const char *lang) cmb->cmb_boxid = strdup(id); cmb->cmb_lang = lang ? strdup(lang) : NULL; + cmb->cmb_refcount = 1; cmb->cmb_last_used = mclk(); mailbox_tally++; @@ -233,36 +234,45 @@ comet_serverIpPort(http_connection_t *hc, comet_mailbox_t *cmb) htsmsg_add_msg(cmb->cmb_messages, NULL, m); } - /** - * Poll callback + * */ -static int -comet_mailbox_poll(http_connection_t *hc, const char *remain, void *opaque) +static htsmsg_t * +comet_message(comet_mailbox_t *cmb, int include_boxid, int ignore_null) { - comet_mailbox_t *cmb = NULL; - const char *cometid = http_arg_get(&hc->hc_req_args, "boxid"); - const char *immediate = http_arg_get(&hc->hc_req_args, "immediate"); - const char *lang = hc->hc_access->aa_lang_ui; - int im = immediate ? atoi(immediate) : 0, e; - int64_t mono; htsmsg_t *m; - if(!im) - tvh_safe_usleep(100000); /* Always sleep 0.1 sec to avoid comet storms */ + if (ignore_null && cmb->cmb_messages == NULL) + return NULL; + m = htsmsg_create_map(); + if (include_boxid) + htsmsg_add_str(m, "boxid", cmb->cmb_boxid); + htsmsg_add_msg(m, "messages", cmb->cmb_messages ?: htsmsg_create_list()); + cmb->cmb_messages = NULL; + cmb->cmb_last_used = mclk(); + return m; +} - pthread_mutex_lock(&comet_mutex); - if (!atomic_get(&comet_running)) { - pthread_mutex_unlock(&comet_mutex); - return HTTP_STATUS_BAD_REQUEST; - } +/** + * + */ +static comet_mailbox_t * +comet_find_mailbox(http_connection_t *hc, const char *cometid, const char *lang, int create) +{ + comet_mailbox_t *cmb = NULL; - if(cometid != NULL) + if (!atomic_get(&comet_running)) + return NULL; + + if (cometid != NULL) LIST_FOREACH(cmb, &mailboxes, cmb_link) if(!strcmp(cmb->cmb_boxid, cometid)) break; + + if (!create) + return cmb; - if(cmb == NULL) { + if (cmb == NULL) { cmb = comet_mailbox_create(lang); comet_access_update(hc, cmb); comet_serverIpPort(hc, cmb); @@ -277,6 +287,33 @@ comet_mailbox_poll(http_connection_t *hc, const char *remain, void *opaque) } } + return cmb; +} + +/** + * Poll callback + */ +static int +comet_mailbox_poll(http_connection_t *hc, const char *remain, void *opaque) +{ + comet_mailbox_t *cmb = NULL; + const char *cometid = http_arg_get(&hc->hc_req_args, "boxid"); + const char *immediate = http_arg_get(&hc->hc_req_args, "immediate"); + const char *lang = hc->hc_access->aa_lang_ui; + int im = immediate ? atoi(immediate) : 0, e; + int64_t mono; + htsmsg_t *m; + + if(!im) + tvh_safe_usleep(100000); /* Always sleep 0.1 sec to avoid comet storms */ + + pthread_mutex_lock(&comet_mutex); + cmb = comet_find_mailbox(hc, cometid, lang, 1); + if (cmb == NULL) { + pthread_mutex_unlock(&comet_mutex); + return HTTP_STATUS_BAD_REQUEST; + } + if(!im && cmb->cmb_messages == NULL) { mono = mclk() + sec2mono(10); comet_waiting++; @@ -288,17 +325,11 @@ comet_mailbox_poll(http_connection_t *hc, const char *remain, void *opaque) comet_waiting--; if (!atomic_get(&comet_running)) { pthread_mutex_unlock(&comet_mutex); - return 400; + return HTTP_STATUS_BAD_REQUEST; } } - m = htsmsg_create_map(); - htsmsg_add_str(m, "boxid", cmb->cmb_boxid); - htsmsg_add_msg(m, "messages", cmb->cmb_messages ?: htsmsg_create_list()); - cmb->cmb_messages = NULL; - - cmb->cmb_last_used = mclk(); - + m = comet_message(cmb, 1, 0); pthread_mutex_unlock(&comet_mutex); htsmsg_json_serialize(m, &hc->hc_reply, 0); @@ -307,7 +338,6 @@ comet_mailbox_poll(http_connection_t *hc, const char *remain, void *opaque) return 0; } - /** * Poll callback */ @@ -320,34 +350,31 @@ comet_mailbox_dbg(http_connection_t *hc, const char *remain, void *opaque) const char *s; if(cometid == NULL) - return 400; + return HTTP_STATUS_BAD_REQUEST; pthread_mutex_lock(&comet_mutex); - - LIST_FOREACH(cmb, &mailboxes, cmb_link) { - if(!strcmp(cmb->cmb_boxid, cometid)) { - char buf[64]; - cmb->cmb_debug = !cmb->cmb_debug; - - if(cmb->cmb_messages == NULL) - cmb->cmb_messages = htsmsg_create_list(); - - if(cmb->cmb_restricted || http_access_verify(hc, ACCESS_ADMIN)) - s = N_("Only admin can watch the realtime log."); - else if(cmb->cmb_debug) - s = N_("Loglevel debug: enabled"); - else - s = N_("Loglevel debug: disabled"); - snprintf(buf, sizeof(buf), "%s", tvh_gettext_lang(lang, s)); - - htsmsg_t *m = htsmsg_create_map(); - htsmsg_add_str(m, "notificationClass", "logmessage"); - htsmsg_add_str(m, "logtxt", buf); - htsmsg_add_msg(cmb->cmb_messages, NULL, m); - - tvh_cond_signal(&comet_cond, 1); - break; - } + cmb = comet_find_mailbox(hc, cometid, lang, 0); + if (cmb) { + char buf[64]; + cmb->cmb_debug = !cmb->cmb_debug; + + if(cmb->cmb_messages == NULL) + cmb->cmb_messages = htsmsg_create_list(); + + if(cmb->cmb_restricted || http_access_verify(hc, ACCESS_ADMIN)) + s = N_("Only admin can watch the realtime log."); + else if(cmb->cmb_debug) + s = N_("Loglevel debug: enabled"); + else + s = N_("Loglevel debug: disabled"); + snprintf(buf, sizeof(buf), "%s", tvh_gettext_lang(lang, s)); + + htsmsg_t *m = htsmsg_create_map(); + htsmsg_add_str(m, "notificationClass", "logmessage"); + htsmsg_add_str(m, "logtxt", buf); + htsmsg_add_msg(cmb->cmb_messages, NULL, m); + + tvh_cond_signal(&comet_cond, 1); } pthread_mutex_unlock(&comet_mutex); @@ -355,17 +382,82 @@ comet_mailbox_dbg(http_connection_t *hc, const char *remain, void *opaque) return 0; } +/** + * + */ +static void +comet_mailbox_ws_msg(http_connection_t *hc, comet_mailbox_t *cmb, int first, htsmsg_t *msg) +{ + htsmsg_t *m = NULL; + pthread_mutex_lock(&comet_mutex); + if (!atomic_get(&comet_running)) { + pthread_mutex_unlock(&comet_mutex); + return; + } + m = comet_message(cmb, first, 1); + cmb->cmb_last_used = 0; + pthread_mutex_unlock(&comet_mutex); + if (m) + http_websocket_send_json(hc, m); +} + +/** + * Websocket handler + */ +static int +comet_mailbox_ws(http_connection_t *hc, const char *remain, void *opaque) +{ + int res, first = 1; + htsmsg_t *msg; + const char *cometid = http_arg_get(&hc->hc_req_args, "boxid"); + const char *lang = hc->hc_access->aa_lang_ui; + comet_mailbox_t *cmb; + + res = http_send_header_websocket(hc, "tvheadend-comet"); + + pthread_mutex_lock(&comet_mutex); + cmb = comet_find_mailbox(hc, cometid, lang, 1); + if (cmb) + cmb->cmb_refcount++; + pthread_mutex_unlock(&comet_mutex); + if (!cmb) + return -1; + + while (res == 0) { + if (!atomic_get(&comet_running)) { + res = -1; + continue; + } + res = http_websocket_read(hc, &msg, 1000); + if (res >= 0) { + comet_mailbox_ws_msg(hc, cmb, first, msg); + htsmsg_destroy(msg); + first = 0; + } + } + + pthread_mutex_lock(&comet_mutex); + cmb->cmb_refcount--; + pthread_mutex_unlock(&comet_mutex); + + return res; +} + /** * */ void comet_init(void) { + http_path_t *hp; + pthread_mutex_lock(&comet_mutex); tvh_cond_init(&comet_cond); atomic_set(&comet_running, 1); comet_waiting = 0; pthread_mutex_unlock(&comet_mutex); + hp = http_path_add("/comet/ws", NULL, comet_mailbox_ws, ACCESS_WEB_INTERFACE); + hp->hp_flags = HTTP_PATH_WEBSOCKET; http_path_add("/comet/poll", NULL, comet_mailbox_poll, ACCESS_WEB_INTERFACE); http_path_add("/comet/debug", NULL, comet_mailbox_dbg, ACCESS_WEB_INTERFACE); } diff --git a/src/webui/static/app/comet.js b/src/webui/static/app/comet.js index bed348ab2..bf5bc6f4f 100644 --- a/src/webui/static/app/comet.js +++ b/src/webui/static/app/comet.js @@ -6,58 +6,102 @@ Ext.extend(tvheadend.Comet = function() { }, Ext.util.Observable); tvheadend.comet = new tvheadend.Comet(); +tvheadend.ws = null; +tvheadend.wsURI = null; tvheadend.boxid = null; -tvheadend.cometPoller = function() { +tvheadend.cometError = function() { + tvheadend.log(_('There seems to be a problem with the ' + + 'live update feed from Tvheadend. ' + + 'Trying to reconnect...'), + 'font-weight: bold; color: #f00'); +}; - var failures = 0; +tvheadend.cometReconnected = function() { + tvheadend.log(_('Reconnected to Tvheadend'), + 'font-weight: bold; color: #080'); +} + +tvheadend.cometParse = function(responsetxt) { + var response = Ext.util.JSON.decode(responsetxt); + if ('boxid' in response) { + if (tvheadend.boxid && tvheadend.boxid !== response.boxid) + window.location.reload(); + tvheadend.boxid = response.boxid; + } + for (x = 0; x < response.messages.length; x++) { + m = response.messages[x]; + if (0) console.log(JSON.stringify(m), null, " "); + try { + tvheadend.comet.fireEvent(m.notificationClass, m); + } catch (e) { + tvheadend.log(_('Comet failure') + ' [e=' + e.message + ']'); + } + } +}; +tvheadend.cometPoller = function() { + var failures = 0; var cometRequest = new Ext.util.DelayedTask(function() { + Ext.Ajax.request({ + url: 'comet/poll', + params: { + boxid: (tvheadend.boxid ? tvheadend.boxid : null), + immediate: failures > 0 ? 1 : 0 + }, + success: function(result, request) { + tvheadend.cometParse(result.responseText); + cometRequest.delay(100); - Ext.Ajax - .request({ - url: 'comet/poll', - params: { - boxid: (tvheadend.boxid ? tvheadend.boxid : null), - immediate: failures > 0 ? 1 : 0 - }, - success: function(result, request) { - parse_comet_response(result.responseText); - - if (failures > 1) { - tvheadend.log(_('Reconnected to Tvheadend'), - 'font-weight: bold; color: #080'); - } - failures = 0; - }, - failure: function(result, request) { - cometRequest.delay(failures ? 1000 : 1); - if (failures === 1) { - tvheadend.log(_('There seems to be a problem with the ' - + 'live update feed from Tvheadend. ' - + 'Trying to reconnect...'), - 'font-weight: bold; color: #f00'); - } - failures++; - } - }); + if (failures > 1) + tvheadend.cometReconnected(); + failures = 0; + }, + failure: function(result, request) { + cometRequest.delay(failures ? 1000 : 1); + if (failures === 1) + tvheadend.cometError(); + failures++; + } + }); }); + cometRequest.delay(100); +}; - function parse_comet_response(responsetxt) { - response = Ext.util.JSON.decode(responsetxt); - tvheadend.boxid = response.boxid; - for (x = 0; x < response.messages.length; x++) { - m = response.messages[x]; - if (0) console.log(JSON.stringify(m), null, " "); - try { - tvheadend.comet.fireEvent(m.notificationClass, m); - } catch (e) { - tvheadend.log(_('Comet failure') + ' [e=' + e.message + ']'); - } +tvheadend.cometWebsocket = function() { + var failures = 0; + var cometRequest = new Ext.util.DelayedTask(function() { + var uri = tvheadend.wsURI; + if (tvheadend.boxid) + uri = uri + '?boxid=' + tvheadend.boxid; + tvheadend.ws = new WebSocket(uri); + if (failures > 5) + window.location.reload(); + if (failures > 1) + tvheadend.cometReconnected(); + tvheadend.ws.onmessage = function(ev) { + failures = 0; + tvheadend.cometParse(ev.data); } - cometRequest.delay(100); - } - ; - + tvheadend.ws.onerror = function(ev) { + if (failures === 1) + tvheadend.cometError(); + failures++; + }; + tvheadend.ws.onclose = function(ev) { + cometRequest.delay(failures ? 1000 : 50); + }; + }); cometRequest.delay(100); }; + +tvheadend.cometInit = function() { + if ("WebSocket" in window) { + var loc = window.location; + var path = loc.pathname.substring(0, loc.pathname.lastIndexOf("/")); + tvheadend.wsURI = (loc.protocol === "https:" ? "wss:" : "ws:") + "//" + loc.host + path + "/comet/ws"; + new tvheadend.cometWebsocket; + } else { + new tvheadend.cometPoller; + } +}; diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index f92fa096f..e86678ccf 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -1088,7 +1088,7 @@ tvheadend.app = function() { tvheadend.log(m.logtxt); }); - new tvheadend.cometPoller; + tvheadend.cometInit(); Ext.QuickTips.init(); } diff --git a/src/webui/webui.c b/src/webui/webui.c index 06392536c..ca55f3656 100644 --- a/src/webui/webui.c +++ b/src/webui/webui.c @@ -1978,10 +1978,10 @@ webui_init(int xspf) http_path_add("", NULL, page_root2, ACCESS_WEB_INTERFACE); hp = http_path_add("/", NULL, page_root, ACCESS_WEB_INTERFACE); - hp->hp_no_verification = 1; /* redirect only */ + hp->hp_flags = HTTP_PATH_NO_VERIFICATION; /* redirect only */ http_path_add("/login", NULL, page_login, ACCESS_WEB_INTERFACE); hp = http_path_add("/logout", NULL, page_logout, ACCESS_WEB_INTERFACE); - hp->hp_no_verification = 1; + hp->hp_flags = HTTP_PATH_NO_VERIFICATION; #if CONFIG_SATIP_SERVER http_path_add("/satip_server", NULL, satip_server_http_page, ACCESS_ANONYMOUS);