11131Smax.romanov@nginx.com
21131Smax.romanov@nginx.com /*
31131Smax.romanov@nginx.com * Copyright (C) NGINX, Inc.
41131Smax.romanov@nginx.com */
51131Smax.romanov@nginx.com
61131Smax.romanov@nginx.com #include <fcntl.h>
71131Smax.romanov@nginx.com #include <stdio.h>
81131Smax.romanov@nginx.com #include <string.h>
91131Smax.romanov@nginx.com #include <errno.h>
101131Smax.romanov@nginx.com
111131Smax.romanov@nginx.com #include <sys/mman.h>
121131Smax.romanov@nginx.com #include <sys/stat.h>
131131Smax.romanov@nginx.com
141131Smax.romanov@nginx.com #include <nxt_unit.h>
151131Smax.romanov@nginx.com #include <nxt_unit_request.h>
161131Smax.romanov@nginx.com #include <nxt_clang.h>
171131Smax.romanov@nginx.com #include <nxt_websocket.h>
181131Smax.romanov@nginx.com #include <nxt_unit_websocket.h>
191131Smax.romanov@nginx.com #include <nxt_main.h>
201131Smax.romanov@nginx.com
211131Smax.romanov@nginx.com
221131Smax.romanov@nginx.com #define CONTENT_TYPE "Content-Type"
231131Smax.romanov@nginx.com #define CONTENT_LENGTH "Content-Length"
241131Smax.romanov@nginx.com #define TEXT_HTML "text/html"
251131Smax.romanov@nginx.com
261131Smax.romanov@nginx.com typedef struct {
271131Smax.romanov@nginx.com nxt_queue_link_t link;
281131Smax.romanov@nginx.com int id;
291131Smax.romanov@nginx.com } ws_chat_request_data_t;
301131Smax.romanov@nginx.com
311131Smax.romanov@nginx.com
321131Smax.romanov@nginx.com static int ws_chat_root(nxt_unit_request_info_t *req);
331710Smax.romanov@nginx.com static void ws_chat_broadcast(const char *buf, size_t size);
341131Smax.romanov@nginx.com
351131Smax.romanov@nginx.com
361131Smax.romanov@nginx.com static const char ws_chat_index_html[];
371131Smax.romanov@nginx.com static const int ws_chat_index_html_size;
381131Smax.romanov@nginx.com
391131Smax.romanov@nginx.com static char ws_chat_index_content_length[34];
401131Smax.romanov@nginx.com static int ws_chat_index_content_length_size;
411131Smax.romanov@nginx.com
421131Smax.romanov@nginx.com static nxt_queue_t ws_chat_sessions;
431131Smax.romanov@nginx.com static int ws_chat_next_id = 0;
441131Smax.romanov@nginx.com
451131Smax.romanov@nginx.com
461131Smax.romanov@nginx.com static void
ws_chat_request_handler(nxt_unit_request_info_t * req)471131Smax.romanov@nginx.com ws_chat_request_handler(nxt_unit_request_info_t *req)
481131Smax.romanov@nginx.com {
491131Smax.romanov@nginx.com static char buf[1024];
501131Smax.romanov@nginx.com int buf_size;
511131Smax.romanov@nginx.com int rc = NXT_UNIT_OK;
521131Smax.romanov@nginx.com nxt_unit_request_t *r;
531131Smax.romanov@nginx.com ws_chat_request_data_t *data;
541131Smax.romanov@nginx.com
551131Smax.romanov@nginx.com r = req->request;
561131Smax.romanov@nginx.com
571131Smax.romanov@nginx.com const char* target = nxt_unit_sptr_get(&r->target);
581131Smax.romanov@nginx.com
591131Smax.romanov@nginx.com if (strcmp(target, "/") == 0) {
601131Smax.romanov@nginx.com rc = ws_chat_root(req);
611131Smax.romanov@nginx.com goto fail;
621131Smax.romanov@nginx.com }
631131Smax.romanov@nginx.com
641131Smax.romanov@nginx.com if (strcmp(target, "/chat") == 0) {
651131Smax.romanov@nginx.com if (!nxt_unit_request_is_websocket_handshake(req)) {
661131Smax.romanov@nginx.com goto notfound;
671131Smax.romanov@nginx.com }
681131Smax.romanov@nginx.com
691131Smax.romanov@nginx.com rc = nxt_unit_response_init(req, 101, 0, 0);
701131Smax.romanov@nginx.com if (nxt_slow_path(rc != NXT_UNIT_OK)) {
711131Smax.romanov@nginx.com goto fail;
721131Smax.romanov@nginx.com }
731131Smax.romanov@nginx.com
741131Smax.romanov@nginx.com data = req->data;
751131Smax.romanov@nginx.com nxt_queue_insert_tail(&ws_chat_sessions, &data->link);
761131Smax.romanov@nginx.com
771131Smax.romanov@nginx.com data->id = ws_chat_next_id++;
781131Smax.romanov@nginx.com
791131Smax.romanov@nginx.com nxt_unit_response_upgrade(req);
801131Smax.romanov@nginx.com nxt_unit_response_send(req);
811131Smax.romanov@nginx.com
821131Smax.romanov@nginx.com
831131Smax.romanov@nginx.com buf_size = snprintf(buf, sizeof(buf), "Guest #%d has joined.", data->id);
841131Smax.romanov@nginx.com
851131Smax.romanov@nginx.com ws_chat_broadcast(buf, buf_size);
861131Smax.romanov@nginx.com
871131Smax.romanov@nginx.com return;
881131Smax.romanov@nginx.com }
891131Smax.romanov@nginx.com
901131Smax.romanov@nginx.com notfound:
911131Smax.romanov@nginx.com
921131Smax.romanov@nginx.com rc = nxt_unit_response_init(req, 404, 0, 0);
931131Smax.romanov@nginx.com
941131Smax.romanov@nginx.com fail:
951131Smax.romanov@nginx.com
961131Smax.romanov@nginx.com nxt_unit_request_done(req, rc);
971131Smax.romanov@nginx.com }
981131Smax.romanov@nginx.com
991131Smax.romanov@nginx.com
1001131Smax.romanov@nginx.com static int
ws_chat_root(nxt_unit_request_info_t * req)1011131Smax.romanov@nginx.com ws_chat_root(nxt_unit_request_info_t *req)
1021131Smax.romanov@nginx.com {
1031131Smax.romanov@nginx.com int rc;
1041131Smax.romanov@nginx.com
1051131Smax.romanov@nginx.com rc = nxt_unit_response_init(req, 200 /* Status code. */,
1061131Smax.romanov@nginx.com 2 /* Number of response headers. */,
1071257Smax.romanov@nginx.com nxt_length(CONTENT_TYPE)
1081257Smax.romanov@nginx.com + nxt_length(TEXT_HTML)
1091257Smax.romanov@nginx.com + nxt_length(CONTENT_LENGTH)
1101257Smax.romanov@nginx.com + ws_chat_index_content_length_size
1111131Smax.romanov@nginx.com + ws_chat_index_html_size);
1121131Smax.romanov@nginx.com if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1131131Smax.romanov@nginx.com return rc;
1141131Smax.romanov@nginx.com }
1151131Smax.romanov@nginx.com
1161131Smax.romanov@nginx.com rc = nxt_unit_response_add_field(req,
1171131Smax.romanov@nginx.com CONTENT_TYPE, nxt_length(CONTENT_TYPE),
1181131Smax.romanov@nginx.com TEXT_HTML, nxt_length(TEXT_HTML));
1191131Smax.romanov@nginx.com if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1201131Smax.romanov@nginx.com return rc;
1211131Smax.romanov@nginx.com }
1221131Smax.romanov@nginx.com
1231131Smax.romanov@nginx.com rc = nxt_unit_response_add_field(req,
1241131Smax.romanov@nginx.com CONTENT_LENGTH, nxt_length(CONTENT_LENGTH),
1251131Smax.romanov@nginx.com ws_chat_index_content_length,
1261131Smax.romanov@nginx.com ws_chat_index_content_length_size);
1271131Smax.romanov@nginx.com if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1281131Smax.romanov@nginx.com return rc;
1291131Smax.romanov@nginx.com }
1301131Smax.romanov@nginx.com
1311131Smax.romanov@nginx.com rc = nxt_unit_response_add_content(req, ws_chat_index_html,
1321131Smax.romanov@nginx.com ws_chat_index_html_size);
1331131Smax.romanov@nginx.com if (nxt_slow_path(rc != NXT_UNIT_OK)) {
1341131Smax.romanov@nginx.com return rc;
1351131Smax.romanov@nginx.com }
1361131Smax.romanov@nginx.com
1371131Smax.romanov@nginx.com return nxt_unit_response_send(req);
1381131Smax.romanov@nginx.com }
1391131Smax.romanov@nginx.com
1401131Smax.romanov@nginx.com
1411131Smax.romanov@nginx.com static void
ws_chat_broadcast(const char * buf,size_t size)1421710Smax.romanov@nginx.com ws_chat_broadcast(const char *buf, size_t size)
1431131Smax.romanov@nginx.com {
1441131Smax.romanov@nginx.com ws_chat_request_data_t *data;
1451131Smax.romanov@nginx.com nxt_unit_request_info_t *req;
1461131Smax.romanov@nginx.com
1471710Smax.romanov@nginx.com nxt_unit_debug(NULL, "broadcast: %*.s", (int) size, buf);
1481131Smax.romanov@nginx.com
1491131Smax.romanov@nginx.com nxt_queue_each(data, &ws_chat_sessions, ws_chat_request_data_t, link) {
1501131Smax.romanov@nginx.com
1511131Smax.romanov@nginx.com req = nxt_unit_get_request_info_from_data(data);
1521131Smax.romanov@nginx.com
1531710Smax.romanov@nginx.com nxt_unit_req_debug(req, "send: %*.s", (int) size, buf);
1541131Smax.romanov@nginx.com
1551131Smax.romanov@nginx.com nxt_unit_websocket_send(req, NXT_WEBSOCKET_OP_TEXT, 1, buf, size);
1561131Smax.romanov@nginx.com } nxt_queue_loop;
1571131Smax.romanov@nginx.com }
1581131Smax.romanov@nginx.com
1591131Smax.romanov@nginx.com
1601131Smax.romanov@nginx.com static void
ws_chat_websocket_handler(nxt_unit_websocket_frame_t * ws)1611131Smax.romanov@nginx.com ws_chat_websocket_handler(nxt_unit_websocket_frame_t *ws)
1621131Smax.romanov@nginx.com {
1631131Smax.romanov@nginx.com int buf_size;
1641131Smax.romanov@nginx.com static char buf[1024];
1651131Smax.romanov@nginx.com ws_chat_request_data_t *data;
1661131Smax.romanov@nginx.com
1671131Smax.romanov@nginx.com if (ws->header->opcode != NXT_WEBSOCKET_OP_TEXT) {
1681131Smax.romanov@nginx.com return;
1691131Smax.romanov@nginx.com }
1701131Smax.romanov@nginx.com
1711131Smax.romanov@nginx.com data = ws->req->data;
1721131Smax.romanov@nginx.com
1731131Smax.romanov@nginx.com buf_size = snprintf(buf, sizeof(buf), "Guest #%d: ", data->id);
1741131Smax.romanov@nginx.com
1751131Smax.romanov@nginx.com buf_size += nxt_unit_websocket_read(ws, buf + buf_size,
1761131Smax.romanov@nginx.com nxt_min(sizeof(buf),
1771131Smax.romanov@nginx.com ws->content_length));
1781131Smax.romanov@nginx.com
1791131Smax.romanov@nginx.com ws_chat_broadcast(buf, buf_size);
1801131Smax.romanov@nginx.com
1811131Smax.romanov@nginx.com nxt_unit_websocket_done(ws);
1821131Smax.romanov@nginx.com }
1831131Smax.romanov@nginx.com
1841131Smax.romanov@nginx.com
1851131Smax.romanov@nginx.com static void
ws_chat_close_handler(nxt_unit_request_info_t * req)1861131Smax.romanov@nginx.com ws_chat_close_handler(nxt_unit_request_info_t *req)
1871131Smax.romanov@nginx.com {
1881131Smax.romanov@nginx.com int buf_size;
1891131Smax.romanov@nginx.com static char buf[1024];
1901131Smax.romanov@nginx.com ws_chat_request_data_t *data;
1911131Smax.romanov@nginx.com
1921131Smax.romanov@nginx.com data = req->data;
1931131Smax.romanov@nginx.com buf_size = snprintf(buf, sizeof(buf), "Guest #%d has disconnected.",
1941131Smax.romanov@nginx.com data->id);
1951131Smax.romanov@nginx.com
1961131Smax.romanov@nginx.com nxt_queue_remove(&data->link);
1971131Smax.romanov@nginx.com nxt_unit_request_done(req, NXT_UNIT_OK);
1981131Smax.romanov@nginx.com
1991131Smax.romanov@nginx.com ws_chat_broadcast(buf, buf_size);
2001131Smax.romanov@nginx.com }
2011131Smax.romanov@nginx.com
2021131Smax.romanov@nginx.com
2031131Smax.romanov@nginx.com int
main(void)204*2229Sa.clayton@nginx.com main(void)
2051131Smax.romanov@nginx.com {
2061131Smax.romanov@nginx.com nxt_unit_ctx_t *ctx;
2071131Smax.romanov@nginx.com nxt_unit_init_t init;
2081131Smax.romanov@nginx.com
2091131Smax.romanov@nginx.com ws_chat_index_content_length_size =
2101131Smax.romanov@nginx.com snprintf(ws_chat_index_content_length,
2111131Smax.romanov@nginx.com sizeof(ws_chat_index_content_length), "%d",
2121131Smax.romanov@nginx.com ws_chat_index_html_size);
2131131Smax.romanov@nginx.com
2141131Smax.romanov@nginx.com nxt_queue_init(&ws_chat_sessions);
2151131Smax.romanov@nginx.com
2161131Smax.romanov@nginx.com memset(&init, 0, sizeof(nxt_unit_init_t));
2171131Smax.romanov@nginx.com
2181131Smax.romanov@nginx.com init.callbacks.request_handler = ws_chat_request_handler;
2191131Smax.romanov@nginx.com init.callbacks.websocket_handler = ws_chat_websocket_handler;
2201131Smax.romanov@nginx.com init.callbacks.close_handler = ws_chat_close_handler;
2211131Smax.romanov@nginx.com
2221131Smax.romanov@nginx.com init.request_data_size = sizeof(ws_chat_request_data_t);
2231131Smax.romanov@nginx.com
2241131Smax.romanov@nginx.com ctx = nxt_unit_init(&init);
2251131Smax.romanov@nginx.com if (ctx == NULL) {
2261131Smax.romanov@nginx.com return 1;
2271131Smax.romanov@nginx.com }
2281131Smax.romanov@nginx.com
2291131Smax.romanov@nginx.com nxt_unit_run(ctx);
2301131Smax.romanov@nginx.com
2311131Smax.romanov@nginx.com nxt_unit_done(ctx);
2321131Smax.romanov@nginx.com
2331131Smax.romanov@nginx.com return 0;
2341131Smax.romanov@nginx.com }
2351131Smax.romanov@nginx.com
2361131Smax.romanov@nginx.com
2371131Smax.romanov@nginx.com static const char ws_chat_index_html[] =
2381131Smax.romanov@nginx.com "<html>\n"
2391131Smax.romanov@nginx.com "<head>\n"
2401131Smax.romanov@nginx.com " <title>WebSocket Chat Examples</title>\n"
2411131Smax.romanov@nginx.com " <style type=\"text/css\">\n"
2421131Smax.romanov@nginx.com " input#chat {\n"
2431131Smax.romanov@nginx.com " width: 410px\n"
2441131Smax.romanov@nginx.com " }\n"
2451131Smax.romanov@nginx.com "\n"
2461131Smax.romanov@nginx.com " #container {\n"
2471131Smax.romanov@nginx.com " width: 400px;\n"
2481131Smax.romanov@nginx.com " }\n"
2491131Smax.romanov@nginx.com "\n"
2501131Smax.romanov@nginx.com " #console {\n"
2511131Smax.romanov@nginx.com " border: 1px solid #CCCCCC;\n"
2521131Smax.romanov@nginx.com " border-right-color: #999999;\n"
2531131Smax.romanov@nginx.com " border-bottom-color: #999999;\n"
2541131Smax.romanov@nginx.com " height: 170px;\n"
2551131Smax.romanov@nginx.com " overflow-y: scroll;\n"
2561131Smax.romanov@nginx.com " padding: 5px;\n"
2571131Smax.romanov@nginx.com " width: 100%;\n"
2581131Smax.romanov@nginx.com " }\n"
2591131Smax.romanov@nginx.com "\n"
2601131Smax.romanov@nginx.com " #console p {\n"
2611131Smax.romanov@nginx.com " padding: 0;\n"
2621131Smax.romanov@nginx.com " margin: 0;\n"
2631131Smax.romanov@nginx.com " }\n"
2641131Smax.romanov@nginx.com " </style>\n"
2651131Smax.romanov@nginx.com " <script>\n"
2661131Smax.romanov@nginx.com " \"use strict\";\n"
2671131Smax.romanov@nginx.com "\n"
2681131Smax.romanov@nginx.com " var Chat = {};\n"
2691131Smax.romanov@nginx.com "\n"
2701131Smax.romanov@nginx.com " Chat.socket = null;\n"
2711131Smax.romanov@nginx.com "\n"
2721131Smax.romanov@nginx.com " Chat.connect = (function(host) {\n"
2731131Smax.romanov@nginx.com " if ('WebSocket' in window) {\n"
2741131Smax.romanov@nginx.com " Chat.socket = new WebSocket(host);\n"
2751131Smax.romanov@nginx.com " } else if ('MozWebSocket' in window) {\n"
2761131Smax.romanov@nginx.com " Chat.socket = new MozWebSocket(host);\n"
2771131Smax.romanov@nginx.com " } else {\n"
2781131Smax.romanov@nginx.com " Console.log('Error: WebSocket is not supported by this browser.');\n"
2791131Smax.romanov@nginx.com " return;\n"
2801131Smax.romanov@nginx.com " }\n"
2811131Smax.romanov@nginx.com "\n"
2821131Smax.romanov@nginx.com " Chat.socket.onopen = function () {\n"
2831131Smax.romanov@nginx.com " Console.log('Info: WebSocket connection opened.');\n"
2841131Smax.romanov@nginx.com " document.getElementById('chat').onkeydown = function(event) {\n"
2851131Smax.romanov@nginx.com " if (event.keyCode == 13) {\n"
2861131Smax.romanov@nginx.com " Chat.sendMessage();\n"
2871131Smax.romanov@nginx.com " }\n"
2881131Smax.romanov@nginx.com " };\n"
2891131Smax.romanov@nginx.com " };\n"
2901131Smax.romanov@nginx.com "\n"
2911131Smax.romanov@nginx.com " Chat.socket.onclose = function () {\n"
2921131Smax.romanov@nginx.com " document.getElementById('chat').onkeydown = null;\n"
2931131Smax.romanov@nginx.com " Console.log('Info: WebSocket closed.');\n"
2941131Smax.romanov@nginx.com " };\n"
2951131Smax.romanov@nginx.com "\n"
2961131Smax.romanov@nginx.com " Chat.socket.onmessage = function (message) {\n"
2971131Smax.romanov@nginx.com " Console.log(message.data);\n"
2981131Smax.romanov@nginx.com " };\n"
2991131Smax.romanov@nginx.com " });\n"
3001131Smax.romanov@nginx.com "\n"
3011131Smax.romanov@nginx.com " Chat.initialize = function() {\n"
3021131Smax.romanov@nginx.com " var proto = 'ws://';\n"
3031131Smax.romanov@nginx.com " if (window.location.protocol == 'https:') {\n"
3041131Smax.romanov@nginx.com " proto = 'wss://'\n"
3051131Smax.romanov@nginx.com " }\n"
3061131Smax.romanov@nginx.com " Chat.connect(proto + window.location.host + '/chat');\n"
3071131Smax.romanov@nginx.com " };\n"
3081131Smax.romanov@nginx.com "\n"
3091131Smax.romanov@nginx.com " Chat.sendMessage = (function() {\n"
3101131Smax.romanov@nginx.com " var message = document.getElementById('chat').value;\n"
3111131Smax.romanov@nginx.com " if (message != '') {\n"
3121131Smax.romanov@nginx.com " Chat.socket.send(message);\n"
3131131Smax.romanov@nginx.com " document.getElementById('chat').value = '';\n"
3141131Smax.romanov@nginx.com " }\n"
3151131Smax.romanov@nginx.com " });\n"
3161131Smax.romanov@nginx.com "\n"
3171131Smax.romanov@nginx.com " var Console = {};\n"
3181131Smax.romanov@nginx.com "\n"
3191131Smax.romanov@nginx.com " Console.log = (function(message) {\n"
3201131Smax.romanov@nginx.com " var console = document.getElementById('console');\n"
3211131Smax.romanov@nginx.com " var p = document.createElement('p');\n"
3221131Smax.romanov@nginx.com " p.style.wordWrap = 'break-word';\n"
3231131Smax.romanov@nginx.com " p.innerHTML = message;\n"
3241131Smax.romanov@nginx.com " console.appendChild(p);\n"
3251131Smax.romanov@nginx.com " while (console.childNodes.length > 25) {\n"
3261131Smax.romanov@nginx.com " console.removeChild(console.firstChild);\n"
3271131Smax.romanov@nginx.com " }\n"
3281131Smax.romanov@nginx.com " console.scrollTop = console.scrollHeight;\n"
3291131Smax.romanov@nginx.com " });\n"
3301131Smax.romanov@nginx.com "\n"
3311131Smax.romanov@nginx.com " Chat.initialize();\n"
3321131Smax.romanov@nginx.com "\n"
3331131Smax.romanov@nginx.com " </script>\n"
3341131Smax.romanov@nginx.com "</head>\n"
3351131Smax.romanov@nginx.com "<body>\n"
3361131Smax.romanov@nginx.com "<noscript><h2 style=\"color: #ff0000\">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable\n"
3371131Smax.romanov@nginx.com " Javascript and reload this page!</h2></noscript>\n"
3381131Smax.romanov@nginx.com "<div>\n"
3391131Smax.romanov@nginx.com " <p><input type=\"text\" placeholder=\"type and press enter to chat\" id=\"chat\" /></p>\n"
3401131Smax.romanov@nginx.com " <div id=\"container\">\n"
3411131Smax.romanov@nginx.com " <div id=\"console\"/>\n"
3421131Smax.romanov@nginx.com " </div>\n"
3431131Smax.romanov@nginx.com "</div>\n"
3441131Smax.romanov@nginx.com "</body>\n"
3451131Smax.romanov@nginx.com "</html>\n"
3461131Smax.romanov@nginx.com ;
3471131Smax.romanov@nginx.com
3481131Smax.romanov@nginx.com static const int ws_chat_index_html_size = nxt_length(ws_chat_index_html);
349