Introducing websocket support in router and libunit.

This commit is contained in:
Max Romanov
2019-08-20 16:31:53 +03:00
parent 9bbf54e23e
commit e501c74ddc
29 changed files with 3545 additions and 451 deletions

View File

@@ -57,6 +57,7 @@ $echo >> $NXT_MAKEFILE
$echo "NXT_LIB_UNIT_OBJS = \\" >> $NXT_MAKEFILE
$echo " $NXT_BUILD_DIR/src/nxt_lvlhsh.o \\" >> $NXT_MAKEFILE
$echo " $NXT_BUILD_DIR/src/nxt_murmur_hash.o \\" >> $NXT_MAKEFILE
$echo " $NXT_BUILD_DIR/src/nxt_websocket.o \\" >> $NXT_MAKEFILE
for nxt_src in $NXT_LIB_UNIT_SRCS
do
@@ -108,7 +109,9 @@ END
# Object files.
for nxt_src in $NXT_LIB_SRCS $NXT_TEST_SRCS $NXT_LIB_UNIT_SRCS \
src/test/nxt_unit_app_test.c
src/test/nxt_unit_app_test.c \
src/test/nxt_unit_websocket_chat.c \
src/test/nxt_unit_websocket_echo.c
do
nxt_obj=${nxt_src%.c}.o
nxt_dep=${nxt_src%.c}.dep
@@ -150,7 +153,8 @@ if [ $NXT_TESTS = YES ]; then
.PHONY: tests
tests: $NXT_BUILD_DIR/tests $NXT_BUILD_DIR/utf8_file_name_test \\
$NXT_BUILD_DIR/unit_app_test
$NXT_BUILD_DIR/unit_app_test $NXT_BUILD_DIR/unit_websocket_chat \\
$NXT_BUILD_DIR/unit_websocket_echo
$NXT_BUILD_DIR/tests: \$(NXT_TEST_OBJS) \\
$NXT_BUILD_DIR/$NXT_LIB_STATIC
@@ -174,6 +178,22 @@ $NXT_BUILD_DIR/unit_app_test: $NXT_BUILD_DIR/src/test/nxt_unit_app_test.o \\
$NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC \\
$NXT_LD_OPT $NXT_LIBM $NXT_LIBS $NXT_LIB_AUX_LIBS
$NXT_BUILD_DIR/unit_websocket_chat: \\
$NXT_BUILD_DIR/src/test/nxt_unit_websocket_chat.o \\
$NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC
\$(NXT_EXEC_LINK) -o $NXT_BUILD_DIR/unit_websocket_chat \\
\$(CFLAGS) $NXT_BUILD_DIR/src/test/nxt_unit_websocket_chat.o \\
$NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC \\
$NXT_LD_OPT $NXT_LIBM $NXT_LIBS $NXT_LIB_AUX_LIBS
$NXT_BUILD_DIR/unit_websocket_echo: \\
$NXT_BUILD_DIR/src/test/nxt_unit_websocket_echo.o \\
$NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC
\$(NXT_EXEC_LINK) -o $NXT_BUILD_DIR/unit_websocket_echo \\
\$(CFLAGS) $NXT_BUILD_DIR/src/test/nxt_unit_websocket_echo.o \\
$NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC \\
$NXT_LD_OPT $NXT_LIBM $NXT_LIBS $NXT_LIB_AUX_LIBS
END
else

View File

@@ -107,6 +107,7 @@ ${NXT_GO}-install-src: ${NXT_VERSION_H}
install -d \$(DESTDIR)\$(NXT_GO_DST)/src/nginx/unit
install -p -m644 ./src/*.h ./build/*.h ./src/go/unit/* \
./src/nxt_unit.c ./src/nxt_lvlhsh.c ./src/nxt_murmur_hash.c \
./src/nxt_websocket.c \
\$(DESTDIR)\$(NXT_GO_DST)/src/nginx/unit/
${NXT_GO}-install-build: ${NXT_GO}-install-src

View File

@@ -86,6 +86,11 @@ NXT_LIB_SRCS=" \
src/nxt_application.c \
src/nxt_external.c \
src/nxt_port_hash.c \
src/nxt_sha1.c \
src/nxt_websocket.c \
src/nxt_websocket_accept.c \
src/nxt_http_websocket.c \
src/nxt_h1proto_websocket.c \
"
NXT_LIB_SRC0=" \

View File

@@ -102,6 +102,26 @@ static nxt_int_t nxt_conf_vldt_java_option(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value);
static nxt_conf_vldt_object_t nxt_conf_vldt_websocket_members[] = {
{ nxt_string("read_timeout"),
NXT_CONF_VLDT_INTEGER,
NULL,
NULL },
{ nxt_string("keepalive_interval"),
NXT_CONF_VLDT_INTEGER,
NULL,
NULL },
{ nxt_string("max_frame_size"),
NXT_CONF_VLDT_INTEGER,
NULL,
NULL },
NXT_CONF_VLDT_END
};
static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = {
{ nxt_string("header_read_timeout"),
NXT_CONF_VLDT_INTEGER,
@@ -128,6 +148,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = {
NULL,
NULL },
{ nxt_string("websocket"),
NXT_CONF_VLDT_OBJECT,
&nxt_conf_vldt_object,
(void *) &nxt_conf_vldt_websocket_members },
NXT_CONF_VLDT_END
};

View File

@@ -6,26 +6,9 @@
#include <nxt_router.h>
#include <nxt_http.h>
struct nxt_h1proto_s {
nxt_http_request_parse_t parser;
uint8_t nbuffers;
uint8_t keepalive; /* 1 bit */
uint8_t chunked; /* 1 bit */
nxt_http_te_t transfer_encoding:8; /* 2 bits */
uint32_t header_size;
nxt_http_request_t *request;
nxt_buf_t *buffers;
/*
* All fields before the conn field will
* be zeroed in a keep-alive connection.
*/
nxt_conn_t *conn;
};
#include <nxt_h1proto.h>
#include <nxt_websocket.h>
#include <nxt_websocket_header.h>
/*
@@ -43,12 +26,18 @@ static void nxt_h1p_conn_proto_init(nxt_task_t *task, void *obj, void *data);
static void nxt_h1p_conn_request_init(nxt_task_t *task, void *obj, void *data);
static void nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj,
void *data);
static nxt_int_t nxt_h1p_header_process(nxt_h1proto_t *h1p,
static nxt_int_t nxt_h1p_header_process(nxt_task_t *task, nxt_h1proto_t *h1p,
nxt_http_request_t *r);
static nxt_int_t nxt_h1p_header_buffer_test(nxt_task_t *task,
nxt_h1proto_t *h1p, nxt_conn_t *c, nxt_socket_conf_t *skcf);
static nxt_int_t nxt_h1p_connection(void *ctx, nxt_http_field_t *field,
uintptr_t data);
static nxt_int_t nxt_h1p_upgrade(void *ctx, nxt_http_field_t *field,
uintptr_t data);
static nxt_int_t nxt_h1p_websocket_key(void *ctx, nxt_http_field_t *field,
uintptr_t data);
static nxt_int_t nxt_h1p_websocket_version(void *ctx, nxt_http_field_t *field,
uintptr_t data);
static nxt_int_t nxt_h1p_transfer_encoding(void *ctx, nxt_http_field_t *field,
uintptr_t data);
static void nxt_h1p_request_body_read(nxt_task_t *task, nxt_http_request_t *r);
@@ -70,8 +59,6 @@ static void nxt_h1p_conn_request_timeout(nxt_task_t *task, void *obj,
void *data);
static void nxt_h1p_conn_request_send_timeout(nxt_task_t *task, void *obj,
void *data);
static nxt_msec_t nxt_h1p_conn_request_timer_value(nxt_conn_t *c,
uintptr_t data);
nxt_inline void nxt_h1p_request_error(nxt_task_t *task, nxt_h1proto_t *h1p,
nxt_http_request_t *r);
static void nxt_h1p_request_close(nxt_task_t *task, nxt_http_proto_t proto,
@@ -91,16 +78,16 @@ static void nxt_h1p_idle_response_timeout(nxt_task_t *task, void *obj,
static nxt_msec_t nxt_h1p_idle_response_timer_value(nxt_conn_t *c,
uintptr_t data);
static void nxt_h1p_shutdown(nxt_task_t *task, nxt_conn_t *c);
static void nxt_h1p_shutdown_(nxt_task_t *task, nxt_conn_t *c);
static void nxt_h1p_conn_ws_shutdown(nxt_task_t *task, void *obj, void *data);
static void nxt_h1p_conn_closing(nxt_task_t *task, void *obj, void *data);
static void nxt_h1p_conn_free(nxt_task_t *task, void *obj, void *data);
#if (NXT_TLS)
static const nxt_conn_state_t nxt_http_idle_state;
static const nxt_conn_state_t nxt_h1p_shutdown_state;
#endif
static const nxt_conn_state_t nxt_h1p_idle_state;
static const nxt_conn_state_t nxt_h1p_idle_close_state;
static const nxt_conn_state_t nxt_h1p_header_parse_state;
static const nxt_conn_state_t nxt_h1p_read_body_state;
static const nxt_conn_state_t nxt_h1p_request_send_state;
@@ -119,6 +106,7 @@ const nxt_http_proto_table_t nxt_http_proto[3] = {
.body_bytes_sent = nxt_h1p_request_body_bytes_sent,
.discard = nxt_h1p_request_discard,
.close = nxt_h1p_request_close,
.ws_frame_start = nxt_h1p_websocket_frame_start,
},
/* NXT_HTTP_PROTO_H2 */
/* NXT_HTTP_PROTO_DEVNULL */
@@ -129,6 +117,10 @@ static nxt_lvlhsh_t nxt_h1p_fields_hash;
static nxt_http_field_proc_t nxt_h1p_fields[] = {
{ nxt_string("Connection"), &nxt_h1p_connection, 0 },
{ nxt_string("Upgrade"), &nxt_h1p_upgrade, 0 },
{ nxt_string("Sec-WebSocket-Key"), &nxt_h1p_websocket_key, 0 },
{ nxt_string("Sec-WebSocket-Version"),
&nxt_h1p_websocket_version, 0 },
{ nxt_string("Transfer-Encoding"), &nxt_h1p_transfer_encoding, 0 },
{ nxt_string("Host"), &nxt_http_request_host, 0 },
@@ -503,7 +495,7 @@ nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj, void *data)
*/
h1p->keepalive = (h1p->parser.version.s.minor != '0');
ret = nxt_h1p_header_process(h1p, r);
ret = nxt_h1p_header_process(task, h1p, r);
if (nxt_fast_path(ret == NXT_OK)) {
@@ -551,7 +543,7 @@ nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj, void *data)
break;
}
(void) nxt_h1p_header_process(h1p, r);
(void) nxt_h1p_header_process(task, h1p, r);
error:
@@ -562,8 +554,12 @@ error:
static nxt_int_t
nxt_h1p_header_process(nxt_h1proto_t *h1p, nxt_http_request_t *r)
nxt_h1p_header_process(nxt_task_t *task, nxt_h1proto_t *h1p,
nxt_http_request_t *r)
{
u_char *m;
nxt_int_t ret;
r->target.start = h1p->parser.target_start;
r->target.length = h1p->parser.target_end - h1p->parser.target_start;
@@ -578,7 +574,46 @@ nxt_h1p_header_process(nxt_h1proto_t *h1p, nxt_http_request_t *r)
r->fields = h1p->parser.fields;
return nxt_http_fields_process(r->fields, &nxt_h1p_fields_hash, r);
ret = nxt_http_fields_process(r->fields, &nxt_h1p_fields_hash, r);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
if (h1p->connection_upgrade && h1p->upgrade_websocket) {
m = h1p->parser.method.start;
if (nxt_slow_path(h1p->parser.method.length != 3
|| m[0] != 'G'
|| m[1] != 'E'
|| m[2] != 'T'))
{
nxt_log(task, NXT_LOG_INFO, "h1p upgrade: bad method");
return NXT_HTTP_BAD_REQUEST;
}
if (nxt_slow_path(h1p->parser.version.s.minor != '1')) {
nxt_log(task, NXT_LOG_INFO, "h1p upgrade: bad protocol version");
return NXT_HTTP_BAD_REQUEST;
}
if (nxt_slow_path(h1p->websocket_key == NULL)) {
nxt_log(task, NXT_LOG_INFO, "h1p upgrade: bad or absent websocket key");
return NXT_HTTP_BAD_REQUEST;
}
if (nxt_slow_path(h1p->websocket_version_ok == 0)) {
nxt_log(task, NXT_LOG_INFO, "h1p upgrade: bad or absent websocket version");
return NXT_HTTP_UPGRADE_REQUIRED;
}
r->websocket_handshake = 1;
}
return ret;
}
@@ -621,11 +656,67 @@ static nxt_int_t
nxt_h1p_connection(void *ctx, nxt_http_field_t *field, uintptr_t data)
{
nxt_http_request_t *r;
static const u_char *upgrade = (const u_char *) "upgrade";
r = ctx;
if (field->value_length == 5 && nxt_memcmp(field->value, "close", 5) == 0) {
r->proto.h1->keepalive = 0;
} else if (field->value_length == 7
&& nxt_memcasecmp(field->value, upgrade, 7) == 0)
{
r->proto.h1->connection_upgrade = 1;
}
return NXT_OK;
}
static nxt_int_t
nxt_h1p_upgrade(void *ctx, nxt_http_field_t *field, uintptr_t data)
{
nxt_http_request_t *r;
static const u_char *websocket = (const u_char *) "websocket";
r = ctx;
if (field->value_length == 9
&& nxt_memcasecmp(field->value, websocket, 9) == 0)
{
r->proto.h1->upgrade_websocket = 1;
}
return NXT_OK;
}
static nxt_int_t
nxt_h1p_websocket_key(void *ctx, nxt_http_field_t *field, uintptr_t data)
{
nxt_http_request_t *r;
r = ctx;
if (field->value_length == 24) {
r->proto.h1->websocket_key = field;
}
return NXT_OK;
}
static nxt_int_t
nxt_h1p_websocket_version(void *ctx, nxt_http_field_t *field, uintptr_t data)
{
nxt_http_request_t *r;
r = ctx;
if (field->value_length == 2
&& field->value[0] == '1' && field->value[1] == '3')
{
r->proto.h1->websocket_version_ok = 1;
}
return NXT_OK;
@@ -727,6 +818,7 @@ nxt_h1p_request_body_read(nxt_task_t *task, nxt_http_request_t *r)
if (size != 0) {
in->next = h1p->buffers;
h1p->buffers = in;
h1p->nbuffers++;
c = h1p->conn;
c->read = b;
@@ -805,6 +897,15 @@ nxt_h1p_request_local_addr(nxt_task_t *task, nxt_http_request_t *r)
}
#define NXT_HTTP_LAST_INFORMATIONAL \
(NXT_HTTP_CONTINUE + nxt_nitems(nxt_http_informational) - 1)
static const nxt_str_t nxt_http_informational[] = {
nxt_string("HTTP/1.1 100 Continue\r\n"),
nxt_string("HTTP/1.1 101 Switching Protocols\r\n"),
};
#define NXT_HTTP_LAST_SUCCESS \
(NXT_HTTP_OK + nxt_nitems(nxt_http_success) - 1)
@@ -861,7 +962,7 @@ static const nxt_str_t nxt_http_client_error[] = {
nxt_string("HTTP/1.1 423\r\n"),
nxt_string("HTTP/1.1 424\r\n"),
nxt_string("HTTP/1.1 425\r\n"),
nxt_string("HTTP/1.1 426\r\n"),
nxt_string("HTTP/1.1 426 Upgrade Required\r\n"),
nxt_string("HTTP/1.1 427\r\n"),
nxt_string("HTTP/1.1 428\r\n"),
nxt_string("HTTP/1.1 429\r\n"),
@@ -911,10 +1012,14 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
u_char buf[UNKNOWN_STATUS_LENGTH];
static const char chunked[] = "Transfer-Encoding: chunked\r\n";
static const char websocket_version[] = "Sec-WebSocket-Version: 13\r\n";
static const nxt_str_t connection[2] = {
static const nxt_str_t connection[3] = {
nxt_string("Connection: close\r\n"),
nxt_string("Connection: keep-alive\r\n"),
nxt_string("Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: "),
};
nxt_debug(task, "h1p request header send");
@@ -923,7 +1028,10 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
h1p = r->proto.h1;
n = r->status;
if (n >= NXT_HTTP_OK && n <= NXT_HTTP_LAST_SUCCESS) {
if (n >= NXT_HTTP_CONTINUE && n <= NXT_HTTP_LAST_INFORMATIONAL) {
status = &nxt_http_informational[n - NXT_HTTP_CONTINUE];
} else if (n >= NXT_HTTP_OK && n <= NXT_HTTP_LAST_SUCCESS) {
status = &nxt_http_success[n - NXT_HTTP_OK];
} else if (n >= NXT_HTTP_MULTIPLE_CHOICES
@@ -955,12 +1063,24 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
/* Trailing CRLF at the end of header. */
size += nxt_length("\r\n");
conn = -1;
if (r->websocket_handshake && n == NXT_HTTP_SWITCHING_PROTOCOLS) {
h1p->websocket = 1;
h1p->keepalive = 0;
conn = 2;
size += NXT_WEBSOCKET_ACCEPT_SIZE + 2;
} else {
http11 = (h1p->parser.version.s.minor != '0');
if (r->resp.content_length == NULL || r->resp.content_length->skip) {
if (http11) {
if (n != NXT_HTTP_NOT_MODIFIED && n != NXT_HTTP_NO_CONTENT) {
if (n != NXT_HTTP_NOT_MODIFIED
&& n != NXT_HTTP_NO_CONTENT
&& !h1p->websocket)
{
h1p->chunked = 1;
size += nxt_length(chunked);
/* Trailing CRLF will be added by the first chunk header. */
@@ -972,10 +1092,12 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
}
}
conn = -1;
if (http11 ^ h1p->keepalive) {
conn = h1p->keepalive;
}
}
if (conn >= 0) {
size += connection[conn].length;
}
@@ -988,15 +1110,17 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
} nxt_list_loop;
if (nxt_slow_path(n == NXT_HTTP_UPGRADE_REQUIRED)) {
size += nxt_length(websocket_version);
}
header = nxt_http_buf_mem(task, r, size);
if (nxt_slow_path(header == NULL)) {
nxt_h1p_request_error(task, h1p, r);
return;
}
p = header->mem.free;
p = nxt_cpymem(p, status->start, status->length);
p = nxt_cpymem(header->mem.free, status->start, status->length);
nxt_list_each(field, r->resp.fields) {
@@ -1013,6 +1137,17 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
p = nxt_cpymem(p, connection[conn].start, connection[conn].length);
}
if (h1p->websocket) {
nxt_websocket_accept(p, h1p->websocket_key->value);
p += NXT_WEBSOCKET_ACCEPT_SIZE;
*p++ = '\r'; *p++ = '\n';
}
if (nxt_slow_path(n == NXT_HTTP_UPGRADE_REQUIRED)) {
p = nxt_cpymem(p, websocket_version, nxt_length(websocket_version));
}
if (h1p->chunked) {
p = nxt_cpymem(p, chunked, nxt_length(chunked));
/* Trailing CRLF will be added by the first chunk header. */
@@ -1031,6 +1166,58 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
c->write_state = &nxt_h1p_request_send_state;
nxt_conn_write(task->thread->engine, c);
if (h1p->websocket) {
nxt_h1p_websocket_first_frame_start(task, r, c->read);
}
}
void
nxt_h1p_complete_buffers(nxt_task_t *task, nxt_h1proto_t *h1p)
{
size_t size;
nxt_buf_t *b, *in, *next;
nxt_conn_t *c;
nxt_debug(task, "h1p complete buffers");
b = h1p->buffers;
c = h1p->conn;
in = c->read;
if (b != NULL) {
if (in == NULL) {
/* A request with large body. */
in = b;
c->read = in;
b = in->next;
in->next = NULL;
}
while (b != NULL) {
next = b->next;
nxt_work_queue_add(&task->thread->engine->fast_work_queue,
b->completion_handler, task, b, b->parent);
b = next;
}
h1p->buffers = NULL;
h1p->nbuffers = 0;
}
if (in != NULL) {
size = nxt_buf_mem_used_size(&in->mem);
if (size == 0) {
nxt_mp_free(c->mem_pool, in);
c->read = NULL;
}
}
}
@@ -1181,8 +1368,13 @@ nxt_h1p_conn_request_error(nxt_task_t *task, void *obj, void *data)
r = h1p->request;
if (nxt_slow_path(r == NULL)) {
nxt_h1p_shutdown(task, h1p->conn);
return;
}
if (r->fields == NULL) {
(void) nxt_h1p_header_process(h1p, r);
(void) nxt_h1p_header_process(task, h1p, r);
}
if (r->status == 0) {
@@ -1218,7 +1410,7 @@ nxt_h1p_conn_request_timeout(nxt_task_t *task, void *obj, void *data)
r = h1p->request;
if (r->fields == NULL) {
(void) nxt_h1p_header_process(h1p, r);
(void) nxt_h1p_header_process(task, h1p, r);
}
nxt_http_request_error(task, r, NXT_HTTP_REQUEST_TIMEOUT);
@@ -1244,7 +1436,7 @@ nxt_h1p_conn_request_send_timeout(nxt_task_t *task, void *obj, void *data)
}
static nxt_msec_t
nxt_msec_t
nxt_h1p_conn_request_timer_value(nxt_conn_t *c, uintptr_t data)
{
nxt_h1proto_t *h1p;
@@ -1351,7 +1543,7 @@ static void
nxt_h1p_keepalive(nxt_task_t *task, nxt_h1proto_t *h1p, nxt_conn_t *c)
{
size_t size;
nxt_buf_t *in, *b, *next;
nxt_buf_t *in;
nxt_debug(task, "h1p keepalive");
@@ -1359,40 +1551,22 @@ nxt_h1p_keepalive(nxt_task_t *task, nxt_h1proto_t *h1p, nxt_conn_t *c)
nxt_conn_tcp_nodelay_on(task, c);
}
b = h1p->buffers;
nxt_h1p_complete_buffers(task, h1p);
in = c->read;
nxt_memzero(h1p, offsetof(nxt_h1proto_t, conn));
c->sent = 0;
in = c->read;
if (in == NULL) {
/* A request with large body. */
in = b;
c->read = in;
b = in->next;
in->next = NULL;
}
while (b != NULL) {
next = b->next;
nxt_mp_free(c->mem_pool, b);
b = next;
}
size = nxt_buf_mem_used_size(&in->mem);
if (size == 0) {
nxt_mp_free(c->mem_pool, in);
c->read = NULL;
c->read_state = &nxt_h1p_keepalive_state;
nxt_conn_read(task->thread->engine, c);
} else {
size = nxt_buf_mem_used_size(&in->mem);
nxt_debug(task, "h1p pipelining");
nxt_memmove(in->mem.start, in->mem.pos, size);
@@ -1421,7 +1595,7 @@ static const nxt_conn_state_t nxt_h1p_keepalive_state
};
static const nxt_conn_state_t nxt_h1p_idle_close_state
const nxt_conn_state_t nxt_h1p_idle_close_state
nxt_aligned(64) =
{
.close_handler = nxt_h1p_idle_close,
@@ -1564,8 +1738,33 @@ nxt_h1p_idle_response_timer_value(nxt_conn_t *c, uintptr_t data)
static void
nxt_h1p_shutdown(nxt_task_t *task, nxt_conn_t *c)
{
nxt_timer_t *timer;
nxt_h1proto_t *h1p;
nxt_debug(task, "h1p shutdown");
h1p = c->socket.data;
if (nxt_slow_path(h1p != NULL && h1p->websocket_timer != NULL)) {
timer = &h1p->websocket_timer->timer;
if (timer->handler != nxt_h1p_conn_ws_shutdown) {
timer->handler = nxt_h1p_conn_ws_shutdown;
nxt_timer_add(task->thread->engine, timer, 0);
} else {
nxt_debug(task, "h1p already scheduled ws shutdown");
}
} else {
nxt_h1p_shutdown_(task, c);
}
}
static void
nxt_h1p_shutdown_(nxt_task_t *task, nxt_conn_t *c)
{
c->socket.data = NULL;
#if (NXT_TLS)
@@ -1596,6 +1795,21 @@ static const nxt_conn_state_t nxt_h1p_shutdown_state
#endif
static void
nxt_h1p_conn_ws_shutdown(nxt_task_t *task, void *obj, void *data)
{
nxt_timer_t *timer;
nxt_h1p_websocket_timer_t *ws_timer;
nxt_debug(task, "h1p conn ws shutdown");
timer = obj;
ws_timer = nxt_timer_data(timer, nxt_h1p_websocket_timer_t, timer);
nxt_h1p_shutdown_(task, ws_timer->h1p->conn);
}
static void
nxt_h1p_conn_closing(nxt_task_t *task, void *obj, void *data)
{

48
src/nxt_h1proto.h Normal file
View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) NGINX, Inc.
*/
#ifndef _NXT_H1PROTO_H_INCLUDED_
#define _NXT_H1PROTO_H_INCLUDED_
#include <nxt_main.h>
#include <nxt_http_parse.h>
#include <nxt_http.h>
#include <nxt_router.h>
typedef struct nxt_h1p_websocket_timer_s nxt_h1p_websocket_timer_t;
struct nxt_h1proto_s {
nxt_http_request_parse_t parser;
uint8_t nbuffers;
uint8_t keepalive; /* 1 bit */
uint8_t chunked; /* 1 bit */
uint8_t websocket; /* 1 bit */
uint8_t connection_upgrade; /* 1 bit */
uint8_t upgrade_websocket; /* 1 bit */
uint8_t websocket_version_ok; /* 1 bit */
nxt_http_te_t transfer_encoding:8; /* 2 bits */
uint8_t websocket_cont_expected; /* 1 bit */
uint8_t websocket_closed; /* 1 bit */
uint32_t header_size;
nxt_http_field_t *websocket_key;
nxt_h1p_websocket_timer_t *websocket_timer;
nxt_http_request_t *request;
nxt_buf_t *buffers;
/*
* All fields before the conn field will
* be zeroed in a keep-alive connection.
*/
nxt_conn_t *conn;
};
#endif /* _NXT_H1PROTO_H_INCLUDED_ */

719
src/nxt_h1proto_websocket.c Normal file
View File

@@ -0,0 +1,719 @@
/*
* Copyright (C) NGINX, Inc.
*/
#include <nxt_main.h>
#include <nxt_router.h>
#include <nxt_http.h>
#include <nxt_h1proto.h>
#include <nxt_websocket.h>
#include <nxt_websocket_header.h>
typedef struct {
uint16_t code;
uint8_t args;
nxt_str_t desc;
} nxt_ws_error_t;
static void nxt_h1p_conn_ws_keepalive(nxt_task_t *task, void *obj, void *data);
static void nxt_h1p_conn_ws_frame_header_read(nxt_task_t *task, void *obj,
void *data);
static void nxt_h1p_conn_ws_keepalive_disable(nxt_task_t *task,
nxt_h1proto_t *h1p);
static void nxt_h1p_conn_ws_keepalive_enable(nxt_task_t *task,
nxt_h1proto_t *h1p);
static void nxt_h1p_conn_ws_frame_process(nxt_task_t *task, nxt_conn_t *c,
nxt_h1proto_t *h1p, nxt_websocket_header_t *wsh);
static void nxt_h1p_conn_ws_error(nxt_task_t *task, void *obj, void *data);
static ssize_t nxt_h1p_ws_io_read_handler(nxt_conn_t *c);
static void nxt_h1p_conn_ws_timeout(nxt_task_t *task, void *obj, void *data);
static void nxt_h1p_conn_ws_frame_payload_read(nxt_task_t *task, void *obj,
void *data);
static void hxt_h1p_send_ws_error(nxt_task_t *task, nxt_http_request_t *r,
const nxt_ws_error_t *err, ...);
static void nxt_h1p_conn_ws_error_sent(nxt_task_t *task, void *obj, void *data);
static void nxt_h1p_conn_ws_pong(nxt_task_t *task, void *obj, void *data);
static const nxt_conn_state_t nxt_h1p_read_ws_frame_header_state;
static const nxt_conn_state_t nxt_h1p_read_ws_frame_payload_state;
static const nxt_ws_error_t nxt_ws_err_out_of_memory = {
NXT_WEBSOCKET_CR_INTERNAL_SERVER_ERROR,
0, nxt_string("Out of memory") };
static const nxt_ws_error_t nxt_ws_err_too_big = {
NXT_WEBSOCKET_CR_MESSAGE_TOO_BIG,
1, nxt_string("Message too big: %uL bytes") };
static const nxt_ws_error_t nxt_ws_err_invalid_close_code = {
NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
1, nxt_string("Close code %ud is not valid") };
static const nxt_ws_error_t nxt_ws_err_going_away = {
NXT_WEBSOCKET_CR_GOING_AWAY,
0, nxt_string("Remote peer is going away") };
static const nxt_ws_error_t nxt_ws_err_not_masked = {
NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
0, nxt_string("Not masked client frame") };
static const nxt_ws_error_t nxt_ws_err_ctrl_fragmented = {
NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
0, nxt_string("Fragmented control frame") };
static const nxt_ws_error_t nxt_ws_err_ctrl_too_big = {
NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
1, nxt_string("Control frame too big: %uL bytes") };
static const nxt_ws_error_t nxt_ws_err_invalid_close_len = {
NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
0, nxt_string("Close frame payload length cannot be 1") };
static const nxt_ws_error_t nxt_ws_err_invalid_opcode = {
NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
1, nxt_string("Unrecognized opcode %ud") };
static const nxt_ws_error_t nxt_ws_err_cont_expected = {
NXT_WEBSOCKET_CR_PROTOCOL_ERROR,
1, nxt_string("Continuation expected, but %ud opcode received") };
void
nxt_h1p_websocket_first_frame_start(nxt_task_t *task, nxt_http_request_t *r,
nxt_buf_t *ws_frame)
{
nxt_conn_t *c;
nxt_timer_t *timer;
nxt_h1proto_t *h1p;
nxt_socket_conf_joint_t *joint;
nxt_debug(task, "h1p ws first frame start");
h1p = r->proto.h1;
c = h1p->conn;
if (!c->tcp_nodelay) {
nxt_conn_tcp_nodelay_on(task, c);
}
joint = c->listen->socket.data;
if (nxt_slow_path(joint != NULL
&& joint->socket_conf->websocket_conf.keepalive_interval != 0))
{
h1p->websocket_timer = nxt_mp_zget(c->mem_pool,
sizeof(nxt_h1p_websocket_timer_t));
if (nxt_slow_path(h1p->websocket_timer == NULL)) {
hxt_h1p_send_ws_error(task, r, &nxt_ws_err_out_of_memory);
return;
}
h1p->websocket_timer->keepalive_interval =
joint->socket_conf->websocket_conf.keepalive_interval;
h1p->websocket_timer->h1p = h1p;
timer = &h1p->websocket_timer->timer;
timer->task = &c->task;
timer->work_queue = &task->thread->engine->fast_work_queue;
timer->log = &c->log;
timer->bias = NXT_TIMER_DEFAULT_BIAS;
timer->handler = nxt_h1p_conn_ws_keepalive;
}
nxt_h1p_websocket_frame_start(task, r, ws_frame);
}
void
nxt_h1p_websocket_frame_start(nxt_task_t *task, nxt_http_request_t *r,
nxt_buf_t *ws_frame)
{
size_t size;
nxt_buf_t *in;
nxt_conn_t *c;
nxt_h1proto_t *h1p;
nxt_debug(task, "h1p ws frame start");
h1p = r->proto.h1;
if (nxt_slow_path(h1p->websocket_closed)) {
return;
}
c = h1p->conn;
c->read = ws_frame;
nxt_h1p_complete_buffers(task, h1p);
in = c->read;
c->read_state = &nxt_h1p_read_ws_frame_header_state;
if (in == NULL) {
nxt_conn_read(task->thread->engine, c);
nxt_h1p_conn_ws_keepalive_enable(task, h1p);
} else {
size = nxt_buf_mem_used_size(&in->mem);
nxt_debug(task, "h1p read client ws frame");
nxt_memmove(in->mem.start, in->mem.pos, size);
in->mem.pos = in->mem.start;
in->mem.free = in->mem.start + size;
nxt_h1p_conn_ws_frame_header_read(task, c, h1p);
}
}
static void
nxt_h1p_conn_ws_keepalive(nxt_task_t *task, void *obj, void *data)
{
nxt_buf_t *out;
nxt_timer_t *timer;
nxt_h1proto_t *h1p;
nxt_http_request_t *r;
nxt_websocket_header_t *wsh;
nxt_h1p_websocket_timer_t *ws_timer;
nxt_debug(task, "h1p conn ws keepalive");
timer = obj;
ws_timer = nxt_timer_data(timer, nxt_h1p_websocket_timer_t, timer);
h1p = ws_timer->h1p;
r = h1p->request;
if (nxt_slow_path(r == NULL)) {
return;
}
out = nxt_http_buf_mem(task, r, 2);
if (nxt_slow_path(out == NULL)) {
nxt_http_request_error_handler(task, r, r->proto.any);
return;
}
out->mem.start[0] = 0;
out->mem.start[1] = 0;
wsh = (nxt_websocket_header_t *) out->mem.start;
out->mem.free = nxt_websocket_frame_init(wsh, 0);
wsh->fin = 1;
wsh->opcode = NXT_WEBSOCKET_OP_PING;
nxt_http_request_send(task, r, out);
}
static const nxt_conn_state_t nxt_h1p_read_ws_frame_header_state
nxt_aligned(64) =
{
.ready_handler = nxt_h1p_conn_ws_frame_header_read,
.close_handler = nxt_h1p_conn_ws_error,
.error_handler = nxt_h1p_conn_ws_error,
.io_read_handler = nxt_h1p_ws_io_read_handler,
.timer_handler = nxt_h1p_conn_ws_timeout,
.timer_value = nxt_h1p_conn_request_timer_value,
.timer_data = offsetof(nxt_socket_conf_t, websocket_conf.read_timeout),
.timer_autoreset = 1,
};
static void
nxt_h1p_conn_ws_frame_header_read(nxt_task_t *task, void *obj, void *data)
{
size_t size, hsize, frame_size, max_frame_size;
uint64_t payload_len;
nxt_conn_t *c;
nxt_h1proto_t *h1p;
nxt_http_request_t *r;
nxt_event_engine_t *engine;
nxt_websocket_header_t *wsh;
nxt_socket_conf_joint_t *joint;
c = obj;
h1p = data;
nxt_h1p_conn_ws_keepalive_disable(task, h1p);
size = nxt_buf_mem_used_size(&c->read->mem);
engine = task->thread->engine;
if (size < 2) {
nxt_debug(task, "h1p conn ws frame header read %z", size);
nxt_conn_read(engine, c);
nxt_h1p_conn_ws_keepalive_enable(task, h1p);
return;
}
wsh = (nxt_websocket_header_t *) c->read->mem.pos;
hsize = nxt_websocket_frame_header_size(wsh);
if (size < hsize) {
nxt_debug(task, "h1p conn ws frame header read %z < %z", size, hsize);
nxt_conn_read(engine, c);
nxt_h1p_conn_ws_keepalive_enable(task, h1p);
return;
}
r = h1p->request;
if (nxt_slow_path(r == NULL)) {
return;
}
r->ws_frame = c->read;
joint = c->listen->socket.data;
if (nxt_slow_path(joint == NULL)) {
/*
* Listening socket had been closed while
* connection was in keep-alive state.
*/
c->read_state = &nxt_h1p_idle_close_state;
return;
}
if (nxt_slow_path(wsh->mask == 0)) {
hxt_h1p_send_ws_error(task, r, &nxt_ws_err_not_masked);
return;
}
if ((wsh->opcode & NXT_WEBSOCKET_OP_CTRL) != 0) {
if (nxt_slow_path(wsh->fin == 0)) {
hxt_h1p_send_ws_error(task, r, &nxt_ws_err_ctrl_fragmented);
return;
}
if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_PING
&& wsh->opcode != NXT_WEBSOCKET_OP_PONG
&& wsh->opcode != NXT_WEBSOCKET_OP_CLOSE))
{
hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_opcode,
wsh->opcode);
return;
}
if (nxt_slow_path(wsh->payload_len > 125)) {
hxt_h1p_send_ws_error(task, r, &nxt_ws_err_ctrl_too_big,
nxt_websocket_frame_payload_len(wsh));
return;
}
if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_CLOSE
&& wsh->payload_len == 1))
{
hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_close_len);
return;
}
} else {
if (h1p->websocket_cont_expected) {
if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_CONT)) {
hxt_h1p_send_ws_error(task, r, &nxt_ws_err_cont_expected,
wsh->opcode);
return;
}
} else {
if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_BINARY
&& wsh->opcode != NXT_WEBSOCKET_OP_TEXT))
{
hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_opcode,
wsh->opcode);
return;
}
}
h1p->websocket_cont_expected = !wsh->fin;
}
max_frame_size = joint->socket_conf->websocket_conf.max_frame_size;
payload_len = nxt_websocket_frame_payload_len(wsh);
if (nxt_slow_path(hsize > max_frame_size
|| payload_len > (max_frame_size - hsize)))
{
hxt_h1p_send_ws_error(task, r, &nxt_ws_err_too_big, payload_len);
return;
}
c->read_state = &nxt_h1p_read_ws_frame_payload_state;
frame_size = payload_len + hsize;
nxt_debug(task, "h1p conn ws frame header read: %z, %z", size, frame_size);
if (frame_size <= size) {
nxt_h1p_conn_ws_frame_process(task, c, h1p, wsh);
return;
}
if (frame_size < (size_t) nxt_buf_mem_size(&c->read->mem)) {
c->read->mem.end = c->read->mem.start + frame_size;
} else {
nxt_buf_t *b = nxt_buf_mem_alloc(c->mem_pool, frame_size - size, 0);
c->read->next = b;
c->read = b;
}
nxt_conn_read(engine, c);
nxt_h1p_conn_ws_keepalive_enable(task, h1p);
}
static void
nxt_h1p_conn_ws_keepalive_disable(nxt_task_t *task, nxt_h1proto_t *h1p)
{
nxt_timer_t *timer;
if (h1p->websocket_timer == NULL) {
return;
}
timer = &h1p->websocket_timer->timer;
if (nxt_slow_path(timer->handler != nxt_h1p_conn_ws_keepalive)) {
nxt_debug(task, "h1p ws keepalive disable: scheduled ws shutdown");
return;
}
nxt_timer_disable(task->thread->engine, timer);
}
static void
nxt_h1p_conn_ws_keepalive_enable(nxt_task_t *task, nxt_h1proto_t *h1p)
{
nxt_timer_t *timer;
if (h1p->websocket_timer == NULL) {
return;
}
timer = &h1p->websocket_timer->timer;
if (nxt_slow_path(timer->handler != nxt_h1p_conn_ws_keepalive)) {
nxt_debug(task, "h1p ws keepalive enable: scheduled ws shutdown");
return;
}
nxt_timer_add(task->thread->engine, timer,
h1p->websocket_timer->keepalive_interval);
}
static void
nxt_h1p_conn_ws_frame_process(nxt_task_t *task, nxt_conn_t *c,
nxt_h1proto_t *h1p, nxt_websocket_header_t *wsh)
{
size_t hsize;
uint8_t *p, *mask;
uint16_t code;
nxt_http_request_t *r;
nxt_event_engine_t *engine;
engine = task->thread->engine;
r = h1p->request;
c->read = NULL;
if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_PING)) {
nxt_work_queue_add(&engine->fast_work_queue, nxt_h1p_conn_ws_pong,
task, r, NULL);
return;
}
if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_CLOSE)) {
if (wsh->payload_len >= 2) {
hsize = nxt_websocket_frame_header_size(wsh);
mask = nxt_pointer_to(wsh, hsize - 4);
p = nxt_pointer_to(wsh, hsize);
code = ((p[0] ^ mask[0]) << 8) + (p[1] ^ mask[1]);
if (nxt_slow_path(code < 1000 || code >= 5000
|| (code > 1003 && code < 1007)
|| (code > 1014 && code < 3000)))
{
hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_close_code,
code);
return;
}
}
h1p->websocket_closed = 1;
}
nxt_work_queue_add(&engine->fast_work_queue, r->state->ready_handler,
task, r, NULL);
}
static void
nxt_h1p_conn_ws_error(nxt_task_t *task, void *obj, void *data)
{
nxt_h1proto_t *h1p;
nxt_http_request_t *r;
h1p = data;
nxt_debug(task, "h1p conn ws error");
r = h1p->request;
h1p->keepalive = 0;
if (nxt_fast_path(r != NULL)) {
r->state->error_handler(task, r, h1p);
}
}
static ssize_t
nxt_h1p_ws_io_read_handler(nxt_conn_t *c)
{
size_t size;
ssize_t n;
nxt_buf_t *b;
b = c->read;
if (b == NULL) {
/* Enough for control frame. */
size = 10 + 125;
b = nxt_buf_mem_alloc(c->mem_pool, size, 0);
if (nxt_slow_path(b == NULL)) {
c->socket.error = NXT_ENOMEM;
return NXT_ERROR;
}
}
n = c->io->recvbuf(c, b);
if (n > 0) {
c->read = b;
} else {
c->read = NULL;
nxt_mp_free(c->mem_pool, b);
}
return n;
}
static void
nxt_h1p_conn_ws_timeout(nxt_task_t *task, void *obj, void *data)
{
nxt_conn_t *c;
nxt_timer_t *timer;
nxt_h1proto_t *h1p;
nxt_http_request_t *r;
timer = obj;
nxt_debug(task, "h1p conn ws timeout");
c = nxt_read_timer_conn(timer);
c->block_read = 1;
/*
* Disable SO_LINGER off during socket closing
* to send "408 Request Timeout" error response.
*/
c->socket.timedout = 0;
h1p = c->socket.data;
h1p->keepalive = 0;
r = h1p->request;
if (nxt_slow_path(r == NULL)) {
return;
}
hxt_h1p_send_ws_error(task, r, &nxt_ws_err_going_away);
}
static const nxt_conn_state_t nxt_h1p_read_ws_frame_payload_state
nxt_aligned(64) =
{
.ready_handler = nxt_h1p_conn_ws_frame_payload_read,
.close_handler = nxt_h1p_conn_ws_error,
.error_handler = nxt_h1p_conn_ws_error,
.timer_handler = nxt_h1p_conn_ws_timeout,
.timer_value = nxt_h1p_conn_request_timer_value,
.timer_data = offsetof(nxt_socket_conf_t, websocket_conf.read_timeout),
.timer_autoreset = 1,
};
static void
nxt_h1p_conn_ws_frame_payload_read(nxt_task_t *task, void *obj, void *data)
{
nxt_conn_t *c;
nxt_h1proto_t *h1p;
nxt_http_request_t *r;
nxt_event_engine_t *engine;
nxt_websocket_header_t *wsh;
c = obj;
h1p = data;
nxt_h1p_conn_ws_keepalive_disable(task, h1p);
nxt_debug(task, "h1p conn ws frame read");
if (nxt_buf_mem_free_size(&c->read->mem) == 0) {
r = h1p->request;
if (nxt_slow_path(r == NULL)) {
return;
}
wsh = (nxt_websocket_header_t *) r->ws_frame->mem.pos;
nxt_h1p_conn_ws_frame_process(task, c, h1p, wsh);
return;
}
engine = task->thread->engine;
nxt_conn_read(engine, c);
nxt_h1p_conn_ws_keepalive_enable(task, h1p);
}
static void
hxt_h1p_send_ws_error(nxt_task_t *task, nxt_http_request_t *r,
const nxt_ws_error_t *err, ...)
{
u_char *p;
va_list args;
nxt_buf_t *out;
nxt_str_t desc;
nxt_websocket_header_t *wsh;
u_char buf[125];
if (nxt_slow_path(err->args)) {
va_start(args, err);
p = nxt_vsprintf(buf, buf + sizeof(buf), (char *) err->desc.start,
args);
va_end(args);
desc.start = buf;
desc.length = p - buf;
} else {
desc = err->desc;
}
nxt_log(task, NXT_LOG_INFO, "websocket error %d: %V", err->code, &desc);
out = nxt_http_buf_mem(task, r, 2 + sizeof(err->code) + desc.length);
if (nxt_slow_path(out == NULL)) {
nxt_http_request_error_handler(task, r, r->proto.any);
return;
}
out->mem.start[0] = 0;
out->mem.start[1] = 0;
wsh = (nxt_websocket_header_t *) out->mem.start;
p = nxt_websocket_frame_init(wsh, sizeof(err->code) + desc.length);
wsh->fin = 1;
wsh->opcode = NXT_WEBSOCKET_OP_CLOSE;
*p++ = (err->code >> 8) & 0xFF;
*p++ = err->code & 0xFF;
out->mem.free = nxt_cpymem(p, desc.start, desc.length);
out->next = nxt_http_buf_last(r);
if (out->next != NULL) {
out->next->completion_handler = nxt_h1p_conn_ws_error_sent;
}
nxt_http_request_send(task, r, out);
}
static void
nxt_h1p_conn_ws_error_sent(nxt_task_t *task, void *obj, void *data)
{
nxt_http_request_t *r;
r = data;
nxt_debug(task, "h1p conn ws error sent");
r->state->error_handler(task, r, r->proto.any);
}
static void
nxt_h1p_conn_ws_pong(nxt_task_t *task, void *obj, void *data)
{
uint8_t payload_len, i;
nxt_buf_t *b, *out, *next;
nxt_http_request_t *r;
nxt_websocket_header_t *wsh;
uint8_t mask[4];
nxt_debug(task, "h1p conn ws pong");
r = obj;
b = r->ws_frame;
wsh = (nxt_websocket_header_t *) b->mem.pos;
payload_len = wsh->payload_len;
b->mem.pos += 2;
nxt_memcpy(mask, b->mem.pos, 4);
b->mem.pos += 4;
out = nxt_http_buf_mem(task, r, 2 + payload_len);
if (nxt_slow_path(out == NULL)) {
nxt_http_request_error_handler(task, r, r->proto.any);
return;
}
out->mem.start[0] = 0;
out->mem.start[1] = 0;
wsh = (nxt_websocket_header_t *) out->mem.start;
out->mem.free = nxt_websocket_frame_init(wsh, payload_len);
wsh->fin = 1;
wsh->opcode = NXT_WEBSOCKET_OP_PONG;
for (i = 0; i < payload_len; i++) {
while (nxt_buf_mem_used_size(&b->mem) == 0) {
next = b->next;
nxt_work_queue_add(&task->thread->engine->fast_work_queue,
b->completion_handler, task, b, b->parent);
b = next;
}
*out->mem.free++ = *b->mem.pos++ ^ mask[i % 4];
}
r->ws_frame = b;
nxt_http_request_send(task, r, out);
nxt_http_request_ws_frame_start(task, r, r->ws_frame);
}

View File

@@ -11,6 +11,9 @@
typedef enum {
NXT_HTTP_INVALID = 0,
NXT_HTTP_CONTINUE = 100,
NXT_HTTP_SWITCHING_PROTOCOLS = 101,
NXT_HTTP_OK = 200,
NXT_HTTP_NO_CONTENT = 204,
@@ -26,6 +29,7 @@ typedef enum {
NXT_HTTP_LENGTH_REQUIRED = 411,
NXT_HTTP_PAYLOAD_TOO_LARGE = 413,
NXT_HTTP_URI_TOO_LONG = 414,
NXT_HTTP_UPGRADE_REQUIRED = 426,
NXT_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
NXT_HTTP_TO_HTTPS = 497,
@@ -61,6 +65,13 @@ typedef struct {
typedef struct nxt_h1proto_s nxt_h1proto_t;
struct nxt_h1p_websocket_timer_s {
nxt_timer_t timer;
nxt_h1proto_t *h1p;
nxt_msec_t keepalive_interval;
};
typedef union {
void *any;
nxt_h1proto_t *h1;
@@ -99,6 +110,7 @@ struct nxt_http_request_s {
nxt_mp_t *mem_pool;
nxt_buf_t *body;
nxt_buf_t *ws_frame;
nxt_buf_t *out;
const nxt_http_request_state_t *state;
@@ -127,6 +139,8 @@ struct nxt_http_request_s {
nxt_timer_t timer;
void *timer_data;
void *req_rpc_data;
nxt_buf_t *last;
nxt_http_response_t resp;
@@ -138,6 +152,7 @@ struct nxt_http_request_s {
uint8_t logged; /* 1 bit */
uint8_t header_sent; /* 1 bit */
uint8_t error; /* 1 bit */
uint8_t websocket_handshake; /* 1 bit */
};
@@ -166,6 +181,8 @@ typedef struct {
void (*discard)(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *last);
void (*close)(nxt_task_t *task, nxt_http_proto_t proto,
nxt_socket_conf_joint_t *joint);
void (*ws_frame_start)(nxt_task_t *task, nxt_http_request_t *r,
nxt_buf_t *ws_frame);
} nxt_http_proto_table_t;
@@ -179,12 +196,15 @@ void nxt_http_request_error(nxt_task_t *task, nxt_http_request_t *r,
nxt_http_status_t status);
void nxt_http_request_read_body(nxt_task_t *task, nxt_http_request_t *r);
void nxt_http_request_header_send(nxt_task_t *task, nxt_http_request_t *r);
void nxt_http_request_ws_frame_start(nxt_task_t *task, nxt_http_request_t *r,
nxt_buf_t *ws_frame);
void nxt_http_request_send(nxt_task_t *task, nxt_http_request_t *r,
nxt_buf_t *out);
nxt_buf_t *nxt_http_buf_mem(nxt_task_t *task, nxt_http_request_t *r,
size_t size);
nxt_buf_t *nxt_http_buf_last(nxt_http_request_t *r);
void nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data);
void nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data);
nxt_int_t nxt_http_request_host(void *ctx, nxt_http_field_t *field,
uintptr_t data);
@@ -212,5 +232,13 @@ extern nxt_lvlhsh_t nxt_response_fields_hash;
extern const nxt_http_proto_table_t nxt_http_proto[];
void nxt_h1p_websocket_first_frame_start(nxt_task_t *task,
nxt_http_request_t *r, nxt_buf_t *ws_frame);
void nxt_h1p_websocket_frame_start(nxt_task_t *task, nxt_http_request_t *r,
nxt_buf_t *ws_frame);
void nxt_h1p_complete_buffers(nxt_task_t *task, nxt_h1proto_t *h1p);
nxt_msec_t nxt_h1p_conn_request_timer_value(nxt_conn_t *c, uintptr_t data);
extern const nxt_conn_state_t nxt_h1p_idle_close_state;
#endif /* _NXT_HTTP_H_INCLUDED_ */

View File

@@ -16,8 +16,6 @@ static void nxt_http_request_proto_info(nxt_task_t *task,
static void nxt_http_request_mem_buf_completion(nxt_task_t *task, void *obj,
void *data);
static void nxt_http_request_done(nxt_task_t *task, void *obj, void *data);
static void nxt_http_request_close_handler(nxt_task_t *task, void *obj,
void *data);
static u_char *nxt_http_date(u_char *buf, nxt_realtime_t *now, struct tm *tm,
size_t size, const char *format);
@@ -443,6 +441,16 @@ fail:
}
void
nxt_http_request_ws_frame_start(nxt_task_t *task, nxt_http_request_t *r,
nxt_buf_t *ws_frame)
{
if (r->proto.any != NULL) {
nxt_http_proto[r->protocol].ws_frame_start(task, r, ws_frame);
}
}
void
nxt_http_request_send(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *out)
{
@@ -530,7 +538,7 @@ nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data)
}
static void
void
nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data)
{
nxt_http_proto_t proto;
@@ -556,12 +564,13 @@ nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data)
}
}
protocol = r->protocol;
r->proto.any = NULL;
nxt_mp_release(r->mem_pool);
if (nxt_fast_path(proto.any != NULL)) {
protocol = r->protocol;
nxt_mp_release(r->mem_pool);
nxt_http_proto[protocol].close(task, proto, conf);
}
}

View File

@@ -28,6 +28,8 @@ static nxt_http_field_proc_t nxt_response_fields[] = {
offsetof(nxt_http_request_t, resp.content_type) },
{ nxt_string("Content-Length"), &nxt_http_response_field,
offsetof(nxt_http_request_t, resp.content_length) },
{ nxt_string("Upgrade"), &nxt_http_response_skip, 0 },
{ nxt_string("Sec-WebSocket-Accept"), &nxt_http_response_skip, 0 },
};

161
src/nxt_http_websocket.c Normal file
View File

@@ -0,0 +1,161 @@
/*
* Copyright (C) NGINX, Inc.
*/
#include <nxt_main.h>
#include <nxt_router.h>
#include <nxt_http.h>
#include <nxt_router_request.h>
#include <nxt_port_memory_int.h>
#include <nxt_websocket.h>
#include <nxt_websocket_header.h>
static void nxt_http_websocket_client(nxt_task_t *task, void *obj, void *data);
static void nxt_http_websocket_error_handler(nxt_task_t *task, void *obj,
void *data);
const nxt_http_request_state_t nxt_http_websocket
nxt_aligned(64) =
{
.ready_handler = nxt_http_websocket_client,
.error_handler = nxt_http_websocket_error_handler,
};
static void
nxt_http_websocket_client(nxt_task_t *task, void *obj, void *data)
{
size_t frame_size, used_size, copy_size, buf_free_size;
size_t chunk_copy_size;
nxt_buf_t *out, *buf, **out_tail, *b, *next;
nxt_int_t res;
nxt_http_request_t *r;
nxt_request_app_link_t *req_app_link;
nxt_request_rpc_data_t *req_rpc_data;
nxt_websocket_header_t *wsh;
r = obj;
if (nxt_slow_path((req_rpc_data = r->req_rpc_data) == NULL
|| (req_app_link = req_rpc_data->req_app_link) == NULL))
{
nxt_debug(task, "websocket client frame for destroyed request");
return;
}
nxt_debug(task, "http websocket client frame");
wsh = (nxt_websocket_header_t *) r->ws_frame->mem.pos;
frame_size = nxt_websocket_frame_header_size(wsh)
+ nxt_websocket_frame_payload_len(wsh);
buf = NULL;
buf_free_size = 0;
out = NULL;
out_tail = &out;
b = r->ws_frame;
while (b != NULL && frame_size > 0) {
used_size = nxt_buf_mem_used_size(&b->mem);
copy_size = nxt_min(used_size, frame_size);
while (copy_size > 0) {
if (buf == NULL || buf_free_size == 0) {
buf_free_size = nxt_min(frame_size, PORT_MMAP_DATA_SIZE);
buf = nxt_port_mmap_get_buf(task, req_app_link->app_port,
buf_free_size);
*out_tail = buf;
out_tail = &buf->next;
}
chunk_copy_size = nxt_min(buf_free_size, copy_size);
buf->mem.free = nxt_cpymem(buf->mem.free, b->mem.pos,
chunk_copy_size);
copy_size -= chunk_copy_size;
b->mem.pos += chunk_copy_size;
buf_free_size -= chunk_copy_size;
}
frame_size -= copy_size;
next = b->next;
if (nxt_buf_mem_used_size(&b->mem) == 0) {
nxt_work_queue_add(&task->thread->engine->fast_work_queue,
b->completion_handler, task, b, b->parent);
r->ws_frame = next;
}
b = next;
}
res = nxt_port_socket_twrite(task, req_app_link->app_port,
NXT_PORT_MSG_WEBSOCKET, -1,
req_app_link->stream,
req_app_link->reply_port->id, out, NULL);
if (nxt_slow_path(res != NXT_OK)) {
// TODO: handle
}
b = r->ws_frame;
if (b != NULL) {
used_size = nxt_buf_mem_used_size(&b->mem);
if (used_size > 0) {
nxt_memmove(b->mem.start, b->mem.pos, used_size);
b->mem.pos = b->mem.start;
b->mem.free = b->mem.start + used_size;
}
}
nxt_http_request_ws_frame_start(task, r, r->ws_frame);
}
static void
nxt_http_websocket_error_handler(nxt_task_t *task, void *obj, void *data)
{
nxt_http_request_t *r;
nxt_request_app_link_t *req_app_link;
nxt_request_rpc_data_t *req_rpc_data;
nxt_debug(task, "http websocket error handler");
r = obj;
if ((req_rpc_data = r->req_rpc_data) == NULL) {
nxt_debug(task, " req_rpc_data is NULL");
goto close_handler;
}
if ((req_app_link = req_rpc_data->req_app_link) == NULL) {
nxt_debug(task, " req_app_link is NULL");
goto close_handler;
}
if (req_app_link->app_port == NULL) {
nxt_debug(task, " app_port is NULL");
goto close_handler;
}
(void) nxt_port_socket_twrite(task, req_app_link->app_port,
NXT_PORT_MSG_WEBSOCKET_LAST,
-1, req_app_link->stream,
req_app_link->reply_port->id, NULL, NULL);
close_handler:
nxt_http_request_close_handler(task, obj, data);
}

View File

@@ -68,6 +68,7 @@ nxt_port_new(nxt_task_t *task, nxt_port_id_t id, nxt_pid_t pid,
nxt_queue_init(&port->messages);
nxt_thread_mutex_create(&port->write_mutex);
nxt_queue_init(&port->pending_requests);
nxt_queue_init(&port->active_websockets);
} else {
nxt_mp_destroy(mp);

View File

@@ -36,6 +36,12 @@ struct nxt_port_handlers_s {
/* Stop process command. */
nxt_port_handler_t quit;
/* Request headers. */
nxt_port_handler_t req_headers;
/* Websocket frame. */
nxt_port_handler_t websocket_frame;
/* Various data. */
nxt_port_handler_t data;
};
@@ -71,6 +77,9 @@ typedef enum {
_NXT_PORT_MSG_REMOVE_PID = nxt_port_handler_idx(remove_pid),
_NXT_PORT_MSG_QUIT = nxt_port_handler_idx(quit),
_NXT_PORT_MSG_REQ_HEADERS = nxt_port_handler_idx(req_headers),
_NXT_PORT_MSG_WEBSOCKET = nxt_port_handler_idx(websocket_frame),
_NXT_PORT_MSG_DATA = nxt_port_handler_idx(data),
NXT_PORT_MSG_MAX = sizeof(nxt_port_handlers_t)
@@ -99,6 +108,10 @@ typedef enum {
NXT_PORT_MSG_QUIT = _NXT_PORT_MSG_QUIT | NXT_PORT_MSG_LAST,
NXT_PORT_MSG_REMOVE_PID = _NXT_PORT_MSG_REMOVE_PID | NXT_PORT_MSG_LAST,
NXT_PORT_MSG_REQ_HEADERS = _NXT_PORT_MSG_REQ_HEADERS,
NXT_PORT_MSG_WEBSOCKET = _NXT_PORT_MSG_WEBSOCKET,
NXT_PORT_MSG_WEBSOCKET_LAST = _NXT_PORT_MSG_WEBSOCKET | NXT_PORT_MSG_LAST,
NXT_PORT_MSG_DATA = _NXT_PORT_MSG_DATA,
NXT_PORT_MSG_DATA_LAST = _NXT_PORT_MSG_DATA | NXT_PORT_MSG_LAST,
} nxt_port_msg_type_t;
@@ -181,6 +194,8 @@ struct nxt_port_s {
uint32_t app_responses;
nxt_queue_t pending_requests;
nxt_queue_t active_websockets;
nxt_port_handler_t handler;
nxt_port_handler_t *data;

View File

@@ -14,7 +14,7 @@
#include <nxt_port_memory_int.h>
#include <nxt_unit_request.h>
#include <nxt_unit_response.h>
#include <nxt_router_request.h>
typedef struct {
nxt_str_t type;
@@ -48,64 +48,6 @@ typedef struct {
#endif
typedef struct nxt_msg_info_s {
nxt_buf_t *buf;
nxt_port_mmap_tracking_t tracking;
nxt_work_handler_t completion_handler;
} nxt_msg_info_t;
typedef struct nxt_request_app_link_s nxt_request_app_link_t;
typedef enum {
NXT_APR_NEW_PORT,
NXT_APR_REQUEST_FAILED,
NXT_APR_GOT_RESPONSE,
NXT_APR_CLOSE,
} nxt_apr_action_t;
typedef struct {
uint32_t stream;
nxt_app_t *app;
nxt_port_t *app_port;
nxt_apr_action_t apr_action;
nxt_http_request_t *request;
nxt_msg_info_t msg_info;
nxt_request_app_link_t *req_app_link;
} nxt_request_rpc_data_t;
struct nxt_request_app_link_s {
uint32_t stream;
nxt_atomic_t use_count;
nxt_port_t *app_port;
nxt_apr_action_t apr_action;
nxt_port_t *reply_port;
nxt_http_request_t *request;
nxt_msg_info_t msg_info;
nxt_request_rpc_data_t *req_rpc_data;
nxt_nsec_t res_time;
nxt_queue_link_t link_app_requests; /* for nxt_app_t.requests */
/* for nxt_port_t.pending_requests */
nxt_queue_link_t link_port_pending;
nxt_queue_link_t link_app_pending; /* for nxt_app_t.pending */
nxt_mp_t *mem_pool;
nxt_work_t work;
int err_code;
const char *err_str;
};
typedef struct {
nxt_socket_conf_t *socket_conf;
nxt_router_temp_conf_t *temp_conf;
@@ -305,6 +247,8 @@ static nxt_int_t nxt_router_http_request_done(nxt_task_t *task,
static void nxt_router_http_request_release(nxt_task_t *task, void *obj,
void *data);
const nxt_http_request_state_t nxt_http_websocket;
static nxt_router_t *nxt_router;
static const nxt_str_t http_prefix = nxt_string("HTTP_");
@@ -663,6 +607,7 @@ nxt_request_app_link_release(nxt_task_t *task,
nxt_request_app_link_t *req_app_link)
{
nxt_mp_t *mp;
nxt_http_request_t *r;
nxt_request_rpc_data_t *req_rpc_data;
nxt_assert(task->thread->engine == req_app_link->work.data);
@@ -683,10 +628,11 @@ nxt_request_app_link_release(nxt_task_t *task,
req_rpc_data->msg_info = req_app_link->msg_info;
if (req_rpc_data->app->timeout != 0) {
req_rpc_data->request->timer.handler = nxt_router_app_timeout;
req_rpc_data->request->timer_data = req_rpc_data;
nxt_timer_add(task->thread->engine,
&req_rpc_data->request->timer,
r = req_rpc_data->request;
r->timer.handler = nxt_router_app_timeout;
r->timer_data = req_rpc_data;
nxt_timer_add(task->thread->engine, &r->timer,
req_rpc_data->app->timeout);
}
@@ -833,14 +779,16 @@ nxt_request_rpc_data_unlink(nxt_task_t *task,
if (req_app_link->link_app_requests.next == NULL
&& req_app_link->link_port_pending.next == NULL
&& req_app_link->link_app_pending.next == NULL)
&& req_app_link->link_app_pending.next == NULL
&& req_app_link->link_port_websockets.next == NULL)
{
req_app_link = NULL;
} else {
ra_use_delta -=
nxt_queue_chk_remove(&req_app_link->link_app_requests)
+ nxt_queue_chk_remove(&req_app_link->link_port_pending);
+ nxt_queue_chk_remove(&req_app_link->link_port_pending)
+ nxt_queue_chk_remove(&req_app_link->link_port_websockets);
nxt_queue_chk_remove(&req_app_link->link_app_pending);
}
@@ -863,6 +811,7 @@ nxt_request_rpc_data_unlink(nxt_task_t *task,
nxt_router_http_request_done(task, req_rpc_data->request);
req_rpc_data->request->req_rpc_data = NULL;
req_rpc_data->request = NULL;
}
}
@@ -1412,6 +1361,28 @@ static nxt_conf_map_t nxt_router_http_conf[] = {
};
static nxt_conf_map_t nxt_router_websocket_conf[] = {
{
nxt_string("max_frame_size"),
NXT_CONF_MAP_SIZE,
offsetof(nxt_websocket_conf_t, max_frame_size),
},
{
nxt_string("read_timeout"),
NXT_CONF_MAP_MSEC,
offsetof(nxt_websocket_conf_t, read_timeout),
},
{
nxt_string("keepalive_interval"),
NXT_CONF_MAP_MSEC,
offsetof(nxt_websocket_conf_t, keepalive_interval),
},
};
static nxt_int_t
nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
u_char *start, u_char *end)
@@ -1425,7 +1396,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
nxt_app_t *app, *prev;
nxt_router_t *router;
nxt_app_joint_t *app_joint;
nxt_conf_value_t *conf, *http, *value;
nxt_conf_value_t *conf, *http, *value, *websocket;
nxt_conf_value_t *applications, *application;
nxt_conf_value_t *listeners, *listener;
nxt_conf_value_t *routes_conf;
@@ -1448,6 +1419,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
#if (NXT_TLS)
static nxt_str_t certificate_path = nxt_string("/tls/certificate");
#endif
static nxt_str_t websocket_path = nxt_string("/settings/http/websocket");
conf = nxt_conf_json_parse(tmcf->mem_pool, start, end, NULL);
if (conf == NULL) {
@@ -1658,6 +1630,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
}
#endif
websocket = nxt_conf_get_path(conf, &websocket_path);
listeners = nxt_conf_get_path(conf, &listeners_path);
if (listeners != NULL) {
@@ -1697,6 +1671,10 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
skcf->body_read_timeout = 30 * 1000;
skcf->send_timeout = 30 * 1000;
skcf->websocket_conf.max_frame_size = 1024 * 1024;
skcf->websocket_conf.read_timeout = 60 * 1000;
skcf->websocket_conf.keepalive_interval = 30 * 1000;
if (http != NULL) {
ret = nxt_conf_map_object(mp, http, nxt_router_http_conf,
nxt_nitems(nxt_router_http_conf),
@@ -1707,6 +1685,17 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
}
}
if (websocket != NULL) {
ret = nxt_conf_map_object(mp, websocket,
nxt_router_websocket_conf,
nxt_nitems(nxt_router_websocket_conf),
&skcf->websocket_conf);
if (ret != NXT_OK) {
nxt_alert(task, "websocket map error");
goto fail;
}
}
#if (NXT_TLS)
value = nxt_conf_get_path(listener, &certificate_path);
@@ -3418,10 +3407,12 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
{
nxt_int_t ret;
nxt_buf_t *b;
nxt_port_t *app_port;
nxt_unit_field_t *f;
nxt_http_field_t *field;
nxt_http_request_t *r;
nxt_unit_response_t *resp;
nxt_request_app_link_t *req_app_link;
nxt_request_rpc_data_t *req_rpc_data;
b = msg->buf;
@@ -3542,7 +3533,48 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
nxt_http_request_header_send(task, r);
if (r->websocket_handshake
&& r->status == NXT_HTTP_SWITCHING_PROTOCOLS)
{
req_app_link = nxt_request_app_link_alloc(task,
req_rpc_data->req_app_link,
req_rpc_data);
if (nxt_slow_path(req_app_link == NULL)) {
goto fail;
}
app_port = req_app_link->app_port;
if (app_port == NULL && req_rpc_data->app_port != NULL) {
req_app_link->app_port = req_rpc_data->app_port;
app_port = req_app_link->app_port;
req_app_link->apr_action = req_rpc_data->apr_action;
req_rpc_data->app_port = NULL;
}
if (nxt_slow_path(app_port == NULL)) {
goto fail;
}
nxt_thread_mutex_lock(&req_rpc_data->app->mutex);
nxt_queue_insert_tail(&app_port->active_websockets,
&req_app_link->link_port_websockets);
nxt_thread_mutex_unlock(&req_rpc_data->app->mutex);
nxt_router_app_port_release(task, app_port, NXT_APR_UPGRADE);
req_app_link->apr_action = NXT_APR_CLOSE;
nxt_debug(task, "req_app_link stream #%uD upgrade",
req_app_link->stream);
r->state = &nxt_http_websocket;
} else {
r->state = &nxt_http_request_send_state;
}
if (r->out) {
nxt_work_queue_add(&task->thread->engine->fast_work_queue,
@@ -3924,6 +3956,10 @@ nxt_router_app_port_release(nxt_task_t *task, nxt_port_t *port,
got_response = 1;
inc_use = -1;
break;
case NXT_APR_UPGRADE:
dec_pending = 1;
got_response = 1;
break;
case NXT_APR_CLOSE:
inc_use = -1;
break;
@@ -4046,9 +4082,10 @@ re_ra_cancelled:
adjust_idle_timer = 0;
if (port->pair[1] != -1 && !send_quit && port->app_pending_responses == 0) {
nxt_assert(port->idle_link.next == NULL);
if (port->pair[1] != -1 && !send_quit && port->app_pending_responses == 0
&& nxt_queue_is_empty(&port->active_websockets)
&& port->idle_link.next == NULL)
{
if (app->idle_processes == app->spare_processes
&& app->adjust_idle_work.data == NULL)
{
@@ -4545,6 +4582,7 @@ nxt_router_process_http_request(nxt_task_t *task, nxt_http_request_t *r,
nxt_router_app_use(task, app, 1);
req_rpc_data->request = r;
r->req_rpc_data = req_rpc_data;
req_app_link = &ra_local;
nxt_request_app_link_init(task, req_app_link, req_rpc_data);
@@ -4635,7 +4673,7 @@ nxt_router_app_prepare_request(nxt_task_t *task,
goto release_port;
}
res = nxt_port_socket_twrite(task, port, NXT_PORT_MSG_DATA,
res = nxt_port_socket_twrite(task, port, NXT_PORT_MSG_REQ_HEADERS,
-1, req_app_link->stream, reply_port->id, buf,
&req_app_link->msg_info.tracking);
@@ -4785,6 +4823,7 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_http_request_t *r,
*p++ = '\0';
req->tls = (r->tls != NULL);
req->websocket_handshake = r->websocket_handshake;
req->server_name_length = r->server_name.length;
nxt_unit_sptr_set(&req->server_name, p);

View File

@@ -138,6 +138,13 @@ struct nxt_app_s {
};
typedef struct {
size_t max_frame_size;
nxt_msec_t read_timeout;
nxt_msec_t keepalive_interval;
} nxt_websocket_conf_t;
typedef struct {
uint32_t count;
nxt_queue_link_t link;
@@ -164,6 +171,8 @@ typedef struct {
nxt_msec_t body_read_timeout;
nxt_msec_t send_timeout;
nxt_websocket_conf_t websocket_conf;
#if (NXT_TLS)
nxt_tls_conf_t *tls;
#endif

71
src/nxt_router_request.h Normal file
View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) NGINX, Inc.
*/
#ifndef _NXT_ROUTER_REQUEST_H_INCLUDED_
#define _NXT_ROUTER_REQUEST_H_INCLUDED_
typedef struct nxt_msg_info_s {
nxt_buf_t *buf;
nxt_port_mmap_tracking_t tracking;
nxt_work_handler_t completion_handler;
} nxt_msg_info_t;
typedef struct nxt_request_app_link_s nxt_request_app_link_t;
typedef enum {
NXT_APR_NEW_PORT,
NXT_APR_REQUEST_FAILED,
NXT_APR_GOT_RESPONSE,
NXT_APR_UPGRADE,
NXT_APR_CLOSE,
} nxt_apr_action_t;
typedef struct {
uint32_t stream;
nxt_app_t *app;
nxt_port_t *app_port;
nxt_apr_action_t apr_action;
nxt_http_request_t *request;
nxt_msg_info_t msg_info;
nxt_request_app_link_t *req_app_link;
} nxt_request_rpc_data_t;
struct nxt_request_app_link_s {
uint32_t stream;
nxt_atomic_t use_count;
nxt_port_t *app_port;
nxt_apr_action_t apr_action;
nxt_port_t *reply_port;
nxt_http_request_t *request;
nxt_msg_info_t msg_info;
nxt_request_rpc_data_t *req_rpc_data;
nxt_nsec_t res_time;
nxt_queue_link_t link_app_requests; /* for nxt_app_t.requests */
/* for nxt_port_t.pending_requests */
nxt_queue_link_t link_port_pending;
nxt_queue_link_t link_app_pending; /* for nxt_app_t.pending */
/* for nxt_port_t.active_websockets */
nxt_queue_link_t link_port_websockets;
nxt_mp_t *mem_pool;
nxt_work_t work;
int err_code;
const char *err_str;
};
#endif /* _NXT_ROUTER_REQUEST_H_INCLUDED_ */

295
src/nxt_sha1.c Normal file
View File

@@ -0,0 +1,295 @@
/*
* Copyright (C) Maxim Dounin
* Copyright (C) NGINX, Inc.
*
* An internal SHA1 implementation.
*/
#include <nxt_main.h>
#include <nxt_sha1.h>
static const u_char *nxt_sha1_body(nxt_sha1_t *ctx, const u_char *data,
size_t size);
void
nxt_sha1_init(nxt_sha1_t *ctx)
{
ctx->a = 0x67452301;
ctx->b = 0xefcdab89;
ctx->c = 0x98badcfe;
ctx->d = 0x10325476;
ctx->e = 0xc3d2e1f0;
ctx->bytes = 0;
}
void
nxt_sha1_update(nxt_sha1_t *ctx, const void *data, size_t size)
{
size_t used, free;
used = (size_t) (ctx->bytes & 0x3f);
ctx->bytes += size;
if (used) {
free = 64 - used;
if (size < free) {
memcpy(&ctx->buffer[used], data, size);
return;
}
memcpy(&ctx->buffer[used], data, free);
data = (u_char *) data + free;
size -= free;
(void) nxt_sha1_body(ctx, ctx->buffer, 64);
}
if (size >= 64) {
data = nxt_sha1_body(ctx, data, size & ~(size_t) 0x3f);
size &= 0x3f;
}
memcpy(ctx->buffer, data, size);
}
void
nxt_sha1_final(u_char result[20], nxt_sha1_t *ctx)
{
size_t used, free;
used = (size_t) (ctx->bytes & 0x3f);
ctx->buffer[used++] = 0x80;
free = 64 - used;
if (free < 8) {
nxt_memzero(&ctx->buffer[used], free);
(void) nxt_sha1_body(ctx, ctx->buffer, 64);
used = 0;
free = 64;
}
nxt_memzero(&ctx->buffer[used], free - 8);
ctx->bytes <<= 3;
ctx->buffer[56] = (u_char) (ctx->bytes >> 56);
ctx->buffer[57] = (u_char) (ctx->bytes >> 48);
ctx->buffer[58] = (u_char) (ctx->bytes >> 40);
ctx->buffer[59] = (u_char) (ctx->bytes >> 32);
ctx->buffer[60] = (u_char) (ctx->bytes >> 24);
ctx->buffer[61] = (u_char) (ctx->bytes >> 16);
ctx->buffer[62] = (u_char) (ctx->bytes >> 8);
ctx->buffer[63] = (u_char) ctx->bytes;
(void) nxt_sha1_body(ctx, ctx->buffer, 64);
result[0] = (u_char) (ctx->a >> 24);
result[1] = (u_char) (ctx->a >> 16);
result[2] = (u_char) (ctx->a >> 8);
result[3] = (u_char) ctx->a;
result[4] = (u_char) (ctx->b >> 24);
result[5] = (u_char) (ctx->b >> 16);
result[6] = (u_char) (ctx->b >> 8);
result[7] = (u_char) ctx->b;
result[8] = (u_char) (ctx->c >> 24);
result[9] = (u_char) (ctx->c >> 16);
result[10] = (u_char) (ctx->c >> 8);
result[11] = (u_char) ctx->c;
result[12] = (u_char) (ctx->d >> 24);
result[13] = (u_char) (ctx->d >> 16);
result[14] = (u_char) (ctx->d >> 8);
result[15] = (u_char) ctx->d;
result[16] = (u_char) (ctx->e >> 24);
result[17] = (u_char) (ctx->e >> 16);
result[18] = (u_char) (ctx->e >> 8);
result[19] = (u_char) ctx->e;
nxt_memzero(ctx, sizeof(*ctx));
}
/*
* Helper functions.
*/
#define ROTATE(bits, word) (((word) << (bits)) | ((word) >> (32 - (bits))))
#define F1(b, c, d) (((b) & (c)) | ((~(b)) & (d)))
#define F2(b, c, d) ((b) ^ (c) ^ (d))
#define F3(b, c, d) (((b) & (c)) | ((b) & (d)) | ((c) & (d)))
#define STEP(f, a, b, c, d, e, w, t) \
temp = ROTATE(5, (a)) + f((b), (c), (d)) + (e) + (w) + (t); \
(e) = (d); \
(d) = (c); \
(c) = ROTATE(30, (b)); \
(b) = (a); \
(a) = temp;
/*
* GET() reads 4 input bytes in big-endian byte order and returns
* them as uint32_t.
*/
#define GET(n) \
( ((uint32_t) p[n * 4 + 3]) \
| ((uint32_t) p[n * 4 + 2] << 8) \
| ((uint32_t) p[n * 4 + 1] << 16) \
| ((uint32_t) p[n * 4] << 24))
/*
* This processes one or more 64-byte data blocks, but does not update
* the bit counters. There are no alignment requirements.
*/
static const u_char *
nxt_sha1_body(nxt_sha1_t *ctx, const u_char *data, size_t size)
{
uint32_t a, b, c, d, e, temp;
uint32_t saved_a, saved_b, saved_c, saved_d, saved_e;
uint32_t words[80];
nxt_uint_t i;
const u_char *p;
p = data;
a = ctx->a;
b = ctx->b;
c = ctx->c;
d = ctx->d;
e = ctx->e;
do {
saved_a = a;
saved_b = b;
saved_c = c;
saved_d = d;
saved_e = e;
/* Load data block into the words array */
for (i = 0; i < 16; i++) {
words[i] = GET(i);
}
for (i = 16; i < 80; i++) {
words[i] = ROTATE(1, words[i - 3]
^ words[i - 8]
^ words[i - 14]
^ words[i - 16]);
}
/* Transformations */
STEP(F1, a, b, c, d, e, words[0], 0x5a827999);
STEP(F1, a, b, c, d, e, words[1], 0x5a827999);
STEP(F1, a, b, c, d, e, words[2], 0x5a827999);
STEP(F1, a, b, c, d, e, words[3], 0x5a827999);
STEP(F1, a, b, c, d, e, words[4], 0x5a827999);
STEP(F1, a, b, c, d, e, words[5], 0x5a827999);
STEP(F1, a, b, c, d, e, words[6], 0x5a827999);
STEP(F1, a, b, c, d, e, words[7], 0x5a827999);
STEP(F1, a, b, c, d, e, words[8], 0x5a827999);
STEP(F1, a, b, c, d, e, words[9], 0x5a827999);
STEP(F1, a, b, c, d, e, words[10], 0x5a827999);
STEP(F1, a, b, c, d, e, words[11], 0x5a827999);
STEP(F1, a, b, c, d, e, words[12], 0x5a827999);
STEP(F1, a, b, c, d, e, words[13], 0x5a827999);
STEP(F1, a, b, c, d, e, words[14], 0x5a827999);
STEP(F1, a, b, c, d, e, words[15], 0x5a827999);
STEP(F1, a, b, c, d, e, words[16], 0x5a827999);
STEP(F1, a, b, c, d, e, words[17], 0x5a827999);
STEP(F1, a, b, c, d, e, words[18], 0x5a827999);
STEP(F1, a, b, c, d, e, words[19], 0x5a827999);
STEP(F2, a, b, c, d, e, words[20], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[21], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[22], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[23], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[24], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[25], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[26], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[27], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[28], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[29], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[30], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[31], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[32], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[33], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[34], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[35], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[36], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[37], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[38], 0x6ed9eba1);
STEP(F2, a, b, c, d, e, words[39], 0x6ed9eba1);
STEP(F3, a, b, c, d, e, words[40], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[41], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[42], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[43], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[44], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[45], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[46], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[47], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[48], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[49], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[50], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[51], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[52], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[53], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[54], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[55], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[56], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[57], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[58], 0x8f1bbcdc);
STEP(F3, a, b, c, d, e, words[59], 0x8f1bbcdc);
STEP(F2, a, b, c, d, e, words[60], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[61], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[62], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[63], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[64], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[65], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[66], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[67], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[68], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[69], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[70], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[71], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[72], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[73], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[74], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[75], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[76], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[77], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[78], 0xca62c1d6);
STEP(F2, a, b, c, d, e, words[79], 0xca62c1d6);
a += saved_a;
b += saved_b;
c += saved_c;
d += saved_d;
e += saved_e;
p += 64;
} while (size -= 64);
ctx->a = a;
ctx->b = b;
ctx->c = c;
ctx->d = d;
ctx->e = e;
return p;
}

24
src/nxt_sha1.h Normal file
View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) Igor Sysoev
* Copyright (C) NGINX, Inc.
*/
#ifndef _NXT_SHA1_H_INCLUDED_
#define _NXT_SHA1_H_INCLUDED_
typedef struct {
uint64_t bytes;
uint32_t a, b, c, d, e;
u_char buffer[64];
} nxt_sha1_t;
NXT_EXPORT void nxt_sha1_init(nxt_sha1_t *ctx);
NXT_EXPORT void nxt_sha1_update(nxt_sha1_t *ctx, const void *data, size_t size);
NXT_EXPORT void nxt_sha1_final(u_char result[20], nxt_sha1_t *ctx);
#endif /* _NXT_SHA1_H_INCLUDED_ */

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@
#include <inttypes.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <string.h>
#include "nxt_version.h"
@@ -106,17 +107,24 @@ struct nxt_unit_request_info_s {
void *data;
};
/*
* Set of application-specific callbacks. Application may leave all optional
* callbacks as NULL.
*/
struct nxt_unit_callbacks_s {
/*
* Process request data. Unlike all other callback, this callback
* Process request. Unlike all other callback, this callback
* need to be defined by application.
*/
void (*request_handler)(nxt_unit_request_info_t *req);
/* Process websocket frame. */
void (*websocket_handler)(nxt_unit_websocket_frame_t *ws);
/* Connection closed. */
void (*close_handler)(nxt_unit_request_info_t *req);
/* Add new Unit port to communicate with process pid. Optional. */
int (*add_port)(nxt_unit_ctx_t *, nxt_unit_port_t *port);
@@ -293,6 +301,14 @@ int nxt_unit_response_is_sent(nxt_unit_request_info_t *req);
nxt_unit_buf_t *nxt_unit_response_buf_alloc(nxt_unit_request_info_t *req,
uint32_t size);
int nxt_unit_request_is_websocket_handshake(nxt_unit_request_info_t *req);
int nxt_unit_response_upgrade(nxt_unit_request_info_t *req);
int nxt_unit_response_is_websocket(nxt_unit_request_info_t *req);
nxt_unit_request_info_t *nxt_unit_get_request_info_from_data(void *data);
int nxt_unit_buf_send(nxt_unit_buf_t *buf);
void nxt_unit_buf_free(nxt_unit_buf_t *buf);
@@ -315,6 +331,20 @@ ssize_t nxt_unit_request_read(nxt_unit_request_info_t *req, void *dst,
void nxt_unit_request_done(nxt_unit_request_info_t *req, int rc);
int nxt_unit_websocket_send(nxt_unit_request_info_t *req, uint8_t opcode,
uint8_t last, const void *start, size_t size);
int nxt_unit_websocket_sendv(nxt_unit_request_info_t *req, uint8_t opcode,
uint8_t last, const struct iovec *iov, int iovcnt);
ssize_t nxt_unit_websocket_read(nxt_unit_websocket_frame_t *ws, void *dst,
size_t size);
int nxt_unit_websocket_retain(nxt_unit_websocket_frame_t *ws);
void nxt_unit_websocket_done(nxt_unit_websocket_frame_t *ws);
void nxt_unit_log(nxt_unit_ctx_t *ctx, int level, const char* fmt, ...);
void nxt_unit_req_log(nxt_unit_request_info_t *req, int level,

View File

@@ -20,6 +20,7 @@ struct nxt_unit_request_s {
uint8_t remote_length;
uint8_t local_length;
uint8_t tls;
uint8_t websocket_handshake;
uint32_t server_name_length;
uint32_t target_length;
uint32_t path_length;

View File

@@ -20,6 +20,7 @@ typedef struct nxt_unit_field_s nxt_unit_field_t;
typedef struct nxt_unit_request_s nxt_unit_request_t;
typedef struct nxt_unit_response_s nxt_unit_response_t;
typedef struct nxt_unit_read_info_s nxt_unit_read_info_t;
typedef struct nxt_unit_websocket_frame_s nxt_unit_websocket_frame_t;
#endif /* _NXT_UNIT_TYPEDEFS_H_INCLUDED_ */

27
src/nxt_unit_websocket.h Normal file
View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) NGINX, Inc.
*/
#ifndef _NXT_UNIT_WEBSOCKET_H_INCLUDED_
#define _NXT_UNIT_WEBSOCKET_H_INCLUDED_
#include <inttypes.h>
#include "nxt_unit_typedefs.h"
#include "nxt_websocket_header.h"
struct nxt_unit_websocket_frame_s {
nxt_unit_request_info_t *req;
uint64_t payload_len;
nxt_websocket_header_t *header;
uint8_t *mask;
nxt_unit_buf_t *content_buf;
uint64_t content_length;
};
#endif /* _NXT_UNIT_WEBSOCKET_H_INCLUDED_ */

122
src/nxt_websocket.c Normal file
View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) NGINX, Inc.
*/
#include <nxt_main.h>
#include <nxt_websocket.h>
#include <nxt_websocket_header.h>
nxt_inline uint16_t
nxt_ntoh16(const uint8_t *b)
{
return ((uint16_t) b[0]) << 8 | ((uint16_t) b[1]);
}
nxt_inline void
nxt_hton16(uint8_t *b, uint16_t v)
{
b[0] = (v >> 8);
b[1] = (v & 0xFFu);
}
nxt_inline uint64_t
nxt_ntoh64(const uint8_t *b)
{
return ((uint64_t) b[0]) << 56
| ((uint64_t) b[1]) << 48
| ((uint64_t) b[2]) << 40
| ((uint64_t) b[3]) << 32
| ((uint64_t) b[4]) << 24
| ((uint64_t) b[5]) << 16
| ((uint64_t) b[6]) << 8
| ((uint64_t) b[7]);
}
nxt_inline void
nxt_hton64(uint8_t *b, uint64_t v)
{
b[0] = (v >> 56);
b[1] = (v >> 48) & 0xFFu;
b[2] = (v >> 40) & 0xFFu;
b[3] = (v >> 32) & 0xFFu;
b[4] = (v >> 24) & 0xFFu;
b[5] = (v >> 16) & 0xFFu;
b[6] = (v >> 8) & 0xFFu;
b[7] = v & 0xFFu;
}
size_t
nxt_websocket_frame_header_size(const void *data)
{
size_t res;
uint64_t p;
const nxt_websocket_header_t *h;
h = data;
p = h->payload_len;
res = 2;
if (p == 126) {
res += 2;
} else if (p == 127) {
res += 8;
}
if (h->mask) {
res += 4;
}
return res;
}
uint64_t
nxt_websocket_frame_payload_len(const void *data)
{
uint64_t p;
const nxt_websocket_header_t *h;
h = data;
p = h->payload_len;
if (p == 126) {
p = nxt_ntoh16(h->payload_len_);
} else if (p == 127) {
p = nxt_ntoh64(h->payload_len_);
}
return p;
}
void *
nxt_websocket_frame_init(void *data, uint64_t payload_len)
{
uint8_t *p;
nxt_websocket_header_t *h;
h = data;
p = data;
if (payload_len < 126) {
h->payload_len = payload_len;
return p + 2;
}
if (payload_len < 65536) {
h->payload_len = 126;
nxt_hton16(h->payload_len_, payload_len);
return p + 4;
}
h->payload_len = 127;
nxt_hton64(h->payload_len_, payload_len);
return p + 10;
}

21
src/nxt_websocket.h Normal file
View File

@@ -0,0 +1,21 @@
/*
* Copyright (C) NGINX, Inc.
*/
#ifndef _NXT_WEBSOCKET_H_INCLUDED_
#define _NXT_WEBSOCKET_H_INCLUDED_
enum {
NXT_WEBSOCKET_ACCEPT_SIZE = 28,
};
NXT_EXPORT size_t nxt_websocket_frame_header_size(const void *data);
NXT_EXPORT uint64_t nxt_websocket_frame_payload_len(const void *data);
NXT_EXPORT void *nxt_websocket_frame_init(void *data, uint64_t payload_len);
NXT_EXPORT void nxt_websocket_accept(u_char *accept, const void *key);
#endif /* _NXT_WEBSOCKET_H_INCLUDED_ */

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) NGINX, Inc.
*/
#include <nxt_main.h>
#include <nxt_websocket.h>
#include <nxt_sha1.h>
static void
nxt_websocket_base64_encode(u_char *d, const uint8_t *s, size_t len)
{
u_char c0, c1, c2;
static u_char basis[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
while (len > 2) {
c0 = s[0];
c1 = s[1];
c2 = s[2];
*d++ = basis[c0 >> 2];
*d++ = basis[((c0 & 0x03) << 4) | (c1 >> 4)];
*d++ = basis[((c1 & 0x0f) << 2) | (c2 >> 6)];
*d++ = basis[c2 & 0x3f];
s += 3;
len -= 3;
}
if (len > 0) {
c0 = s[0];
*d++ = basis[c0 >> 2];
if (len == 1) {
*d++ = basis[(c0 & 0x03) << 4];
*d++ = '=';
*d++ = '=';
} else {
c1 = s[1];
*d++ = basis[((c0 & 0x03) << 4) | (c1 >> 4)];
*d++ = basis[(c1 & 0x0f) << 2];
*d++ = '=';
}
}
}
void
nxt_websocket_accept(u_char *accept, const void *key)
{
u_char bin_accept[20];
nxt_sha1_t ctx;
static const char accept_guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
nxt_sha1_init(&ctx);
nxt_sha1_update(&ctx, key, 24);
nxt_sha1_update(&ctx, accept_guid, nxt_length(accept_guid));
nxt_sha1_final(bin_accept, &ctx);
nxt_websocket_base64_encode(accept, bin_accept, sizeof(bin_accept));
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) NGINX, Inc.
*/
#ifndef _NXT_WEBSOCKET_HEADER_H_INCLUDED_
#define _NXT_WEBSOCKET_HEADER_H_INCLUDED_
#include <netinet/in.h>
typedef struct {
#if (BYTE_ORDER == BIG_ENDIAN)
uint8_t fin:1;
uint8_t rsv1:1;
uint8_t rsv2:1;
uint8_t rsv3:1;
uint8_t opcode:4;
uint8_t mask:1;
uint8_t payload_len:7;
#endif
#if (BYTE_ORDER == LITTLE_ENDIAN)
uint8_t opcode:4;
uint8_t rsv3:1;
uint8_t rsv2:1;
uint8_t rsv1:1;
uint8_t fin:1;
uint8_t payload_len:7;
uint8_t mask:1;
#endif
uint8_t payload_len_[8];
} nxt_websocket_header_t;
enum {
NXT_WEBSOCKET_OP_CONT = 0x00,
NXT_WEBSOCKET_OP_TEXT = 0x01,
NXT_WEBSOCKET_OP_BINARY = 0x02,
NXT_WEBSOCKET_OP_CLOSE = 0x08,
NXT_WEBSOCKET_OP_PING = 0x09,
NXT_WEBSOCKET_OP_PONG = 0x0A,
NXT_WEBSOCKET_OP_CTRL = 0x08,
};
enum {
NXT_WEBSOCKET_CR_NORMAL = 1000,
NXT_WEBSOCKET_CR_GOING_AWAY = 1001,
NXT_WEBSOCKET_CR_PROTOCOL_ERROR = 1002,
NXT_WEBSOCKET_CR_UNPROCESSABLE_INPUT = 1003,
NXT_WEBSOCKET_CR_RESERVED = 1004,
NXT_WEBSOCKET_CR_NOT_PROVIDED = 1005,
NXT_WEBSOCKET_CR_ABNORMAL = 1006,
NXT_WEBSOCKET_CR_INVALID_DATA = 1007,
NXT_WEBSOCKET_CR_POLICY_VIOLATION = 1008,
NXT_WEBSOCKET_CR_MESSAGE_TOO_BIG = 1009,
NXT_WEBSOCKET_CR_EXTENSION_REQUIRED = 1010,
NXT_WEBSOCKET_CR_INTERNAL_SERVER_ERROR = 1011,
NXT_WEBSOCKET_CR_TLS_HANDSHAKE_FAILED = 1015,
};
#endif /* _NXT_WEBSOCKET_HEADER_H_INCLUDED_ */

View File

@@ -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);

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) NGINX, Inc.
*/
#include <string.h>
#include <stdlib.h>
#include <nxt_unit.h>
#include <nxt_unit_request.h>
#include <nxt_clang.h>
#include <nxt_websocket.h>
#include <nxt_unit_websocket.h>
static void
ws_echo_request_handler(nxt_unit_request_info_t *req)
{
int rc;
const char *target;
rc = NXT_UNIT_OK;
target = nxt_unit_sptr_get(&req->request->target);
if (strcmp(target, "/") == 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;
}
nxt_unit_response_upgrade(req);
nxt_unit_response_send(req);
return;
}
notfound:
rc = nxt_unit_response_init(req, 404, 0, 0);
fail:
nxt_unit_request_done(req, rc);
}
static void
ws_echo_websocket_handler(nxt_unit_websocket_frame_t *ws)
{
uint8_t opcode;
ssize_t size;
nxt_unit_request_info_t *req;
static size_t buf_size = 0;
static uint8_t *buf = NULL;
if (buf_size < ws->content_length) {
buf = realloc(buf, ws->content_length);
buf_size = ws->content_length;
}
req = ws->req;
opcode = ws->header->opcode;
if (opcode == NXT_WEBSOCKET_OP_PONG) {
nxt_unit_websocket_done(ws);
return;
}
size = nxt_unit_websocket_read(ws, buf, ws->content_length);
nxt_unit_websocket_send(req, opcode, ws->header->fin, buf, size);
nxt_unit_websocket_done(ws);
if (opcode == NXT_WEBSOCKET_OP_CLOSE) {
nxt_unit_request_done(req, NXT_UNIT_OK);
}
}
int
main()
{
nxt_unit_ctx_t *ctx;
nxt_unit_init_t init;
memset(&init, 0, sizeof(nxt_unit_init_t));
init.callbacks.request_handler = ws_echo_request_handler;
init.callbacks.websocket_handler = ws_echo_websocket_handler;
ctx = nxt_unit_init(&init);
if (ctx == NULL) {
return 1;
}
nxt_unit_run(ctx);
nxt_unit_done(ctx);
return 0;
}