diff --git a/daemon/control_ng.c b/daemon/control_ng.c index eff94d123..00daf4699 100644 --- a/daemon/control_ng.c +++ b/daemon/control_ng.c @@ -421,6 +421,19 @@ int control_ng_process(str *buf, const endpoint_t *sin, char *addr, const sockad return 0; } +int control_ng_process_plain(str *data, const endpoint_t *sin, char *addr, const sockaddr_t *local, + void (*cb)(str *, str *, const endpoint_t *, const sockaddr_t *, void *), + void *p1, struct obj *ref) +{ + g_autoptr(ng_buffer) ngbuf = NULL; + + str reply; + control_ng_process_payload(&reply, data, sin, addr, ref, &ngbuf); + cb(NULL, &reply, sin, local, p1); + + return 0; +} + INLINE void control_ng_send_generic(str *cookie, str *body, const endpoint_t *sin, const sockaddr_t *from, void *p1) { diff --git a/daemon/websocket.c b/daemon/websocket.c index ab1c935a5..92b910c5f 100644 --- a/daemon/websocket.c +++ b/daemon/websocket.c @@ -474,8 +474,10 @@ static void websocket_ng_send_ws(str *cookie, str *body, const endpoint_t *sin, void *p1) { struct websocket_conn *wc = p1; - websocket_queue_raw(wc, cookie->s, cookie->len); - websocket_queue_raw(wc, " ", 1); + if (cookie) { + websocket_queue_raw(wc, cookie->s, cookie->len); + websocket_queue_raw(wc, " ", 1); + } websocket_queue_raw(wc, body->s, body->len); websocket_write_binary(wc, NULL, 0, true); } @@ -483,9 +485,12 @@ static void websocket_ng_send_http(str *cookie, str *body, const endpoint_t *sin void *p1) { struct websocket_conn *wc = p1; - websocket_http_response(wc, 200, "application/x-rtpengine-ng", cookie->len + 1 + body->len); - websocket_queue_raw(wc, cookie->s, cookie->len); - websocket_queue_raw(wc, " ", 1); + websocket_http_response(wc, 200, "application/x-rtpengine-ng", + (cookie ? (cookie->len + 1) : 0) + body->len); + if (cookie) { + websocket_queue_raw(wc, cookie->s, cookie->len); + websocket_queue_raw(wc, " ", 1); + } websocket_queue_raw(wc, body->s, body->len); websocket_write_http(wc, NULL, true); } @@ -495,7 +500,9 @@ static void __ng_buf_free(void *p) { g_string_free(buf->body, TRUE); } -static const char *websocket_ng_process(struct websocket_message *wm) { +static const char *websocket_ng_process_generic(struct websocket_message *wm, + __typeof__(control_ng_process) cb) +{ struct websocket_ng_buf *buf = obj_alloc0("websocket_ng_buf", sizeof(*buf), __ng_buf_free); endpoint_print(&wm->wc->endpoint, buf->addr, sizeof(buf->addr)); @@ -508,13 +515,21 @@ static const char *websocket_ng_process(struct websocket_message *wm) { str_init_len(&buf->cmd, buf->body->str, buf->body->len); buf->endpoint = wm->wc->endpoint; - control_ng_process(&buf->cmd, &buf->endpoint, buf->addr, NULL, websocket_ng_send_ws, wm->wc, &buf->obj); + cb(&buf->cmd, &buf->endpoint, buf->addr, NULL, websocket_ng_send_ws, wm->wc, &buf->obj); obj_put(buf); return NULL; } -static const char *websocket_http_ng(struct websocket_message *wm) { +static const char *websocket_ng_process(struct websocket_message *wm) { + return websocket_ng_process_generic(wm, control_ng_process); +} +static const char *websocket_ng_plain_process(struct websocket_message *wm) { + return websocket_ng_process_generic(wm, control_ng_process_plain); +} +static const char *websocket_http_ng_generic(struct websocket_message *wm, + __typeof__(control_ng_process) cb) +{ struct websocket_ng_buf *buf = obj_alloc0("websocket_ng_buf", sizeof(*buf), __ng_buf_free); endpoint_print(&wm->wc->endpoint, buf->addr, sizeof(buf->addr)); @@ -527,7 +542,7 @@ static const char *websocket_http_ng(struct websocket_message *wm) { str_init_len(&buf->cmd, buf->body->str, buf->body->len); buf->endpoint = wm->wc->endpoint; - if (control_ng_process(&buf->cmd, &buf->endpoint, buf->addr, NULL, websocket_ng_send_http, wm->wc, + if (cb(&buf->cmd, &buf->endpoint, buf->addr, NULL, websocket_ng_send_http, wm->wc, &buf->obj)) websocket_http_complete(wm->wc, 600, "text/plain", 6, "error\n"); @@ -535,6 +550,12 @@ static const char *websocket_http_ng(struct websocket_message *wm) { return NULL; } +static const char *websocket_http_ng(struct websocket_message *wm) { + return websocket_http_ng_generic(wm, control_ng_process); +} +static const char *websocket_http_ng_plain(struct websocket_message *wm) { + return websocket_http_ng_generic(wm, control_ng_process_plain); +} @@ -645,6 +666,9 @@ static int websocket_http_body(struct websocket_conn *wc, const char *body, size if (!strcmp(uri, "/ng") && wm->method == M_POST && wm->content_type == CT_NG) handler = websocket_http_ng; + if (!strcmp(uri, "/ng-plain") && wm->method == M_POST + && (wm->content_type == CT_NG || wm->content_type == CT_JSON)) + handler = websocket_http_ng_plain; else if (!strcmp(uri, "/admin") && wm->method == M_POST && wm->content_type == CT_JSON) handler = websocket_janus_process; else if (!strcmp(uri, "/janus") && wm->method == M_POST && wm->content_type == CT_JSON) @@ -945,6 +969,11 @@ static int websocket_rtpengine_ng(struct lws *wsi, enum lws_callback_reasons rea { return websocket_protocol(wsi, reason, user, in, len, websocket_ng_process, "rtpengine-ng"); } +static int websocket_rtpengine_ng_plain(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, + size_t len) +{ + return websocket_protocol(wsi, reason, user, in, len, websocket_ng_plain_process, "rtpengine-ng-plain"); +} static const struct lws_protocols websocket_protocols[] = { @@ -973,6 +1002,11 @@ static const struct lws_protocols websocket_protocols[] = { .callback = websocket_rtpengine_ng, .per_session_data_size = sizeof(struct websocket_conn), }, + { + .name = "ng-plain.rtpengine.com", + .callback = websocket_rtpengine_ng_plain, + .per_session_data_size = sizeof(struct websocket_conn), + }, { 0, } }; diff --git a/docs/http_websocket_support.md b/docs/http_websocket_support.md index 901136477..e08ccc459 100644 --- a/docs/http_websocket_support.md +++ b/docs/http_websocket_support.md @@ -31,12 +31,20 @@ For HTTP and HTTPS, the URI `/ng` is used, with the request being made by `POST` and the content-type set to `application/x-rtpengine-ng`. The message body must be in the same format as the body of an UDP-based *ng* message and must therefore consist of a unique cookie string, followed by a single space, -followed by the message in *bencode* format. Likewise, the response will be in -the same format, including the unique cookie. +followed by the message in *bencode* format or *JSON* format. Likewise, the +response will be in the same format, including the unique cookie. For WebSockets, the subprotocol `ng.rtpengine.com` is used and the protocol follows the same format. Messages must consist of a unique cookie and a string -in bencode format, and responses will also be in the same format. +in *bencode* format or *JSON* format, and responses will also be in the same +format. + +Additionally the URI `/ng-plain` and the WebSocket subprotocol +`ng-plain.rtpengine.com` are supported, which operate identical to what is +described above except that they carry *ng* protocol messages without the +unique cookie. In other words, each payload is just a plain *bencode* +dictionary or a *JSON* object. Therefore the content-type `application/json` +can also be used for HTTP `POST`. ## Prometheus Stats Exporter diff --git a/include/control_ng.h b/include/control_ng.h index 9cea26817..03d3ce5b2 100644 --- a/include/control_ng.h +++ b/include/control_ng.h @@ -74,6 +74,8 @@ void control_ng_init(void); void control_ng_cleanup(void); int control_ng_process(str *buf, const endpoint_t *sin, char *addr, const sockaddr_t *local, void (*cb)(str *, str *, const endpoint_t *, const sockaddr_t *, void *), void *p1, struct obj *); +int control_ng_process_plain(str *buf, const endpoint_t *sin, char *addr, const sockaddr_t *local, + void (*cb)(str *, str *, const endpoint_t *, const sockaddr_t *, void *), void *p1, struct obj *); ng_buffer *ng_buffer_new(struct obj *ref); diff --git a/t/auto-daemon-tests-websocket.py b/t/auto-daemon-tests-websocket.py index d039aec0e..270ceb874 100644 --- a/t/auto-daemon-tests-websocket.py +++ b/t/auto-daemon-tests-websocket.py @@ -132,6 +132,40 @@ class TestWSCli(unittest.TestCase): ) +class TestNGPlain(unittest.TestCase): + @classmethod + def setUpClass(cls): + eventloop.run_until_complete(get_ws(cls, "ng-plain.rtpengine.com")) + + @classmethod + def tearDownClass(cls): + eventloop.run_until_complete(close_ws(cls)) + + def testPing(self): + eventloop.run_until_complete(testIO(self, "d7:command4:pinge")) + self.assertEqual( + self._res, + b"d6:result4:ponge", + ) + + +class TestNGPlainJSON(unittest.TestCase): + @classmethod + def setUpClass(cls): + eventloop.run_until_complete(get_ws(cls, "ng-plain.rtpengine.com")) + + @classmethod + def tearDownClass(cls): + eventloop.run_until_complete(close_ws(cls)) + + def testPing(self): + eventloop.run_until_complete(testIOJson(self, {"command": "ping"})) + self.assertEqual( + self._res, + {"result": "pong"}, + ) + + class TestWSJanus(unittest.TestCase): @classmethod def setUpClass(cls):