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); 331131Smax.romanov@nginx.com static void ws_chat_broadcast(const void *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 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 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. */, 107*1257Smax.romanov@nginx.com nxt_length(CONTENT_TYPE) 108*1257Smax.romanov@nginx.com + nxt_length(TEXT_HTML) 109*1257Smax.romanov@nginx.com + nxt_length(CONTENT_LENGTH) 110*1257Smax.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 1421131Smax.romanov@nginx.com ws_chat_broadcast(const void *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 1471131Smax.romanov@nginx.com nxt_unit_debug(NULL, "broadcast: %s", 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 1531131Smax.romanov@nginx.com nxt_unit_req_debug(req, "broadcast: %s", 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 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 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 2041131Smax.romanov@nginx.com main() 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