Introducing websocket support in router and libunit.
This commit is contained in:
24
auto/make
24
auto/make
@@ -57,6 +57,7 @@ $echo >> $NXT_MAKEFILE
|
|||||||
$echo "NXT_LIB_UNIT_OBJS = \\" >> $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_lvlhsh.o \\" >> $NXT_MAKEFILE
|
||||||
$echo " $NXT_BUILD_DIR/src/nxt_murmur_hash.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
|
for nxt_src in $NXT_LIB_UNIT_SRCS
|
||||||
do
|
do
|
||||||
@@ -108,7 +109,9 @@ END
|
|||||||
# Object files.
|
# Object files.
|
||||||
|
|
||||||
for nxt_src in $NXT_LIB_SRCS $NXT_TEST_SRCS $NXT_LIB_UNIT_SRCS \
|
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
|
do
|
||||||
nxt_obj=${nxt_src%.c}.o
|
nxt_obj=${nxt_src%.c}.o
|
||||||
nxt_dep=${nxt_src%.c}.dep
|
nxt_dep=${nxt_src%.c}.dep
|
||||||
@@ -150,7 +153,8 @@ if [ $NXT_TESTS = YES ]; then
|
|||||||
|
|
||||||
.PHONY: tests
|
.PHONY: tests
|
||||||
tests: $NXT_BUILD_DIR/tests $NXT_BUILD_DIR/utf8_file_name_test \\
|
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/tests: \$(NXT_TEST_OBJS) \\
|
||||||
$NXT_BUILD_DIR/$NXT_LIB_STATIC
|
$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_BUILD_DIR/$NXT_LIB_UNIT_STATIC \\
|
||||||
$NXT_LD_OPT $NXT_LIBM $NXT_LIBS $NXT_LIB_AUX_LIBS
|
$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
|
END
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ ${NXT_GO}-install-src: ${NXT_VERSION_H}
|
|||||||
install -d \$(DESTDIR)\$(NXT_GO_DST)/src/nginx/unit
|
install -d \$(DESTDIR)\$(NXT_GO_DST)/src/nginx/unit
|
||||||
install -p -m644 ./src/*.h ./build/*.h ./src/go/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_unit.c ./src/nxt_lvlhsh.c ./src/nxt_murmur_hash.c \
|
||||||
|
./src/nxt_websocket.c \
|
||||||
\$(DESTDIR)\$(NXT_GO_DST)/src/nginx/unit/
|
\$(DESTDIR)\$(NXT_GO_DST)/src/nginx/unit/
|
||||||
|
|
||||||
${NXT_GO}-install-build: ${NXT_GO}-install-src
|
${NXT_GO}-install-build: ${NXT_GO}-install-src
|
||||||
|
|||||||
@@ -86,6 +86,11 @@ NXT_LIB_SRCS=" \
|
|||||||
src/nxt_application.c \
|
src/nxt_application.c \
|
||||||
src/nxt_external.c \
|
src/nxt_external.c \
|
||||||
src/nxt_port_hash.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=" \
|
NXT_LIB_SRC0=" \
|
||||||
|
|||||||
@@ -102,6 +102,26 @@ static nxt_int_t nxt_conf_vldt_java_option(nxt_conf_validation_t *vldt,
|
|||||||
nxt_conf_value_t *value);
|
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[] = {
|
static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = {
|
||||||
{ nxt_string("header_read_timeout"),
|
{ nxt_string("header_read_timeout"),
|
||||||
NXT_CONF_VLDT_INTEGER,
|
NXT_CONF_VLDT_INTEGER,
|
||||||
@@ -128,6 +148,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = {
|
|||||||
NULL,
|
NULL,
|
||||||
NULL },
|
NULL },
|
||||||
|
|
||||||
|
{ nxt_string("websocket"),
|
||||||
|
NXT_CONF_VLDT_OBJECT,
|
||||||
|
&nxt_conf_vldt_object,
|
||||||
|
(void *) &nxt_conf_vldt_websocket_members },
|
||||||
|
|
||||||
NXT_CONF_VLDT_END
|
NXT_CONF_VLDT_END
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,26 +6,9 @@
|
|||||||
|
|
||||||
#include <nxt_router.h>
|
#include <nxt_router.h>
|
||||||
#include <nxt_http.h>
|
#include <nxt_http.h>
|
||||||
|
#include <nxt_h1proto.h>
|
||||||
|
#include <nxt_websocket.h>
|
||||||
struct nxt_h1proto_s {
|
#include <nxt_websocket_header.h>
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -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_init(nxt_task_t *task, void *obj, void *data);
|
||||||
static void nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj,
|
static void nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj,
|
||||||
void *data);
|
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);
|
nxt_http_request_t *r);
|
||||||
static nxt_int_t nxt_h1p_header_buffer_test(nxt_task_t *task,
|
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);
|
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,
|
static nxt_int_t nxt_h1p_connection(void *ctx, nxt_http_field_t *field,
|
||||||
uintptr_t data);
|
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,
|
static nxt_int_t nxt_h1p_transfer_encoding(void *ctx, nxt_http_field_t *field,
|
||||||
uintptr_t data);
|
uintptr_t data);
|
||||||
static void nxt_h1p_request_body_read(nxt_task_t *task, nxt_http_request_t *r);
|
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);
|
void *data);
|
||||||
static void nxt_h1p_conn_request_send_timeout(nxt_task_t *task, void *obj,
|
static void nxt_h1p_conn_request_send_timeout(nxt_task_t *task, void *obj,
|
||||||
void *data);
|
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_inline void nxt_h1p_request_error(nxt_task_t *task, nxt_h1proto_t *h1p,
|
||||||
nxt_http_request_t *r);
|
nxt_http_request_t *r);
|
||||||
static void nxt_h1p_request_close(nxt_task_t *task, nxt_http_proto_t proto,
|
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,
|
static nxt_msec_t nxt_h1p_idle_response_timer_value(nxt_conn_t *c,
|
||||||
uintptr_t data);
|
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_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_closing(nxt_task_t *task, void *obj, void *data);
|
||||||
static void nxt_h1p_conn_free(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)
|
#if (NXT_TLS)
|
||||||
static const nxt_conn_state_t nxt_http_idle_state;
|
static const nxt_conn_state_t nxt_http_idle_state;
|
||||||
static const nxt_conn_state_t nxt_h1p_shutdown_state;
|
static const nxt_conn_state_t nxt_h1p_shutdown_state;
|
||||||
#endif
|
#endif
|
||||||
static const nxt_conn_state_t nxt_h1p_idle_state;
|
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_header_parse_state;
|
||||||
static const nxt_conn_state_t nxt_h1p_read_body_state;
|
static const nxt_conn_state_t nxt_h1p_read_body_state;
|
||||||
static const nxt_conn_state_t nxt_h1p_request_send_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,
|
.body_bytes_sent = nxt_h1p_request_body_bytes_sent,
|
||||||
.discard = nxt_h1p_request_discard,
|
.discard = nxt_h1p_request_discard,
|
||||||
.close = nxt_h1p_request_close,
|
.close = nxt_h1p_request_close,
|
||||||
|
.ws_frame_start = nxt_h1p_websocket_frame_start,
|
||||||
},
|
},
|
||||||
/* NXT_HTTP_PROTO_H2 */
|
/* NXT_HTTP_PROTO_H2 */
|
||||||
/* NXT_HTTP_PROTO_DEVNULL */
|
/* 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[] = {
|
static nxt_http_field_proc_t nxt_h1p_fields[] = {
|
||||||
{ nxt_string("Connection"), &nxt_h1p_connection, 0 },
|
{ 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("Transfer-Encoding"), &nxt_h1p_transfer_encoding, 0 },
|
||||||
|
|
||||||
{ nxt_string("Host"), &nxt_http_request_host, 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');
|
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)) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
(void) nxt_h1p_header_process(h1p, r);
|
(void) nxt_h1p_header_process(task, h1p, r);
|
||||||
|
|
||||||
error:
|
error:
|
||||||
|
|
||||||
@@ -562,8 +554,12 @@ error:
|
|||||||
|
|
||||||
|
|
||||||
static nxt_int_t
|
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.start = h1p->parser.target_start;
|
||||||
r->target.length = h1p->parser.target_end - 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;
|
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_h1p_connection(void *ctx, nxt_http_field_t *field, uintptr_t data)
|
||||||
{
|
{
|
||||||
nxt_http_request_t *r;
|
nxt_http_request_t *r;
|
||||||
|
static const u_char *upgrade = (const u_char *) "upgrade";
|
||||||
|
|
||||||
r = ctx;
|
r = ctx;
|
||||||
|
|
||||||
if (field->value_length == 5 && nxt_memcmp(field->value, "close", 5) == 0) {
|
if (field->value_length == 5 && nxt_memcmp(field->value, "close", 5) == 0) {
|
||||||
r->proto.h1->keepalive = 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;
|
return NXT_OK;
|
||||||
@@ -727,6 +818,7 @@ nxt_h1p_request_body_read(nxt_task_t *task, nxt_http_request_t *r)
|
|||||||
if (size != 0) {
|
if (size != 0) {
|
||||||
in->next = h1p->buffers;
|
in->next = h1p->buffers;
|
||||||
h1p->buffers = in;
|
h1p->buffers = in;
|
||||||
|
h1p->nbuffers++;
|
||||||
|
|
||||||
c = h1p->conn;
|
c = h1p->conn;
|
||||||
c->read = b;
|
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 \
|
#define NXT_HTTP_LAST_SUCCESS \
|
||||||
(NXT_HTTP_OK + nxt_nitems(nxt_http_success) - 1)
|
(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 423\r\n"),
|
||||||
nxt_string("HTTP/1.1 424\r\n"),
|
nxt_string("HTTP/1.1 424\r\n"),
|
||||||
nxt_string("HTTP/1.1 425\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 427\r\n"),
|
||||||
nxt_string("HTTP/1.1 428\r\n"),
|
nxt_string("HTTP/1.1 428\r\n"),
|
||||||
nxt_string("HTTP/1.1 429\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];
|
u_char buf[UNKNOWN_STATUS_LENGTH];
|
||||||
|
|
||||||
static const char chunked[] = "Transfer-Encoding: chunked\r\n";
|
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: close\r\n"),
|
||||||
nxt_string("Connection: keep-alive\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");
|
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;
|
h1p = r->proto.h1;
|
||||||
n = r->status;
|
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];
|
status = &nxt_http_success[n - NXT_HTTP_OK];
|
||||||
|
|
||||||
} else if (n >= NXT_HTTP_MULTIPLE_CHOICES
|
} 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. */
|
/* Trailing CRLF at the end of header. */
|
||||||
size += nxt_length("\r\n");
|
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');
|
http11 = (h1p->parser.version.s.minor != '0');
|
||||||
|
|
||||||
if (r->resp.content_length == NULL || r->resp.content_length->skip) {
|
if (r->resp.content_length == NULL || r->resp.content_length->skip) {
|
||||||
|
|
||||||
if (http11) {
|
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;
|
h1p->chunked = 1;
|
||||||
size += nxt_length(chunked);
|
size += nxt_length(chunked);
|
||||||
/* Trailing CRLF will be added by the first chunk header. */
|
/* 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) {
|
if (http11 ^ h1p->keepalive) {
|
||||||
conn = h1p->keepalive;
|
conn = h1p->keepalive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn >= 0) {
|
||||||
size += connection[conn].length;
|
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;
|
} 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);
|
header = nxt_http_buf_mem(task, r, size);
|
||||||
if (nxt_slow_path(header == NULL)) {
|
if (nxt_slow_path(header == NULL)) {
|
||||||
nxt_h1p_request_error(task, h1p, r);
|
nxt_h1p_request_error(task, h1p, r);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
p = header->mem.free;
|
p = nxt_cpymem(header->mem.free, status->start, status->length);
|
||||||
|
|
||||||
p = nxt_cpymem(p, status->start, status->length);
|
|
||||||
|
|
||||||
nxt_list_each(field, r->resp.fields) {
|
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);
|
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) {
|
if (h1p->chunked) {
|
||||||
p = nxt_cpymem(p, chunked, nxt_length(chunked));
|
p = nxt_cpymem(p, chunked, nxt_length(chunked));
|
||||||
/* Trailing CRLF will be added by the first chunk header. */
|
/* 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;
|
c->write_state = &nxt_h1p_request_send_state;
|
||||||
|
|
||||||
nxt_conn_write(task->thread->engine, c);
|
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;
|
r = h1p->request;
|
||||||
|
|
||||||
|
if (nxt_slow_path(r == NULL)) {
|
||||||
|
nxt_h1p_shutdown(task, h1p->conn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (r->fields == NULL) {
|
if (r->fields == NULL) {
|
||||||
(void) nxt_h1p_header_process(h1p, r);
|
(void) nxt_h1p_header_process(task, h1p, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r->status == 0) {
|
if (r->status == 0) {
|
||||||
@@ -1218,7 +1410,7 @@ nxt_h1p_conn_request_timeout(nxt_task_t *task, void *obj, void *data)
|
|||||||
r = h1p->request;
|
r = h1p->request;
|
||||||
|
|
||||||
if (r->fields == NULL) {
|
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);
|
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_h1p_conn_request_timer_value(nxt_conn_t *c, uintptr_t data)
|
||||||
{
|
{
|
||||||
nxt_h1proto_t *h1p;
|
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)
|
nxt_h1p_keepalive(nxt_task_t *task, nxt_h1proto_t *h1p, nxt_conn_t *c)
|
||||||
{
|
{
|
||||||
size_t size;
|
size_t size;
|
||||||
nxt_buf_t *in, *b, *next;
|
nxt_buf_t *in;
|
||||||
|
|
||||||
nxt_debug(task, "h1p keepalive");
|
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);
|
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));
|
nxt_memzero(h1p, offsetof(nxt_h1proto_t, conn));
|
||||||
|
|
||||||
c->sent = 0;
|
c->sent = 0;
|
||||||
|
|
||||||
in = c->read;
|
|
||||||
|
|
||||||
if (in == 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_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;
|
c->read_state = &nxt_h1p_keepalive_state;
|
||||||
|
|
||||||
nxt_conn_read(task->thread->engine, c);
|
nxt_conn_read(task->thread->engine, c);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
size = nxt_buf_mem_used_size(&in->mem);
|
||||||
|
|
||||||
nxt_debug(task, "h1p pipelining");
|
nxt_debug(task, "h1p pipelining");
|
||||||
|
|
||||||
nxt_memmove(in->mem.start, in->mem.pos, size);
|
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) =
|
nxt_aligned(64) =
|
||||||
{
|
{
|
||||||
.close_handler = nxt_h1p_idle_close,
|
.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
|
static void
|
||||||
nxt_h1p_shutdown(nxt_task_t *task, nxt_conn_t *c)
|
nxt_h1p_shutdown(nxt_task_t *task, nxt_conn_t *c)
|
||||||
{
|
{
|
||||||
|
nxt_timer_t *timer;
|
||||||
|
nxt_h1proto_t *h1p;
|
||||||
|
|
||||||
nxt_debug(task, "h1p shutdown");
|
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;
|
c->socket.data = NULL;
|
||||||
|
|
||||||
#if (NXT_TLS)
|
#if (NXT_TLS)
|
||||||
@@ -1596,6 +1795,21 @@ static const nxt_conn_state_t nxt_h1p_shutdown_state
|
|||||||
#endif
|
#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
|
static void
|
||||||
nxt_h1p_conn_closing(nxt_task_t *task, void *obj, void *data)
|
nxt_h1p_conn_closing(nxt_task_t *task, void *obj, void *data)
|
||||||
{
|
{
|
||||||
|
|||||||
48
src/nxt_h1proto.h
Normal file
48
src/nxt_h1proto.h
Normal 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
719
src/nxt_h1proto_websocket.c
Normal 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);
|
||||||
|
}
|
||||||
@@ -11,6 +11,9 @@
|
|||||||
typedef enum {
|
typedef enum {
|
||||||
NXT_HTTP_INVALID = 0,
|
NXT_HTTP_INVALID = 0,
|
||||||
|
|
||||||
|
NXT_HTTP_CONTINUE = 100,
|
||||||
|
NXT_HTTP_SWITCHING_PROTOCOLS = 101,
|
||||||
|
|
||||||
NXT_HTTP_OK = 200,
|
NXT_HTTP_OK = 200,
|
||||||
NXT_HTTP_NO_CONTENT = 204,
|
NXT_HTTP_NO_CONTENT = 204,
|
||||||
|
|
||||||
@@ -26,6 +29,7 @@ typedef enum {
|
|||||||
NXT_HTTP_LENGTH_REQUIRED = 411,
|
NXT_HTTP_LENGTH_REQUIRED = 411,
|
||||||
NXT_HTTP_PAYLOAD_TOO_LARGE = 413,
|
NXT_HTTP_PAYLOAD_TOO_LARGE = 413,
|
||||||
NXT_HTTP_URI_TOO_LONG = 414,
|
NXT_HTTP_URI_TOO_LONG = 414,
|
||||||
|
NXT_HTTP_UPGRADE_REQUIRED = 426,
|
||||||
NXT_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
NXT_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
||||||
|
|
||||||
NXT_HTTP_TO_HTTPS = 497,
|
NXT_HTTP_TO_HTTPS = 497,
|
||||||
@@ -61,6 +65,13 @@ typedef struct {
|
|||||||
|
|
||||||
typedef struct nxt_h1proto_s nxt_h1proto_t;
|
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 {
|
typedef union {
|
||||||
void *any;
|
void *any;
|
||||||
nxt_h1proto_t *h1;
|
nxt_h1proto_t *h1;
|
||||||
@@ -99,6 +110,7 @@ struct nxt_http_request_s {
|
|||||||
nxt_mp_t *mem_pool;
|
nxt_mp_t *mem_pool;
|
||||||
|
|
||||||
nxt_buf_t *body;
|
nxt_buf_t *body;
|
||||||
|
nxt_buf_t *ws_frame;
|
||||||
nxt_buf_t *out;
|
nxt_buf_t *out;
|
||||||
const nxt_http_request_state_t *state;
|
const nxt_http_request_state_t *state;
|
||||||
|
|
||||||
@@ -127,6 +139,8 @@ struct nxt_http_request_s {
|
|||||||
nxt_timer_t timer;
|
nxt_timer_t timer;
|
||||||
void *timer_data;
|
void *timer_data;
|
||||||
|
|
||||||
|
void *req_rpc_data;
|
||||||
|
|
||||||
nxt_buf_t *last;
|
nxt_buf_t *last;
|
||||||
|
|
||||||
nxt_http_response_t resp;
|
nxt_http_response_t resp;
|
||||||
@@ -138,6 +152,7 @@ struct nxt_http_request_s {
|
|||||||
uint8_t logged; /* 1 bit */
|
uint8_t logged; /* 1 bit */
|
||||||
uint8_t header_sent; /* 1 bit */
|
uint8_t header_sent; /* 1 bit */
|
||||||
uint8_t error; /* 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 (*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,
|
void (*close)(nxt_task_t *task, nxt_http_proto_t proto,
|
||||||
nxt_socket_conf_joint_t *joint);
|
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;
|
} 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);
|
nxt_http_status_t status);
|
||||||
void nxt_http_request_read_body(nxt_task_t *task, nxt_http_request_t *r);
|
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_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,
|
void nxt_http_request_send(nxt_task_t *task, nxt_http_request_t *r,
|
||||||
nxt_buf_t *out);
|
nxt_buf_t *out);
|
||||||
nxt_buf_t *nxt_http_buf_mem(nxt_task_t *task, nxt_http_request_t *r,
|
nxt_buf_t *nxt_http_buf_mem(nxt_task_t *task, nxt_http_request_t *r,
|
||||||
size_t size);
|
size_t size);
|
||||||
nxt_buf_t *nxt_http_buf_last(nxt_http_request_t *r);
|
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_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,
|
nxt_int_t nxt_http_request_host(void *ctx, nxt_http_field_t *field,
|
||||||
uintptr_t data);
|
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[];
|
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_ */
|
#endif /* _NXT_HTTP_H_INCLUDED_ */
|
||||||
|
|||||||
@@ -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,
|
static void nxt_http_request_mem_buf_completion(nxt_task_t *task, void *obj,
|
||||||
void *data);
|
void *data);
|
||||||
static void nxt_http_request_done(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,
|
static u_char *nxt_http_date(u_char *buf, nxt_realtime_t *now, struct tm *tm,
|
||||||
size_t size, const char *format);
|
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
|
void
|
||||||
nxt_http_request_send(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *out)
|
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_request_close_handler(nxt_task_t *task, void *obj, void *data)
|
||||||
{
|
{
|
||||||
nxt_http_proto_t proto;
|
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;
|
r->proto.any = NULL;
|
||||||
nxt_mp_release(r->mem_pool);
|
|
||||||
|
|
||||||
if (nxt_fast_path(proto.any != NULL)) {
|
if (nxt_fast_path(proto.any != NULL)) {
|
||||||
|
protocol = r->protocol;
|
||||||
|
|
||||||
|
nxt_mp_release(r->mem_pool);
|
||||||
|
|
||||||
nxt_http_proto[protocol].close(task, proto, conf);
|
nxt_http_proto[protocol].close(task, proto, conf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ static nxt_http_field_proc_t nxt_response_fields[] = {
|
|||||||
offsetof(nxt_http_request_t, resp.content_type) },
|
offsetof(nxt_http_request_t, resp.content_type) },
|
||||||
{ nxt_string("Content-Length"), &nxt_http_response_field,
|
{ nxt_string("Content-Length"), &nxt_http_response_field,
|
||||||
offsetof(nxt_http_request_t, resp.content_length) },
|
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
161
src/nxt_http_websocket.c
Normal 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);
|
||||||
|
}
|
||||||
@@ -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_queue_init(&port->messages);
|
||||||
nxt_thread_mutex_create(&port->write_mutex);
|
nxt_thread_mutex_create(&port->write_mutex);
|
||||||
nxt_queue_init(&port->pending_requests);
|
nxt_queue_init(&port->pending_requests);
|
||||||
|
nxt_queue_init(&port->active_websockets);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
nxt_mp_destroy(mp);
|
nxt_mp_destroy(mp);
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ struct nxt_port_handlers_s {
|
|||||||
/* Stop process command. */
|
/* Stop process command. */
|
||||||
nxt_port_handler_t quit;
|
nxt_port_handler_t quit;
|
||||||
|
|
||||||
|
/* Request headers. */
|
||||||
|
nxt_port_handler_t req_headers;
|
||||||
|
|
||||||
|
/* Websocket frame. */
|
||||||
|
nxt_port_handler_t websocket_frame;
|
||||||
|
|
||||||
/* Various data. */
|
/* Various data. */
|
||||||
nxt_port_handler_t 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_REMOVE_PID = nxt_port_handler_idx(remove_pid),
|
||||||
_NXT_PORT_MSG_QUIT = nxt_port_handler_idx(quit),
|
_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_DATA = nxt_port_handler_idx(data),
|
||||||
|
|
||||||
NXT_PORT_MSG_MAX = sizeof(nxt_port_handlers_t)
|
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_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_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 = _NXT_PORT_MSG_DATA,
|
||||||
NXT_PORT_MSG_DATA_LAST = _NXT_PORT_MSG_DATA | NXT_PORT_MSG_LAST,
|
NXT_PORT_MSG_DATA_LAST = _NXT_PORT_MSG_DATA | NXT_PORT_MSG_LAST,
|
||||||
} nxt_port_msg_type_t;
|
} nxt_port_msg_type_t;
|
||||||
@@ -181,6 +194,8 @@ struct nxt_port_s {
|
|||||||
uint32_t app_responses;
|
uint32_t app_responses;
|
||||||
nxt_queue_t pending_requests;
|
nxt_queue_t pending_requests;
|
||||||
|
|
||||||
|
nxt_queue_t active_websockets;
|
||||||
|
|
||||||
nxt_port_handler_t handler;
|
nxt_port_handler_t handler;
|
||||||
nxt_port_handler_t *data;
|
nxt_port_handler_t *data;
|
||||||
|
|
||||||
|
|||||||
179
src/nxt_router.c
179
src/nxt_router.c
@@ -14,7 +14,7 @@
|
|||||||
#include <nxt_port_memory_int.h>
|
#include <nxt_port_memory_int.h>
|
||||||
#include <nxt_unit_request.h>
|
#include <nxt_unit_request.h>
|
||||||
#include <nxt_unit_response.h>
|
#include <nxt_unit_response.h>
|
||||||
|
#include <nxt_router_request.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
nxt_str_t type;
|
nxt_str_t type;
|
||||||
@@ -48,64 +48,6 @@ typedef struct {
|
|||||||
#endif
|
#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 {
|
typedef struct {
|
||||||
nxt_socket_conf_t *socket_conf;
|
nxt_socket_conf_t *socket_conf;
|
||||||
nxt_router_temp_conf_t *temp_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,
|
static void nxt_router_http_request_release(nxt_task_t *task, void *obj,
|
||||||
void *data);
|
void *data);
|
||||||
|
|
||||||
|
const nxt_http_request_state_t nxt_http_websocket;
|
||||||
|
|
||||||
static nxt_router_t *nxt_router;
|
static nxt_router_t *nxt_router;
|
||||||
|
|
||||||
static const nxt_str_t http_prefix = nxt_string("HTTP_");
|
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_request_app_link_t *req_app_link)
|
||||||
{
|
{
|
||||||
nxt_mp_t *mp;
|
nxt_mp_t *mp;
|
||||||
|
nxt_http_request_t *r;
|
||||||
nxt_request_rpc_data_t *req_rpc_data;
|
nxt_request_rpc_data_t *req_rpc_data;
|
||||||
|
|
||||||
nxt_assert(task->thread->engine == req_app_link->work.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;
|
req_rpc_data->msg_info = req_app_link->msg_info;
|
||||||
|
|
||||||
if (req_rpc_data->app->timeout != 0) {
|
if (req_rpc_data->app->timeout != 0) {
|
||||||
req_rpc_data->request->timer.handler = nxt_router_app_timeout;
|
r = req_rpc_data->request;
|
||||||
req_rpc_data->request->timer_data = req_rpc_data;
|
|
||||||
nxt_timer_add(task->thread->engine,
|
r->timer.handler = nxt_router_app_timeout;
|
||||||
&req_rpc_data->request->timer,
|
r->timer_data = req_rpc_data;
|
||||||
|
nxt_timer_add(task->thread->engine, &r->timer,
|
||||||
req_rpc_data->app->timeout);
|
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
|
if (req_app_link->link_app_requests.next == NULL
|
||||||
&& req_app_link->link_port_pending.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;
|
req_app_link = NULL;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ra_use_delta -=
|
ra_use_delta -=
|
||||||
nxt_queue_chk_remove(&req_app_link->link_app_requests)
|
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);
|
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);
|
nxt_router_http_request_done(task, req_rpc_data->request);
|
||||||
|
|
||||||
|
req_rpc_data->request->req_rpc_data = NULL;
|
||||||
req_rpc_data->request = 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
|
static nxt_int_t
|
||||||
nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
|
nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
|
||||||
u_char *start, u_char *end)
|
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_app_t *app, *prev;
|
||||||
nxt_router_t *router;
|
nxt_router_t *router;
|
||||||
nxt_app_joint_t *app_joint;
|
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 *applications, *application;
|
||||||
nxt_conf_value_t *listeners, *listener;
|
nxt_conf_value_t *listeners, *listener;
|
||||||
nxt_conf_value_t *routes_conf;
|
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)
|
#if (NXT_TLS)
|
||||||
static nxt_str_t certificate_path = nxt_string("/tls/certificate");
|
static nxt_str_t certificate_path = nxt_string("/tls/certificate");
|
||||||
#endif
|
#endif
|
||||||
|
static nxt_str_t websocket_path = nxt_string("/settings/http/websocket");
|
||||||
|
|
||||||
conf = nxt_conf_json_parse(tmcf->mem_pool, start, end, NULL);
|
conf = nxt_conf_json_parse(tmcf->mem_pool, start, end, NULL);
|
||||||
if (conf == NULL) {
|
if (conf == NULL) {
|
||||||
@@ -1658,6 +1630,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
websocket = nxt_conf_get_path(conf, &websocket_path);
|
||||||
|
|
||||||
listeners = nxt_conf_get_path(conf, &listeners_path);
|
listeners = nxt_conf_get_path(conf, &listeners_path);
|
||||||
|
|
||||||
if (listeners != NULL) {
|
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->body_read_timeout = 30 * 1000;
|
||||||
skcf->send_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) {
|
if (http != NULL) {
|
||||||
ret = nxt_conf_map_object(mp, http, nxt_router_http_conf,
|
ret = nxt_conf_map_object(mp, http, nxt_router_http_conf,
|
||||||
nxt_nitems(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)
|
#if (NXT_TLS)
|
||||||
value = nxt_conf_get_path(listener, &certificate_path);
|
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_int_t ret;
|
||||||
nxt_buf_t *b;
|
nxt_buf_t *b;
|
||||||
|
nxt_port_t *app_port;
|
||||||
nxt_unit_field_t *f;
|
nxt_unit_field_t *f;
|
||||||
nxt_http_field_t *field;
|
nxt_http_field_t *field;
|
||||||
nxt_http_request_t *r;
|
nxt_http_request_t *r;
|
||||||
nxt_unit_response_t *resp;
|
nxt_unit_response_t *resp;
|
||||||
|
nxt_request_app_link_t *req_app_link;
|
||||||
nxt_request_rpc_data_t *req_rpc_data;
|
nxt_request_rpc_data_t *req_rpc_data;
|
||||||
|
|
||||||
b = msg->buf;
|
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);
|
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;
|
r->state = &nxt_http_request_send_state;
|
||||||
|
}
|
||||||
|
|
||||||
if (r->out) {
|
if (r->out) {
|
||||||
nxt_work_queue_add(&task->thread->engine->fast_work_queue,
|
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;
|
got_response = 1;
|
||||||
inc_use = -1;
|
inc_use = -1;
|
||||||
break;
|
break;
|
||||||
|
case NXT_APR_UPGRADE:
|
||||||
|
dec_pending = 1;
|
||||||
|
got_response = 1;
|
||||||
|
break;
|
||||||
case NXT_APR_CLOSE:
|
case NXT_APR_CLOSE:
|
||||||
inc_use = -1;
|
inc_use = -1;
|
||||||
break;
|
break;
|
||||||
@@ -4046,9 +4082,10 @@ re_ra_cancelled:
|
|||||||
|
|
||||||
adjust_idle_timer = 0;
|
adjust_idle_timer = 0;
|
||||||
|
|
||||||
if (port->pair[1] != -1 && !send_quit && port->app_pending_responses == 0) {
|
if (port->pair[1] != -1 && !send_quit && port->app_pending_responses == 0
|
||||||
nxt_assert(port->idle_link.next == NULL);
|
&& nxt_queue_is_empty(&port->active_websockets)
|
||||||
|
&& port->idle_link.next == NULL)
|
||||||
|
{
|
||||||
if (app->idle_processes == app->spare_processes
|
if (app->idle_processes == app->spare_processes
|
||||||
&& app->adjust_idle_work.data == NULL)
|
&& 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);
|
nxt_router_app_use(task, app, 1);
|
||||||
|
|
||||||
req_rpc_data->request = r;
|
req_rpc_data->request = r;
|
||||||
|
r->req_rpc_data = req_rpc_data;
|
||||||
|
|
||||||
req_app_link = &ra_local;
|
req_app_link = &ra_local;
|
||||||
nxt_request_app_link_init(task, req_app_link, req_rpc_data);
|
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;
|
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,
|
-1, req_app_link->stream, reply_port->id, buf,
|
||||||
&req_app_link->msg_info.tracking);
|
&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';
|
*p++ = '\0';
|
||||||
|
|
||||||
req->tls = (r->tls != NULL);
|
req->tls = (r->tls != NULL);
|
||||||
|
req->websocket_handshake = r->websocket_handshake;
|
||||||
|
|
||||||
req->server_name_length = r->server_name.length;
|
req->server_name_length = r->server_name.length;
|
||||||
nxt_unit_sptr_set(&req->server_name, p);
|
nxt_unit_sptr_set(&req->server_name, p);
|
||||||
|
|||||||
@@ -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 {
|
typedef struct {
|
||||||
uint32_t count;
|
uint32_t count;
|
||||||
nxt_queue_link_t link;
|
nxt_queue_link_t link;
|
||||||
@@ -164,6 +171,8 @@ typedef struct {
|
|||||||
nxt_msec_t body_read_timeout;
|
nxt_msec_t body_read_timeout;
|
||||||
nxt_msec_t send_timeout;
|
nxt_msec_t send_timeout;
|
||||||
|
|
||||||
|
nxt_websocket_conf_t websocket_conf;
|
||||||
|
|
||||||
#if (NXT_TLS)
|
#if (NXT_TLS)
|
||||||
nxt_tls_conf_t *tls;
|
nxt_tls_conf_t *tls;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
71
src/nxt_router_request.h
Normal file
71
src/nxt_router_request.h
Normal 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
295
src/nxt_sha1.c
Normal 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
24
src/nxt_sha1.h
Normal 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_ */
|
||||||
1133
src/nxt_unit.c
1133
src/nxt_unit.c
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "nxt_version.h"
|
#include "nxt_version.h"
|
||||||
@@ -106,17 +107,24 @@ struct nxt_unit_request_info_s {
|
|||||||
void *data;
|
void *data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set of application-specific callbacks. Application may leave all optional
|
* Set of application-specific callbacks. Application may leave all optional
|
||||||
* callbacks as NULL.
|
* callbacks as NULL.
|
||||||
*/
|
*/
|
||||||
struct nxt_unit_callbacks_s {
|
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.
|
* need to be defined by application.
|
||||||
*/
|
*/
|
||||||
void (*request_handler)(nxt_unit_request_info_t *req);
|
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. */
|
/* Add new Unit port to communicate with process pid. Optional. */
|
||||||
int (*add_port)(nxt_unit_ctx_t *, nxt_unit_port_t *port);
|
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,
|
nxt_unit_buf_t *nxt_unit_response_buf_alloc(nxt_unit_request_info_t *req,
|
||||||
uint32_t size);
|
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);
|
int nxt_unit_buf_send(nxt_unit_buf_t *buf);
|
||||||
|
|
||||||
void nxt_unit_buf_free(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);
|
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_log(nxt_unit_ctx_t *ctx, int level, const char* fmt, ...);
|
||||||
|
|
||||||
void nxt_unit_req_log(nxt_unit_request_info_t *req, int level,
|
void nxt_unit_req_log(nxt_unit_request_info_t *req, int level,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ struct nxt_unit_request_s {
|
|||||||
uint8_t remote_length;
|
uint8_t remote_length;
|
||||||
uint8_t local_length;
|
uint8_t local_length;
|
||||||
uint8_t tls;
|
uint8_t tls;
|
||||||
|
uint8_t websocket_handshake;
|
||||||
uint32_t server_name_length;
|
uint32_t server_name_length;
|
||||||
uint32_t target_length;
|
uint32_t target_length;
|
||||||
uint32_t path_length;
|
uint32_t path_length;
|
||||||
|
|||||||
@@ -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_request_s nxt_unit_request_t;
|
||||||
typedef struct nxt_unit_response_s nxt_unit_response_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_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_ */
|
#endif /* _NXT_UNIT_TYPEDEFS_H_INCLUDED_ */
|
||||||
|
|||||||
27
src/nxt_unit_websocket.h
Normal file
27
src/nxt_unit_websocket.h
Normal 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
122
src/nxt_websocket.c
Normal 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
21
src/nxt_websocket.h
Normal 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_ */
|
||||||
68
src/nxt_websocket_accept.c
Normal file
68
src/nxt_websocket_accept.c
Normal 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
68
src/nxt_websocket_header.h
Normal file
68
src/nxt_websocket_header.h
Normal 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_ */
|
||||||
348
src/test/nxt_unit_websocket_chat.c
Normal file
348
src/test/nxt_unit_websocket_chat.c
Normal 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);
|
||||||
105
src/test/nxt_unit_websocket_echo.c
Normal file
105
src/test/nxt_unit_websocket_echo.c
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user