Files
nginx-unit/src/test/nxt_unit_websocket_chat.c
Andrew Clayton 58248a6220 Fixed some function definitions.
Future releases of GCC will render function definitions like

  func()

invalid by default. See the previous commit 09f88c9 ("Fixed main()
prototypes in auto tests.") for details.

Such functions should be defined like

  func(void)

This is a good thing to do regardless of the upcoming GCC changes.

Reviewed-by: Alejandro Colomar <alx@nginx.com>
Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
2022-10-28 03:18:33 +01:00

349 lines
9.3 KiB
C

/*
* 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 char *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)
+ nxt_length(TEXT_HTML)
+ nxt_length(CONTENT_LENGTH)
+ ws_chat_index_content_length_size
+ 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 char *buf, size_t size)
{
ws_chat_request_data_t *data;
nxt_unit_request_info_t *req;
nxt_unit_debug(NULL, "broadcast: %*.s", (int) size, 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, "send: %*.s", (int) size, 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(void)
{
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);