xref: /unit/src/test/nxt_unit_websocket_chat.c (revision 2229:3a230013e58a)
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