]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
http server: implement websockets for instant messages
authorJaroslav Kysela <perex@perex.cz>
Sun, 30 Jul 2017 12:48:37 +0000 (14:48 +0200)
committerJaroslav Kysela <perex@perex.cz>
Tue, 1 Aug 2017 07:54:45 +0000 (09:54 +0200)
src/http.c
src/http.h
src/webui/comet.c
src/webui/static/app/comet.js
src/webui/static/app/tvheadend.js
src/webui/webui.c

index 2921f3e63dfb0aab3d6120f4171731047375525f..120637f0b471130eb3b83fdce5db7def405b029b 100644 (file)
@@ -39,6 +39,7 @@
 #include "notify.h"
 #include "channels.h"
 #include "config.h"
+#include "htsmsg_json.h"
 
 #if ENABLE_ANDROID
 #include <sys/socket.h>
@@ -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
  */
index 36c8e97bfd1c8c4285a6de8082a91f709a8cbe3d..f26fc8f8ff6b0d7331cd51734102648419b75bc5 100644 (file)
 #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;
index 8ae35f43250294c145eb73f95b159fd0274038bb..1da5a917d6391b860f6dd00ba0c5569e76d2a4ab 100644 (file)
@@ -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);
 }
index bed348ab22dca2b7750e0d934e75e7c6bf8042f0..bf5bc6f4f61b4c45fef7c773087ff50d70ee933f 100644 (file)
@@ -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;
+    }
+};
index f92fa096f26bcfe59f7574f09439abf0508eeb27..e86678ccffe8fea9d3df548a6dc88cb4b919da52 100644 (file)
@@ -1088,7 +1088,7 @@ tvheadend.app = function() {
                 tvheadend.log(m.logtxt);
             });
 
-            new tvheadend.cometPoller;
+            tvheadend.cometInit();
 
             Ext.QuickTips.init();
         }
index 06392536ca58fe5f826cdb37f79ab7f4b018e31d..ca55f36566125aa6a4a21bc04c5931a945d8e26d 100644 (file)
@@ -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);