summaryrefslogtreecommitdiffhomepage
path: root/src/test/nxt_unit_websocket_chat.c
diff options
context:
space:
mode:
authorMax Romanov <max.romanov@nginx.com>2019-08-20 16:31:53 +0300
committerMax Romanov <max.romanov@nginx.com>2019-08-20 16:31:53 +0300
commite501c74ddceab86e48c031ca9b5e154f52dcdae0 (patch)
tree7bfe94354df516d1ceefc5af3194ba943e443aa2 /src/test/nxt_unit_websocket_chat.c
parent9bbf54e23e185e94054072fff2673f6f5cd203e9 (diff)
downloadunit-e501c74ddceab86e48c031ca9b5e154f52dcdae0.tar.gz
unit-e501c74ddceab86e48c031ca9b5e154f52dcdae0.tar.bz2
Introducing websocket support in router and libunit.
Diffstat (limited to '')
-rw-r--r--src/test/nxt_unit_websocket_chat.c348
1 files changed, 348 insertions, 0 deletions
diff --git a/src/test/nxt_unit_websocket_chat.c b/src/test/nxt_unit_websocket_chat.c
new file mode 100644
index 00000000..ecc9a243
--- /dev/null
+++ b/src/test/nxt_unit_websocket_chat.c
@@ -0,0 +1,348 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <nxt_unit.h>
+#include <nxt_unit_request.h>
+#include <nxt_clang.h>
+#include <nxt_websocket.h>
+#include <nxt_unit_websocket.h>
+#include <nxt_main.h>
+
+
+#define CONTENT_TYPE "Content-Type"
+#define CONTENT_LENGTH "Content-Length"
+#define TEXT_HTML "text/html"
+
+typedef struct {
+ nxt_queue_link_t link;
+ int id;
+} ws_chat_request_data_t;
+
+
+static int ws_chat_root(nxt_unit_request_info_t *req);
+static void ws_chat_broadcast(const void *buf, size_t size);
+
+
+static const char ws_chat_index_html[];
+static const int ws_chat_index_html_size;
+
+static char ws_chat_index_content_length[34];
+static int ws_chat_index_content_length_size;
+
+static nxt_queue_t ws_chat_sessions;
+static int ws_chat_next_id = 0;
+
+
+static void
+ws_chat_request_handler(nxt_unit_request_info_t *req)
+{
+ static char buf[1024];
+ int buf_size;
+ int rc = NXT_UNIT_OK;
+ nxt_unit_request_t *r;
+ ws_chat_request_data_t *data;
+
+ r = req->request;
+
+ const char* target = nxt_unit_sptr_get(&r->target);
+
+ if (strcmp(target, "/") == 0) {
+ rc = ws_chat_root(req);
+ goto fail;
+ }
+
+ if (strcmp(target, "/chat") == 0) {
+ if (!nxt_unit_request_is_websocket_handshake(req)) {
+ goto notfound;
+ }
+
+ rc = nxt_unit_response_init(req, 101, 0, 0);
+ if (nxt_slow_path(rc != NXT_UNIT_OK)) {
+ goto fail;
+ }
+
+ data = req->data;
+ nxt_queue_insert_tail(&ws_chat_sessions, &data->link);
+
+ data->id = ws_chat_next_id++;
+
+ nxt_unit_response_upgrade(req);
+ nxt_unit_response_send(req);
+
+
+ buf_size = snprintf(buf, sizeof(buf), "Guest #%d has joined.", data->id);
+
+ ws_chat_broadcast(buf, buf_size);
+
+ return;
+ }
+
+notfound:
+
+ rc = nxt_unit_response_init(req, 404, 0, 0);
+
+fail:
+
+ nxt_unit_request_done(req, rc);
+}
+
+
+static int
+ws_chat_root(nxt_unit_request_info_t *req)
+{
+ int rc;
+
+ rc = nxt_unit_response_init(req, 200 /* Status code. */,
+ 2 /* Number of response headers. */,
+ nxt_length(CONTENT_TYPE) + 1
+ + nxt_length(TEXT_HTML) + 1
+ + nxt_length(CONTENT_LENGTH) + 1
+ + ws_chat_index_content_length_size + 1
+ + ws_chat_index_html_size);
+ if (nxt_slow_path(rc != NXT_UNIT_OK)) {
+ return rc;
+ }
+
+ rc = nxt_unit_response_add_field(req,
+ CONTENT_TYPE, nxt_length(CONTENT_TYPE),
+ TEXT_HTML, nxt_length(TEXT_HTML));
+ if (nxt_slow_path(rc != NXT_UNIT_OK)) {
+ return rc;
+ }
+
+ rc = nxt_unit_response_add_field(req,
+ CONTENT_LENGTH, nxt_length(CONTENT_LENGTH),
+ ws_chat_index_content_length,
+ ws_chat_index_content_length_size);
+ if (nxt_slow_path(rc != NXT_UNIT_OK)) {
+ return rc;
+ }
+
+ rc = nxt_unit_response_add_content(req, ws_chat_index_html,
+ ws_chat_index_html_size);
+ if (nxt_slow_path(rc != NXT_UNIT_OK)) {
+ return rc;
+ }
+
+ return nxt_unit_response_send(req);
+}
+
+
+static void
+ws_chat_broadcast(const void *buf, size_t size)
+{
+ ws_chat_request_data_t *data;
+ nxt_unit_request_info_t *req;
+
+ nxt_unit_debug(NULL, "broadcast: %s", buf);
+
+ nxt_queue_each(data, &ws_chat_sessions, ws_chat_request_data_t, link) {
+
+ req = nxt_unit_get_request_info_from_data(data);
+
+ nxt_unit_req_debug(req, "broadcast: %s", buf);
+
+ nxt_unit_websocket_send(req, NXT_WEBSOCKET_OP_TEXT, 1, buf, size);
+ } nxt_queue_loop;
+}
+
+
+static void
+ws_chat_websocket_handler(nxt_unit_websocket_frame_t *ws)
+{
+ int buf_size;
+ static char buf[1024];
+ ws_chat_request_data_t *data;
+
+ if (ws->header->opcode != NXT_WEBSOCKET_OP_TEXT) {
+ return;
+ }
+
+ data = ws->req->data;
+
+ buf_size = snprintf(buf, sizeof(buf), "Guest #%d: ", data->id);
+
+ buf_size += nxt_unit_websocket_read(ws, buf + buf_size,
+ nxt_min(sizeof(buf),
+ ws->content_length));
+
+ ws_chat_broadcast(buf, buf_size);
+
+ nxt_unit_websocket_done(ws);
+}
+
+
+static void
+ws_chat_close_handler(nxt_unit_request_info_t *req)
+{
+ int buf_size;
+ static char buf[1024];
+ ws_chat_request_data_t *data;
+
+ data = req->data;
+ buf_size = snprintf(buf, sizeof(buf), "Guest #%d has disconnected.",
+ data->id);
+
+ nxt_queue_remove(&data->link);
+ nxt_unit_request_done(req, NXT_UNIT_OK);
+
+ ws_chat_broadcast(buf, buf_size);
+}
+
+
+int
+main()
+{
+ nxt_unit_ctx_t *ctx;
+ nxt_unit_init_t init;
+
+ ws_chat_index_content_length_size =
+ snprintf(ws_chat_index_content_length,
+ sizeof(ws_chat_index_content_length), "%d",
+ ws_chat_index_html_size);
+
+ nxt_queue_init(&ws_chat_sessions);
+
+ memset(&init, 0, sizeof(nxt_unit_init_t));
+
+ init.callbacks.request_handler = ws_chat_request_handler;
+ init.callbacks.websocket_handler = ws_chat_websocket_handler;
+ init.callbacks.close_handler = ws_chat_close_handler;
+
+ init.request_data_size = sizeof(ws_chat_request_data_t);
+
+ ctx = nxt_unit_init(&init);
+ if (ctx == NULL) {
+ return 1;
+ }
+
+ nxt_unit_run(ctx);
+
+ nxt_unit_done(ctx);
+
+ return 0;
+}
+
+
+static const char ws_chat_index_html[] =
+"<html>\n"
+"<head>\n"
+" <title>WebSocket Chat Examples</title>\n"
+" <style type=\"text/css\">\n"
+" input#chat {\n"
+" width: 410px\n"
+" }\n"
+"\n"
+" #container {\n"
+" width: 400px;\n"
+" }\n"
+"\n"
+" #console {\n"
+" border: 1px solid #CCCCCC;\n"
+" border-right-color: #999999;\n"
+" border-bottom-color: #999999;\n"
+" height: 170px;\n"
+" overflow-y: scroll;\n"
+" padding: 5px;\n"
+" width: 100%;\n"
+" }\n"
+"\n"
+" #console p {\n"
+" padding: 0;\n"
+" margin: 0;\n"
+" }\n"
+" </style>\n"
+" <script>\n"
+" \"use strict\";\n"
+"\n"
+" var Chat = {};\n"
+"\n"
+" Chat.socket = null;\n"
+"\n"
+" Chat.connect = (function(host) {\n"
+" if ('WebSocket' in window) {\n"
+" Chat.socket = new WebSocket(host);\n"
+" } else if ('MozWebSocket' in window) {\n"
+" Chat.socket = new MozWebSocket(host);\n"
+" } else {\n"
+" Console.log('Error: WebSocket is not supported by this browser.');\n"
+" return;\n"
+" }\n"
+"\n"
+" Chat.socket.onopen = function () {\n"
+" Console.log('Info: WebSocket connection opened.');\n"
+" document.getElementById('chat').onkeydown = function(event) {\n"
+" if (event.keyCode == 13) {\n"
+" Chat.sendMessage();\n"
+" }\n"
+" };\n"
+" };\n"
+"\n"
+" Chat.socket.onclose = function () {\n"
+" document.getElementById('chat').onkeydown = null;\n"
+" Console.log('Info: WebSocket closed.');\n"
+" };\n"
+"\n"
+" Chat.socket.onmessage = function (message) {\n"
+" Console.log(message.data);\n"
+" };\n"
+" });\n"
+"\n"
+" Chat.initialize = function() {\n"
+" var proto = 'ws://';\n"
+" if (window.location.protocol == 'https:') {\n"
+" proto = 'wss://'\n"
+" }\n"
+" Chat.connect(proto + window.location.host + '/chat');\n"
+" };\n"
+"\n"
+" Chat.sendMessage = (function() {\n"
+" var message = document.getElementById('chat').value;\n"
+" if (message != '') {\n"
+" Chat.socket.send(message);\n"
+" document.getElementById('chat').value = '';\n"
+" }\n"
+" });\n"
+"\n"
+" var Console = {};\n"
+"\n"
+" Console.log = (function(message) {\n"
+" var console = document.getElementById('console');\n"
+" var p = document.createElement('p');\n"
+" p.style.wordWrap = 'break-word';\n"
+" p.innerHTML = message;\n"
+" console.appendChild(p);\n"
+" while (console.childNodes.length > 25) {\n"
+" console.removeChild(console.firstChild);\n"
+" }\n"
+" console.scrollTop = console.scrollHeight;\n"
+" });\n"
+"\n"
+" Chat.initialize();\n"
+"\n"
+" </script>\n"
+"</head>\n"
+"<body>\n"
+"<noscript><h2 style=\"color: #ff0000\">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable\n"
+" Javascript and reload this page!</h2></noscript>\n"
+"<div>\n"
+" <p><input type=\"text\" placeholder=\"type and press enter to chat\" id=\"chat\" /></p>\n"
+" <div id=\"container\">\n"
+" <div id=\"console\"/>\n"
+" </div>\n"
+"</div>\n"
+"</body>\n"
+"</html>\n"
+;
+
+static const int ws_chat_index_html_size = nxt_length(ws_chat_index_html);