summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.hgtags1
-rw-r--r--CHANGES25
-rw-r--r--auto/make24
-rw-r--r--auto/modules/go1
-rw-r--r--auto/modules/perl19
-rw-r--r--auto/sources5
-rw-r--r--docs/changes.xml87
-rw-r--r--pkg/deb/debian/unit.default2
-rw-r--r--pkg/deb/debian/unit.init2
-rw-r--r--pkg/deb/debian/unit.service2
-rw-r--r--pkg/docker/Dockerfile.full2
-rw-r--r--pkg/docker/Dockerfile.go1.7-dev2
-rw-r--r--pkg/docker/Dockerfile.go1.8-dev2
-rw-r--r--pkg/docker/Dockerfile.minimal2
-rw-r--r--pkg/docker/Dockerfile.perl5.242
-rw-r--r--pkg/docker/Dockerfile.php7.02
-rw-r--r--pkg/docker/Dockerfile.python2.72
-rw-r--r--pkg/docker/Dockerfile.python3.52
-rw-r--r--pkg/docker/Dockerfile.ruby2.32
-rw-r--r--pkg/docker/Makefile2
-rw-r--r--pkg/rpm/Makefile4
-rw-r--r--pkg/rpm/rpmbuild/SOURCES/unit.service4
-rw-r--r--src/java/nginx/unit/Context.java32
-rw-r--r--src/java/nginx/unit/ForwardRequestWrapper.java12
-rw-r--r--src/java/nginx/unit/IncludeRequestWrapper.java12
-rw-r--r--src/java/nginx/unit/Request.java151
-rw-r--r--[-rwxr-xr-x]src/nodejs/unit-http/http.js0
-rw-r--r--[-rwxr-xr-x]src/nodejs/unit-http/http_server.js78
-rw-r--r--src/nodejs/unit-http/nxt_napi.h195
-rw-r--r--src/nodejs/unit-http/package.json9
-rw-r--r--[-rwxr-xr-x]src/nodejs/unit-http/socket.js8
-rw-r--r--src/nodejs/unit-http/unit.cpp517
-rw-r--r--src/nodejs/unit-http/unit.h29
-rw-r--r--src/nodejs/unit-http/utils.js73
-rw-r--r--src/nodejs/unit-http/websocket.js14
-rw-r--r--src/nodejs/unit-http/websocket_connection.js683
-rw-r--r--src/nodejs/unit-http/websocket_frame.js11
-rw-r--r--src/nodejs/unit-http/websocket_request.js509
-rw-r--r--src/nodejs/unit-http/websocket_router.js157
-rw-r--r--src/nodejs/unit-http/websocket_router_request.js54
-rw-r--r--src/nodejs/unit-http/websocket_server.js213
-rw-r--r--src/nxt_application.c5
-rw-r--r--src/nxt_conf_validation.c54
-rw-r--r--src/nxt_h1proto.c431
-rw-r--r--src/nxt_h1proto.h48
-rw-r--r--src/nxt_h1proto_websocket.c719
-rw-r--r--src/nxt_http.h87
-rw-r--r--src/nxt_http_error.c6
-rw-r--r--src/nxt_http_parse.c23
-rw-r--r--src/nxt_http_parse.h24
-rw-r--r--src/nxt_http_request.c48
-rw-r--r--src/nxt_http_response.c2
-rw-r--r--src/nxt_http_route.c44
-rw-r--r--src/nxt_http_websocket.c161
-rw-r--r--src/nxt_java.c2
-rw-r--r--src/nxt_php_sapi.c190
-rw-r--r--src/nxt_port.c1
-rw-r--r--src/nxt_port.h23
-rw-r--r--src/nxt_port_memory.c6
-rw-r--r--src/nxt_port_memory.h2
-rw-r--r--src/nxt_port_socket.c310
-rw-r--r--src/nxt_router.c1280
-rw-r--r--src/nxt_router.h13
-rw-r--r--src/nxt_router_request.h71
-rw-r--r--src/nxt_sha1.c295
-rw-r--r--src/nxt_sha1.h24
-rw-r--r--src/nxt_socket.c4
-rw-r--r--src/nxt_unit.c1159
-rw-r--r--src/nxt_unit.h32
-rw-r--r--src/nxt_unit_request.h1
-rw-r--r--src/nxt_unit_typedefs.h27
-rw-r--r--src/nxt_unit_websocket.h27
-rw-r--r--src/nxt_websocket.c122
-rw-r--r--src/nxt_websocket.h21
-rw-r--r--src/nxt_websocket_accept.c68
-rw-r--r--src/nxt_websocket_header.h68
-rw-r--r--src/perl/nxt_perl_psgi_layer.h1
-rw-r--r--src/test/nxt_unit_websocket_chat.c348
-rw-r--r--src/test/nxt_unit_websocket_echo.c105
-rw-r--r--test/go/404/app.go22
-rw-r--r--test/go/command_line_arguments/app.go24
-rw-r--r--test/go/cookies/app.go16
-rw-r--r--test/go/empty/app.go8
-rw-r--r--test/go/get_variables/app.go14
-rw-r--r--test/go/mirror/app.go20
-rw-r--r--test/go/post_variables/app.go16
-rw-r--r--test/go/variables/app.go36
-rw-r--r--test/java/empty_war/empty.warbin0 -> 484 bytes
-rw-r--r--test/java/multipart/app.java93
-rw-r--r--test/java/session_inactive/app.java8
-rwxr-xr-xtest/node/404/app.js3
-rwxr-xr-xtest/node/basic/app.js4
-rwxr-xr-xtest/node/double_end/app.js3
-rwxr-xr-xtest/node/mirror/app.js4
-rwxr-xr-xtest/node/promise_handler/app.js3
-rwxr-xr-xtest/node/status_message/app.js3
-rwxr-xr-xtest/node/variables/app.js3
-rwxr-xr-xtest/node/websockets/mirror/app.js31
-rwxr-xr-xtest/node/websockets/mirror_fragmentation/app.js26
-rwxr-xr-xtest/node/write_before_write_head/app.js3
-rwxr-xr-xtest/node/write_buffer/app.js4
-rwxr-xr-xtest/node/write_return/app.js4
-rw-r--r--test/php/header/index.php4
-rw-r--r--test/php/script/phpinfo.php3
-rw-r--r--test/php/variables/index.php1
-rw-r--r--test/test_access_log.py12
-rw-r--r--test/test_java_application.py95
-rw-r--r--test/test_node_websockets.py1585
-rw-r--r--test/test_php_application.py83
-rw-r--r--test/test_php_basic.py26
-rw-r--r--test/test_python_basic.py27
-rw-r--r--test/test_routing.py1198
-rw-r--r--test/test_routing_tls.py58
-rw-r--r--test/test_tls.py5
-rw-r--r--test/unit/applications/lang/java.py2
-rw-r--r--test/unit/applications/lang/perl.py4
-rw-r--r--test/unit/applications/lang/php.py4
-rw-r--r--test/unit/applications/lang/python.py4
-rw-r--r--test/unit/applications/lang/ruby.py4
-rw-r--r--test/unit/applications/tls.py19
-rw-r--r--test/unit/applications/websockets.py215
-rw-r--r--test/unit/http.py12
-rw-r--r--test/unit/main.py35
-rw-r--r--version4
124 files changed, 10199 insertions, 2354 deletions
diff --git a/.hgtags b/.hgtags
index 87a07d7a..4556b887 100644
--- a/.hgtags
+++ b/.hgtags
@@ -23,3 +23,4 @@ fe0d5eb09b66e77a2b66455faa51d3fa04146d3d 1.7.1-1
f47fc64d3d9e3dedb95042e93c7f73b31f458338 1.8.0-1
dda6319de785dc2d225d818349aba56fc48d47f6 1.9.0
c927a739754ade5ecf7be8da30f6c42446e72d8c 1.9.0-1
+cdbba3c3e3762eacc308a5407877c3665a05058d 1.10.0
diff --git a/CHANGES b/CHANGES
index 79761847..1c2852dc 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,4 +1,29 @@
+Changes with Unit 1.10.0 22 Aug 2019
+
+ *) Change: matching of cookies in routes made case sensitive.
+
+ *) Change: decreased log level of common errors when clients close
+ connections.
+
+ *) Change: removed the Perl module's "--include=" ./configure option.
+
+ *) Feature: built-in WebSocket server implementation for Node.js module.
+
+ *) Feature: splitting PATH_INFO from request URI in PHP module.
+
+ *) Feature: request routing by scheme (HTTP or HTTPS).
+
+ *) Feature: support for multipart requests body in Java module.
+
+ *) Feature: improved API compatibility with Node.js 11.10 or later.
+
+ *) Bugfix: reconfiguration failed if "listeners" or "applications"
+ objects were missing.
+
+ *) Bugfix: applying a large configuration might have failed.
+
+
Changes with Unit 1.9.0 30 May 2019
*) Feature: request routing by arguments, headers, and cookies.
diff --git a/auto/make b/auto/make
index 1eee2a78..44d0bdfc 100644
--- a/auto/make
+++ b/auto/make
@@ -57,6 +57,7 @@ $echo >> $NXT_MAKEFILE
$echo "NXT_LIB_UNIT_OBJS = \\" >> $NXT_MAKEFILE
$echo " $NXT_BUILD_DIR/src/nxt_lvlhsh.o \\" >> $NXT_MAKEFILE
$echo " $NXT_BUILD_DIR/src/nxt_murmur_hash.o \\" >> $NXT_MAKEFILE
+$echo " $NXT_BUILD_DIR/src/nxt_websocket.o \\" >> $NXT_MAKEFILE
for nxt_src in $NXT_LIB_UNIT_SRCS
do
@@ -108,7 +109,9 @@ END
# Object files.
for nxt_src in $NXT_LIB_SRCS $NXT_TEST_SRCS $NXT_LIB_UNIT_SRCS \
- src/test/nxt_unit_app_test.c
+ src/test/nxt_unit_app_test.c \
+ src/test/nxt_unit_websocket_chat.c \
+ src/test/nxt_unit_websocket_echo.c
do
nxt_obj=${nxt_src%.c}.o
nxt_dep=${nxt_src%.c}.dep
@@ -150,7 +153,8 @@ if [ $NXT_TESTS = YES ]; then
.PHONY: tests
tests: $NXT_BUILD_DIR/tests $NXT_BUILD_DIR/utf8_file_name_test \\
- $NXT_BUILD_DIR/unit_app_test
+ $NXT_BUILD_DIR/unit_app_test $NXT_BUILD_DIR/unit_websocket_chat \\
+ $NXT_BUILD_DIR/unit_websocket_echo
$NXT_BUILD_DIR/tests: \$(NXT_TEST_OBJS) \\
$NXT_BUILD_DIR/$NXT_LIB_STATIC
@@ -174,6 +178,22 @@ $NXT_BUILD_DIR/unit_app_test: $NXT_BUILD_DIR/src/test/nxt_unit_app_test.o \\
$NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC \\
$NXT_LD_OPT $NXT_LIBM $NXT_LIBS $NXT_LIB_AUX_LIBS
+$NXT_BUILD_DIR/unit_websocket_chat: \\
+ $NXT_BUILD_DIR/src/test/nxt_unit_websocket_chat.o \\
+ $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC
+ \$(NXT_EXEC_LINK) -o $NXT_BUILD_DIR/unit_websocket_chat \\
+ \$(CFLAGS) $NXT_BUILD_DIR/src/test/nxt_unit_websocket_chat.o \\
+ $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC \\
+ $NXT_LD_OPT $NXT_LIBM $NXT_LIBS $NXT_LIB_AUX_LIBS
+
+$NXT_BUILD_DIR/unit_websocket_echo: \\
+ $NXT_BUILD_DIR/src/test/nxt_unit_websocket_echo.o \\
+ $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC
+ \$(NXT_EXEC_LINK) -o $NXT_BUILD_DIR/unit_websocket_echo \\
+ \$(CFLAGS) $NXT_BUILD_DIR/src/test/nxt_unit_websocket_echo.o \\
+ $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC \\
+ $NXT_LD_OPT $NXT_LIBM $NXT_LIBS $NXT_LIB_AUX_LIBS
+
END
else
diff --git a/auto/modules/go b/auto/modules/go
index 62c3743f..51b5979d 100644
--- a/auto/modules/go
+++ b/auto/modules/go
@@ -107,6 +107,7 @@ ${NXT_GO}-install-src: ${NXT_VERSION_H}
install -d \$(DESTDIR)\$(NXT_GO_DST)/src/nginx/unit
install -p -m644 ./src/*.h ./build/*.h ./src/go/unit/* \
./src/nxt_unit.c ./src/nxt_lvlhsh.c ./src/nxt_murmur_hash.c \
+ ./src/nxt_websocket.c \
\$(DESTDIR)\$(NXT_GO_DST)/src/nginx/unit/
${NXT_GO}-install-build: ${NXT_GO}-install-src
diff --git a/auto/modules/perl b/auto/modules/perl
index bf6fe6f0..a4fd5437 100644
--- a/auto/modules/perl
+++ b/auto/modules/perl
@@ -14,14 +14,12 @@ for nxt_option; do
case "$nxt_option" in
--perl=*) NXT_PERL="$value" ;;
- --include=*) NXT_PERL_INCPATH="$value" ;;
--module=*) NXT_PERL_MODULE="$value" ;;
--help)
cat << END
--perl=FILE set perl executable, default: perl
- --include=DIRECTORY set directory path to perl headers
--module=NAME set unit perl module name
END
@@ -59,10 +57,9 @@ nxt_found=no
if /bin/sh -c "$NXT_PERL -MConfig -e 'print \"Perl version: \",
\$Config{version}, \"\\n\"'" >> $NXT_AUTOCONF_ERR 2>&1; then
- NXT_PERL_INCPATH=${NXT_PERL_INCPATH=`$NXT_PERL -MConfig -e 'print $Config{archlib}, "/CORE"'`}
- NXT_PERL_INCLUDE="-I ${NXT_PERL_INCPATH}"
-
- NXT_PERL_LDOPTS=`$NXT_PERL -MExtUtils::Embed -e ldopts`
+ NXT_PERL_CFLAGS=`$NXT_PERL -MExtUtils::Embed -e ccflags | sed -e 's/^ //;s/ $//'`
+ NXT_PERL_INCLUDE=`$NXT_PERL -MExtUtils::Embed -e perl_inc | sed -e 's/^ //;s/ $//'`
+ NXT_PERL_LDOPTS=`$NXT_PERL -MExtUtils::Embed -e ldopts | sed -e 's/^ //;s/ $//'`
if [ "$NXT_SYSTEM" = "Darwin" ]; then
# OS X system perl wants to link universal binaries
@@ -70,13 +67,11 @@ if /bin/sh -c "$NXT_PERL -MConfig -e 'print \"Perl version: \",
| sed -e 's/-arch i386//' -e 's/-arch x86_64//'`
fi
- NXT_PERL_LIBS="-L ${NXT_PERL_INCPATH} ${NXT_PERL_LDOPTS}"
-
nxt_feature="Perl"
nxt_feature_name=""
nxt_feature_run=no
nxt_feature_incs="${NXT_PERL_INCLUDE}"
- nxt_feature_libs="${NXT_PERL_LIBS}"
+ nxt_feature_libs="${NXT_PERL_LDOPTS}"
nxt_feature_test="
#define _GNU_SOURCE
#include <EXTERN.h>
@@ -123,7 +118,7 @@ nxt_feature="Perl version"
nxt_feature_name=""
nxt_feature_run=value
nxt_feature_incs="${NXT_PERL_INCLUDE}"
-nxt_feature_libs="${NXT_PERL_LIBS}"
+nxt_feature_libs="${NXT_PERL_LDOPTS}"
nxt_feature_test="
#define _GNU_SOURCE
#include <EXTERN.h>
@@ -171,7 +166,7 @@ for nxt_src in $NXT_PERL_MODULE_SRCS; do
$NXT_BUILD_DIR/$nxt_obj: $nxt_src
mkdir -p $NXT_BUILD_DIR/src/perl
- \$(CC) -c \$(CFLAGS) \$(NXT_INCS) $NXT_PERL_INCLUDE \\
+ \$(CC) -c \$(CFLAGS) $NXT_PERL_CFLAGS \$(NXT_INCS) $NXT_PERL_INCLUDE \\
$nxt_dep_flags \\
-o $NXT_BUILD_DIR/$nxt_obj $nxt_src
$nxt_dep_post
@@ -194,7 +189,7 @@ ${NXT_PERL_MODULE}: $NXT_BUILD_DIR/${NXT_PERL_MODULE}.unit.so
$NXT_BUILD_DIR/${NXT_PERL_MODULE}.unit.so: $nxt_objs
\$(NXT_MODULE_LINK) -o $NXT_BUILD_DIR/${NXT_PERL_MODULE}.unit.so \\
- $nxt_objs $NXT_PERL_LIBS $NXT_LD_OPT
+ $nxt_objs $NXT_PERL_LDOPTS $NXT_LD_OPT
install: ${NXT_PERL_MODULE}-install
diff --git a/auto/sources b/auto/sources
index 4c4fd742..8ac8fb19 100644
--- a/auto/sources
+++ b/auto/sources
@@ -86,6 +86,11 @@ NXT_LIB_SRCS=" \
src/nxt_application.c \
src/nxt_external.c \
src/nxt_port_hash.c \
+ src/nxt_sha1.c \
+ src/nxt_websocket.c \
+ src/nxt_websocket_accept.c \
+ src/nxt_http_websocket.c \
+ src/nxt_h1proto_websocket.c \
"
NXT_LIB_SRC0=" \
diff --git a/docs/changes.xml b/docs/changes.xml
index e1a8734c..1358e8b8 100644
--- a/docs/changes.xml
+++ b/docs/changes.xml
@@ -5,6 +5,93 @@
<change_log title="unit">
+<changes apply="unit-php
+ unit-python unit-python2.7
+ unit-python3.4 unit-python3.5 unit-python3.6 unit-python3.7
+ unit-go unit-go1.7 unit-go1.8 unit-go1.9 unit-go1.10 unit-go1.11
+ unit-perl
+ unit-ruby
+ unit-jsc-common unit-jsc8 unit-jsc10 unit-jsc11"
+ ver="1.10.0" rev="1"
+ date="2019-08-22" time="18:00:00 +0300"
+ packager="Andrei Belov &lt;defan@nginx.com&gt;">
+
+<change>
+<para>
+NGINX Unit updated to 1.10.0.
+</para>
+</change>
+
+</changes>
+
+
+<changes apply="unit" ver="1.10.0" rev="1"
+ date="2019-08-22" time="18:00:00 +0300"
+ packager="Andrei Belov &lt;defan@nginx.com&gt;">
+
+<change type="change">
+<para>
+matching of cookies in routes made case sensitive.
+</para>
+</change>
+
+<change type="change">
+<para>
+decreased log level of common errors when clients close connections.
+</para>
+</change>
+
+<change type="change">
+<para>
+removed the Perl module's "--include=" ./configure option.
+</para>
+</change>
+
+<change type="feature">
+<para>
+built-in WebSocket server implementation for Node.js module.
+</para>
+</change>
+
+<change type="feature">
+<para>
+splitting PATH_INFO from request URI in PHP module.
+</para>
+</change>
+
+<change type="feature">
+<para>
+request routing by scheme (HTTP or HTTPS).
+</para>
+</change>
+
+<change type="feature">
+<para>
+support for multipart requests body in Java module.
+</para>
+</change>
+
+<change type="feature">
+<para>
+improved API compatibility with Node.js 11.10 or later.
+</para>
+</change>
+
+<change type="bugfix">
+<para>
+reconfiguration failed if "listeners" or "applications" objects were missing.
+</para>
+</change>
+
+<change type="bugfix">
+<para>
+applying a large configuration might have failed.
+</para>
+</change>
+
+</changes>
+
+
<changes apply="unit-go1.11" ver="1.9.0" rev="1"
date="2019-05-30" time="18:00:00 +0300"
packager="Andrei Belov &lt;defan@nginx.com&gt;">
diff --git a/pkg/deb/debian/unit.default b/pkg/deb/debian/unit.default
index b0f0f72d..8aff8bfe 100644
--- a/pkg/deb/debian/unit.default
+++ b/pkg/deb/debian/unit.default
@@ -1 +1 @@
-DAEMON_ARGS="--log /var/log/unit.log --pid /run/unit.pid"
+DAEMON_ARGS="--log /var/log/unit.log --pid /var/run/unit.pid"
diff --git a/pkg/deb/debian/unit.init b/pkg/deb/debian/unit.init
index c991b912..2f573f99 100644
--- a/pkg/deb/debian/unit.init
+++ b/pkg/deb/debian/unit.init
@@ -31,7 +31,7 @@ umask 022
case "$1" in
start)
log_daemon_msg "Starting $DESC" "$NAME"
- if start-stop-daemon --start --quiet --pidfile /run/$NAME.pid \
+ if start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
--exec $DAEMON -- $DAEMON_ARGS; then
log_end_msg 0
else
diff --git a/pkg/deb/debian/unit.service b/pkg/deb/debian/unit.service
index 445851a5..d07a06d3 100644
--- a/pkg/deb/debian/unit.service
+++ b/pkg/deb/debian/unit.service
@@ -5,7 +5,7 @@ After=network-online.target
[Service]
Type=forking
-PIDFile=/run/unit.pid
+PIDFile=/var/run/unit.pid
EnvironmentFile=-/etc/default/unit
ExecStart=/usr/sbin/unitd $DAEMON_ARGS
ExecReload=
diff --git a/pkg/docker/Dockerfile.full b/pkg/docker/Dockerfile.full
index 14afc75b..8c4baa35 100644
--- a/pkg/docker/Dockerfile.full
+++ b/pkg/docker/Dockerfile.full
@@ -2,7 +2,7 @@ FROM debian:stretch-slim
LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"
-ENV UNIT_VERSION 1.9.0-1~stretch
+ENV UNIT_VERSION 1.10.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.go1.7-dev b/pkg/docker/Dockerfile.go1.7-dev
index ad3d888d..895ac69e 100644
--- a/pkg/docker/Dockerfile.go1.7-dev
+++ b/pkg/docker/Dockerfile.go1.7-dev
@@ -2,7 +2,7 @@ FROM debian:stretch-slim
LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"
-ENV UNIT_VERSION 1.9.0-1~stretch
+ENV UNIT_VERSION 1.10.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.go1.8-dev b/pkg/docker/Dockerfile.go1.8-dev
index 915d859a..1ba8a92e 100644
--- a/pkg/docker/Dockerfile.go1.8-dev
+++ b/pkg/docker/Dockerfile.go1.8-dev
@@ -2,7 +2,7 @@ FROM debian:stretch-slim
LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"
-ENV UNIT_VERSION 1.9.0-1~stretch
+ENV UNIT_VERSION 1.10.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.minimal b/pkg/docker/Dockerfile.minimal
index 5214c24a..44592364 100644
--- a/pkg/docker/Dockerfile.minimal
+++ b/pkg/docker/Dockerfile.minimal
@@ -2,7 +2,7 @@ FROM debian:stretch-slim
LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"
-ENV UNIT_VERSION 1.9.0-1~stretch
+ENV UNIT_VERSION 1.10.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.perl5.24 b/pkg/docker/Dockerfile.perl5.24
index c2c91866..fc483796 100644
--- a/pkg/docker/Dockerfile.perl5.24
+++ b/pkg/docker/Dockerfile.perl5.24
@@ -2,7 +2,7 @@ FROM debian:stretch-slim
LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"
-ENV UNIT_VERSION 1.9.0-1~stretch
+ENV UNIT_VERSION 1.10.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.php7.0 b/pkg/docker/Dockerfile.php7.0
index b14a38b4..e766bfbf 100644
--- a/pkg/docker/Dockerfile.php7.0
+++ b/pkg/docker/Dockerfile.php7.0
@@ -2,7 +2,7 @@ FROM debian:stretch-slim
LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"
-ENV UNIT_VERSION 1.9.0-1~stretch
+ENV UNIT_VERSION 1.10.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.python2.7 b/pkg/docker/Dockerfile.python2.7
index 82de3318..1d0410e1 100644
--- a/pkg/docker/Dockerfile.python2.7
+++ b/pkg/docker/Dockerfile.python2.7
@@ -2,7 +2,7 @@ FROM debian:stretch-slim
LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"
-ENV UNIT_VERSION 1.9.0-1~stretch
+ENV UNIT_VERSION 1.10.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.python3.5 b/pkg/docker/Dockerfile.python3.5
index b103d8f9..d83fb984 100644
--- a/pkg/docker/Dockerfile.python3.5
+++ b/pkg/docker/Dockerfile.python3.5
@@ -2,7 +2,7 @@ FROM debian:stretch-slim
LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"
-ENV UNIT_VERSION 1.9.0-1~stretch
+ENV UNIT_VERSION 1.10.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.ruby2.3 b/pkg/docker/Dockerfile.ruby2.3
index 50ec7ed6..0cdc76c2 100644
--- a/pkg/docker/Dockerfile.ruby2.3
+++ b/pkg/docker/Dockerfile.ruby2.3
@@ -2,7 +2,7 @@ FROM debian:stretch-slim
LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"
-ENV UNIT_VERSION 1.9.0-1~stretch
+ENV UNIT_VERSION 1.10.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Makefile b/pkg/docker/Makefile
index cf6de78d..e826cb95 100644
--- a/pkg/docker/Makefile
+++ b/pkg/docker/Makefile
@@ -49,7 +49,7 @@ dockerfiles: $(addprefix Dockerfile., $(MODULES))
build: dockerfiles $(addprefix build-,$(MODULES))
push: build $(addprefix push-,$(MODULES)) latest
-Dockerfile.%: ../../src/nxt_main.h
+Dockerfile.%: ../../version
@echo "===> Building $@"
cat Dockerfile.tmpl | sed \
-e 's,@@UNITPACKAGES@@,$(MODULE_$*),g' \
diff --git a/pkg/rpm/Makefile b/pkg/rpm/Makefile
index 9e343aa2..4e970e45 100644
--- a/pkg/rpm/Makefile
+++ b/pkg/rpm/Makefile
@@ -140,8 +140,8 @@ CONFIGURE_ARGS=\
--prefix=/usr \
--state=%{_sharedstatedir}/unit \
--control="unix:/var/run/unit/control.sock" \
- --pid=/var/run/unit.pid \
- --log=/var/log/unit.log \
+ --pid=/var/run/unit/unit.pid \
+ --log=/var/log/unit/unit.log \
--tests \
--openssl
diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.service b/pkg/rpm/rpmbuild/SOURCES/unit.service
index f888685f..6df00fbb 100644
--- a/pkg/rpm/rpmbuild/SOURCES/unit.service
+++ b/pkg/rpm/rpmbuild/SOURCES/unit.service
@@ -7,7 +7,7 @@
# the following:
# [Service]
-# Environment="UNITD_OPTIONS=--log /var/log/unit/unit.log --pid /run/unit/unit.pid"
+# Environment="UNITD_OPTIONS=--log /var/log/unit/unit.log --pid /var/run/unit/unit.pid"
[Unit]
Description=NGINX Unit
@@ -16,7 +16,7 @@ After=network-online.target
[Service]
Type=simple
-Environment="UNITD_OPTIONS=--log /var/log/unit/unit.log --pid /run/unit/unit.pid"
+Environment="UNITD_OPTIONS=--log /var/log/unit/unit.log --pid /var/run/unit/unit.pid"
ExecStart=/usr/sbin/unitd $UNITD_OPTIONS --no-daemon
ExecReload=
RuntimeDirectory=unit
diff --git a/src/java/nginx/unit/Context.java b/src/java/nginx/unit/Context.java
index f6d5e339..e1482903 100644
--- a/src/java/nginx/unit/Context.java
+++ b/src/java/nginx/unit/Context.java
@@ -81,6 +81,7 @@ import javax.servlet.ServletSecurityElement;
import javax.servlet.SessionCookieConfig;
import javax.servlet.SessionTrackingMode;
import javax.servlet.annotation.HandlesTypes;
+import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.annotation.WebFilter;
@@ -313,6 +314,7 @@ public class Context implements ServletContext, InitParams
} else {
response.setContentLengthLong(f.length());
+ response.setContentType(getMimeType(f.getName()));
InputStream is = new FileInputStream(f);
byte[] buffer = new byte[response.getBufferSize()];
@@ -953,6 +955,8 @@ public class Context implements ServletContext, InitParams
ServletReg servlet = findServlet(path, req);
+ req.setMultipartConfig(servlet.multipart_config_);
+
FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.REQUEST);
fc.doFilter(req, resp);
@@ -1073,6 +1077,8 @@ public class Context implements ServletContext, InitParams
ServletReg servlet = findServlet(path, req);
+ req.setMultipartConfig(servlet.multipart_config_);
+
FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.ERROR);
fc.doFilter(req, resp);
@@ -1851,11 +1857,13 @@ public class Context implements ServletContext, InitParams
private boolean initialized_ = false;
private final List<FilterMap> filters_ = new ArrayList<>();
private boolean system_jsp_servlet_ = false;
+ private MultipartConfigElement multipart_config_;
public ServletReg(String name, Class<?> servlet_class)
{
super(name, servlet_class.getName());
servlet_class_ = servlet_class;
+ getAnnotationMultipartConfig();
}
public ServletReg(String name, Servlet servlet)
@@ -1892,6 +1900,7 @@ public class Context implements ServletContext, InitParams
try {
if (servlet_class_ == null) {
servlet_class_ = loader_.loadClass(getClassName());
+ getAnnotationMultipartConfig();
}
Constructor<?> ctor = servlet_class_.getConstructor();
@@ -1947,6 +1956,20 @@ public class Context implements ServletContext, InitParams
super.setClassName(servlet_class.getName());
servlet_class_ = servlet_class;
+ getAnnotationMultipartConfig();
+ }
+
+ private void getAnnotationMultipartConfig() {
+ if (servlet_class_ == null) {
+ return;
+ }
+
+ MultipartConfig mpc = servlet_class_.getAnnotation(MultipartConfig.class);
+ if (mpc == null) {
+ return;
+ }
+
+ multipart_config_ = new MultipartConfigElement(mpc);
}
public void service(ServletRequest request, ServletResponse response)
@@ -2026,7 +2049,8 @@ public class Context implements ServletContext, InitParams
public void setMultipartConfig(
MultipartConfigElement multipartConfig)
{
- log("ServletReg.setMultipartConfig");
+ trace("ServletReg.setMultipartConfig");
+ multipart_config_ = multipartConfig;
}
@Override
@@ -2507,6 +2531,8 @@ public class Context implements ServletContext, InitParams
ServletReg servlet = findServlet(path, req);
+ req.setMultipartConfig(servlet.multipart_config_);
+
req.setRequestURI(uri_.getRawPath());
req.setQueryString(uri_.getRawQuery());
req.setDispatcherType(DispatcherType.FORWARD);
@@ -2576,6 +2602,8 @@ public class Context implements ServletContext, InitParams
ServletReg servlet = findServlet(path, req);
+ req.setMultipartConfig(servlet.multipart_config_);
+
req.setRequestURI(uri_.getRawPath());
req.setQueryString(uri_.getRawQuery());
req.setDispatcherType(DispatcherType.INCLUDE);
@@ -2756,7 +2784,7 @@ public class Context implements ServletContext, InitParams
{
trace("getRealPath for " + path);
- File f = new File(webapp_, path.substring(1));
+ File f = new File(webapp_, path.isEmpty() ? "" : path.substring(1));
return f.getAbsolutePath();
}
diff --git a/src/java/nginx/unit/ForwardRequestWrapper.java b/src/java/nginx/unit/ForwardRequestWrapper.java
index f88b6aef..fe8adf8a 100644
--- a/src/java/nginx/unit/ForwardRequestWrapper.java
+++ b/src/java/nginx/unit/ForwardRequestWrapper.java
@@ -4,6 +4,7 @@ import java.util.List;
import java.util.Map;
import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
@@ -24,6 +25,8 @@ public class ForwardRequestWrapper implements DynamicPathRequest
private final String orig_context_path;
private final String orig_query;
+ private final MultipartConfigElement orig_multipart_config;
+
private final DispatcherType orig_dtype;
private MultiMap<String> orig_parameters;
@@ -46,6 +49,8 @@ public class ForwardRequestWrapper implements DynamicPathRequest
orig_uri = request_.getRequestURI();
orig_context_path = request_.getContextPath();
orig_query = request_.getQueryString();
+
+ orig_multipart_config = request_.getMultipartConfig();
}
@Override
@@ -125,6 +130,11 @@ public class ForwardRequestWrapper implements DynamicPathRequest
return request_.getFilterPath();
}
+ public void setMultipartConfig(MultipartConfigElement mce)
+ {
+ request_.setMultipartConfig(mce);
+ }
+
public void close()
{
request_.setDispatcherType(orig_dtype);
@@ -137,6 +147,8 @@ public class ForwardRequestWrapper implements DynamicPathRequest
request_.setParameters(orig_parameters);
}
+ request_.setMultipartConfig(orig_multipart_config);
+
if (keep_attrs) {
return;
}
diff --git a/src/java/nginx/unit/IncludeRequestWrapper.java b/src/java/nginx/unit/IncludeRequestWrapper.java
index 67a51b24..761a0d52 100644
--- a/src/java/nginx/unit/IncludeRequestWrapper.java
+++ b/src/java/nginx/unit/IncludeRequestWrapper.java
@@ -1,6 +1,7 @@
package nginx.unit;
import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletRequest;
@@ -14,6 +15,8 @@ public class IncludeRequestWrapper implements DynamicPathRequest
private final Object orig_context_path_attr;
private final Object orig_query_string_attr;
+ private final MultipartConfigElement orig_multipart_config;
+
private final DispatcherType orig_dtype;
private String filter_path_;
@@ -32,6 +35,8 @@ public class IncludeRequestWrapper implements DynamicPathRequest
orig_context_path_attr = request_.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH);
orig_query_string_attr = request_.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING);
+ orig_multipart_config = request_.getMultipartConfig();
+
orig_dtype = request_.getDispatcherType();
request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, request_.getContextPath());
@@ -75,6 +80,11 @@ public class IncludeRequestWrapper implements DynamicPathRequest
return filter_path_;
}
+ public void setMultipartConfig(MultipartConfigElement mce)
+ {
+ request_.setMultipartConfig(mce);
+ }
+
public void close()
{
request_.setDispatcherType(orig_dtype);
@@ -84,5 +94,7 @@ public class IncludeRequestWrapper implements DynamicPathRequest
request_.setAttribute_(RequestDispatcher.INCLUDE_REQUEST_URI, orig_uri_attr);
request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, orig_context_path_attr);
request_.setAttribute_(RequestDispatcher.INCLUDE_QUERY_STRING, orig_query_string_attr);
+
+ request_.setMultipartConfig(orig_multipart_config);
}
}
diff --git a/src/java/nginx/unit/Request.java b/src/java/nginx/unit/Request.java
index 3ba46f6c..98584efe 100644
--- a/src/java/nginx/unit/Request.java
+++ b/src/java/nginx/unit/Request.java
@@ -1,6 +1,8 @@
package nginx.unit;
import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -14,6 +16,9 @@ import java.lang.StringBuffer;
import java.net.URI;
import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
import java.text.ParseException;
import java.text.SimpleDateFormat;
@@ -32,6 +37,7 @@ import java.security.Principal;
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@@ -49,11 +55,14 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpUpgradeHandler;
import javax.servlet.http.Part;
+import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.UrlEncoded;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.server.CookieCutter;
+import org.eclipse.jetty.http.MultiPartFormInputStream;
+import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.MimeTypes;
public class Request implements HttpServletRequest, DynamicPathRequest
@@ -109,6 +118,9 @@ public class Request implements HttpServletRequest, DynamicPathRequest
public static final String BARE = "nginx.unit.request.bare";
+ private MultiPartFormInputStream multi_parts;
+ private MultipartConfigElement multipart_config;
+
public Request(Context ctx, long req_info, long req) {
context = ctx;
req_info_ptr = req_info;
@@ -271,17 +283,64 @@ public class Request implements HttpServletRequest, DynamicPathRequest
@Override
public Part getPart(String name) throws IOException, ServletException
{
- log("getPart: " + name);
+ trace("getPart: " + name);
- return null;
+ if (multi_parts == null) {
+ parseMultiParts();
+ }
+
+ return multi_parts.getPart(name);
}
@Override
public Collection<Part> getParts() throws IOException, ServletException
{
- log("getParts");
+ trace("getParts");
+
+ if (multi_parts == null) {
+ parseMultiParts();
+ }
+
+ return multi_parts.getParts();
+ }
+
+ private boolean checkMultiPart(String content_type)
+ {
+ return content_type != null
+ && MimeTypes.Type.MULTIPART_FORM_DATA.is(HttpFields.valueParameters(content_type, null));
+ }
+
+ private void parseMultiParts() throws IOException, ServletException, IllegalStateException
+ {
+ String content_type = getContentType();
+
+ if (!checkMultiPart(content_type)) {
+ throw new ServletException("Content-Type != multipart/form-data");
+ }
+
+ if (multipart_config == null) {
+ throw new IllegalStateException("No multipart config for servlet");
+ }
- return Collections.emptyList();
+ parseMultiParts(content_type);
+ }
+
+ private void parseMultiParts(String content_type) throws IOException
+ {
+ File tmpDir = (File) context.getAttribute(ServletContext.TEMPDIR);
+
+ multi_parts = new MultiPartFormInputStream(getInputStream(),
+ content_type, multipart_config, tmpDir);
+ }
+
+ public void setMultipartConfig(MultipartConfigElement mce)
+ {
+ multipart_config = mce;
+ }
+
+ public MultipartConfigElement getMultipartConfig()
+ {
+ return multipart_config;
}
@Override
@@ -766,16 +825,84 @@ public class Request implements HttpServletRequest, DynamicPathRequest
UrlEncoded.decodeUtf8To(query, parameters);
}
- if (getContentLength() > 0 &&
- getMethod().equals("POST") &&
- getContentType().startsWith("application/x-www-form-urlencoded"))
- {
- try {
+ int content_length = getContentLength();
+
+ if (content_length == 0 || !getMethod().equals("POST")) {
+ return parameters;
+ }
+
+ String content_type = getContentType();
+
+ try {
+ if (content_type.startsWith("application/x-www-form-urlencoded")) {
UrlEncoded.decodeUtf8To(new InputStream(req_info_ptr),
- parameters, getContentLength(), -1);
- } catch (IOException e) {
- log("Unhandled IOException: " + e);
+ parameters, content_length, -1);
+ } else if (checkMultiPart(content_type) && multipart_config != null) {
+ if (multi_parts == null) {
+ parseMultiParts(content_type);
+ }
+
+ if (multi_parts != null) {
+ Collection<Part> parts = multi_parts.getParts();
+
+ String _charset_ = null;
+ Part charset_part = multi_parts.getPart("_charset_");
+ if (charset_part != null) {
+ try (java.io.InputStream is = charset_part.getInputStream())
+ {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ IO.copy(is, os);
+ _charset_ = new String(os.toByteArray(),StandardCharsets.UTF_8);
+ }
+ }
+
+ /*
+ Select Charset to use for this part. (NOTE: charset behavior is for the part value only and not the part header/field names)
+ 1. Use the part specific charset as provided in that part's Content-Type header; else
+ 2. Use the overall default charset. Determined by:
+ a. if part name _charset_ exists, use that part's value.
+ b. if the request.getCharacterEncoding() returns a value, use that.
+ (note, this can be either from the charset field on the request Content-Type
+ header, or from a manual call to request.setCharacterEncoding())
+ c. use utf-8.
+ */
+ Charset def_charset;
+ if (_charset_ != null) {
+ def_charset = Charset.forName(_charset_);
+ } else if (getCharacterEncoding() != null) {
+ def_charset = Charset.forName(getCharacterEncoding());
+ } else {
+ def_charset = StandardCharsets.UTF_8;
+ }
+
+ ByteArrayOutputStream os = null;
+ for (Part p : parts) {
+ if (p.getSubmittedFileName() != null) {
+ continue;
+ }
+
+ // Servlet Spec 3.0 pg 23, parts without filename must be put into params.
+ String charset = null;
+ if (p.getContentType() != null) {
+ charset = MimeTypes.getCharsetFromContentType(p.getContentType());
+ }
+
+ try (java.io.InputStream is = p.getInputStream())
+ {
+ if (os == null) {
+ os = new ByteArrayOutputStream();
+ }
+ IO.copy(is, os);
+
+ String content = new String(os.toByteArray(), charset == null ? def_charset : Charset.forName(charset));
+ parameters.add(p.getName(), content);
+ }
+ os.reset();
+ }
+ }
}
+ } catch (IOException e) {
+ log("Unhandled IOException: " + e);
}
return parameters;
diff --git a/src/nodejs/unit-http/http.js b/src/nodejs/unit-http/http.js
index 3a25fa2f..3a25fa2f 100755..100644
--- a/src/nodejs/unit-http/http.js
+++ b/src/nodejs/unit-http/http.js
diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js
index ae8e204a..c42149a5 100755..100644
--- a/src/nodejs/unit-http/http_server.js
+++ b/src/nodejs/unit-http/http_server.js
@@ -8,16 +8,21 @@
const EventEmitter = require('events');
const http = require('http');
const util = require('util');
-const unit_lib = require('unit-http/build/Release/unit-http.node');
-const unit_socket = require('unit-http/socket');
-
-const { Socket } = unit_socket;
+const unit_lib = require('./build/Release/unit-http');
+const Socket = require('./socket');
+const WebSocketFrame = require('./websocket_frame');
function ServerResponse(req) {
EventEmitter.call(this);
this.headers = {};
+
+ this.server = req.server;
+ this._request = req;
+ req._response = this;
+ this.socket = req.socket;
+ this.connection = req.connection;
}
util.inherits(ServerResponse, EventEmitter);
@@ -195,6 +200,8 @@ function writeHead(statusCode, reason, obj) {
}
}
}
+
+ return this;
};
/*
@@ -205,15 +212,23 @@ ServerResponse.prototype._implicitHeader = function _implicitHeader() {
this.writeHead(this.statusCode);
};
-ServerResponse.prototype._writeBody = function(chunk, encoding, callback) {
- var contentLength = 0;
+ServerResponse.prototype._send_headers = unit_lib.response_send_headers;
+ServerResponse.prototype._sendHeaders = function _sendHeaders() {
if (!this.headersSent) {
- unit_lib.unit_response_headers(this, this.statusCode, this.headers,
- this.headers_count, this.headers_len);
+ this._send_headers(this.statusCode, this.headers, this.headers_count,
+ this.headers_len);
this.headersSent = true;
}
+};
+
+ServerResponse.prototype._write = unit_lib.response_write;
+
+ServerResponse.prototype._writeBody = function(chunk, encoding, callback) {
+ var contentLength = 0;
+
+ this._sendHeaders();
if (typeof chunk === 'function') {
callback = chunk;
@@ -236,7 +251,7 @@ ServerResponse.prototype._writeBody = function(chunk, encoding, callback) {
contentLength = chunk.length;
}
- unit_lib.unit_response_write(this, chunk, contentLength);
+ this._write(chunk, contentLength);
}
if (typeof callback === 'function') {
@@ -266,11 +281,13 @@ ServerResponse.prototype.write = function write(chunk, encoding, callback) {
return true;
};
+ServerResponse.prototype._end = unit_lib.response_end;
+
ServerResponse.prototype.end = function end(chunk, encoding, callback) {
if (!this.finished) {
this._writeBody(chunk, encoding, callback);
- unit_lib.unit_response_end(this);
+ this._end();
this.finished = true;
}
@@ -278,10 +295,12 @@ ServerResponse.prototype.end = function end(chunk, encoding, callback) {
return this;
};
-function ServerRequest(server) {
+function ServerRequest(server, socket) {
EventEmitter.call(this);
this.server = server;
+ this.socket = socket;
+ this.connection = socket;
}
util.inherits(ServerRequest, EventEmitter);
@@ -337,8 +356,8 @@ ServerRequest.prototype.on = function on(ev, fn) {
if (ev === "data") {
process.nextTick(function () {
- if (this.server.buffer.length !== 0) {
- this.emit("data", this.server.buffer);
+ if (this._data.length !== 0) {
+ this.emit("data", this._data);
}
}.bind(this));
@@ -355,14 +374,27 @@ function Server(requestListener) {
this.unit.createServer();
- this.socket = Socket;
- this.request = ServerRequest;
- this.response = ServerResponse;
+ this.Socket = Socket;
+ this.ServerRequest = ServerRequest;
+ this.ServerResponse = ServerResponse;
+ this.WebSocketFrame = WebSocketFrame;
if (requestListener) {
this.on('request', requestListener);
}
+
+ this._upgradeListenerCount = 0;
+ this.on('newListener', function(ev) {
+ if (ev === 'upgrade'){
+ this._upgradeListenerCount++;
+ }
+ }).on('removeListener', function(ev) {
+ if (ev === 'upgrade') {
+ this._upgradeListenerCount--;
+ }
+ });
}
+
util.inherits(Server, EventEmitter);
Server.prototype.setTimeout = function setTimeout(msecs, callback) {
@@ -379,15 +411,13 @@ Server.prototype.listen = function () {
this.unit.listen();
};
-Server.prototype.emit_events = function (server, req, res) {
- req.server = server;
- res.server = server;
- req.res = res;
- res.req = req;
-
- server.buffer = server.unit._read(req.socket.req_pointer);
+Server.prototype.emit_request = function (req, res) {
+ if (req._websocket_handshake && this._upgradeListenerCount > 0) {
+ this.emit('upgrade', req, req.socket);
- server.emit("request", req, res);
+ } else {
+ this.emit("request", req, res);
+ }
process.nextTick(() => {
req.emit("finish");
diff --git a/src/nodejs/unit-http/nxt_napi.h b/src/nodejs/unit-http/nxt_napi.h
index 9bcf3a21..d9721a40 100644
--- a/src/nodejs/unit-http/nxt_napi.h
+++ b/src/nodejs/unit-http/nxt_napi.h
@@ -188,6 +188,21 @@ struct nxt_napi {
}
+ inline void *
+ get_buffer_info(napi_value val, size_t &size)
+ {
+ void *res;
+ napi_status status;
+
+ status = napi_get_buffer_info(env_, val, &res, &size);
+ if (status != napi_ok) {
+ throw exception("Failed to get buffer info");
+ }
+
+ return res;
+ }
+
+
inline napi_value
get_cb_info(napi_callback_info info, size_t &argc, napi_value *argv)
{
@@ -219,6 +234,23 @@ struct nxt_napi {
inline napi_value
+ get_cb_info(napi_callback_info info, napi_value &arg)
+ {
+ size_t argc;
+ napi_value res;
+
+ argc = 1;
+ res = get_cb_info(info, argc, &arg);
+
+ if (argc != 1) {
+ throw exception("Wrong args count. Expected 1");
+ }
+
+ return res;
+ }
+
+
+ inline napi_value
get_element(napi_value obj, uint32_t i)
{
napi_value res;
@@ -311,15 +343,22 @@ struct nxt_napi {
inline nxt_unit_request_info_t *
get_request_info(napi_value obj)
{
- int64_t n;
+ return (nxt_unit_request_info_t *) unwrap(obj);
+ }
+
+
+ inline uint32_t
+ get_value_bool(napi_value obj)
+ {
+ bool res;
napi_status status;
- status = napi_get_value_int64(env_, obj, &n);
+ status = napi_get_value_bool(env_, obj, &res);
if (status != napi_ok) {
- throw exception("Failed to get request pointer");
+ throw exception("Failed to get bool");
}
- return (nxt_unit_request_info_t *) (intptr_t) n;
+ return res;
}
@@ -353,6 +392,21 @@ struct nxt_napi {
}
+ inline size_t
+ get_value_string_utf8(napi_value val, char *buf, size_t bufsize)
+ {
+ size_t res;
+ napi_status status;
+
+ status = napi_get_value_string_utf8(env_, val, buf, bufsize, &res);
+ if (status != napi_ok) {
+ throw exception("Failed to get string utf8");
+ }
+
+ return res;
+ }
+
+
inline bool
is_array(napi_value val)
{
@@ -368,6 +422,21 @@ struct nxt_napi {
}
+ inline bool
+ is_buffer(napi_value val)
+ {
+ bool res;
+ napi_status status;
+
+ status = napi_is_buffer(env_, val, &res);
+ if (status != napi_ok) {
+ throw exception("Failed to confirm value is buffer");
+ }
+
+ return res;
+ }
+
+
inline napi_value
make_callback(napi_async_context ctx, napi_value val, napi_value func,
int argc, const napi_value *argv)
@@ -398,6 +467,41 @@ struct nxt_napi {
inline napi_value
+ make_callback(napi_async_context ctx, napi_value val, napi_value func)
+ {
+ return make_callback(ctx, val, func, 0, NULL);
+ }
+
+
+ inline napi_value
+ make_callback(napi_async_context ctx, napi_value val, napi_value func,
+ napi_value arg1)
+ {
+ return make_callback(ctx, val, func, 1, &arg1);
+ }
+
+
+ inline napi_value
+ make_callback(napi_async_context ctx, napi_value val, napi_value func,
+ napi_value arg1, napi_value arg2)
+ {
+ napi_value args[2] = { arg1, arg2 };
+
+ return make_callback(ctx, val, func, 2, args);
+ }
+
+
+ inline napi_value
+ make_callback(napi_async_context ctx, napi_value val, napi_value func,
+ napi_value arg1, napi_value arg2, napi_value arg3)
+ {
+ napi_value args[3] = { arg1, arg2, arg3 };
+
+ return make_callback(ctx, val, func, 3, args);
+ }
+
+
+ inline napi_value
new_instance(napi_value ctor)
{
napi_value res;
@@ -427,6 +531,22 @@ struct nxt_napi {
}
+ inline napi_value
+ new_instance(napi_value ctor, napi_value param1, napi_value param2)
+ {
+ napi_value res;
+ napi_status status;
+ napi_value param[2] = { param1, param2 };
+
+ status = napi_new_instance(env_, ctor, 2, param, &res);
+ if (status != napi_ok) {
+ throw exception("Failed to create instance");
+ }
+
+ return res;
+ }
+
+
inline void
set_element(napi_value obj, uint32_t i, napi_value val)
{
@@ -472,8 +592,46 @@ struct nxt_napi {
}
+ template<typename T>
inline void
- set_named_property(napi_value obj, const char *name, intptr_t val)
+ set_named_property(napi_value obj, const char *name, T val)
+ {
+ set_named_property(obj, name, create(val));
+ }
+
+
+ inline napi_value
+ create(int32_t val)
+ {
+ napi_value ptr;
+ napi_status status;
+
+ status = napi_create_int32(env_, val, &ptr);
+ if (status != napi_ok) {
+ throw exception("Failed to create int32");
+ }
+
+ return ptr;
+ }
+
+
+ inline napi_value
+ create(uint32_t val)
+ {
+ napi_value ptr;
+ napi_status status;
+
+ status = napi_create_uint32(env_, val, &ptr);
+ if (status != napi_ok) {
+ throw exception("Failed to create uint32");
+ }
+
+ return ptr;
+ }
+
+
+ inline napi_value
+ create(int64_t val)
{
napi_value ptr;
napi_status status;
@@ -483,7 +641,32 @@ struct nxt_napi {
throw exception("Failed to create int64");
}
- set_named_property(obj, name, ptr);
+ return ptr;
+ }
+
+
+ inline void
+ remove_wrap(napi_ref& ref)
+ {
+ if (ref != nullptr) {
+ remove_wrap(get_reference_value(ref));
+ ref = nullptr;
+ }
+ }
+
+
+ inline void *
+ remove_wrap(napi_value val)
+ {
+ void *res;
+ napi_status status;
+
+ status = napi_remove_wrap(env_, val, &res);
+ if (status != napi_ok) {
+ throw exception("Failed to remove_wrap");
+ }
+
+ return res;
}
diff --git a/src/nodejs/unit-http/package.json b/src/nodejs/unit-http/package.json
index 6a6c00b4..7ee01346 100644
--- a/src/nodejs/unit-http/package.json
+++ b/src/nodejs/unit-http/package.json
@@ -14,7 +14,14 @@
"package.json",
"socket.js",
"binding.gyp",
- "README.md"
+ "README.md",
+ "websocket.js",
+ "websocket_connection.js",
+ "websocket_frame.js",
+ "websocket_request.js",
+ "websocket_router.js",
+ "websocket_router_request.js",
+ "websocket_server.js"
],
"scripts": {
"clean": "node-gyp clean",
diff --git a/src/nodejs/unit-http/socket.js b/src/nodejs/unit-http/socket.js
index 6e836949..b1a3abb8 100755..100644
--- a/src/nodejs/unit-http/socket.js
+++ b/src/nodejs/unit-http/socket.js
@@ -7,7 +7,7 @@
const EventEmitter = require('events');
const util = require('util');
-const unit_lib = require('unit-http/build/Release/unit-http.node');
+const unit_lib = require('./build/Release/unit-http');
function Socket(options) {
EventEmitter.call(this);
@@ -89,7 +89,7 @@ Socket.prototype.setTimeout = function setTimeout(timeout, callback) {
this.timeout = timeout;
- this.on('timeout', callback);
+ // this.on('timeout', callback);
return this;
};
@@ -101,6 +101,4 @@ Socket.prototype.write = function write(data, encoding, callback) {
};
-module.exports = {
- Socket
-};
+module.exports = Socket;
diff --git a/src/nodejs/unit-http/unit.cpp b/src/nodejs/unit-http/unit.cpp
index 3f66189a..ac10024c 100644
--- a/src/nodejs/unit-http/unit.cpp
+++ b/src/nodejs/unit-http/unit.cpp
@@ -10,6 +10,8 @@
#include <uv.h>
+#include <nxt_unit_websocket.h>
+
napi_ref Unit::constructor_;
@@ -20,17 +22,27 @@ struct nxt_nodejs_ctx_t {
};
+struct req_data_t {
+ napi_ref sock_ref;
+ napi_ref resp_ref;
+ napi_ref conn_ref;
+};
+
+
Unit::Unit(napi_env env, napi_value jsthis):
nxt_napi(env),
wrapper_(wrap(jsthis, this, destroy)),
unit_ctx_(nullptr)
{
+ nxt_unit_debug(NULL, "Unit::Unit()");
}
Unit::~Unit()
{
delete_reference(wrapper_);
+
+ nxt_unit_debug(NULL, "Unit::~Unit()");
}
@@ -38,23 +50,26 @@ napi_value
Unit::init(napi_env env, napi_value exports)
{
nxt_napi napi(env);
- napi_value cons;
+ napi_value ctor;
- napi_property_descriptor properties[] = {
+ napi_property_descriptor unit_props[] = {
{ "createServer", 0, create_server, 0, 0, 0, napi_default, 0 },
{ "listen", 0, listen, 0, 0, 0, napi_default, 0 },
- { "_read", 0, _read, 0, 0, 0, napi_default, 0 }
};
try {
- cons = napi.define_class("Unit", create, 3, properties);
- constructor_ = napi.create_reference(cons);
+ ctor = napi.define_class("Unit", create, 2, unit_props);
+ constructor_ = napi.create_reference(ctor);
- napi.set_named_property(exports, "Unit", cons);
- napi.set_named_property(exports, "unit_response_headers",
+ napi.set_named_property(exports, "Unit", ctor);
+ napi.set_named_property(exports, "response_send_headers",
response_send_headers);
- napi.set_named_property(exports, "unit_response_write", response_write);
- napi.set_named_property(exports, "unit_response_end", response_end);
+ napi.set_named_property(exports, "response_write", response_write);
+ napi.set_named_property(exports, "response_end", response_end);
+ napi.set_named_property(exports, "websocket_send_frame",
+ websocket_send_frame);
+ napi.set_named_property(exports, "websocket_set_sock",
+ websocket_set_sock);
} catch (exception &e) {
napi.throw_error(e);
@@ -78,7 +93,7 @@ napi_value
Unit::create(napi_env env, napi_callback_info info)
{
nxt_napi napi(env);
- napi_value target, cons, instance, jsthis;
+ napi_value target, ctor, instance, jsthis;
try {
target = napi.get_new_target(info);
@@ -94,8 +109,8 @@ Unit::create(napi_env env, napi_callback_info info)
}
/* Invoked as plain function `Unit(...)`, turn into construct call. */
- cons = napi.get_reference_value(constructor_);
- instance = napi.new_instance(cons);
+ ctor = napi.get_reference_value(constructor_);
+ instance = napi.new_instance(ctor);
napi.create_reference(instance);
} catch (exception &e) {
@@ -130,10 +145,14 @@ Unit::create_server(napi_env env, napi_callback_info info)
memset(&unit_init, 0, sizeof(nxt_unit_init_t));
unit_init.data = obj;
- unit_init.callbacks.request_handler = request_handler;
- unit_init.callbacks.add_port = add_port;
- unit_init.callbacks.remove_port = remove_port;
- unit_init.callbacks.quit = quit;
+ unit_init.callbacks.request_handler = request_handler_cb;
+ unit_init.callbacks.websocket_handler = websocket_handler_cb;
+ unit_init.callbacks.close_handler = close_handler_cb;
+ unit_init.callbacks.add_port = add_port;
+ unit_init.callbacks.remove_port = remove_port;
+ unit_init.callbacks.quit = quit_cb;
+
+ unit_init.request_data_size = sizeof(req_data_t);
obj->unit_ctx_ = nxt_unit_init(&unit_init);
if (obj->unit_ctx_ == NULL) {
@@ -157,74 +176,139 @@ Unit::listen(napi_env env, napi_callback_info info)
}
-napi_value
-Unit::_read(napi_env env, napi_callback_info info)
+void
+Unit::request_handler_cb(nxt_unit_request_info_t *req)
{
- void *data;
- size_t argc;
- nxt_napi napi(env);
- napi_value buffer, argv;
- nxt_unit_request_info_t *req;
+ Unit *obj;
- argc = 1;
+ obj = reinterpret_cast<Unit *>(req->unit->data);
+
+ obj->request_handler(req);
+}
+
+
+void
+Unit::request_handler(nxt_unit_request_info_t *req)
+{
+ napi_value socket, request, response, server_obj, emit_request;
+
+ memset(req->data, 0, sizeof(req_data_t));
try {
- napi.get_cb_info(info, argc, &argv);
+ nxt_handle_scope scope(env());
+
+ server_obj = get_server_object();
+
+ socket = create_socket(server_obj, req);
+ request = create_request(server_obj, socket);
+ response = create_response(server_obj, request, req);
+
+ create_headers(req, request);
- req = napi.get_request_info(argv);
- buffer = napi.create_buffer((size_t) req->content_length, &data);
+ emit_request = get_named_property(server_obj, "emit_request");
+
+ nxt_async_context async_context(env(), "request_handler");
+ nxt_callback_scope async_scope(async_context);
+
+ make_callback(async_context, server_obj, emit_request, request,
+ response);
} catch (exception &e) {
- napi.throw_error(e);
- return nullptr;
+ nxt_unit_req_warn(req, "request_handler: %s", e.str);
}
+}
- nxt_unit_request_read(req, data, req->content_length);
- return buffer;
+void
+Unit::websocket_handler_cb(nxt_unit_websocket_frame_t *ws)
+{
+ Unit *obj;
+
+ obj = reinterpret_cast<Unit *>(ws->req->unit->data);
+
+ obj->websocket_handler(ws);
}
void
-Unit::request_handler(nxt_unit_request_info_t *req)
+Unit::websocket_handler(nxt_unit_websocket_frame_t *ws)
{
- Unit *obj;
- napi_value socket, request, response, server_obj;
- napi_value emit_events;
- napi_value events_args[3];
+ napi_value frame, server_obj, process_frame, conn;
+ req_data_t *req_data;
- obj = reinterpret_cast<Unit *>(req->unit->data);
+ req_data = (req_data_t *) ws->req->data;
try {
- nxt_handle_scope scope(obj->env());
-
- server_obj = obj->get_server_object();
+ nxt_handle_scope scope(env());
- socket = obj->create_socket(server_obj, req);
- request = obj->create_request(server_obj, socket);
- response = obj->create_response(server_obj, socket, request, req);
+ server_obj = get_server_object();
- obj->create_headers(req, request);
+ frame = create_websocket_frame(server_obj, ws);
- emit_events = obj->get_named_property(server_obj, "emit_events");
+ conn = get_reference_value(req_data->conn_ref);
- events_args[0] = server_obj;
- events_args[1] = request;
- events_args[2] = response;
+ process_frame = get_named_property(conn, "processFrame");
- nxt_async_context async_context(obj->env(), "unit_request_handler");
+ nxt_async_context async_context(env(), "websocket_handler");
nxt_callback_scope async_scope(async_context);
- obj->make_callback(async_context, server_obj, emit_events,
- 3, events_args);
+ make_callback(async_context, conn, process_frame, frame);
} catch (exception &e) {
- obj->throw_error(e);
+ nxt_unit_req_warn(ws->req, "websocket_handler: %s", e.str);
}
+
+ nxt_unit_websocket_done(ws);
+}
+
+
+void
+Unit::close_handler_cb(nxt_unit_request_info_t *req)
+{
+ Unit *obj;
+
+ obj = reinterpret_cast<Unit *>(req->unit->data);
+
+ obj->close_handler(req);
}
void
+Unit::close_handler(nxt_unit_request_info_t *req)
+{
+ napi_value conn_handle_close, conn;
+ req_data_t *req_data;
+
+ req_data = (req_data_t *) req->data;
+
+ try {
+ nxt_handle_scope scope(env());
+
+ conn = get_reference_value(req_data->conn_ref);
+
+ conn_handle_close = get_named_property(conn, "handleSocketClose");
+
+ nxt_async_context async_context(env(), "close_handler");
+ nxt_callback_scope async_scope(async_context);
+
+ make_callback(async_context, conn, conn_handle_close,
+ nxt_napi::create(0));
+
+ remove_wrap(req_data->sock_ref);
+ remove_wrap(req_data->resp_ref);
+ remove_wrap(req_data->conn_ref);
+
+ } catch (exception &e) {
+ nxt_unit_req_warn(req, "close_handler: %s", e.str);
+
+ return;
+ }
+
+ nxt_unit_request_done(req, NXT_UNIT_OK);
+}
+
+
+static void
nxt_uv_read_callback(uv_poll_t *handle, int status, int events)
{
nxt_unit_run_once((nxt_unit_ctx_t *) handle->data);
@@ -244,14 +328,14 @@ Unit::add_port(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port)
obj = reinterpret_cast<Unit *>(ctx->unit->data);
if (fcntl(port->in_fd, F_SETFL, O_NONBLOCK) == -1) {
- obj->throw_error("Failed to upgrade read"
- " file descriptor to O_NONBLOCK");
+ nxt_unit_warn(ctx, "fcntl(%d, O_NONBLOCK) failed: %s (%d)",
+ port->in_fd, strerror(errno), errno);
return -1;
}
status = napi_get_uv_event_loop(obj->env(), &loop);
if (status != napi_ok) {
- obj->throw_error("Failed to get uv.loop");
+ nxt_unit_warn(ctx, "Failed to get uv.loop");
return NXT_UNIT_ERROR;
}
@@ -259,13 +343,13 @@ Unit::add_port(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port)
err = uv_poll_init(loop, &node_ctx->poll, port->in_fd);
if (err < 0) {
- obj->throw_error("Failed to init uv.poll");
+ nxt_unit_warn(ctx, "Failed to init uv.poll");
return NXT_UNIT_ERROR;
}
err = uv_poll_start(&node_ctx->poll, UV_READABLE, nxt_uv_read_callback);
if (err < 0) {
- obj->throw_error("Failed to start uv.poll");
+ nxt_unit_warn(ctx, "Failed to start uv.poll");
return NXT_UNIT_ERROR;
}
@@ -308,27 +392,35 @@ Unit::remove_port(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id)
void
-Unit::quit(nxt_unit_ctx_t *ctx)
+Unit::quit_cb(nxt_unit_ctx_t *ctx)
{
- Unit *obj;
- napi_value server_obj, emit_close;
+ Unit *obj;
obj = reinterpret_cast<Unit *>(ctx->unit->data);
+ obj->quit(ctx);
+}
+
+
+void
+Unit::quit(nxt_unit_ctx_t *ctx)
+{
+ napi_value server_obj, emit_close;
+
try {
- nxt_handle_scope scope(obj->env());
+ nxt_handle_scope scope(env());
- server_obj = obj->get_server_object();
+ server_obj = get_server_object();
- emit_close = obj->get_named_property(server_obj, "emit_close");
+ emit_close = get_named_property(server_obj, "emit_close");
- nxt_async_context async_context(obj->env(), "unit_quit");
+ nxt_async_context async_context(env(), "unit_quit");
nxt_callback_scope async_scope(async_context);
- obj->make_callback(async_context, server_obj, emit_close, 0, NULL);
+ make_callback(async_context, server_obj, emit_close);
} catch (exception &e) {
- obj->throw_error(e);
+ nxt_unit_debug(ctx, "quit: %s", e.str);
}
nxt_unit_done(ctx);
@@ -349,8 +441,9 @@ Unit::get_server_object()
void
Unit::create_headers(nxt_unit_request_info_t *req, napi_value request)
{
+ void *data;
uint32_t i;
- napi_value headers, raw_headers;
+ napi_value headers, raw_headers, buffer;
napi_status status;
nxt_unit_request_t *r;
@@ -373,6 +466,13 @@ Unit::create_headers(nxt_unit_request_info_t *req, napi_value request)
set_named_property(request, "httpVersion", r->version, r->version_length);
set_named_property(request, "method", r->method, r->method_length);
set_named_property(request, "url", r->target, r->target_length);
+
+ set_named_property(request, "_websocket_handshake", r->websocket_handshake);
+
+ buffer = create_buffer((size_t) req->content_length, &data);
+ nxt_unit_request_read(req, data, req->content_length);
+
+ set_named_property(request, "_data", buffer);
}
@@ -410,15 +510,18 @@ napi_value
Unit::create_socket(napi_value server_obj, nxt_unit_request_info_t *req)
{
napi_value constructor, res;
+ req_data_t *req_data;
nxt_unit_request_t *r;
r = req->request;
- constructor = get_named_property(server_obj, "socket");
+ constructor = get_named_property(server_obj, "Socket");
res = new_instance(constructor);
- set_named_property(res, "req_pointer", (intptr_t) req);
+ req_data = (req_data_t *) req->data;
+ req_data->sock_ref = wrap(res, req, sock_destroy);
+
set_named_property(res, "remoteAddress", r->remote, r->remote_length);
set_named_property(res, "localAddress", r->local, r->local_length);
@@ -429,34 +532,66 @@ Unit::create_socket(napi_value server_obj, nxt_unit_request_info_t *req)
napi_value
Unit::create_request(napi_value server_obj, napi_value socket)
{
- napi_value constructor, return_val;
+ napi_value constructor;
- constructor = get_named_property(server_obj, "request");
+ constructor = get_named_property(server_obj, "ServerRequest");
- return_val = new_instance(constructor, server_obj);
+ return new_instance(constructor, server_obj, socket);
+}
- set_named_property(return_val, "socket", socket);
- set_named_property(return_val, "connection", socket);
- return return_val;
+napi_value
+Unit::create_response(napi_value server_obj, napi_value request,
+ nxt_unit_request_info_t *req)
+{
+ napi_value constructor, res;
+ req_data_t *req_data;
+
+ constructor = get_named_property(server_obj, "ServerResponse");
+
+ res = new_instance(constructor, request);
+
+ req_data = (req_data_t *) req->data;
+ req_data->resp_ref = wrap(res, req, resp_destroy);
+
+ return res;
}
napi_value
-Unit::create_response(napi_value server_obj, napi_value socket,
- napi_value request, nxt_unit_request_info_t *req)
+Unit::create_websocket_frame(napi_value server_obj,
+ nxt_unit_websocket_frame_t *ws)
{
- napi_value constructor, return_val;
+ void *data;
+ napi_value constructor, res, buffer;
+ uint8_t sc[2];
+
+ constructor = get_named_property(server_obj, "WebSocketFrame");
- constructor = get_named_property(server_obj, "response");
+ res = new_instance(constructor);
- return_val = new_instance(constructor, request);
+ set_named_property(res, "fin", (bool) ws->header->fin);
+ set_named_property(res, "opcode", ws->header->opcode);
+ set_named_property(res, "length", (int64_t) ws->payload_len);
- set_named_property(return_val, "socket", socket);
- set_named_property(return_val, "connection", socket);
- set_named_property(return_val, "_req_point", (intptr_t) req);
+ if (ws->header->opcode == NXT_WEBSOCKET_OP_CLOSE) {
+ if (ws->payload_len >= 2) {
+ nxt_unit_websocket_read(ws, sc, 2);
- return return_val;
+ set_named_property(res, "closeStatus",
+ (((uint16_t) sc[0]) << 8) | sc[1]);
+
+ } else {
+ set_named_property(res, "closeStatus", -1);
+ }
+ }
+
+ buffer = create_buffer((size_t) ws->content_length, &data);
+ nxt_unit_websocket_read(ws, data, ws->content_length);
+
+ set_named_property(res, "binaryPayload", buffer);
+
+ return res;
}
@@ -472,35 +607,32 @@ Unit::response_send_headers(napi_env env, napi_callback_info info)
uint16_t hash;
nxt_napi napi(env);
napi_value this_arg, headers, keys, name, value, array_val;
- napi_value req_num, array_entry;
+ napi_value array_entry;
napi_valuetype val_type;
nxt_unit_field_t *f;
nxt_unit_request_info_t *req;
- napi_value argv[5];
+ napi_value argv[4];
- argc = 5;
+ argc = 4;
try {
this_arg = napi.get_cb_info(info, argc, argv);
- if (argc != 5) {
+ if (argc != 4) {
napi.throw_error("Wrong args count. Expected: "
"statusCode, headers, headers count, "
"headers length");
return nullptr;
}
- req_num = napi.get_named_property(argv[0], "_req_point");
-
- req = napi.get_request_info(req_num);
-
- status_code = napi.get_value_uint32(argv[1]);
- keys_count = napi.get_value_uint32(argv[3]);
- header_len = napi.get_value_uint32(argv[4]);
+ req = napi.get_request_info(this_arg);
+ status_code = napi.get_value_uint32(argv[0]);
+ keys_count = napi.get_value_uint32(argv[2]);
+ header_len = napi.get_value_uint32(argv[3]);
/* Need to reserve extra byte for C-string 0-termination. */
header_len++;
- headers = argv[2];
+ headers = argv[1];
ret = nxt_unit_response_init(req, status_code, keys_count, header_len);
if (ret != NXT_UNIT_OK) {
@@ -611,65 +743,151 @@ napi_value
Unit::response_write(napi_env env, napi_callback_info info)
{
int ret;
- char *ptr;
+ void *ptr;
size_t argc, have_buf_len;
uint32_t buf_len;
nxt_napi napi(env);
- napi_value this_arg, req_num;
- napi_status status;
+ napi_value this_arg;
nxt_unit_buf_t *buf;
napi_valuetype buf_type;
nxt_unit_request_info_t *req;
- napi_value argv[3];
+ napi_value argv[2];
- argc = 3;
+ argc = 2;
try {
this_arg = napi.get_cb_info(info, argc, argv);
- if (argc != 3) {
+ if (argc != 2) {
throw exception("Wrong args count. Expected: "
"chunk, chunk length");
}
- req_num = napi.get_named_property(argv[0], "_req_point");
- req = napi.get_request_info(req_num);
+ req = napi.get_request_info(this_arg);
+ buf_type = napi.type_of(argv[0]);
+ buf_len = napi.get_value_uint32(argv[1]) + 1;
+
+ buf = nxt_unit_response_buf_alloc(req, buf_len);
+ if (buf == NULL) {
+ throw exception("Failed to allocate response buffer");
+ }
+
+ if (buf_type == napi_string) {
+ /* TODO: will work only for utf8 content-type */
+
+ have_buf_len = napi.get_value_string_utf8(argv[0], buf->free,
+ buf_len);
- buf_len = napi.get_value_uint32(argv[2]);
+ } else {
+ ptr = napi.get_buffer_info(argv[0], have_buf_len);
- buf_type = napi.type_of(argv[1]);
+ memcpy(buf->free, ptr, have_buf_len);
+ }
+ buf->free += have_buf_len;
+
+ ret = nxt_unit_buf_send(buf);
+ if (ret != NXT_UNIT_OK) {
+ throw exception("Failed to send body buf");
+ }
} catch (exception &e) {
napi.throw_error(e);
return nullptr;
}
- buf_len++;
+ return this_arg;
+}
- buf = nxt_unit_response_buf_alloc(req, buf_len);
- if (buf == NULL) {
- goto failed;
- }
- if (buf_type == napi_string) {
- /* TODO: will work only for utf8 content-type */
+napi_value
+Unit::response_end(napi_env env, napi_callback_info info)
+{
+ nxt_napi napi(env);
+ napi_value this_arg;
+ req_data_t *req_data;
+ nxt_unit_request_info_t *req;
+
+ try {
+ this_arg = napi.get_cb_info(info);
+
+ req = napi.get_request_info(this_arg);
- status = napi_get_value_string_utf8(env, argv[1], buf->free,
- buf_len, &have_buf_len);
+ req_data = (req_data_t *) req->data;
- } else {
- status = napi_get_buffer_info(env, argv[1], (void **) &ptr,
- &have_buf_len);
+ napi.remove_wrap(req_data->sock_ref);
+ napi.remove_wrap(req_data->resp_ref);
+ napi.remove_wrap(req_data->conn_ref);
- memcpy(buf->free, ptr, have_buf_len);
+ } catch (exception &e) {
+ napi.throw_error(e);
+ return nullptr;
}
- if (status != napi_ok) {
- goto failed;
+ nxt_unit_request_done(req, NXT_UNIT_OK);
+
+ return this_arg;
+}
+
+
+napi_value
+Unit::websocket_send_frame(napi_env env, napi_callback_info info)
+{
+ int ret, iovec_len;
+ bool fin;
+ size_t buf_len;
+ uint32_t opcode, sc;
+ nxt_napi napi(env);
+ napi_value this_arg, frame, payload;
+ nxt_unit_request_info_t *req;
+ char status_code[2];
+ struct iovec iov[2];
+
+ iovec_len = 0;
+
+ try {
+ this_arg = napi.get_cb_info(info, frame);
+
+ req = napi.get_request_info(this_arg);
+
+ opcode = napi.get_value_uint32(napi.get_named_property(frame,
+ "opcode"));
+ if (opcode == NXT_WEBSOCKET_OP_CLOSE) {
+ sc = napi.get_value_uint32(napi.get_named_property(frame,
+ "closeStatus"));
+ status_code[0] = (sc >> 8) & 0xFF;
+ status_code[1] = sc & 0xFF;
+
+ iov[iovec_len].iov_base = status_code;
+ iov[iovec_len].iov_len = 2;
+ iovec_len++;
+ }
+
+ try {
+ fin = napi.get_value_bool(napi.get_named_property(frame, "fin"));
+
+ } catch (exception &e) {
+ fin = true;
+ }
+
+ payload = napi.get_named_property(frame, "binaryPayload");
+
+ if (napi.is_buffer(payload)) {
+ iov[iovec_len].iov_base = napi.get_buffer_info(payload, buf_len);
+
+ } else {
+ buf_len = 0;
+ }
+
+ } catch (exception &e) {
+ napi.throw_error(e);
+ return nullptr;
}
- buf->free += have_buf_len;
+ if (buf_len > 0) {
+ iov[iovec_len].iov_len = buf_len;
+ iovec_len++;
+ }
- ret = nxt_unit_buf_send(buf);
+ ret = nxt_unit_websocket_sendv(req, opcode, fin ? 1 : 0, iov, iovec_len);
if (ret != NXT_UNIT_OK) {
goto failed;
}
@@ -678,34 +896,65 @@ Unit::response_write(napi_env env, napi_callback_info info)
failed:
- napi.throw_error("Failed to write body");
+ napi.throw_error("Failed to send frame");
return nullptr;
}
napi_value
-Unit::response_end(napi_env env, napi_callback_info info)
+Unit::websocket_set_sock(napi_env env, napi_callback_info info)
{
- size_t argc;
nxt_napi napi(env);
- napi_value resp, this_arg, req_num;
+ napi_value this_arg, sock;
+ req_data_t *req_data;
nxt_unit_request_info_t *req;
- argc = 1;
-
try {
- this_arg = napi.get_cb_info(info, argc, &resp);
+ this_arg = napi.get_cb_info(info, sock);
- req_num = napi.get_named_property(resp, "_req_point");
- req = napi.get_request_info(req_num);
+ req = napi.get_request_info(sock);
+
+ req_data = (req_data_t *) req->data;
+ req_data->conn_ref = napi.wrap(this_arg, req, conn_destroy);
} catch (exception &e) {
napi.throw_error(e);
return nullptr;
}
- nxt_unit_request_done(req, NXT_UNIT_OK);
-
return this_arg;
}
+
+
+void
+Unit::conn_destroy(napi_env env, void *nativeObject, void *finalize_hint)
+{
+ nxt_unit_request_info_t *req;
+
+ req = (nxt_unit_request_info_t *) nativeObject;
+
+ nxt_unit_warn(NULL, "conn_destroy: %p", req);
+}
+
+
+void
+Unit::sock_destroy(napi_env env, void *nativeObject, void *finalize_hint)
+{
+ nxt_unit_request_info_t *req;
+
+ req = (nxt_unit_request_info_t *) nativeObject;
+
+ nxt_unit_warn(NULL, "sock_destroy: %p", req);
+}
+
+
+void
+Unit::resp_destroy(napi_env env, void *nativeObject, void *finalize_hint)
+{
+ nxt_unit_request_info_t *req;
+
+ req = (nxt_unit_request_info_t *) nativeObject;
+
+ nxt_unit_warn(NULL, "resp_destroy: %p", req);
+}
diff --git a/src/nodejs/unit-http/unit.h b/src/nodejs/unit-http/unit.h
index e76d805a..f5eaf9fd 100644
--- a/src/nodejs/unit-http/unit.h
+++ b/src/nodejs/unit-http/unit.h
@@ -19,14 +19,28 @@ private:
static napi_value create(napi_env env, napi_callback_info info);
static void destroy(napi_env env, void *nativeObject, void *finalize_hint);
+ static void conn_destroy(napi_env env, void *nativeObject, void *finalize_hint);
+ static void sock_destroy(napi_env env, void *nativeObject, void *finalize_hint);
+ static void resp_destroy(napi_env env, void *nativeObject, void *finalize_hint);
static napi_value create_server(napi_env env, napi_callback_info info);
static napi_value listen(napi_env env, napi_callback_info info);
static napi_value _read(napi_env env, napi_callback_info info);
- static void request_handler(nxt_unit_request_info_t *req);
+
+ static void request_handler_cb(nxt_unit_request_info_t *req);
+ void request_handler(nxt_unit_request_info_t *req);
+
+ static void websocket_handler_cb(nxt_unit_websocket_frame_t *ws);
+ void websocket_handler(nxt_unit_websocket_frame_t *ws);
+
+ static void close_handler_cb(nxt_unit_request_info_t *req);
+ void close_handler(nxt_unit_request_info_t *req);
+
static int add_port(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port);
static void remove_port(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id);
- static void quit(nxt_unit_ctx_t *ctx);
+
+ static void quit_cb(nxt_unit_ctx_t *ctx);
+ void quit(nxt_unit_ctx_t *ctx);
napi_value get_server_object();
@@ -35,20 +49,25 @@ private:
napi_value create_request(napi_value server_obj, napi_value socket);
- napi_value create_response(napi_value server_obj, napi_value socket,
- napi_value request,
+ napi_value create_response(napi_value server_obj, napi_value request,
nxt_unit_request_info_t *req);
+ napi_value create_websocket_frame(napi_value server_obj,
+ nxt_unit_websocket_frame_t *ws);
+
static napi_value response_send_headers(napi_env env,
napi_callback_info info);
static napi_value response_write(napi_env env, napi_callback_info info);
static napi_value response_end(napi_env env, napi_callback_info info);
+ static napi_value websocket_send_frame(napi_env env,
+ napi_callback_info info);
+ static napi_value websocket_set_sock(napi_env env, napi_callback_info info);
void create_headers(nxt_unit_request_info_t *req, napi_value request);
void append_header(nxt_unit_field_t *f, napi_value headers,
- napi_value raw_headers, uint32_t idx);
+ napi_value raw_headers, uint32_t idx);
static napi_ref constructor_;
diff --git a/src/nodejs/unit-http/utils.js b/src/nodejs/unit-http/utils.js
new file mode 100644
index 00000000..e1e51b0e
--- /dev/null
+++ b/src/nodejs/unit-http/utils.js
@@ -0,0 +1,73 @@
+var noop = exports.noop = function(){};
+
+exports.extend = function extend(dest, source) {
+ for (var prop in source) {
+ dest[prop] = source[prop];
+ }
+};
+
+exports.eventEmitterListenerCount =
+ require('events').EventEmitter.listenerCount ||
+ function(emitter, type) { return emitter.listeners(type).length; };
+
+exports.bufferAllocUnsafe = Buffer.allocUnsafe ?
+ Buffer.allocUnsafe :
+ function oldBufferAllocUnsafe(size) { return new Buffer(size); };
+
+exports.bufferFromString = Buffer.from ?
+ Buffer.from :
+ function oldBufferFromString(string, encoding) {
+ return new Buffer(string, encoding);
+ };
+
+exports.BufferingLogger = function createBufferingLogger(identifier, uniqueID) {
+ try {
+ var logFunction = require('debug')(identifier);
+ }
+ catch(e) {
+ logFunction = noop;
+ logFunction.enabled = false;
+ }
+
+ if (logFunction.enabled) {
+ var logger = new BufferingLogger(identifier, uniqueID, logFunction);
+ var debug = logger.log.bind(logger);
+ debug.printOutput = logger.printOutput.bind(logger);
+ debug.enabled = logFunction.enabled;
+ return debug;
+ }
+ logFunction.printOutput = noop;
+ return logFunction;
+};
+
+function BufferingLogger(identifier, uniqueID, logFunction) {
+ this.logFunction = logFunction;
+ this.identifier = identifier;
+ this.uniqueID = uniqueID;
+ this.buffer = [];
+}
+
+BufferingLogger.prototype.log = function() {
+ this.buffer.push([ new Date(), Array.prototype.slice.call(arguments) ]);
+ return this;
+};
+
+BufferingLogger.prototype.clear = function() {
+ this.buffer = [];
+ return this;
+};
+
+BufferingLogger.prototype.printOutput = function(logFunction) {
+ if (!logFunction) { logFunction = this.logFunction; }
+ var uniqueID = this.uniqueID;
+ this.buffer.forEach(function(entry) {
+ var date = entry[0].toLocaleString();
+ var args = entry[1].slice();
+ var formatString = args[0];
+ if (formatString !== (void 0) && formatString !== null) {
+ formatString = '%s - %s - ' + formatString.toString();
+ args.splice(0, 1, formatString, date, uniqueID);
+ logFunction.apply(global, args);
+ }
+ });
+};
diff --git a/src/nodejs/unit-http/websocket.js b/src/nodejs/unit-http/websocket.js
new file mode 100644
index 00000000..36d0e07a
--- /dev/null
+++ b/src/nodejs/unit-http/websocket.js
@@ -0,0 +1,14 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+'use strict';
+
+module.exports = {
+ 'server' : require('./websocket_server'),
+ 'router' : require('./websocket_router'),
+ 'frame' : require('./websocket_frame'),
+ 'request' : require('./websocket_request'),
+ 'connection' : require('./websocket_connection'),
+};
diff --git a/src/nodejs/unit-http/websocket_connection.js b/src/nodejs/unit-http/websocket_connection.js
new file mode 100644
index 00000000..4eccf6bf
--- /dev/null
+++ b/src/nodejs/unit-http/websocket_connection.js
@@ -0,0 +1,683 @@
+/************************************************************************
+ * Copyright 2010-2015 Brian McKelvey.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ***********************************************************************/
+
+var util = require('util');
+var utils = require('./utils');
+var unit_lib = require('./build/Release/unit-http');
+var EventEmitter = require('events').EventEmitter;
+var WebSocketFrame = require('./websocket_frame');
+var bufferAllocUnsafe = utils.bufferAllocUnsafe;
+var bufferFromString = utils.bufferFromString;
+
+// Connected, fully-open, ready to send and receive frames
+const STATE_OPEN = 'open';
+// Received a close frame from the remote peer
+const STATE_PEER_REQUESTED_CLOSE = 'peer_requested_close';
+// Sent close frame to remote peer. No further data can be sent.
+const STATE_ENDING = 'ending';
+// Connection is fully closed. No further data can be sent or received.
+const STATE_CLOSED = 'closed';
+
+var idCounter = 0;
+
+function WebSocketConnection(socket, extensions, protocol, maskOutgoingPackets, config) {
+ this._debug = utils.BufferingLogger('websocket:connection', ++idCounter);
+ this._debug('constructor');
+
+ if (this._debug.enabled) {
+ instrumentSocketForDebugging(this, socket);
+ }
+
+ // Superclass Constructor
+ EventEmitter.call(this);
+
+ this._pingListenerCount = 0;
+ this.on('newListener', function(ev) {
+ if (ev === 'ping'){
+ this._pingListenerCount++;
+ }
+ }).on('removeListener', function(ev) {
+ if (ev === 'ping') {
+ this._pingListenerCount--;
+ }
+ });
+
+ this.config = config;
+ this.socket = socket;
+ this.protocol = protocol;
+ this.extensions = extensions;
+ this.remoteAddress = socket.remoteAddress;
+ this.closeReasonCode = -1;
+ this.closeDescription = null;
+ this.closeEventEmitted = false;
+
+ // We have to mask outgoing packets if we're acting as a WebSocket client.
+ this.maskOutgoingPackets = maskOutgoingPackets;
+
+ this.fragmentationSize = 0; // data received so far...
+ this.frameQueue = [];
+
+ // Various bits of connection state
+ this.connected = true;
+ this.state = STATE_OPEN;
+ this.waitingForCloseResponse = false;
+ // Received TCP FIN, socket's readable stream is finished.
+ this.receivedEnd = false;
+
+ this.closeTimeout = this.config.closeTimeout;
+ this.assembleFragments = this.config.assembleFragments;
+ this.maxReceivedMessageSize = this.config.maxReceivedMessageSize;
+
+ this.outputBufferFull = false;
+ this.inputPaused = false;
+ this._closeTimerHandler = this.handleCloseTimer.bind(this);
+
+ // Disable nagle algorithm?
+ this.socket.setNoDelay(this.config.disableNagleAlgorithm);
+
+ // Make sure there is no socket inactivity timeout
+ this.socket.setTimeout(0);
+
+ // The HTTP Client seems to subscribe to socket error events
+ // and re-dispatch them in such a way that doesn't make sense
+ // for users of our client, so we want to make sure nobody
+ // else is listening for error events on the socket besides us.
+ this.socket.removeAllListeners('error');
+
+ this._set_sock(this.socket);
+}
+
+WebSocketConnection.prototype._set_sock = unit_lib.websocket_set_sock;
+WebSocketConnection.prototype._end = unit_lib.response_end;
+
+WebSocketConnection.CLOSE_REASON_NORMAL = 1000;
+WebSocketConnection.CLOSE_REASON_GOING_AWAY = 1001;
+WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR = 1002;
+WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT = 1003;
+WebSocketConnection.CLOSE_REASON_RESERVED = 1004; // Reserved value. Undefined meaning.
+WebSocketConnection.CLOSE_REASON_NOT_PROVIDED = 1005; // Not to be used on the wire
+WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006; // Not to be used on the wire
+WebSocketConnection.CLOSE_REASON_INVALID_DATA = 1007;
+WebSocketConnection.CLOSE_REASON_POLICY_VIOLATION = 1008;
+WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG = 1009;
+WebSocketConnection.CLOSE_REASON_EXTENSION_REQUIRED = 1010;
+WebSocketConnection.CLOSE_REASON_INTERNAL_SERVER_ERROR = 1011;
+WebSocketConnection.CLOSE_REASON_TLS_HANDSHAKE_FAILED = 1015; // Not to be used on the wire
+
+WebSocketConnection.CLOSE_DESCRIPTIONS = {
+ 1000: 'Normal connection closure',
+ 1001: 'Remote peer is going away',
+ 1002: 'Protocol error',
+ 1003: 'Unprocessable input',
+ 1004: 'Reserved',
+ 1005: 'Reason not provided',
+ 1006: 'Abnormal closure, no further detail available',
+ 1007: 'Invalid data received',
+ 1008: 'Policy violation',
+ 1009: 'Message too big',
+ 1010: 'Extension requested by client is required',
+ 1011: 'Internal Server Error',
+ 1015: 'TLS Handshake Failed'
+};
+
+function validateCloseReason(code) {
+ if (code < 1000) {
+ // Status codes in the range 0-999 are not used
+ return false;
+ }
+ if (code >= 1000 && code <= 2999) {
+ // Codes from 1000 - 2999 are reserved for use by the protocol. Only
+ // a few codes are defined, all others are currently illegal.
+ return [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014].indexOf(code) !== -1;
+ }
+ if (code >= 3000 && code <= 3999) {
+ // Reserved for use by libraries, frameworks, and applications.
+ // Should be registered with IANA. Interpretation of these codes is
+ // undefined by the WebSocket protocol.
+ return true;
+ }
+ if (code >= 4000 && code <= 4999) {
+ // Reserved for private use. Interpretation of these codes is
+ // undefined by the WebSocket protocol.
+ return true;
+ }
+ if (code >= 5000) {
+ return false;
+ }
+}
+
+util.inherits(WebSocketConnection, EventEmitter);
+
+WebSocketConnection.prototype._addSocketEventListeners = function() {
+ this.socket.on('error', this.handleSocketError.bind(this));
+ this.socket.on('end', this.handleSocketEnd.bind(this));
+ this.socket.on('close', this.handleSocketClose.bind(this));
+};
+
+WebSocketConnection.prototype.handleSocketError = function(error) {
+ this._debug('handleSocketError: %j', error);
+ if (this.state === STATE_CLOSED) {
+ // See https://github.com/theturtle32/WebSocket-Node/issues/288
+ this._debug(' --- Socket \'error\' after \'close\'');
+ return;
+ }
+ this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL;
+ this.closeDescription = 'Socket Error: ' + error.syscall + ' ' + error.code;
+ this.connected = false;
+ this.state = STATE_CLOSED;
+ this.fragmentationSize = 0;
+ if (utils.eventEmitterListenerCount(this, 'error') > 0) {
+ this.emit('error', error);
+ }
+ this.socket.destroy(error);
+ this._debug.printOutput();
+
+ this._end();
+};
+
+WebSocketConnection.prototype.handleSocketEnd = function() {
+ this._debug('handleSocketEnd: received socket end. state = %s', this.state);
+ this.receivedEnd = true;
+ if (this.state === STATE_CLOSED) {
+ // When using the TLS module, sometimes the socket will emit 'end'
+ // after it emits 'close'. I don't think that's correct behavior,
+ // but we should deal with it gracefully by ignoring it.
+ this._debug(' --- Socket \'end\' after \'close\'');
+ return;
+ }
+ if (this.state !== STATE_PEER_REQUESTED_CLOSE &&
+ this.state !== STATE_ENDING) {
+ this._debug(' --- UNEXPECTED socket end.');
+ this.socket.end();
+
+ this._end();
+ }
+};
+
+WebSocketConnection.prototype.handleSocketClose = function(hadError) {
+ this._debug('handleSocketClose: received socket close');
+ this.socketHadError = hadError;
+ this.connected = false;
+ this.state = STATE_CLOSED;
+ // If closeReasonCode is still set to -1 at this point then we must
+ // not have received a close frame!!
+ if (this.closeReasonCode === -1) {
+ this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL;
+ this.closeDescription = 'Connection dropped by remote peer.';
+ }
+ this.clearCloseTimer();
+ if (!this.closeEventEmitted) {
+ this.closeEventEmitted = true;
+ this._debug('-- Emitting WebSocketConnection close event');
+ this.emit('close', this.closeReasonCode, this.closeDescription);
+ }
+};
+
+WebSocketConnection.prototype.close = function(reasonCode, description) {
+ if (this.connected) {
+ this._debug('close: Initating clean WebSocket close sequence.');
+ if ('number' !== typeof reasonCode) {
+ reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
+ }
+ if (!validateCloseReason(reasonCode)) {
+ throw new Error('Close code ' + reasonCode + ' is not valid.');
+ }
+ if ('string' !== typeof description) {
+ description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode];
+ }
+ this.closeReasonCode = reasonCode;
+ this.closeDescription = description;
+ this.setCloseTimer();
+ this.sendCloseFrame(this.closeReasonCode, this.closeDescription);
+ this.state = STATE_ENDING;
+ this.connected = false;
+ }
+};
+
+WebSocketConnection.prototype.drop = function(reasonCode, description, skipCloseFrame) {
+ this._debug('drop');
+ if (typeof(reasonCode) !== 'number') {
+ reasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
+ }
+
+ if (typeof(description) !== 'string') {
+ // If no description is provided, try to look one up based on the
+ // specified reasonCode.
+ description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode];
+ }
+
+ this._debug('Forcefully dropping connection. skipCloseFrame: %s, code: %d, description: %s',
+ skipCloseFrame, reasonCode, description
+ );
+
+ this.closeReasonCode = reasonCode;
+ this.closeDescription = description;
+ this.frameQueue = [];
+ this.fragmentationSize = 0;
+ if (!skipCloseFrame) {
+ this.sendCloseFrame(reasonCode, description);
+ }
+ this.connected = false;
+ this.state = STATE_CLOSED;
+ this.clearCloseTimer();
+
+ if (!this.closeEventEmitted) {
+ this.closeEventEmitted = true;
+ this._debug('Emitting WebSocketConnection close event');
+ this.emit('close', this.closeReasonCode, this.closeDescription);
+ }
+
+ this._debug('Drop: destroying socket');
+ this.socket.destroy();
+
+ this._end();
+};
+
+WebSocketConnection.prototype.setCloseTimer = function() {
+ this._debug('setCloseTimer');
+ this.clearCloseTimer();
+ this._debug('Setting close timer');
+ this.waitingForCloseResponse = true;
+ this.closeTimer = setTimeout(this._closeTimerHandler, this.closeTimeout);
+};
+
+WebSocketConnection.prototype.clearCloseTimer = function() {
+ this._debug('clearCloseTimer');
+ if (this.closeTimer) {
+ this._debug('Clearing close timer');
+ clearTimeout(this.closeTimer);
+ this.waitingForCloseResponse = false;
+ this.closeTimer = null;
+ }
+};
+
+WebSocketConnection.prototype.handleCloseTimer = function() {
+ this._debug('handleCloseTimer');
+ this.closeTimer = null;
+ if (this.waitingForCloseResponse) {
+ this._debug('Close response not received from client. Forcing socket end.');
+ this.waitingForCloseResponse = false;
+ this.state = STATE_CLOSED;
+ this.socket.end();
+
+ this._end();
+ }
+};
+
+WebSocketConnection.prototype.processFrame = function(frame) {
+ if (!this.connected) {
+ return;
+ }
+
+ this._debug('processFrame');
+ this._debug(' -- frame: %s', frame);
+
+ // Any non-control opcode besides 0x00 (continuation) received in the
+ // middle of a fragmented message is illegal.
+ if (this.frameQueue.length !== 0 && (frame.opcode > 0x00 && frame.opcode < 0x08)) {
+ this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
+ 'Illegal frame opcode 0x' + frame.opcode.toString(16) + ' ' +
+ 'received in middle of fragmented message.');
+ return;
+ }
+
+ switch(frame.opcode) {
+ case 0x02: // WebSocketFrame.BINARY_FRAME
+ this._debug('-- Binary Frame');
+ if (this.assembleFragments) {
+ if (frame.fin) {
+ // Complete single-frame message received
+ this._debug('---- Emitting \'message\' event');
+ this.emit('message', {
+ type: 'binary',
+ binaryData: frame.binaryPayload
+ });
+ }
+ else {
+ // beginning of a fragmented message
+ this.frameQueue.push(frame);
+ this.fragmentationSize = frame.length;
+ }
+ }
+ break;
+ case 0x01: // WebSocketFrame.TEXT_FRAME
+ this._debug('-- Text Frame');
+ if (this.assembleFragments) {
+ if (frame.fin) {
+ // Complete single-frame message received
+ this._debug('---- Emitting \'message\' event');
+ this.emit('message', {
+ type: 'utf8',
+ utf8Data: frame.binaryPayload.toString('utf8')
+ });
+ }
+ else {
+ // beginning of a fragmented message
+ this.frameQueue.push(frame);
+ this.fragmentationSize = frame.length;
+ }
+ }
+ break;
+ case 0x00: // WebSocketFrame.CONTINUATION
+ this._debug('-- Continuation Frame');
+ if (this.assembleFragments) {
+ if (this.frameQueue.length === 0) {
+ this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
+ 'Unexpected Continuation Frame');
+ return;
+ }
+
+ this.fragmentationSize += frame.length;
+
+ if (this.fragmentationSize > this.maxReceivedMessageSize) {
+ this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG,
+ 'Maximum message size exceeded.');
+ return;
+ }
+
+ this.frameQueue.push(frame);
+
+ if (frame.fin) {
+ // end of fragmented message, so we process the whole
+ // message now. We also have to decode the utf-8 data
+ // for text frames after combining all the fragments.
+ var bytesCopied = 0;
+ var binaryPayload = bufferAllocUnsafe(this.fragmentationSize);
+ var opcode = this.frameQueue[0].opcode;
+ this.frameQueue.forEach(function (currentFrame) {
+ currentFrame.binaryPayload.copy(binaryPayload, bytesCopied);
+ bytesCopied += currentFrame.binaryPayload.length;
+ });
+ this.frameQueue = [];
+ this.fragmentationSize = 0;
+
+ switch (opcode) {
+ case 0x02: // WebSocketOpcode.BINARY_FRAME
+ this.emit('message', {
+ type: 'binary',
+ binaryData: binaryPayload
+ });
+ break;
+ case 0x01: // WebSocketOpcode.TEXT_FRAME
+ this.emit('message', {
+ type: 'utf8',
+ utf8Data: binaryPayload.toString('utf8')
+ });
+ break;
+ default:
+ this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
+ 'Unexpected first opcode in fragmentation sequence: 0x' + opcode.toString(16));
+ return;
+ }
+ }
+ }
+ break;
+ case 0x09: // WebSocketFrame.PING
+ this._debug('-- Ping Frame');
+
+ if (this._pingListenerCount > 0) {
+ // logic to emit the ping frame: this is only done when a listener is known to exist
+ // Expose a function allowing the user to override the default ping() behavior
+ var cancelled = false;
+ var cancel = function() {
+ cancelled = true;
+ };
+ this.emit('ping', cancel, frame.binaryPayload);
+
+ // Only send a pong if the client did not indicate that he would like to cancel
+ if (!cancelled) {
+ this.pong(frame.binaryPayload);
+ }
+ }
+ else {
+ this.pong(frame.binaryPayload);
+ }
+
+ break;
+ case 0x0A: // WebSocketFrame.PONG
+ this._debug('-- Pong Frame');
+ this.emit('pong', frame.binaryPayload);
+ break;
+ case 0x08: // WebSocketFrame.CONNECTION_CLOSE
+ this._debug('-- Close Frame');
+ if (this.waitingForCloseResponse) {
+ // Got response to our request to close the connection.
+ // Close is complete, so we just hang up.
+ this._debug('---- Got close response from peer. Completing closing handshake.');
+ this.clearCloseTimer();
+ this.waitingForCloseResponse = false;
+ this.state = STATE_CLOSED;
+ this.socket.end();
+
+ this._end();
+ return;
+ }
+
+ this._debug('---- Closing handshake initiated by peer.');
+ // Got request from other party to close connection.
+ // Send back acknowledgement and then hang up.
+ this.state = STATE_PEER_REQUESTED_CLOSE;
+ var respondCloseReasonCode;
+
+ // Make sure the close reason provided is legal according to
+ // the protocol spec. Providing no close status is legal.
+ // WebSocketFrame sets closeStatus to -1 by default, so if it
+ // is still -1, then no status was provided.
+ if (frame.invalidCloseFrameLength) {
+ this.closeReasonCode = 1005; // 1005 = No reason provided.
+ respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
+ }
+ else if (frame.closeStatus === -1 || validateCloseReason(frame.closeStatus)) {
+ this.closeReasonCode = frame.closeStatus;
+ respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
+ }
+ else {
+ this.closeReasonCode = frame.closeStatus;
+ respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
+ }
+
+ // If there is a textual description in the close frame, extract it.
+ if (frame.binaryPayload.length > 1) {
+ this.closeDescription = frame.binaryPayload.toString('utf8');
+ }
+ else {
+ this.closeDescription = WebSocketConnection.CLOSE_DESCRIPTIONS[this.closeReasonCode];
+ }
+ this._debug(
+ '------ Remote peer %s - code: %d - %s - close frame payload length: %d',
+ this.remoteAddress, this.closeReasonCode,
+ this.closeDescription, frame.length
+ );
+ this._debug('------ responding to remote peer\'s close request.');
+ this.drop(respondCloseReasonCode, null);
+ this.connected = false;
+ break;
+ default:
+ this._debug('-- Unrecognized Opcode %d', frame.opcode);
+ this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
+ 'Unrecognized Opcode: 0x' + frame.opcode.toString(16));
+ break;
+ }
+};
+
+WebSocketConnection.prototype.send = function(data, cb) {
+ this._debug('send');
+ if (Buffer.isBuffer(data)) {
+ this.sendBytes(data, cb);
+ }
+ else if (typeof(data['toString']) === 'function') {
+ this.sendUTF(data, cb);
+ }
+ else {
+ throw new Error('Data provided must either be a Node Buffer or implement toString()');
+ }
+};
+
+WebSocketConnection.prototype.sendUTF = function(data, cb) {
+ data = bufferFromString(data.toString(), 'utf8');
+ this._debug('sendUTF: %d bytes', data.length);
+
+ var frame = new WebSocketFrame();
+ frame.opcode = 0x01; // WebSocketOpcode.TEXT_FRAME
+ frame.binaryPayload = data;
+
+ this.fragmentAndSend(frame, cb);
+};
+
+WebSocketConnection.prototype.sendBytes = function(data, cb) {
+ this._debug('sendBytes');
+ if (!Buffer.isBuffer(data)) {
+ throw new Error('You must pass a Node Buffer object to WebSocketConnection.prototype.sendBytes()');
+ }
+
+ var frame = new WebSocketFrame();
+ frame.opcode = 0x02; // WebSocketOpcode.BINARY_FRAME
+ frame.binaryPayload = data;
+
+ this.fragmentAndSend(frame, cb);
+};
+
+WebSocketConnection.prototype.ping = function(data) {
+ this._debug('ping');
+
+ var frame = new WebSocketFrame();
+ frame.opcode = 0x09; // WebSocketOpcode.PING
+ frame.fin = true;
+
+ if (data) {
+ if (!Buffer.isBuffer(data)) {
+ data = bufferFromString(data.toString(), 'utf8');
+ }
+ if (data.length > 125) {
+ this._debug('WebSocket: Data for ping is longer than 125 bytes. Truncating.');
+ data = data.slice(0,124);
+ }
+ frame.binaryPayload = data;
+ }
+
+ this.sendFrame(frame);
+};
+
+// Pong frames have to echo back the contents of the data portion of the
+// ping frame exactly, byte for byte.
+WebSocketConnection.prototype.pong = function(binaryPayload) {
+ this._debug('pong');
+
+ var frame = new WebSocketFrame();
+ frame.opcode = 0x0A; // WebSocketOpcode.PONG
+ if (Buffer.isBuffer(binaryPayload) && binaryPayload.length > 125) {
+ this._debug('WebSocket: Data for pong is longer than 125 bytes. Truncating.');
+ binaryPayload = binaryPayload.slice(0,124);
+ }
+ frame.binaryPayload = binaryPayload;
+ frame.fin = true;
+
+ this.sendFrame(frame);
+};
+
+WebSocketConnection.prototype.fragmentAndSend = function(frame, cb) {
+ this._debug('fragmentAndSend');
+ if (frame.opcode > 0x07) {
+ throw new Error('You cannot fragment control frames.');
+ }
+
+ var threshold = this.config.fragmentationThreshold;
+ var length = frame.binaryPayload.length;
+
+ // Send immediately if fragmentation is disabled or the message is not
+ // larger than the fragmentation threshold.
+ if (!this.config.fragmentOutgoingMessages || (frame.binaryPayload && length <= threshold)) {
+ frame.fin = true;
+ this.sendFrame(frame, cb);
+ return;
+ }
+
+ var numFragments = Math.ceil(length / threshold);
+ var sentFragments = 0;
+ var sentCallback = function fragmentSentCallback(err) {
+ if (err) {
+ if (typeof cb === 'function') {
+ // pass only the first error
+ cb(err);
+ cb = null;
+ }
+ return;
+ }
+ ++sentFragments;
+ if ((sentFragments === numFragments) && (typeof cb === 'function')) {
+ cb();
+ }
+ };
+ for (var i=1; i <= numFragments; i++) {
+ var currentFrame = new WebSocketFrame();
+
+ // continuation opcode except for first frame.
+ currentFrame.opcode = (i === 1) ? frame.opcode : 0x00;
+
+ // fin set on last frame only
+ currentFrame.fin = (i === numFragments);
+
+ // length is likely to be shorter on the last fragment
+ var currentLength = (i === numFragments) ? length - (threshold * (i-1)) : threshold;
+ var sliceStart = threshold * (i-1);
+
+ // Slice the right portion of the original payload
+ currentFrame.binaryPayload = frame.binaryPayload.slice(sliceStart, sliceStart + currentLength);
+
+ this.sendFrame(currentFrame, sentCallback);
+ }
+};
+
+WebSocketConnection.prototype.sendCloseFrame = function(reasonCode, description, cb) {
+ if (typeof(reasonCode) !== 'number') {
+ reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
+ }
+
+ this._debug('sendCloseFrame state: %s, reasonCode: %d, description: %s', this.state, reasonCode, description);
+
+ if (this.state !== STATE_OPEN && this.state !== STATE_PEER_REQUESTED_CLOSE) { return; }
+
+ var frame = new WebSocketFrame();
+ frame.fin = true;
+ frame.opcode = 0x08; // WebSocketOpcode.CONNECTION_CLOSE
+ frame.closeStatus = reasonCode;
+ if (typeof(description) === 'string') {
+ frame.binaryPayload = bufferFromString(description, 'utf8');
+ }
+
+ this.sendFrame(frame, cb);
+ this.socket.end();
+};
+
+WebSocketConnection.prototype._send_frame = unit_lib.websocket_send_frame;
+
+WebSocketConnection.prototype.sendFrame = function(frame, cb) {
+ this._debug('sendFrame');
+
+ frame.mask = this.maskOutgoingPackets;
+
+ this._send_frame(frame);
+
+ if (typeof cb === 'function') {
+ cb();
+ }
+
+ var flushed = 0; // this.socket.write(frame.toBuffer(), cb);
+ this.outputBufferFull = !flushed;
+ return flushed;
+};
+
+module.exports = WebSocketConnection;
diff --git a/src/nodejs/unit-http/websocket_frame.js b/src/nodejs/unit-http/websocket_frame.js
new file mode 100644
index 00000000..9989937d
--- /dev/null
+++ b/src/nodejs/unit-http/websocket_frame.js
@@ -0,0 +1,11 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+'use strict';
+
+function WebSocketFrame() {
+}
+
+module.exports = WebSocketFrame;
diff --git a/src/nodejs/unit-http/websocket_request.js b/src/nodejs/unit-http/websocket_request.js
new file mode 100644
index 00000000..d84e428b
--- /dev/null
+++ b/src/nodejs/unit-http/websocket_request.js
@@ -0,0 +1,509 @@
+/************************************************************************
+ * Copyright 2010-2015 Brian McKelvey.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ***********************************************************************/
+
+var util = require('util');
+var url = require('url');
+var EventEmitter = require('events').EventEmitter;
+var WebSocketConnection = require('./websocket_connection');
+
+var headerValueSplitRegExp = /,\s*/;
+var headerParamSplitRegExp = /;\s*/;
+var headerSanitizeRegExp = /[\r\n]/g;
+var xForwardedForSeparatorRegExp = /,\s*/;
+var separators = [
+ '(', ')', '<', '>', '@',
+ ',', ';', ':', '\\', '\"',
+ '/', '[', ']', '?', '=',
+ '{', '}', ' ', String.fromCharCode(9)
+];
+var controlChars = [String.fromCharCode(127) /* DEL */];
+for (var i=0; i < 31; i ++) {
+ /* US-ASCII Control Characters */
+ controlChars.push(String.fromCharCode(i));
+}
+
+var cookieNameValidateRegEx = /([\x00-\x20\x22\x28\x29\x2c\x2f\x3a-\x3f\x40\x5b-\x5e\x7b\x7d\x7f])/;
+var cookieValueValidateRegEx = /[^\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]/;
+var cookieValueDQuoteValidateRegEx = /^"[^"]*"$/;
+var controlCharsAndSemicolonRegEx = /[\x00-\x20\x3b]/g;
+
+var cookieSeparatorRegEx = /[;,] */;
+
+var httpStatusDescriptions = {
+ 100: 'Continue',
+ 101: 'Switching Protocols',
+ 200: 'OK',
+ 201: 'Created',
+ 203: 'Non-Authoritative Information',
+ 204: 'No Content',
+ 205: 'Reset Content',
+ 206: 'Partial Content',
+ 300: 'Multiple Choices',
+ 301: 'Moved Permanently',
+ 302: 'Found',
+ 303: 'See Other',
+ 304: 'Not Modified',
+ 305: 'Use Proxy',
+ 307: 'Temporary Redirect',
+ 400: 'Bad Request',
+ 401: 'Unauthorized',
+ 402: 'Payment Required',
+ 403: 'Forbidden',
+ 404: 'Not Found',
+ 406: 'Not Acceptable',
+ 407: 'Proxy Authorization Required',
+ 408: 'Request Timeout',
+ 409: 'Conflict',
+ 410: 'Gone',
+ 411: 'Length Required',
+ 412: 'Precondition Failed',
+ 413: 'Request Entity Too Long',
+ 414: 'Request-URI Too Long',
+ 415: 'Unsupported Media Type',
+ 416: 'Requested Range Not Satisfiable',
+ 417: 'Expectation Failed',
+ 426: 'Upgrade Required',
+ 500: 'Internal Server Error',
+ 501: 'Not Implemented',
+ 502: 'Bad Gateway',
+ 503: 'Service Unavailable',
+ 504: 'Gateway Timeout',
+ 505: 'HTTP Version Not Supported'
+};
+
+function WebSocketRequest(socket, httpRequest, serverConfig) {
+ // Superclass Constructor
+ EventEmitter.call(this);
+
+ this.socket = socket;
+ this.httpRequest = httpRequest;
+ this.resource = httpRequest.url;
+ this.remoteAddress = socket.remoteAddress;
+ this.remoteAddresses = [this.remoteAddress];
+ this.serverConfig = serverConfig;
+
+ // Watch for the underlying TCP socket closing before we call accept
+ this._socketIsClosing = false;
+ this._socketCloseHandler = this._handleSocketCloseBeforeAccept.bind(this);
+ this.socket.on('end', this._socketCloseHandler);
+ this.socket.on('close', this._socketCloseHandler);
+
+ this._resolved = false;
+}
+
+util.inherits(WebSocketRequest, EventEmitter);
+
+WebSocketRequest.prototype.readHandshake = function() {
+ var self = this;
+ var request = this.httpRequest;
+
+ // Decode URL
+ this.resourceURL = url.parse(this.resource, true);
+
+ this.host = request.headers['host'];
+ if (!this.host) {
+ throw new Error('Client must provide a Host header.');
+ }
+
+ this.key = request.headers['sec-websocket-key'];
+ if (!this.key) {
+ throw new Error('Client must provide a value for Sec-WebSocket-Key.');
+ }
+
+ this.webSocketVersion = parseInt(request.headers['sec-websocket-version'], 10);
+
+ if (!this.webSocketVersion || isNaN(this.webSocketVersion)) {
+ throw new Error('Client must provide a value for Sec-WebSocket-Version.');
+ }
+
+ switch (this.webSocketVersion) {
+ case 8:
+ case 13:
+ break;
+ default:
+ var e = new Error('Unsupported websocket client version: ' + this.webSocketVersion +
+ 'Only versions 8 and 13 are supported.');
+ e.httpCode = 426;
+ e.headers = {
+ 'Sec-WebSocket-Version': '13'
+ };
+ throw e;
+ }
+
+ if (this.webSocketVersion === 13) {
+ this.origin = request.headers['origin'];
+ }
+ else if (this.webSocketVersion === 8) {
+ this.origin = request.headers['sec-websocket-origin'];
+ }
+
+ // Protocol is optional.
+ var protocolString = request.headers['sec-websocket-protocol'];
+ this.protocolFullCaseMap = {};
+ this.requestedProtocols = [];
+ if (protocolString) {
+ var requestedProtocolsFullCase = protocolString.split(headerValueSplitRegExp);
+ requestedProtocolsFullCase.forEach(function(protocol) {
+ var lcProtocol = protocol.toLocaleLowerCase();
+ self.requestedProtocols.push(lcProtocol);
+ self.protocolFullCaseMap[lcProtocol] = protocol;
+ });
+ }
+
+ if (!this.serverConfig.ignoreXForwardedFor &&
+ request.headers['x-forwarded-for']) {
+ var immediatePeerIP = this.remoteAddress;
+ this.remoteAddresses = request.headers['x-forwarded-for']
+ .split(xForwardedForSeparatorRegExp);
+ this.remoteAddresses.push(immediatePeerIP);
+ this.remoteAddress = this.remoteAddresses[0];
+ }
+
+ // Extensions are optional.
+ var extensionsString = request.headers['sec-websocket-extensions'];
+ this.requestedExtensions = this.parseExtensions(extensionsString);
+
+ // Cookies are optional
+ var cookieString = request.headers['cookie'];
+ this.cookies = this.parseCookies(cookieString);
+};
+
+WebSocketRequest.prototype.parseExtensions = function(extensionsString) {
+ if (!extensionsString || extensionsString.length === 0) {
+ return [];
+ }
+ var extensions = extensionsString.toLocaleLowerCase().split(headerValueSplitRegExp);
+ extensions.forEach(function(extension, index, array) {
+ var params = extension.split(headerParamSplitRegExp);
+ var extensionName = params[0];
+ var extensionParams = params.slice(1);
+ extensionParams.forEach(function(rawParam, index, array) {
+ var arr = rawParam.split('=');
+ var obj = {
+ name: arr[0],
+ value: arr[1]
+ };
+ array.splice(index, 1, obj);
+ });
+ var obj = {
+ name: extensionName,
+ params: extensionParams
+ };
+ array.splice(index, 1, obj);
+ });
+ return extensions;
+};
+
+// This function adapted from node-cookie
+// https://github.com/shtylman/node-cookie
+WebSocketRequest.prototype.parseCookies = function(str) {
+ // Sanity Check
+ if (!str || typeof(str) !== 'string') {
+ return [];
+ }
+
+ var cookies = [];
+ var pairs = str.split(cookieSeparatorRegEx);
+
+ pairs.forEach(function(pair) {
+ var eq_idx = pair.indexOf('=');
+ if (eq_idx === -1) {
+ cookies.push({
+ name: pair,
+ value: null
+ });
+ return;
+ }
+
+ var key = pair.substr(0, eq_idx).trim();
+ var val = pair.substr(++eq_idx, pair.length).trim();
+
+ // quoted values
+ if ('"' === val[0]) {
+ val = val.slice(1, -1);
+ }
+
+ cookies.push({
+ name: key,
+ value: decodeURIComponent(val)
+ });
+ });
+
+ return cookies;
+};
+
+WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, cookies) {
+ this._verifyResolution();
+
+ // TODO: Handle extensions
+
+ var protocolFullCase;
+
+ if (acceptedProtocol) {
+ protocolFullCase = this.protocolFullCaseMap[acceptedProtocol.toLocaleLowerCase()];
+ if (typeof(protocolFullCase) === 'undefined') {
+ protocolFullCase = acceptedProtocol;
+ }
+ }
+ else {
+ protocolFullCase = acceptedProtocol;
+ }
+ this.protocolFullCaseMap = null;
+
+ var response = this.httpRequest._response;
+ response.statusCode = 101;
+
+ if (protocolFullCase) {
+ // validate protocol
+ for (var i=0; i < protocolFullCase.length; i++) {
+ var charCode = protocolFullCase.charCodeAt(i);
+ var character = protocolFullCase.charAt(i);
+ if (charCode < 0x21 || charCode > 0x7E || separators.indexOf(character) !== -1) {
+ this.reject(500);
+ throw new Error('Illegal character "' + String.fromCharCode(character) + '" in subprotocol.');
+ }
+ }
+ if (this.requestedProtocols.indexOf(acceptedProtocol) === -1) {
+ this.reject(500);
+ throw new Error('Specified protocol was not requested by the client.');
+ }
+
+ protocolFullCase = protocolFullCase.replace(headerSanitizeRegExp, '');
+ response += 'Sec-WebSocket-Protocol: ' + protocolFullCase + '\r\n';
+ }
+ this.requestedProtocols = null;
+
+ if (allowedOrigin) {
+ allowedOrigin = allowedOrigin.replace(headerSanitizeRegExp, '');
+ if (this.webSocketVersion === 13) {
+ response.setHeader('Origin', allowedOrigin);
+ }
+ else if (this.webSocketVersion === 8) {
+ response.setHeader('Sec-WebSocket-Origin', allowedOrigin);
+ }
+ }
+
+ if (cookies) {
+ if (!Array.isArray(cookies)) {
+ this.reject(500);
+ throw new Error('Value supplied for "cookies" argument must be an array.');
+ }
+ var seenCookies = {};
+ cookies.forEach(function(cookie) {
+ if (!cookie.name || !cookie.value) {
+ this.reject(500);
+ throw new Error('Each cookie to set must at least provide a "name" and "value"');
+ }
+
+ // Make sure there are no \r\n sequences inserted
+ cookie.name = cookie.name.replace(controlCharsAndSemicolonRegEx, '');
+ cookie.value = cookie.value.replace(controlCharsAndSemicolonRegEx, '');
+
+ if (seenCookies[cookie.name]) {
+ this.reject(500);
+ throw new Error('You may not specify the same cookie name twice.');
+ }
+ seenCookies[cookie.name] = true;
+
+ // token (RFC 2616, Section 2.2)
+ var invalidChar = cookie.name.match(cookieNameValidateRegEx);
+ if (invalidChar) {
+ this.reject(500);
+ throw new Error('Illegal character ' + invalidChar[0] + ' in cookie name');
+ }
+
+ // RFC 6265, Section 4.1.1
+ // *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) | %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
+ if (cookie.value.match(cookieValueDQuoteValidateRegEx)) {
+ invalidChar = cookie.value.slice(1, -1).match(cookieValueValidateRegEx);
+ } else {
+ invalidChar = cookie.value.match(cookieValueValidateRegEx);
+ }
+ if (invalidChar) {
+ this.reject(500);
+ throw new Error('Illegal character ' + invalidChar[0] + ' in cookie value');
+ }
+
+ var cookieParts = [cookie.name + '=' + cookie.value];
+
+ // RFC 6265, Section 4.1.1
+ // 'Path=' path-value | <any CHAR except CTLs or ';'>
+ if(cookie.path){
+ invalidChar = cookie.path.match(controlCharsAndSemicolonRegEx);
+ if (invalidChar) {
+ this.reject(500);
+ throw new Error('Illegal character ' + invalidChar[0] + ' in cookie path');
+ }
+ cookieParts.push('Path=' + cookie.path);
+ }
+
+ // RFC 6265, Section 4.1.2.3
+ // 'Domain=' subdomain
+ if (cookie.domain) {
+ if (typeof(cookie.domain) !== 'string') {
+ this.reject(500);
+ throw new Error('Domain must be specified and must be a string.');
+ }
+ invalidChar = cookie.domain.match(controlCharsAndSemicolonRegEx);
+ if (invalidChar) {
+ this.reject(500);
+ throw new Error('Illegal character ' + invalidChar[0] + ' in cookie domain');
+ }
+ cookieParts.push('Domain=' + cookie.domain.toLowerCase());
+ }
+
+ // RFC 6265, Section 4.1.1
+ //'Expires=' sane-cookie-date | Force Date object requirement by using only epoch
+ if (cookie.expires) {
+ if (!(cookie.expires instanceof Date)){
+ this.reject(500);
+ throw new Error('Value supplied for cookie "expires" must be a vaild date object');
+ }
+ cookieParts.push('Expires=' + cookie.expires.toGMTString());
+ }
+
+ // RFC 6265, Section 4.1.1
+ //'Max-Age=' non-zero-digit *DIGIT
+ if (cookie.maxage) {
+ var maxage = cookie.maxage;
+ if (typeof(maxage) === 'string') {
+ maxage = parseInt(maxage, 10);
+ }
+ if (isNaN(maxage) || maxage <= 0 ) {
+ this.reject(500);
+ throw new Error('Value supplied for cookie "maxage" must be a non-zero number');
+ }
+ maxage = Math.round(maxage);
+ cookieParts.push('Max-Age=' + maxage.toString(10));
+ }
+
+ // RFC 6265, Section 4.1.1
+ //'Secure;'
+ if (cookie.secure) {
+ if (typeof(cookie.secure) !== 'boolean') {
+ this.reject(500);
+ throw new Error('Value supplied for cookie "secure" must be of type boolean');
+ }
+ cookieParts.push('Secure');
+ }
+
+ // RFC 6265, Section 4.1.1
+ //'HttpOnly;'
+ if (cookie.httponly) {
+ if (typeof(cookie.httponly) !== 'boolean') {
+ this.reject(500);
+ throw new Error('Value supplied for cookie "httponly" must be of type boolean');
+ }
+ cookieParts.push('HttpOnly');
+ }
+
+ response.addHeader('Set-Cookie', cookieParts.join(';'));
+ }.bind(this));
+ }
+
+ // TODO: handle negotiated extensions
+ // if (negotiatedExtensions) {
+ // response += 'Sec-WebSocket-Extensions: ' + negotiatedExtensions.join(', ') + '\r\n';
+ // }
+
+ // Mark the request resolved now so that the user can't call accept or
+ // reject a second time.
+ this._resolved = true;
+ this.emit('requestResolved', this);
+
+ var connection = new WebSocketConnection(this.socket, [], acceptedProtocol, false, this.serverConfig);
+ connection.webSocketVersion = this.webSocketVersion;
+ connection.remoteAddress = this.remoteAddress;
+ connection.remoteAddresses = this.remoteAddresses;
+
+ var self = this;
+
+ if (this._socketIsClosing) {
+ // Handle case when the client hangs up before we get a chance to
+ // accept the connection and send our side of the opening handshake.
+ cleanupFailedConnection(connection);
+
+ } else {
+ response._sendHeaders();
+ connection._addSocketEventListeners();
+ }
+
+ this.emit('requestAccepted', connection);
+ return connection;
+};
+
+WebSocketRequest.prototype.reject = function(status, reason, extraHeaders) {
+ this._verifyResolution();
+
+ // Mark the request resolved now so that the user can't call accept or
+ // reject a second time.
+ this._resolved = true;
+ this.emit('requestResolved', this);
+
+ if (typeof(status) !== 'number') {
+ status = 403;
+ }
+
+ var response = this.httpRequest._response;
+
+ response.statusCode = status;
+
+ if (reason) {
+ reason = reason.replace(headerSanitizeRegExp, '');
+ response.addHeader('X-WebSocket-Reject-Reason', reason);
+ }
+
+ if (extraHeaders) {
+ for (var key in extraHeaders) {
+ var sanitizedValue = extraHeaders[key].toString().replace(headerSanitizeRegExp, '');
+ var sanitizedKey = key.replace(headerSanitizeRegExp, '');
+ response += (sanitizedKey + ': ' + sanitizedValue + '\r\n');
+ }
+ }
+
+ response.end();
+
+ this.emit('requestRejected', this);
+};
+
+WebSocketRequest.prototype._handleSocketCloseBeforeAccept = function() {
+ this._socketIsClosing = true;
+ this._removeSocketCloseListeners();
+};
+
+WebSocketRequest.prototype._removeSocketCloseListeners = function() {
+ this.socket.removeListener('end', this._socketCloseHandler);
+ this.socket.removeListener('close', this._socketCloseHandler);
+};
+
+WebSocketRequest.prototype._verifyResolution = function() {
+ if (this._resolved) {
+ throw new Error('WebSocketRequest may only be accepted or rejected one time.');
+ }
+};
+
+function cleanupFailedConnection(connection) {
+ // Since we have to return a connection object even if the socket is
+ // already dead in order not to break the API, we schedule a 'close'
+ // event on the connection object to occur immediately.
+ process.nextTick(function() {
+ // WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006
+ // Third param: Skip sending the close frame to a dead socket
+ connection.drop(1006, 'TCP connection lost before handshake completed.', true);
+ });
+}
+
+module.exports = WebSocketRequest;
diff --git a/src/nodejs/unit-http/websocket_router.js b/src/nodejs/unit-http/websocket_router.js
new file mode 100644
index 00000000..4efa35d2
--- /dev/null
+++ b/src/nodejs/unit-http/websocket_router.js
@@ -0,0 +1,157 @@
+/************************************************************************
+ * Copyright 2010-2015 Brian McKelvey.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ***********************************************************************/
+
+var extend = require('./utils').extend;
+var util = require('util');
+var EventEmitter = require('events').EventEmitter;
+var WebSocketRouterRequest = require('./websocket_router_request');
+
+function WebSocketRouter(config) {
+ // Superclass Constructor
+ EventEmitter.call(this);
+
+ this.config = {
+ // The WebSocketServer instance to attach to.
+ server: null
+ };
+ if (config) {
+ extend(this.config, config);
+ }
+ this.handlers = [];
+
+ this._requestHandler = this.handleRequest.bind(this);
+ if (this.config.server) {
+ this.attachServer(this.config.server);
+ }
+}
+
+util.inherits(WebSocketRouter, EventEmitter);
+
+WebSocketRouter.prototype.attachServer = function(server) {
+ if (server) {
+ this.server = server;
+ this.server.on('request', this._requestHandler);
+ }
+ else {
+ throw new Error('You must specify a WebSocketServer instance to attach to.');
+ }
+};
+
+WebSocketRouter.prototype.detachServer = function() {
+ if (this.server) {
+ this.server.removeListener('request', this._requestHandler);
+ this.server = null;
+ }
+ else {
+ throw new Error('Cannot detach from server: not attached.');
+ }
+};
+
+WebSocketRouter.prototype.mount = function(path, protocol, callback) {
+ if (!path) {
+ throw new Error('You must specify a path for this handler.');
+ }
+ if (!protocol) {
+ protocol = '____no_protocol____';
+ }
+ if (!callback) {
+ throw new Error('You must specify a callback for this handler.');
+ }
+
+ path = this.pathToRegExp(path);
+ if (!(path instanceof RegExp)) {
+ throw new Error('Path must be specified as either a string or a RegExp.');
+ }
+ var pathString = path.toString();
+
+ // normalize protocol to lower-case
+ protocol = protocol.toLocaleLowerCase();
+
+ if (this.findHandlerIndex(pathString, protocol) !== -1) {
+ throw new Error('You may only mount one handler per path/protocol combination.');
+ }
+
+ this.handlers.push({
+ 'path': path,
+ 'pathString': pathString,
+ 'protocol': protocol,
+ 'callback': callback
+ });
+};
+WebSocketRouter.prototype.unmount = function(path, protocol) {
+ var index = this.findHandlerIndex(this.pathToRegExp(path).toString(), protocol);
+ if (index !== -1) {
+ this.handlers.splice(index, 1);
+ }
+ else {
+ throw new Error('Unable to find a route matching the specified path and protocol.');
+ }
+};
+
+WebSocketRouter.prototype.findHandlerIndex = function(pathString, protocol) {
+ protocol = protocol.toLocaleLowerCase();
+ for (var i=0, len=this.handlers.length; i < len; i++) {
+ var handler = this.handlers[i];
+ if (handler.pathString === pathString && handler.protocol === protocol) {
+ return i;
+ }
+ }
+ return -1;
+};
+
+WebSocketRouter.prototype.pathToRegExp = function(path) {
+ if (typeof(path) === 'string') {
+ if (path === '*') {
+ path = /^.*$/;
+ }
+ else {
+ path = path.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
+ path = new RegExp('^' + path + '$');
+ }
+ }
+ return path;
+};
+
+WebSocketRouter.prototype.handleRequest = function(request) {
+ var requestedProtocols = request.requestedProtocols;
+ if (requestedProtocols.length === 0) {
+ requestedProtocols = ['____no_protocol____'];
+ }
+
+ // Find a handler with the first requested protocol first
+ for (var i=0; i < requestedProtocols.length; i++) {
+ var requestedProtocol = requestedProtocols[i].toLocaleLowerCase();
+
+ // find the first handler that can process this request
+ for (var j=0, len=this.handlers.length; j < len; j++) {
+ var handler = this.handlers[j];
+ if (handler.path.test(request.resourceURL.pathname)) {
+ if (requestedProtocol === handler.protocol ||
+ handler.protocol === '*')
+ {
+ var routerRequest = new WebSocketRouterRequest(request, requestedProtocol);
+ handler.callback(routerRequest);
+ return;
+ }
+ }
+ }
+ }
+
+ // If we get here we were unable to find a suitable handler.
+ request.reject(404, 'No handler is available for the given request.');
+};
+
+module.exports = WebSocketRouter;
diff --git a/src/nodejs/unit-http/websocket_router_request.js b/src/nodejs/unit-http/websocket_router_request.js
new file mode 100644
index 00000000..d3e37457
--- /dev/null
+++ b/src/nodejs/unit-http/websocket_router_request.js
@@ -0,0 +1,54 @@
+/************************************************************************
+ * Copyright 2010-2015 Brian McKelvey.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ***********************************************************************/
+
+var util = require('util');
+var EventEmitter = require('events').EventEmitter;
+
+function WebSocketRouterRequest(webSocketRequest, resolvedProtocol) {
+ // Superclass Constructor
+ EventEmitter.call(this);
+
+ this.webSocketRequest = webSocketRequest;
+ if (resolvedProtocol === '____no_protocol____') {
+ this.protocol = null;
+ }
+ else {
+ this.protocol = resolvedProtocol;
+ }
+ this.origin = webSocketRequest.origin;
+ this.resource = webSocketRequest.resource;
+ this.resourceURL = webSocketRequest.resourceURL;
+ this.httpRequest = webSocketRequest.httpRequest;
+ this.remoteAddress = webSocketRequest.remoteAddress;
+ this.webSocketVersion = webSocketRequest.webSocketVersion;
+ this.requestedExtensions = webSocketRequest.requestedExtensions;
+ this.cookies = webSocketRequest.cookies;
+}
+
+util.inherits(WebSocketRouterRequest, EventEmitter);
+
+WebSocketRouterRequest.prototype.accept = function(origin, cookies) {
+ var connection = this.webSocketRequest.accept(this.protocol, origin, cookies);
+ this.emit('requestAccepted', connection);
+ return connection;
+};
+
+WebSocketRouterRequest.prototype.reject = function(status, reason, extraHeaders) {
+ this.webSocketRequest.reject(status, reason, extraHeaders);
+ this.emit('requestRejected', this);
+};
+
+module.exports = WebSocketRouterRequest;
diff --git a/src/nodejs/unit-http/websocket_server.js b/src/nodejs/unit-http/websocket_server.js
new file mode 100644
index 00000000..306f3f67
--- /dev/null
+++ b/src/nodejs/unit-http/websocket_server.js
@@ -0,0 +1,213 @@
+/************************************************************************
+ * Copyright 2010-2015 Brian McKelvey.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ***********************************************************************/
+
+var extend = require('./utils').extend;
+var utils = require('./utils');
+var util = require('util');
+var EventEmitter = require('events').EventEmitter;
+var WebSocketRequest = require('./websocket_request');
+
+var WebSocketServer = function WebSocketServer(config) {
+ // Superclass Constructor
+ EventEmitter.call(this);
+
+ this._handlers = {
+ upgrade: this.handleUpgrade.bind(this),
+ requestAccepted: this.handleRequestAccepted.bind(this),
+ requestResolved: this.handleRequestResolved.bind(this)
+ };
+ this.connections = [];
+ this.pendingRequests = [];
+ if (config) {
+ this.mount(config);
+ }
+};
+
+util.inherits(WebSocketServer, EventEmitter);
+
+WebSocketServer.prototype.mount = function(config) {
+ this.config = {
+ // The http server instance to attach to. Required.
+ httpServer: null,
+
+ // 64KiB max frame size.
+ maxReceivedFrameSize: 0x10000,
+
+ // 1MiB max message size, only applicable if
+ // assembleFragments is true
+ maxReceivedMessageSize: 0x100000,
+
+ // Outgoing messages larger than fragmentationThreshold will be
+ // split into multiple fragments.
+ fragmentOutgoingMessages: true,
+
+ // Outgoing frames are fragmented if they exceed this threshold.
+ // Default is 16KiB
+ fragmentationThreshold: 0x4000,
+
+ // If true, fragmented messages will be automatically assembled
+ // and the full message will be emitted via a 'message' event.
+ // If false, each frame will be emitted via a 'frame' event and
+ // the application will be responsible for aggregating multiple
+ // fragmented frames. Single-frame messages will emit a 'message'
+ // event in addition to the 'frame' event.
+ // Most users will want to leave this set to 'true'
+ assembleFragments: true,
+
+ // If this is true, websocket connections will be accepted
+ // regardless of the path and protocol specified by the client.
+ // The protocol accepted will be the first that was requested
+ // by the client. Clients from any origin will be accepted.
+ // This should only be used in the simplest of cases. You should
+ // probably leave this set to 'false' and inspect the request
+ // object to make sure it's acceptable before accepting it.
+ autoAcceptConnections: false,
+
+ // Whether or not the X-Forwarded-For header should be respected.
+ // It's important to set this to 'true' when accepting connections
+ // from untrusted clients, as a malicious client could spoof its
+ // IP address by simply setting this header. It's meant to be added
+ // by a trusted proxy or other intermediary within your own
+ // infrastructure.
+ // See: http://en.wikipedia.org/wiki/X-Forwarded-For
+ ignoreXForwardedFor: false,
+
+ // The Nagle Algorithm makes more efficient use of network resources
+ // by introducing a small delay before sending small packets so that
+ // multiple messages can be batched together before going onto the
+ // wire. This however comes at the cost of latency, so the default
+ // is to disable it. If you don't need low latency and are streaming
+ // lots of small messages, you can change this to 'false'
+ disableNagleAlgorithm: true,
+
+ // The number of milliseconds to wait after sending a close frame
+ // for an acknowledgement to come back before giving up and just
+ // closing the socket.
+ closeTimeout: 5000
+ };
+ extend(this.config, config);
+
+ if (this.config.httpServer) {
+ if (!Array.isArray(this.config.httpServer)) {
+ this.config.httpServer = [this.config.httpServer];
+ }
+ var upgradeHandler = this._handlers.upgrade;
+ this.config.httpServer.forEach(function(httpServer) {
+ httpServer.on('upgrade', upgradeHandler);
+ });
+ }
+ else {
+ throw new Error('You must specify an httpServer on which to mount the WebSocket server.');
+ }
+};
+
+WebSocketServer.prototype.unmount = function() {
+ var upgradeHandler = this._handlers.upgrade;
+ this.config.httpServer.forEach(function(httpServer) {
+ httpServer.removeListener('upgrade', upgradeHandler);
+ });
+};
+
+WebSocketServer.prototype.closeAllConnections = function() {
+ this.connections.forEach(function(connection) {
+ connection.close();
+ });
+ this.pendingRequests.forEach(function(request) {
+ process.nextTick(function() {
+ request.reject(503); // HTTP 503 Service Unavailable
+ });
+ });
+};
+
+WebSocketServer.prototype.broadcast = function(data) {
+ if (Buffer.isBuffer(data)) {
+ this.broadcastBytes(data);
+ }
+ else if (typeof(data.toString) === 'function') {
+ this.broadcastUTF(data);
+ }
+};
+
+WebSocketServer.prototype.broadcastUTF = function(utfData) {
+ this.connections.forEach(function(connection) {
+ connection.sendUTF(utfData);
+ });
+};
+
+WebSocketServer.prototype.broadcastBytes = function(binaryData) {
+ this.connections.forEach(function(connection) {
+ connection.sendBytes(binaryData);
+ });
+};
+
+WebSocketServer.prototype.shutDown = function() {
+ this.unmount();
+ this.closeAllConnections();
+};
+
+WebSocketServer.prototype.handleUpgrade = function(request, socket) {
+ var wsRequest = new WebSocketRequest(socket, request, this.config);
+ try {
+ wsRequest.readHandshake();
+ }
+ catch(e) {
+ wsRequest.reject(
+ e.httpCode ? e.httpCode : 400,
+ e.message,
+ e.headers
+ );
+ return;
+ }
+
+ this.pendingRequests.push(wsRequest);
+
+ wsRequest.once('requestAccepted', this._handlers.requestAccepted);
+ wsRequest.once('requestResolved', this._handlers.requestResolved);
+
+ if (!this.config.autoAcceptConnections && utils.eventEmitterListenerCount(this, 'request') > 0) {
+ this.emit('request', wsRequest);
+ }
+ else if (this.config.autoAcceptConnections) {
+ wsRequest.accept(wsRequest.requestedProtocols[0], wsRequest.origin);
+ }
+ else {
+ wsRequest.reject(404, 'No handler is configured to accept the connection.');
+ }
+};
+
+WebSocketServer.prototype.handleRequestAccepted = function(connection) {
+ var self = this;
+ connection.once('close', function(closeReason, description) {
+ self.handleConnectionClose(connection, closeReason, description);
+ });
+ this.connections.push(connection);
+ this.emit('connect', connection);
+};
+
+WebSocketServer.prototype.handleConnectionClose = function(connection, closeReason, description) {
+ var index = this.connections.indexOf(connection);
+ if (index !== -1) {
+ this.connections.splice(index, 1);
+ }
+ this.emit('close', connection, closeReason, description);
+};
+
+WebSocketServer.prototype.handleRequestResolved = function(request) {
+ var index = this.pendingRequests.indexOf(request);
+ if (index !== -1) { this.pendingRequests.splice(index, 1); }
+};
+
+module.exports = WebSocketServer;
diff --git a/src/nxt_application.c b/src/nxt_application.c
index f63b90fb..468bc627 100644
--- a/src/nxt_application.c
+++ b/src/nxt_application.c
@@ -333,10 +333,7 @@ nxt_app_start(nxt_task_t *task, void *data)
ret = nxt_app->pre_init(task, data);
if (nxt_slow_path(ret != NXT_OK)) {
- nxt_debug(task, "application pre_init failed");
-
- } else {
- nxt_debug(task, "application pre_init done");
+ return ret;
}
}
diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c
index bee82dd4..ca8ec62e 100644
--- a/src/nxt_conf_validation.c
+++ b/src/nxt_conf_validation.c
@@ -72,6 +72,8 @@ static nxt_int_t nxt_conf_vldt_match_patterns_set(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value);
static nxt_int_t nxt_conf_vldt_match_patterns_set_member(
nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value);
+static nxt_int_t nxt_conf_vldt_match_scheme_pattern(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_app_name(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_app(nxt_conf_validation_t *vldt,
@@ -100,6 +102,26 @@ static nxt_int_t nxt_conf_vldt_java_option(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value);
+static nxt_conf_vldt_object_t nxt_conf_vldt_websocket_members[] = {
+ { nxt_string("read_timeout"),
+ NXT_CONF_VLDT_INTEGER,
+ NULL,
+ NULL },
+
+ { nxt_string("keepalive_interval"),
+ NXT_CONF_VLDT_INTEGER,
+ NULL,
+ NULL },
+
+ { nxt_string("max_frame_size"),
+ NXT_CONF_VLDT_INTEGER,
+ NULL,
+ NULL },
+
+ NXT_CONF_VLDT_END
+};
+
+
static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = {
{ nxt_string("header_read_timeout"),
NXT_CONF_VLDT_INTEGER,
@@ -126,6 +148,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = {
NULL,
NULL },
+ { nxt_string("websocket"),
+ NXT_CONF_VLDT_OBJECT,
+ &nxt_conf_vldt_object,
+ (void *) &nxt_conf_vldt_websocket_members },
+
NXT_CONF_VLDT_END
};
@@ -214,6 +241,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_match_members[] = {
&nxt_conf_vldt_match_patterns,
NULL },
+ { nxt_string("scheme"),
+ NXT_CONF_VLDT_STRING,
+ &nxt_conf_vldt_match_scheme_pattern,
+ NULL },
+
{ nxt_string("host"),
NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY,
&nxt_conf_vldt_match_patterns,
@@ -820,6 +852,28 @@ nxt_conf_vldt_match_pattern(nxt_conf_validation_t *vldt,
static nxt_int_t
+nxt_conf_vldt_match_scheme_pattern(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value, void *data)
+{
+ nxt_str_t scheme;
+
+ static const nxt_str_t http = nxt_string("http");
+ static const nxt_str_t https = nxt_string("https");
+
+ nxt_conf_get_string(value, &scheme);
+
+ if (nxt_strcasestr_eq(&scheme, &http)
+ || nxt_strcasestr_eq(&scheme, &https))
+ {
+ return NXT_OK;
+ }
+
+ return nxt_conf_vldt_error(vldt, "The \"scheme\" can either be "
+ "\"http\" or \"https\".");
+}
+
+
+static nxt_int_t
nxt_conf_vldt_match_patterns_sets(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data)
{
diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c
index 3a822042..a60bdb36 100644
--- a/src/nxt_h1proto.c
+++ b/src/nxt_h1proto.c
@@ -6,6 +6,9 @@
#include <nxt_router.h>
#include <nxt_http.h>
+#include <nxt_h1proto.h>
+#include <nxt_websocket.h>
+#include <nxt_websocket_header.h>
/*
@@ -23,19 +26,24 @@ static void nxt_h1p_conn_proto_init(nxt_task_t *task, void *obj, void *data);
static void nxt_h1p_conn_request_init(nxt_task_t *task, void *obj, void *data);
static void nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj,
void *data);
-static nxt_int_t nxt_h1p_header_process(nxt_h1proto_t *h1p,
+static nxt_int_t nxt_h1p_header_process(nxt_task_t *task, nxt_h1proto_t *h1p,
nxt_http_request_t *r);
static nxt_int_t nxt_h1p_header_buffer_test(nxt_task_t *task,
nxt_h1proto_t *h1p, nxt_conn_t *c, nxt_socket_conf_t *skcf);
static nxt_int_t nxt_h1p_connection(void *ctx, nxt_http_field_t *field,
uintptr_t data);
+static nxt_int_t nxt_h1p_upgrade(void *ctx, nxt_http_field_t *field,
+ uintptr_t data);
+static nxt_int_t nxt_h1p_websocket_key(void *ctx, nxt_http_field_t *field,
+ uintptr_t data);
+static nxt_int_t nxt_h1p_websocket_version(void *ctx, nxt_http_field_t *field,
+ uintptr_t data);
static nxt_int_t nxt_h1p_transfer_encoding(void *ctx, nxt_http_field_t *field,
uintptr_t data);
static void nxt_h1p_request_body_read(nxt_task_t *task, nxt_http_request_t *r);
static void nxt_h1p_conn_request_body_read(nxt_task_t *task, void *obj,
void *data);
static void nxt_h1p_request_local_addr(nxt_task_t *task, nxt_http_request_t *r);
-static void nxt_h1p_request_tls(nxt_task_t *task, nxt_http_request_t *r);
static void nxt_h1p_request_header_send(nxt_task_t *task,
nxt_http_request_t *r);
static void nxt_h1p_request_send(nxt_task_t *task, nxt_http_request_t *r,
@@ -51,8 +59,6 @@ static void nxt_h1p_conn_request_timeout(nxt_task_t *task, void *obj,
void *data);
static void nxt_h1p_conn_request_send_timeout(nxt_task_t *task, void *obj,
void *data);
-static nxt_msec_t nxt_h1p_conn_request_timer_value(nxt_conn_t *c,
- uintptr_t data);
nxt_inline void nxt_h1p_request_error(nxt_task_t *task, nxt_h1proto_t *h1p,
nxt_http_request_t *r);
static void nxt_h1p_request_close(nxt_task_t *task, nxt_http_proto_t proto,
@@ -72,16 +78,16 @@ static void nxt_h1p_idle_response_timeout(nxt_task_t *task, void *obj,
static nxt_msec_t nxt_h1p_idle_response_timer_value(nxt_conn_t *c,
uintptr_t data);
static void nxt_h1p_shutdown(nxt_task_t *task, nxt_conn_t *c);
+static void nxt_h1p_shutdown_(nxt_task_t *task, nxt_conn_t *c);
+static void nxt_h1p_conn_ws_shutdown(nxt_task_t *task, void *obj, void *data);
static void nxt_h1p_conn_closing(nxt_task_t *task, void *obj, void *data);
static void nxt_h1p_conn_free(nxt_task_t *task, void *obj, void *data);
-
#if (NXT_TLS)
static const nxt_conn_state_t nxt_http_idle_state;
static const nxt_conn_state_t nxt_h1p_shutdown_state;
#endif
static const nxt_conn_state_t nxt_h1p_idle_state;
-static const nxt_conn_state_t nxt_h1p_idle_close_state;
static const nxt_conn_state_t nxt_h1p_header_parse_state;
static const nxt_conn_state_t nxt_h1p_read_body_state;
static const nxt_conn_state_t nxt_h1p_request_send_state;
@@ -90,59 +96,20 @@ static const nxt_conn_state_t nxt_h1p_keepalive_state;
static const nxt_conn_state_t nxt_h1p_close_state;
-const nxt_http_proto_body_read_t nxt_http_proto_body_read[3] = {
- nxt_h1p_request_body_read,
- NULL,
- NULL,
-};
-
-
-const nxt_http_proto_local_addr_t nxt_http_proto_local_addr[3] = {
- nxt_h1p_request_local_addr,
- NULL,
- NULL,
-};
-
-
-const nxt_http_proto_tls_t nxt_http_proto_tls[3] = {
- nxt_h1p_request_tls,
- NULL,
- NULL,
-};
-
-
-const nxt_http_proto_header_send_t nxt_http_proto_header_send[3] = {
- nxt_h1p_request_header_send,
- NULL,
- NULL,
-};
-
-
-const nxt_http_proto_send_t nxt_http_proto_send[3] = {
- nxt_h1p_request_send,
- NULL,
- NULL,
-};
-
-
-const nxt_http_proto_body_bytes_sent_t nxt_http_proto_body_bytes_sent[3] = {
- nxt_h1p_request_body_bytes_sent,
- NULL,
- NULL,
-};
-
-
-const nxt_http_proto_discard_t nxt_http_proto_discard[3] = {
- nxt_h1p_request_discard,
- NULL,
- NULL,
-};
-
-
-const nxt_http_proto_close_t nxt_http_proto_close[3] = {
- nxt_h1p_request_close,
- NULL,
- NULL,
+const nxt_http_proto_table_t nxt_http_proto[3] = {
+ /* NXT_HTTP_PROTO_H1 */
+ {
+ .body_read = nxt_h1p_request_body_read,
+ .local_addr = nxt_h1p_request_local_addr,
+ .header_send = nxt_h1p_request_header_send,
+ .send = nxt_h1p_request_send,
+ .body_bytes_sent = nxt_h1p_request_body_bytes_sent,
+ .discard = nxt_h1p_request_discard,
+ .close = nxt_h1p_request_close,
+ .ws_frame_start = nxt_h1p_websocket_frame_start,
+ },
+ /* NXT_HTTP_PROTO_H2 */
+ /* NXT_HTTP_PROTO_DEVNULL */
};
@@ -150,6 +117,10 @@ static nxt_lvlhsh_t nxt_h1p_fields_hash;
static nxt_http_field_proc_t nxt_h1p_fields[] = {
{ nxt_string("Connection"), &nxt_h1p_connection, 0 },
+ { nxt_string("Upgrade"), &nxt_h1p_upgrade, 0 },
+ { nxt_string("Sec-WebSocket-Key"), &nxt_h1p_websocket_key, 0 },
+ { nxt_string("Sec-WebSocket-Version"),
+ &nxt_h1p_websocket_version, 0 },
{ nxt_string("Transfer-Encoding"), &nxt_h1p_transfer_encoding, 0 },
{ nxt_string("Host"), &nxt_http_request_host, 0 },
@@ -446,8 +417,13 @@ nxt_h1p_conn_request_init(nxt_task_t *task, void *obj, void *data)
h1p->request = r;
r->proto.h1 = h1p;
+ /* r->protocol = NXT_HTTP_PROTO_H1 is done by zeroing. */
r->remote = c->remote;
+#if (NXT_TLS)
+ r->tls = c->u.tls;
+#endif
+
ret = nxt_http_parse_request_init(&h1p->parser, r->mem_pool);
if (nxt_fast_path(ret == NXT_OK)) {
@@ -519,7 +495,7 @@ nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj, void *data)
*/
h1p->keepalive = (h1p->parser.version.s.minor != '0');
- ret = nxt_h1p_header_process(h1p, r);
+ ret = nxt_h1p_header_process(task, h1p, r);
if (nxt_fast_path(ret == NXT_OK)) {
@@ -567,7 +543,7 @@ nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj, void *data)
break;
}
- (void) nxt_h1p_header_process(h1p, r);
+ (void) nxt_h1p_header_process(task, h1p, r);
error:
@@ -578,8 +554,12 @@ error:
static nxt_int_t
-nxt_h1p_header_process(nxt_h1proto_t *h1p, nxt_http_request_t *r)
+nxt_h1p_header_process(nxt_task_t *task, nxt_h1proto_t *h1p,
+ nxt_http_request_t *r)
{
+ u_char *m;
+ nxt_int_t ret;
+
r->target.start = h1p->parser.target_start;
r->target.length = h1p->parser.target_end - h1p->parser.target_start;
@@ -594,7 +574,46 @@ nxt_h1p_header_process(nxt_h1proto_t *h1p, nxt_http_request_t *r)
r->fields = h1p->parser.fields;
- return nxt_http_fields_process(r->fields, &nxt_h1p_fields_hash, r);
+ ret = nxt_http_fields_process(r->fields, &nxt_h1p_fields_hash, r);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return ret;
+ }
+
+ if (h1p->connection_upgrade && h1p->upgrade_websocket) {
+ m = h1p->parser.method.start;
+
+ if (nxt_slow_path(h1p->parser.method.length != 3
+ || m[0] != 'G'
+ || m[1] != 'E'
+ || m[2] != 'T'))
+ {
+ nxt_log(task, NXT_LOG_INFO, "h1p upgrade: bad method");
+
+ return NXT_HTTP_BAD_REQUEST;
+ }
+
+ if (nxt_slow_path(h1p->parser.version.s.minor != '1')) {
+ nxt_log(task, NXT_LOG_INFO, "h1p upgrade: bad protocol version");
+
+ return NXT_HTTP_BAD_REQUEST;
+ }
+
+ if (nxt_slow_path(h1p->websocket_key == NULL)) {
+ nxt_log(task, NXT_LOG_INFO, "h1p upgrade: bad or absent websocket key");
+
+ return NXT_HTTP_BAD_REQUEST;
+ }
+
+ if (nxt_slow_path(h1p->websocket_version_ok == 0)) {
+ nxt_log(task, NXT_LOG_INFO, "h1p upgrade: bad or absent websocket version");
+
+ return NXT_HTTP_UPGRADE_REQUIRED;
+ }
+
+ r->websocket_handshake = 1;
+ }
+
+ return ret;
}
@@ -636,12 +655,68 @@ nxt_h1p_header_buffer_test(nxt_task_t *task, nxt_h1proto_t *h1p, nxt_conn_t *c,
static nxt_int_t
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;
if (field->value_length == 5 && nxt_memcmp(field->value, "close", 5) == 0) {
r->proto.h1->keepalive = 0;
+
+ } else if (field->value_length == 7
+ && nxt_memcasecmp(field->value, upgrade, 7) == 0)
+ {
+ r->proto.h1->connection_upgrade = 1;
+ }
+
+ return NXT_OK;
+}
+
+
+static nxt_int_t
+nxt_h1p_upgrade(void *ctx, nxt_http_field_t *field, uintptr_t data)
+{
+ nxt_http_request_t *r;
+ static const u_char *websocket = (const u_char *) "websocket";
+
+ r = ctx;
+
+ if (field->value_length == 9
+ && nxt_memcasecmp(field->value, websocket, 9) == 0)
+ {
+ r->proto.h1->upgrade_websocket = 1;
+ }
+
+ return NXT_OK;
+}
+
+
+static nxt_int_t
+nxt_h1p_websocket_key(void *ctx, nxt_http_field_t *field, uintptr_t data)
+{
+ nxt_http_request_t *r;
+
+ r = ctx;
+
+ if (field->value_length == 24) {
+ r->proto.h1->websocket_key = field;
+ }
+
+ return NXT_OK;
+}
+
+
+static nxt_int_t
+nxt_h1p_websocket_version(void *ctx, nxt_http_field_t *field, uintptr_t data)
+{
+ nxt_http_request_t *r;
+
+ r = ctx;
+
+ if (field->value_length == 2
+ && field->value[0] == '1' && field->value[1] == '3')
+ {
+ r->proto.h1->websocket_version_ok = 1;
}
return NXT_OK;
@@ -743,6 +818,7 @@ nxt_h1p_request_body_read(nxt_task_t *task, nxt_http_request_t *r)
if (size != 0) {
in->next = h1p->buffers;
h1p->buffers = in;
+ h1p->nbuffers++;
c = h1p->conn;
c->read = b;
@@ -821,13 +897,13 @@ nxt_h1p_request_local_addr(nxt_task_t *task, nxt_http_request_t *r)
}
-static void
-nxt_h1p_request_tls(nxt_task_t *task, nxt_http_request_t *r)
-{
-#if (NXT_TLS)
- r->tls = r->proto.h1->conn->u.tls;
-#endif
-}
+#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 \
@@ -886,7 +962,7 @@ static const nxt_str_t nxt_http_client_error[] = {
nxt_string("HTTP/1.1 423\r\n"),
nxt_string("HTTP/1.1 424\r\n"),
nxt_string("HTTP/1.1 425\r\n"),
- nxt_string("HTTP/1.1 426\r\n"),
+ nxt_string("HTTP/1.1 426 Upgrade Required\r\n"),
nxt_string("HTTP/1.1 427\r\n"),
nxt_string("HTTP/1.1 428\r\n"),
nxt_string("HTTP/1.1 429\r\n"),
@@ -933,14 +1009,17 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
nxt_h1proto_t *h1p;
const nxt_str_t *status;
nxt_http_field_t *field;
- nxt_event_engine_t *engine;
u_char buf[UNKNOWN_STATUS_LENGTH];
static const char chunked[] = "Transfer-Encoding: chunked\r\n";
+ static const char websocket_version[] = "Sec-WebSocket-Version: 13\r\n";
- static const nxt_str_t connection[2] = {
+ static const nxt_str_t connection[3] = {
nxt_string("Connection: close\r\n"),
nxt_string("Connection: keep-alive\r\n"),
+ nxt_string("Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: "),
};
nxt_debug(task, "h1p request header send");
@@ -949,7 +1028,10 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
h1p = r->proto.h1;
n = r->status;
- if (n >= NXT_HTTP_OK && n <= NXT_HTTP_LAST_SUCCESS) {
+ if (n >= NXT_HTTP_CONTINUE && n <= NXT_HTTP_LAST_INFORMATIONAL) {
+ status = &nxt_http_informational[n - NXT_HTTP_CONTINUE];
+
+ } else if (n >= NXT_HTTP_OK && n <= NXT_HTTP_LAST_SUCCESS) {
status = &nxt_http_success[n - NXT_HTTP_OK];
} else if (n >= NXT_HTTP_MULTIPLE_CHOICES
@@ -981,27 +1063,41 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
/* Trailing CRLF at the end of header. */
size += nxt_length("\r\n");
- http11 = (h1p->parser.version.s.minor != '0');
+ conn = -1;
+
+ if (r->websocket_handshake && n == NXT_HTTP_SWITCHING_PROTOCOLS) {
+ h1p->websocket = 1;
+ h1p->keepalive = 0;
+ conn = 2;
+ size += NXT_WEBSOCKET_ACCEPT_SIZE + 2;
+
+ } else {
+ http11 = (h1p->parser.version.s.minor != '0');
+
+ if (r->resp.content_length == NULL || r->resp.content_length->skip) {
- if (r->resp.content_length == NULL || r->resp.content_length->skip) {
+ if (http11) {
+ if (n != NXT_HTTP_NOT_MODIFIED
+ && n != NXT_HTTP_NO_CONTENT
+ && !h1p->websocket)
+ {
+ h1p->chunked = 1;
+ size += nxt_length(chunked);
+ /* Trailing CRLF will be added by the first chunk header. */
+ size -= nxt_length("\r\n");
+ }
- if (http11) {
- if (n != NXT_HTTP_NOT_MODIFIED && n != NXT_HTTP_NO_CONTENT) {
- h1p->chunked = 1;
- size += nxt_length(chunked);
- /* Trailing CRLF will be added by the first chunk header. */
- size -= nxt_length("\r\n");
+ } else {
+ h1p->keepalive = 0;
}
+ }
- } else {
- h1p->keepalive = 0;
+ if (http11 ^ h1p->keepalive) {
+ conn = h1p->keepalive;
}
}
- conn = -1;
-
- if (http11 ^ h1p->keepalive) {
- conn = h1p->keepalive;
+ if (conn >= 0) {
size += connection[conn].length;
}
@@ -1014,15 +1110,17 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
} nxt_list_loop;
+ if (nxt_slow_path(n == NXT_HTTP_UPGRADE_REQUIRED)) {
+ size += nxt_length(websocket_version);
+ }
+
header = nxt_http_buf_mem(task, r, size);
if (nxt_slow_path(header == NULL)) {
nxt_h1p_request_error(task, h1p, r);
return;
}
- p = header->mem.free;
-
- p = nxt_cpymem(p, status->start, status->length);
+ p = nxt_cpymem(header->mem.free, status->start, status->length);
nxt_list_each(field, r->resp.fields) {
@@ -1039,6 +1137,17 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
p = nxt_cpymem(p, connection[conn].start, connection[conn].length);
}
+ if (h1p->websocket) {
+ nxt_websocket_accept(p, h1p->websocket_key->value);
+ p += NXT_WEBSOCKET_ACCEPT_SIZE;
+
+ *p++ = '\r'; *p++ = '\n';
+ }
+
+ if (nxt_slow_path(n == NXT_HTTP_UPGRADE_REQUIRED)) {
+ p = nxt_cpymem(p, websocket_version, nxt_length(websocket_version));
+ }
+
if (h1p->chunked) {
p = nxt_cpymem(p, chunked, nxt_length(chunked));
/* Trailing CRLF will be added by the first chunk header. */
@@ -1056,12 +1165,59 @@ nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
c->write = header;
c->write_state = &nxt_h1p_request_send_state;
- engine = task->thread->engine;
+ nxt_conn_write(task->thread->engine, c);
- nxt_work_queue_add(&engine->fast_work_queue, r->state->ready_handler,
- task, r, NULL);
+ 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;
- nxt_conn_write(engine, c);
+ 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;
+ }
+ }
}
@@ -1212,8 +1368,13 @@ nxt_h1p_conn_request_error(nxt_task_t *task, void *obj, void *data)
r = h1p->request;
+ if (nxt_slow_path(r == NULL)) {
+ nxt_h1p_shutdown(task, h1p->conn);
+ return;
+ }
+
if (r->fields == NULL) {
- (void) nxt_h1p_header_process(h1p, r);
+ (void) nxt_h1p_header_process(task, h1p, r);
}
if (r->status == 0) {
@@ -1249,7 +1410,7 @@ nxt_h1p_conn_request_timeout(nxt_task_t *task, void *obj, void *data)
r = h1p->request;
if (r->fields == NULL) {
- (void) nxt_h1p_header_process(h1p, r);
+ (void) nxt_h1p_header_process(task, h1p, r);
}
nxt_http_request_error(task, r, NXT_HTTP_REQUEST_TIMEOUT);
@@ -1275,7 +1436,7 @@ nxt_h1p_conn_request_send_timeout(nxt_task_t *task, void *obj, void *data)
}
-static nxt_msec_t
+nxt_msec_t
nxt_h1p_conn_request_timer_value(nxt_conn_t *c, uintptr_t data)
{
nxt_h1proto_t *h1p;
@@ -1382,7 +1543,7 @@ static void
nxt_h1p_keepalive(nxt_task_t *task, nxt_h1proto_t *h1p, nxt_conn_t *c)
{
size_t size;
- nxt_buf_t *in, *b, *next;
+ nxt_buf_t *in;
nxt_debug(task, "h1p keepalive");
@@ -1390,40 +1551,22 @@ nxt_h1p_keepalive(nxt_task_t *task, nxt_h1proto_t *h1p, nxt_conn_t *c)
nxt_conn_tcp_nodelay_on(task, c);
}
- b = h1p->buffers;
+ nxt_h1p_complete_buffers(task, h1p);
+
+ in = c->read;
nxt_memzero(h1p, offsetof(nxt_h1proto_t, conn));
c->sent = 0;
- in = c->read;
-
if (in == NULL) {
- /* A request with large body. */
- in = b;
- c->read = in;
-
- b = in->next;
- in->next = NULL;
- }
-
- while (b != NULL) {
- next = b->next;
- nxt_mp_free(c->mem_pool, b);
- b = next;
- }
-
- size = nxt_buf_mem_used_size(&in->mem);
-
- if (size == 0) {
- nxt_mp_free(c->mem_pool, in);
-
- c->read = NULL;
c->read_state = &nxt_h1p_keepalive_state;
nxt_conn_read(task->thread->engine, c);
} else {
+ size = nxt_buf_mem_used_size(&in->mem);
+
nxt_debug(task, "h1p pipelining");
nxt_memmove(in->mem.start, in->mem.pos, size);
@@ -1452,7 +1595,7 @@ static const nxt_conn_state_t nxt_h1p_keepalive_state
};
-static const nxt_conn_state_t nxt_h1p_idle_close_state
+const nxt_conn_state_t nxt_h1p_idle_close_state
nxt_aligned(64) =
{
.close_handler = nxt_h1p_idle_close,
@@ -1595,8 +1738,33 @@ nxt_h1p_idle_response_timer_value(nxt_conn_t *c, uintptr_t data)
static void
nxt_h1p_shutdown(nxt_task_t *task, nxt_conn_t *c)
{
+ nxt_timer_t *timer;
+ nxt_h1proto_t *h1p;
+
nxt_debug(task, "h1p shutdown");
+ h1p = c->socket.data;
+
+ if (nxt_slow_path(h1p != NULL && h1p->websocket_timer != NULL)) {
+ timer = &h1p->websocket_timer->timer;
+
+ if (timer->handler != nxt_h1p_conn_ws_shutdown) {
+ timer->handler = nxt_h1p_conn_ws_shutdown;
+ nxt_timer_add(task->thread->engine, timer, 0);
+
+ } else {
+ nxt_debug(task, "h1p already scheduled ws shutdown");
+ }
+
+ } else {
+ nxt_h1p_shutdown_(task, c);
+ }
+}
+
+
+static void
+nxt_h1p_shutdown_(nxt_task_t *task, nxt_conn_t *c)
+{
c->socket.data = NULL;
#if (NXT_TLS)
@@ -1628,6 +1796,21 @@ static const nxt_conn_state_t nxt_h1p_shutdown_state
static void
+nxt_h1p_conn_ws_shutdown(nxt_task_t *task, void *obj, void *data)
+{
+ nxt_timer_t *timer;
+ nxt_h1p_websocket_timer_t *ws_timer;
+
+ nxt_debug(task, "h1p conn ws shutdown");
+
+ timer = obj;
+ ws_timer = nxt_timer_data(timer, nxt_h1p_websocket_timer_t, timer);
+
+ nxt_h1p_shutdown_(task, ws_timer->h1p->conn);
+}
+
+
+static void
nxt_h1p_conn_closing(nxt_task_t *task, void *obj, void *data)
{
nxt_conn_t *c;
diff --git a/src/nxt_h1proto.h b/src/nxt_h1proto.h
new file mode 100644
index 00000000..c6d3bd53
--- /dev/null
+++ b/src/nxt_h1proto.h
@@ -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_ */
diff --git a/src/nxt_h1proto_websocket.c b/src/nxt_h1proto_websocket.c
new file mode 100644
index 00000000..dd9b6848
--- /dev/null
+++ b/src/nxt_h1proto_websocket.c
@@ -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);
+}
diff --git a/src/nxt_http.h b/src/nxt_http.h
index 835cf66d..ac1eedcf 100644
--- a/src/nxt_http.h
+++ b/src/nxt_http.h
@@ -11,6 +11,9 @@
typedef enum {
NXT_HTTP_INVALID = 0,
+ NXT_HTTP_CONTINUE = 100,
+ NXT_HTTP_SWITCHING_PROTOCOLS = 101,
+
NXT_HTTP_OK = 200,
NXT_HTTP_NO_CONTENT = 204,
@@ -26,6 +29,7 @@ typedef enum {
NXT_HTTP_LENGTH_REQUIRED = 411,
NXT_HTTP_PAYLOAD_TOO_LARGE = 413,
NXT_HTTP_URI_TOO_LONG = 414,
+ NXT_HTTP_UPGRADE_REQUIRED = 426,
NXT_HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
NXT_HTTP_TO_HTTPS = 497,
@@ -46,30 +50,26 @@ typedef enum {
} nxt_http_te_t;
+typedef enum {
+ NXT_HTTP_PROTO_H1 = 0,
+ NXT_HTTP_PROTO_H2,
+ NXT_HTTP_PROTO_DEVNULL,
+} nxt_http_protocol_t;
+
+
typedef struct {
nxt_work_handler_t ready_handler;
nxt_work_handler_t error_handler;
} nxt_http_request_state_t;
-typedef struct {
- 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;
+typedef struct nxt_h1proto_s nxt_h1proto_t;
- 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;
-} nxt_h1proto_t;
+struct nxt_h1p_websocket_timer_s {
+ nxt_timer_t timer;
+ nxt_h1proto_t *h1p;
+ nxt_msec_t keepalive_interval;
+};
typedef union {
@@ -110,6 +110,7 @@ struct nxt_http_request_s {
nxt_mp_t *mem_pool;
nxt_buf_t *body;
+ nxt_buf_t *ws_frame;
nxt_buf_t *out;
const nxt_http_request_state_t *state;
@@ -138,6 +139,8 @@ struct nxt_http_request_s {
nxt_timer_t timer;
void *timer_data;
+ void *req_rpc_data;
+
nxt_buf_t *last;
nxt_http_response_t resp;
@@ -145,10 +148,11 @@ struct nxt_http_request_s {
nxt_http_status_t status:16;
uint8_t pass_count; /* 8 bits */
- uint8_t protocol; /* 2 bits */
+ nxt_http_protocol_t protocol:8; /* 2 bits */
uint8_t logged; /* 1 bit */
uint8_t header_sent; /* 1 bit */
uint8_t error; /* 1 bit */
+ uint8_t websocket_handshake; /* 1 bit */
};
@@ -168,21 +172,18 @@ struct nxt_http_pass_s {
};
-typedef void (*nxt_http_proto_body_read_t)(nxt_task_t *task,
- nxt_http_request_t *r);
-typedef void (*nxt_http_proto_local_addr_t)(nxt_task_t *task,
- nxt_http_request_t *r);
-typedef void (*nxt_http_proto_tls_t)(nxt_task_t *task, nxt_http_request_t *r);
-typedef void (*nxt_http_proto_header_send_t)(nxt_task_t *task,
- nxt_http_request_t *r);
-typedef void (*nxt_http_proto_send_t)(nxt_task_t *task, nxt_http_request_t *r,
- nxt_buf_t *out);
-typedef nxt_off_t (*nxt_http_proto_body_bytes_sent_t)(nxt_task_t *task,
- nxt_http_proto_t proto);
-typedef void (*nxt_http_proto_discard_t)(nxt_task_t *task,
- nxt_http_request_t *r, nxt_buf_t *last);
-typedef void (*nxt_http_proto_close_t)(nxt_task_t *task,
- nxt_http_proto_t proto, nxt_socket_conf_joint_t *joint);
+typedef struct {
+ void (*body_read)(nxt_task_t *task, nxt_http_request_t *r);
+ void (*local_addr)(nxt_task_t *task, nxt_http_request_t *r);
+ void (*header_send)(nxt_task_t *task, nxt_http_request_t *r);
+ void (*send)(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *out);
+ nxt_off_t (*body_bytes_sent)(nxt_task_t *task, nxt_http_proto_t proto);
+ void (*discard)(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *last);
+ void (*close)(nxt_task_t *task, nxt_http_proto_t proto,
+ nxt_socket_conf_joint_t *joint);
+ void (*ws_frame_start)(nxt_task_t *task, nxt_http_request_t *r,
+ nxt_buf_t *ws_frame);
+} nxt_http_proto_table_t;
nxt_int_t nxt_http_init(nxt_task_t *task, nxt_runtime_t *rt);
@@ -195,12 +196,15 @@ void nxt_http_request_error(nxt_task_t *task, nxt_http_request_t *r,
nxt_http_status_t status);
void nxt_http_request_read_body(nxt_task_t *task, nxt_http_request_t *r);
void nxt_http_request_header_send(nxt_task_t *task, nxt_http_request_t *r);
+void nxt_http_request_ws_frame_start(nxt_task_t *task, nxt_http_request_t *r,
+ nxt_buf_t *ws_frame);
void nxt_http_request_send(nxt_task_t *task, nxt_http_request_t *r,
nxt_buf_t *out);
nxt_buf_t *nxt_http_buf_mem(nxt_task_t *task, nxt_http_request_t *r,
size_t size);
nxt_buf_t *nxt_http_buf_last(nxt_http_request_t *r);
void nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data);
+void nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data);
nxt_int_t nxt_http_request_host(void *ctx, nxt_http_field_t *field,
uintptr_t data);
@@ -226,14 +230,15 @@ extern nxt_time_string_t nxt_http_date_cache;
extern nxt_lvlhsh_t nxt_response_fields_hash;
-extern const nxt_http_proto_body_read_t nxt_http_proto_body_read[];
-extern const nxt_http_proto_local_addr_t nxt_http_proto_local_addr[];
-extern const nxt_http_proto_tls_t nxt_http_proto_tls[];
-extern const nxt_http_proto_header_send_t nxt_http_proto_header_send[];
-extern const nxt_http_proto_send_t nxt_http_proto_send[];
-extern const nxt_http_proto_body_bytes_sent_t nxt_http_proto_body_bytes_sent[];
-extern const nxt_http_proto_discard_t nxt_http_proto_discard[];
-extern const nxt_http_proto_close_t nxt_http_proto_close[];
+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_ */
diff --git a/src/nxt_http_error.c b/src/nxt_http_error.c
index 99d27903..c7c7e81a 100644
--- a/src/nxt_http_error.c
+++ b/src/nxt_http_error.c
@@ -51,9 +51,12 @@ nxt_http_request_error(nxt_task_t *task, nxt_http_request_t *r,
r->resp.content_length = NULL;
r->resp.content_length_n = nxt_length(error);
+ nxt_http_request_header_send(task, r);
+
r->state = &nxt_http_request_send_error_body_state;
- nxt_http_request_header_send(task, r);
+ nxt_work_queue_add(&task->thread->engine->fast_work_queue,
+ nxt_http_request_send_error_body, task, r, NULL);
return;
fail:
@@ -65,7 +68,6 @@ fail:
static const nxt_http_request_state_t nxt_http_request_send_error_body_state
nxt_aligned(64) =
{
- .ready_handler = nxt_http_request_send_error_body,
.error_handler = nxt_http_request_error_handler,
};
diff --git a/src/nxt_http_parse.c b/src/nxt_http_parse.c
index 05df245e..8b4bf47c 100644
--- a/src/nxt_http_parse.c
+++ b/src/nxt_http_parse.c
@@ -1110,7 +1110,7 @@ done:
}
-static const nxt_lvlhsh_proto_t nxt_http_fields_hash_proto nxt_aligned(64) = {
+const nxt_lvlhsh_proto_t nxt_http_fields_hash_proto nxt_aligned(64) = {
NXT_LVLHSH_BUCKET_SIZE(64),
{ NXT_HTTP_FIELD_LVLHSH_SHIFT, 0, 0, 0, 0, 0, 0, 0 },
nxt_http_field_hash_test,
@@ -1240,27 +1240,12 @@ nxt_http_fields_hash_collisions(nxt_lvlhsh_t *hash, nxt_mp_t *mp,
nxt_int_t
nxt_http_fields_process(nxt_list_t *fields, nxt_lvlhsh_t *hash, void *ctx)
{
- nxt_int_t ret;
- nxt_http_field_t *field;
- nxt_lvlhsh_query_t lhq;
- nxt_http_field_proc_t *proc;
-
- lhq.proto = &nxt_http_fields_hash_proto;
+ nxt_int_t ret;
+ nxt_http_field_t *field;
nxt_list_each(field, fields) {
- lhq.key_hash = field->hash;
- lhq.key.length = field->name_length;
- lhq.key.start = field->name;
-
- if (nxt_lvlhsh_find(hash, &lhq) != NXT_OK) {
- continue;
- }
-
- proc = lhq.value;
-
- ret = proc->handler(ctx, field, proc->data);
-
+ ret = nxt_http_field_process(field, hash, ctx);
if (nxt_slow_path(ret != NXT_OK)) {
return ret;
}
diff --git a/src/nxt_http_parse.h b/src/nxt_http_parse.h
index 6c629936..c5b11bf3 100644
--- a/src/nxt_http_parse.h
+++ b/src/nxt_http_parse.h
@@ -113,4 +113,28 @@ nxt_int_t nxt_http_fields_process(nxt_list_t *fields, nxt_lvlhsh_t *hash,
void *ctx);
+const nxt_lvlhsh_proto_t nxt_http_fields_hash_proto;
+
+nxt_inline nxt_int_t
+nxt_http_field_process(nxt_http_field_t *field, nxt_lvlhsh_t *hash, void *ctx)
+{
+ nxt_lvlhsh_query_t lhq;
+ nxt_http_field_proc_t *proc;
+
+ lhq.proto = &nxt_http_fields_hash_proto;
+
+ lhq.key_hash = field->hash;
+ lhq.key.length = field->name_length;
+ lhq.key.start = field->name;
+
+ if (nxt_lvlhsh_find(hash, &lhq) != NXT_OK) {
+ return NXT_OK;
+ }
+
+ proc = lhq.value;
+
+ return proc->handler(ctx, field, proc->data);
+}
+
+
#endif /* _NXT_HTTP_PARSER_H_INCLUDED_ */
diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c
index 1265c186..916004d2 100644
--- a/src/nxt_http_request.c
+++ b/src/nxt_http_request.c
@@ -16,8 +16,6 @@ static void nxt_http_request_proto_info(nxt_task_t *task,
static void nxt_http_request_mem_buf_completion(nxt_task_t *task, void *obj,
void *data);
static void nxt_http_request_done(nxt_task_t *task, void *obj, void *data);
-static void nxt_http_request_close_handler(nxt_task_t *task, void *obj,
- void *data);
static u_char *nxt_http_date(u_char *buf, nxt_realtime_t *now, struct tm *tm,
size_t size, const char *format);
@@ -355,9 +353,8 @@ nxt_http_request_application(nxt_task_t *task, nxt_http_request_t *r,
static void
nxt_http_request_proto_info(nxt_task_t *task, nxt_http_request_t *r)
{
- if (r->proto.any != NULL) {
- nxt_http_proto_local_addr[r->protocol](task, r);
- nxt_http_proto_tls[r->protocol](task, r);
+ if (nxt_fast_path(r->proto.any != NULL)) {
+ nxt_http_proto[r->protocol].local_addr(task, r);
}
}
@@ -365,8 +362,8 @@ nxt_http_request_proto_info(nxt_task_t *task, nxt_http_request_t *r)
void
nxt_http_request_read_body(nxt_task_t *task, nxt_http_request_t *r)
{
- if (r->proto.any != NULL) {
- nxt_http_proto_body_read[r->protocol](task, r);
+ if (nxt_fast_path(r->proto.any != NULL)) {
+ nxt_http_proto[r->protocol].body_read(task, r);
}
}
@@ -432,8 +429,8 @@ nxt_http_request_header_send(nxt_task_t *task, nxt_http_request_t *r)
r->resp.content_length = content_length;
}
- if (r->proto.any != NULL) {
- nxt_http_proto_header_send[r->protocol](task, r);
+ if (nxt_fast_path(r->proto.any != NULL)) {
+ nxt_http_proto[r->protocol].header_send(task, r);
}
return;
@@ -445,10 +442,20 @@ fail:
void
-nxt_http_request_send(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *out)
+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_send[r->protocol](task, r, out);
+ nxt_http_proto[r->protocol].ws_frame_start(task, r, ws_frame);
+ }
+}
+
+
+void
+nxt_http_request_send(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *out)
+{
+ if (nxt_fast_path(r->proto.any != NULL)) {
+ nxt_http_proto[r->protocol].send(task, r, out);
}
}
@@ -525,18 +532,18 @@ nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data)
r->error = 1;
- if (proto.any != NULL) {
- nxt_http_proto_discard[r->protocol](task, r, nxt_http_buf_last(r));
+ if (nxt_fast_path(proto.any != NULL)) {
+ nxt_http_proto[r->protocol].discard(task, r, nxt_http_buf_last(r));
}
}
-static void
+void
nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data)
{
nxt_http_proto_t proto;
nxt_http_request_t *r;
- nxt_http_proto_close_t handler;
+ nxt_http_protocol_t protocol;
nxt_socket_conf_joint_t *conf;
nxt_router_access_log_t *access_log;
@@ -557,13 +564,14 @@ nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data)
}
}
- handler = nxt_http_proto_close[r->protocol];
-
r->proto.any = NULL;
- nxt_mp_release(r->mem_pool);
- if (proto.any != NULL) {
- handler(task, proto, conf);
+ if (nxt_fast_path(proto.any != NULL)) {
+ protocol = r->protocol;
+
+ nxt_mp_release(r->mem_pool);
+
+ nxt_http_proto[protocol].close(task, proto, conf);
}
}
diff --git a/src/nxt_http_response.c b/src/nxt_http_response.c
index 755182db..00ecff00 100644
--- a/src/nxt_http_response.c
+++ b/src/nxt_http_response.c
@@ -28,6 +28,8 @@ static nxt_http_field_proc_t nxt_response_fields[] = {
offsetof(nxt_http_request_t, resp.content_type) },
{ nxt_string("Content-Length"), &nxt_http_response_field,
offsetof(nxt_http_request_t, resp.content_length) },
+ { nxt_string("Upgrade"), &nxt_http_response_skip, 0 },
+ { nxt_string("Sec-WebSocket-Accept"), &nxt_http_response_skip, 0 },
};
diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c
index d6749acb..0b665573 100644
--- a/src/nxt_http_route.c
+++ b/src/nxt_http_route.c
@@ -15,6 +15,7 @@ typedef enum {
NXT_HTTP_ROUTE_HEADER,
NXT_HTTP_ROUTE_ARGUMENT,
NXT_HTTP_ROUTE_COOKIE,
+ NXT_HTTP_ROUTE_SCHEME,
} nxt_http_route_object_t;
@@ -41,6 +42,7 @@ typedef struct {
nxt_conf_value_t *headers;
nxt_conf_value_t *arguments;
nxt_conf_value_t *cookies;
+ nxt_conf_value_t *scheme;
} nxt_http_route_match_conf_t;
@@ -197,6 +199,8 @@ static nxt_http_name_value_t *nxt_http_route_argument(nxt_array_t *array,
u_char *end);
static nxt_int_t nxt_http_route_test_argument(nxt_http_request_t *r,
nxt_http_route_rule_t *rule, nxt_array_t *array);
+static nxt_int_t nxt_http_route_scheme(nxt_http_request_t *r,
+ nxt_http_route_rule_t *rule);
static nxt_int_t nxt_http_route_cookies(nxt_http_request_t *r,
nxt_http_route_rule_t *rule);
static nxt_array_t *nxt_http_route_cookies_parse(nxt_http_request_t *r);
@@ -277,6 +281,11 @@ nxt_http_routes_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
static nxt_conf_map_t nxt_http_route_match_conf[] = {
{
+ nxt_string("scheme"),
+ NXT_CONF_MAP_PTR,
+ offsetof(nxt_http_route_match_conf_t, scheme)
+ },
+ {
nxt_string("host"),
NXT_CONF_MAP_PTR,
offsetof(nxt_http_route_match_conf_t, host),
@@ -412,6 +421,18 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
test = &match->test[0];
+ if (mtcf.scheme != NULL) {
+ rule = nxt_http_route_rule_create(task, mp, mtcf.scheme, 1,
+ NXT_HTTP_ROUTE_PATTERN_NOCASE);
+ if (rule == NULL) {
+ return NULL;
+ }
+
+ rule->object = NXT_HTTP_ROUTE_SCHEME;
+ test->rule = rule;
+ test++;
+ }
+
if (mtcf.host != NULL) {
rule = nxt_http_route_rule_create(task, mp, mtcf.host, 1,
NXT_HTTP_ROUTE_PATTERN_LOWCASE);
@@ -475,7 +496,7 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
if (mtcf.cookies != NULL) {
table = nxt_http_route_table_create(task, mp, mtcf.cookies,
- NXT_HTTP_ROUTE_COOKIE, 0);
+ NXT_HTTP_ROUTE_COOKIE, 1);
if (table == NULL) {
return NULL;
}
@@ -613,7 +634,7 @@ nxt_http_route_rule_name_create(nxt_task_t *task, nxt_mp_t *mp,
c = name->start[i];
*p++ = c;
- c = nxt_lowcase(c);
+ c = case_sensitive ? c : nxt_lowcase(c);
hash = nxt_http_field_hash_char(hash, c);
}
@@ -1125,6 +1146,9 @@ nxt_http_route_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule)
case NXT_HTTP_ROUTE_COOKIE:
return nxt_http_route_cookies(r, rule);
+ case NXT_HTTP_ROUTE_SCHEME:
+ return nxt_http_route_scheme(r, rule);
+
default:
break;
}
@@ -1331,6 +1355,18 @@ nxt_http_route_test_argument(nxt_http_request_t *r,
static nxt_int_t
+nxt_http_route_scheme(nxt_http_request_t *r, nxt_http_route_rule_t *rule)
+{
+ nxt_bool_t tls, https;
+
+ https = (rule->pattern[0].length1 == nxt_length("https"));
+ tls = (r->tls != NULL);
+
+ return (tls == https);
+}
+
+
+static nxt_int_t
nxt_http_route_cookies(nxt_http_request_t *r, nxt_http_route_rule_t *rule)
{
nxt_array_t *cookies;
@@ -1452,7 +1488,6 @@ nxt_http_route_cookie(nxt_array_t *array, u_char *name, size_t name_length,
for (p = name; p < name + name_length; p++) {
c = *p;
- c = nxt_lowcase(c);
hash = nxt_http_field_hash_char(hash, c);
}
@@ -1483,8 +1518,7 @@ nxt_http_route_test_cookie(nxt_http_request_t *r,
if (rule->u.name.hash == nv->hash
&& rule->u.name.length == nv->name_length
- && nxt_strncasecmp(rule->u.name.start, nv->name, nv->name_length)
- == 0)
+ && nxt_memcmp(rule->u.name.start, nv->name, nv->name_length) == 0)
{
ret = nxt_http_route_test_rule(r, rule, nv->value,
nv->value_length);
diff --git a/src/nxt_http_websocket.c b/src/nxt_http_websocket.c
new file mode 100644
index 00000000..d58d615c
--- /dev/null
+++ b/src/nxt_http_websocket.c
@@ -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);
+}
diff --git a/src/nxt_java.c b/src/nxt_java.c
index bf4931ab..3421d825 100644
--- a/src/nxt_java.c
+++ b/src/nxt_java.c
@@ -68,7 +68,7 @@ nxt_java_pre_init(nxt_task_t *task, nxt_common_app_conf_t *conf)
nxt_java_modules = realpath(unit_jars, NULL);
if (nxt_java_modules == NULL) {
- nxt_alert(task, "realpath(%s) failed: %E", NXT_JARS, nxt_errno);
+ nxt_alert(task, "realpath(%s) failed: %E", unit_jars, nxt_errno);
return NXT_ERROR;
}
diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c
index a6ec6c60..f5f115f5 100644
--- a/src/nxt_php_sapi.c
+++ b/src/nxt_php_sapi.c
@@ -143,14 +143,16 @@ static sapi_module_struct nxt_php_sapi_module =
struct nxt_php_run_ctx_s {
char *cookie;
- nxt_str_t script;
+ nxt_str_t path_info;
+ nxt_str_t script_name;
+ nxt_str_t script_filename;
nxt_unit_request_info_t *req;
};
-static nxt_str_t nxt_php_path;
static nxt_str_t nxt_php_root;
-static nxt_str_t nxt_php_script;
+static nxt_str_t nxt_php_script_name;
+static nxt_str_t nxt_php_script_filename;
static nxt_str_t nxt_php_index = nxt_string("index.php");
@@ -178,9 +180,9 @@ static void ***tsrm_ls;
static nxt_int_t
nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf)
{
- u_char *p;
- nxt_str_t rpath, ini_path;
- nxt_str_t *root, *path, *script, *index;
+ u_char *p, *tmp;
+ nxt_str_t ini_path;
+ nxt_str_t *root, *script_filename, *script_name, *index;
nxt_port_t *my_port, *main_port;
nxt_runtime_t *rt;
nxt_unit_ctx_t *unit_ctx;
@@ -202,8 +204,8 @@ nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf)
}
root = &nxt_php_root;
- path = &nxt_php_path;
- script = &nxt_php_script;
+ script_filename = &nxt_php_script_filename;
+ script_name = &nxt_php_script_name;
index = &nxt_php_index;
root->start = nxt_realpath(c->root);
@@ -219,47 +221,46 @@ nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf)
if (c->script.length > 0) {
nxt_php_str_trim_lead(&c->script, '/');
- path->length = root->length + 1 + c->script.length;
- path->start = nxt_malloc(path->length + 1);
- if (nxt_slow_path(path->start == NULL)) {
+ tmp = nxt_malloc(root->length + 1 + c->script.length + 1);
+ if (nxt_slow_path(tmp == NULL)) {
return NXT_ERROR;
}
- p = nxt_cpymem(path->start, root->start, root->length);
+ p = tmp;
+
+ p = nxt_cpymem(p, root->start, root->length);
*p++ = '/';
p = nxt_cpymem(p, c->script.start, c->script.length);
*p = '\0';
- rpath.start = nxt_realpath(path->start);
- if (nxt_slow_path(rpath.start == NULL)) {
- nxt_alert(task, "script realpath(%V) failed %E", path, nxt_errno);
+ script_filename->start = nxt_realpath(tmp);
+ if (nxt_slow_path(script_filename->start == NULL)) {
+ nxt_alert(task, "script realpath(%s) failed %E", tmp, nxt_errno);
return NXT_ERROR;
}
- rpath.length = nxt_strlen(rpath.start);
+ nxt_free(tmp);
+
+ script_filename->length = nxt_strlen(script_filename->start);
- if (!nxt_str_start(&rpath, root->start, root->length)) {
+ if (!nxt_str_start(script_filename, root->start, root->length)) {
nxt_alert(task, "script is not under php root");
return NXT_ERROR;
}
- nxt_free(path->start);
-
- *path = rpath;
-
- script->length = c->script.length + 1;
- script->start = nxt_malloc(script->length);
- if (nxt_slow_path(script->start == NULL)) {
+ script_name->length = c->script.length + 1;
+ script_name->start = nxt_malloc(script_name->length);
+ if (nxt_slow_path(script_name->start == NULL)) {
return NXT_ERROR;
}
- script->start[0] = '/';
- nxt_memcpy(script->start + 1, c->script.start, c->script.length);
+ script_name->start[0] = '/';
+ nxt_memcpy(script_name->start + 1, c->script.start, c->script.length);
nxt_log_error(NXT_LOG_INFO, task->log,
"(ABS_MODE) php script \"%V\" root: \"%V\"",
- script, root);
+ script_name, root);
} else {
nxt_log_error(NXT_LOG_INFO, task->log,
@@ -596,7 +597,15 @@ nxt_php_request_handler(nxt_unit_request_info_t *req)
path.length = r->path_length;
path.start = nxt_unit_sptr_get(&r->path);
- if (nxt_php_path.start == NULL) {
+ if (nxt_php_script_filename.start == NULL) {
+ ctx->path_info.start = (u_char *) strstr((char *) path.start, ".php/");
+ if (ctx->path_info.start != NULL) {
+ ctx->path_info.start += 4;
+ path.length = ctx->path_info.start - path.start;
+
+ ctx->path_info.length = r->path_length - path.length;
+ }
+
if (path.start[path.length - 1] == '/') {
script_name = nxt_php_index;
@@ -605,15 +614,20 @@ nxt_php_request_handler(nxt_unit_request_info_t *req)
script_name.start = NULL;
}
- ctx->script.length = nxt_php_root.length + path.length
- + script_name.length;
- p = ctx->script.start = nxt_malloc(ctx->script.length + 1);
+ ctx->script_filename.length = nxt_php_root.length + path.length
+ + script_name.length;
+ p = nxt_malloc(ctx->script_filename.length + 1);
if (nxt_slow_path(p == NULL)) {
nxt_unit_request_done(req, NXT_UNIT_ERROR);
return;
}
+ ctx->script_filename.start = p;
+
+ ctx->script_name.length = path.length + script_name.length;
+ ctx->script_name.start = p + nxt_php_root.length;
+
p = nxt_cpymem(p, nxt_php_root.start, nxt_php_root.length);
p = nxt_cpymem(p, path.start, path.length);
@@ -624,7 +638,8 @@ nxt_php_request_handler(nxt_unit_request_info_t *req)
*p = '\0';
} else {
- ctx->script = nxt_php_path;
+ ctx->script_filename = nxt_php_script_filename;
+ ctx->script_name = nxt_php_script_name;
}
SG(server_context) = ctx;
@@ -654,20 +669,22 @@ nxt_php_request_handler(nxt_unit_request_info_t *req)
SG(request_info).path_translated = NULL;
file_handle.type = ZEND_HANDLE_FILENAME;
- file_handle.filename = (char *) ctx->script.start;
+ file_handle.filename = (char *) ctx->script_filename.start;
file_handle.free_filename = 0;
file_handle.opened_path = NULL;
- nxt_unit_req_debug(req, "handle.filename = '%s'", ctx->script.start);
+ nxt_unit_req_debug(req, "handle.filename = '%s'",
+ ctx->script_filename.start);
- if (nxt_php_path.start != NULL) {
+ if (nxt_php_script_filename.start != NULL) {
nxt_unit_req_debug(req, "run script %.*s in absolute mode",
- (int) nxt_php_path.length,
- (char *) nxt_php_path.start);
+ (int) nxt_php_script_filename.length,
+ (char *) nxt_php_script_filename.start);
} else {
- nxt_unit_req_debug(req, "run script %.*s", (int) ctx->script.length,
- (char *) ctx->script.start);
+ nxt_unit_req_debug(req, "run script %.*s",
+ (int) ctx->script_filename.length,
+ (char *) ctx->script_filename.start);
}
#if (NXT_PHP7)
@@ -690,8 +707,8 @@ fail:
nxt_unit_request_done(req, rc);
- if (ctx->script.start != nxt_php_path.start) {
- nxt_free(ctx->script.start);
+ if (ctx->script_filename.start != nxt_php_script_filename.start) {
+ nxt_free(ctx->script_filename.start);
}
}
@@ -730,7 +747,7 @@ static int
nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
{
int rc, fields_count;
- char *colon, *status_line, *value;
+ char *colon, *value;
uint16_t status;
uint32_t resp_size;
nxt_php_run_ctx_t *ctx;
@@ -762,17 +779,7 @@ nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
resp_size += h->header_len;
}
- if (SG(sapi_headers).http_status_line) {
- status_line = SG(sapi_headers).http_status_line;
-
- status = nxt_int_parse((u_char *) status_line + 9, 3);
-
- } else if (SG(sapi_headers).http_response_code) {
- status = SG(sapi_headers).http_response_code;
-
- } else {
- status = 200;
- }
+ status = SG(sapi_headers).http_response_code;
rc = nxt_unit_response_init(req, status, fields_count, resp_size);
if (nxt_slow_path(rc != NXT_UNIT_OK)) {
@@ -783,8 +790,6 @@ nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
h;
h = zend_llist_get_next_ex(&sapi_headers->headers, &zpos))
{
- nxt_unit_req_debug(req, "header: %.*s", (int) h->header_len, h->header);
-
colon = memchr(h->header, ':', h->header_len);
if (nxt_slow_path(colon == NULL)) {
nxt_unit_req_warn(req, "colon not found in header '%.*s'",
@@ -867,49 +872,56 @@ nxt_php_register_variables(zval *track_vars_array TSRMLS_DC)
nxt_php_set_sptr(req, "SERVER_PROTOCOL", &r->version, r->version_length,
track_vars_array TSRMLS_CC);
-/*
- * 'SCRIPT_NAME'
- * Contains the current script's path. This is useful for pages which need to
- * point to themselves. The __FILE__ constant contains the full path and
- * filename of the current (i.e. included) file.
- */
-
-/*
- * 'SCRIPT_FILENAME'
- * The absolute pathname of the currently executing script.
- */
-
-/*
- * 'DOCUMENT_ROOT'
- * The document root directory under which the current script is executing,
- * as defined in the server's configuration file.
- */
+ /*
+ * 'PHP_SELF'
+ * The filename of the currently executing script, relative to the document
+ * root. For instance, $_SERVER['PHP_SELF'] in a script at the address
+ * http://example.com/foo/bar.php would be /foo/bar.php. The __FILE__
+ * constant contains the full path and filename of the current (i.e.
+ * included) file. If PHP is running as a command-line processor this
+ * variable contains the script name since PHP 4.3.0. Previously it was not
+ * available.
+ */
- if (nxt_php_script.start != NULL) {
- // ABS_MODE
-/*
- * 'PHP_SELF'
- * The filename of the currently executing script, relative to the document
- * root. For instance, $_SERVER['PHP_SELF'] in a script at the address
- * http://example.com/foo/bar.php would be /foo/bar.php. The __FILE__ constant
- * contains the full path and filename of the current (i.e. included) file.
- * If PHP is running as a command-line processor this variable contains the
- * script name since PHP 4.3.0. Previously it was not available.
- */
- nxt_php_set_str(req, "PHP_SELF", &nxt_php_script,
- track_vars_array TSRMLS_CC);
- nxt_php_set_str(req, "SCRIPT_NAME", &nxt_php_script,
+ if (nxt_php_script_name.start != NULL) {
+ /* ABS_MODE */
+ nxt_php_set_str(req, "PHP_SELF", &nxt_php_script_name,
track_vars_array TSRMLS_CC);
} else {
nxt_php_set_sptr(req, "PHP_SELF", &r->path, r->path_length,
track_vars_array TSRMLS_CC);
- nxt_php_set_sptr(req, "SCRIPT_NAME", &r->path, r->path_length,
- track_vars_array TSRMLS_CC);
}
- nxt_php_set_str(req, "SCRIPT_FILENAME", &ctx->script,
+ if (ctx->path_info.length != 0) {
+ nxt_php_set_str(req, "PATH_INFO", &ctx->path_info,
+ track_vars_array TSRMLS_CC);
+ }
+
+ /*
+ * 'SCRIPT_NAME'
+ * Contains the current script's path. This is useful for pages which need
+ * to point to themselves. The __FILE__ constant contains the full path and
+ * filename of the current (i.e. included) file.
+ */
+
+ nxt_php_set_str(req, "SCRIPT_NAME", &ctx->script_name,
track_vars_array TSRMLS_CC);
+
+ /*
+ * 'SCRIPT_FILENAME'
+ * The absolute pathname of the currently executing script.
+ */
+
+ nxt_php_set_str(req, "SCRIPT_FILENAME", &ctx->script_filename,
+ track_vars_array TSRMLS_CC);
+
+ /*
+ * 'DOCUMENT_ROOT'
+ * The document root directory under which the current script is executing,
+ * as defined in the server's configuration file.
+ */
+
nxt_php_set_str(req, "DOCUMENT_ROOT", &nxt_php_root,
track_vars_array TSRMLS_CC);
diff --git a/src/nxt_port.c b/src/nxt_port.c
index aff46666..cef65cab 100644
--- a/src/nxt_port.c
+++ b/src/nxt_port.c
@@ -68,6 +68,7 @@ nxt_port_new(nxt_task_t *task, nxt_port_id_t id, nxt_pid_t pid,
nxt_queue_init(&port->messages);
nxt_thread_mutex_create(&port->write_mutex);
nxt_queue_init(&port->pending_requests);
+ nxt_queue_init(&port->active_websockets);
} else {
nxt_mp_destroy(mp);
diff --git a/src/nxt_port.h b/src/nxt_port.h
index 76faa7d2..eeb6caa5 100644
--- a/src/nxt_port.h
+++ b/src/nxt_port.h
@@ -36,6 +36,12 @@ struct nxt_port_handlers_s {
/* Stop process command. */
nxt_port_handler_t quit;
+ /* Request headers. */
+ nxt_port_handler_t req_headers;
+
+ /* Websocket frame. */
+ nxt_port_handler_t websocket_frame;
+
/* Various data. */
nxt_port_handler_t data;
};
@@ -71,6 +77,9 @@ typedef enum {
_NXT_PORT_MSG_REMOVE_PID = nxt_port_handler_idx(remove_pid),
_NXT_PORT_MSG_QUIT = nxt_port_handler_idx(quit),
+ _NXT_PORT_MSG_REQ_HEADERS = nxt_port_handler_idx(req_headers),
+ _NXT_PORT_MSG_WEBSOCKET = nxt_port_handler_idx(websocket_frame),
+
_NXT_PORT_MSG_DATA = nxt_port_handler_idx(data),
NXT_PORT_MSG_MAX = sizeof(nxt_port_handlers_t)
@@ -99,6 +108,10 @@ typedef enum {
NXT_PORT_MSG_QUIT = _NXT_PORT_MSG_QUIT | NXT_PORT_MSG_LAST,
NXT_PORT_MSG_REMOVE_PID = _NXT_PORT_MSG_REMOVE_PID | NXT_PORT_MSG_LAST,
+ NXT_PORT_MSG_REQ_HEADERS = _NXT_PORT_MSG_REQ_HEADERS,
+ NXT_PORT_MSG_WEBSOCKET = _NXT_PORT_MSG_WEBSOCKET,
+ NXT_PORT_MSG_WEBSOCKET_LAST = _NXT_PORT_MSG_WEBSOCKET | NXT_PORT_MSG_LAST,
+
NXT_PORT_MSG_DATA = _NXT_PORT_MSG_DATA,
NXT_PORT_MSG_DATA_LAST = _NXT_PORT_MSG_DATA | NXT_PORT_MSG_LAST,
} nxt_port_msg_type_t;
@@ -134,11 +147,10 @@ typedef struct {
nxt_buf_t *buf;
size_t share;
nxt_fd_t fd;
- nxt_bool_t close_fd;
nxt_port_msg_t port_msg;
uint32_t tracking_msg[2];
-
- nxt_work_t work;
+ uint8_t close_fd; /* 1 bit */
+ uint8_t allocated; /* 1 bit */
} nxt_port_send_msg_t;
@@ -182,6 +194,8 @@ struct nxt_port_s {
uint32_t app_responses;
nxt_queue_t pending_requests;
+ nxt_queue_t active_websockets;
+
nxt_port_handler_t handler;
nxt_port_handler_t *data;
@@ -202,9 +216,6 @@ struct nxt_port_s {
nxt_atomic_t use_count;
nxt_process_type_t type;
-
- struct iovec *iov;
- void *mmsg_buf;
};
diff --git a/src/nxt_port_memory.c b/src/nxt_port_memory.c
index b908041c..b7068c88 100644
--- a/src/nxt_port_memory.c
+++ b/src/nxt_port_memory.c
@@ -798,7 +798,7 @@ nxt_port_mmap_get_incoming_buf(nxt_task_t *task, nxt_port_t *port,
void
nxt_port_mmap_write(nxt_task_t *task, nxt_port_t *port,
- nxt_port_send_msg_t *msg, nxt_sendbuf_coalesce_t *sb)
+ nxt_port_send_msg_t *msg, nxt_sendbuf_coalesce_t *sb, void *mmsg_buf)
{
size_t bsize;
nxt_buf_t *bmem;
@@ -811,7 +811,7 @@ nxt_port_mmap_write(nxt_task_t *task, nxt_port_t *port,
"via shared memory", sb->size, port->pid);
bsize = sb->niov * sizeof(nxt_port_mmap_msg_t);
- mmap_msg = port->mmsg_buf;
+ mmap_msg = mmsg_buf;
bmem = msg->buf;
@@ -841,7 +841,7 @@ nxt_port_mmap_write(nxt_task_t *task, nxt_port_t *port,
port->pid);
}
- sb->iobuf[0].iov_base = port->mmsg_buf;
+ sb->iobuf[0].iov_base = mmsg_buf;
sb->iobuf[0].iov_len = bsize;
sb->niov = 1;
sb->size = bsize;
diff --git a/src/nxt_port_memory.h b/src/nxt_port_memory.h
index c6a49ccf..748549b1 100644
--- a/src/nxt_port_memory.h
+++ b/src/nxt_port_memory.h
@@ -55,7 +55,7 @@ nxt_port_incoming_port_mmap(nxt_task_t *task, nxt_process_t *process,
void
nxt_port_mmap_write(nxt_task_t *task, nxt_port_t *port,
- nxt_port_send_msg_t *msg, nxt_sendbuf_coalesce_t *sb);
+ nxt_port_send_msg_t *msg, nxt_sendbuf_coalesce_t *sb, void *mmsg_buf);
void
nxt_port_mmap_read(nxt_task_t *task, nxt_port_recv_msg_t *msg);
diff --git a/src/nxt_port_socket.c b/src/nxt_port_socket.c
index c9b5105b..4edc423a 100644
--- a/src/nxt_port_socket.c
+++ b/src/nxt_port_socket.c
@@ -7,9 +7,15 @@
#include <nxt_main.h>
+static nxt_int_t nxt_port_msg_chk_insert(nxt_task_t *task, nxt_port_t *port,
+ nxt_port_send_msg_t *msg);
+static nxt_port_send_msg_t *nxt_port_msg_alloc(nxt_port_send_msg_t *m);
static void nxt_port_write_handler(nxt_task_t *task, void *obj, void *data);
+static nxt_port_send_msg_t *nxt_port_msg_first(nxt_port_t *port);
static nxt_buf_t *nxt_port_buf_completion(nxt_task_t *task,
nxt_work_queue_t *wq, nxt_buf_t *b, size_t sent, nxt_bool_t mmap_mode);
+static nxt_port_send_msg_t *nxt_port_msg_insert_tail(nxt_port_t *port,
+ nxt_port_send_msg_t *msg);
static void nxt_port_read_handler(nxt_task_t *task, void *obj, void *data);
static void nxt_port_read_msg_process(nxt_task_t *task, nxt_port_t *port,
nxt_port_recv_msg_t *msg);
@@ -116,13 +122,6 @@ nxt_port_write_enable(nxt_task_t *task, nxt_port_t *port)
port->socket.write_work_queue = &port->engine->fast_work_queue;
port->socket.write_handler = nxt_port_write_handler;
port->socket.error_handler = nxt_port_error_handler;
-
- if (port->iov == NULL) {
- port->iov = nxt_mp_get(port->mem_pool,
- sizeof(struct iovec) * NXT_IOBUF_MAX * 10);
- port->mmsg_buf = nxt_mp_get(port->mem_pool,
- sizeof(uint32_t) * 3 * NXT_IOBUF_MAX * 10);
- }
}
@@ -135,109 +134,11 @@ nxt_port_write_close(nxt_port_t *port)
static void
-nxt_port_release_send_msg(nxt_task_t *task, void *obj, void *data)
-{
- nxt_event_engine_t *engine;
- nxt_port_send_msg_t *msg;
-
- msg = obj;
- engine = data;
-
- nxt_assert(data == msg->work.data);
-
- if (engine != task->thread->engine) {
-
- nxt_debug(task, "current thread is %PT, expected %PT",
- task->thread->tid, engine->task.thread->tid);
-
- nxt_event_engine_post(engine, &msg->work);
-
- return;
- }
-
- nxt_mp_free(engine->mem_pool, obj);
- nxt_mp_release(engine->mem_pool);
-}
-
-
-static nxt_port_send_msg_t *
-nxt_port_msg_create(nxt_task_t *task, nxt_port_send_msg_t *m)
-{
- nxt_mp_t *mp;
- nxt_port_send_msg_t *msg;
-
- mp = task->thread->engine->mem_pool;
-
- msg = nxt_mp_alloc(mp, sizeof(nxt_port_send_msg_t));
- if (nxt_slow_path(msg == NULL)) {
- return NULL;
- }
-
- nxt_mp_retain(mp);
-
- msg->link.next = NULL;
- msg->link.prev = NULL;
-
- msg->buf = m->buf;
- msg->share = m->share;
- msg->fd = m->fd;
- msg->close_fd = m->close_fd;
- msg->port_msg = m->port_msg;
-
- msg->work.next = NULL;
- msg->work.handler = nxt_port_release_send_msg;
- msg->work.task = task;
- msg->work.obj = msg;
- msg->work.data = task->thread->engine;
-
- return msg;
-}
-
-
-static nxt_port_send_msg_t *
-nxt_port_msg_insert_head(nxt_task_t *task, nxt_port_t *port,
- nxt_port_send_msg_t *msg)
+nxt_port_release_send_msg(nxt_port_send_msg_t *msg)
{
- if (msg->work.data == NULL) {
- msg = nxt_port_msg_create(task, msg);
+ if (msg->allocated) {
+ nxt_free(msg);
}
-
- if (msg != NULL) {
- nxt_queue_insert_head(&port->messages, &msg->link);
- }
-
- return msg;
-}
-
-
-static nxt_port_send_msg_t *
-nxt_port_msg_insert_tail(nxt_task_t *task, nxt_port_t *port,
- nxt_port_send_msg_t *msg)
-{
- if (msg->work.data == NULL) {
- msg = nxt_port_msg_create(task, msg);
- }
-
- if (msg != NULL) {
- nxt_queue_insert_tail(&port->messages, &msg->link);
- }
-
- return msg;
-}
-
-
-static nxt_port_send_msg_t *
-nxt_port_msg_first(nxt_task_t *task, nxt_port_t *port, nxt_port_send_msg_t *msg)
-{
- nxt_queue_link_t *lnk;
-
- lnk = nxt_queue_first(&port->messages);
-
- if (lnk == nxt_queue_tail(&port->messages)) {
- return msg;
- }
-
- return nxt_queue_link_data(lnk, nxt_port_send_msg_t, link);
}
@@ -246,15 +147,17 @@ nxt_port_socket_twrite(nxt_task_t *task, nxt_port_t *port, nxt_uint_t type,
nxt_fd_t fd, uint32_t stream, nxt_port_id_t reply_port, nxt_buf_t *b,
void *tracking)
{
- nxt_port_send_msg_t msg, *res;
+ nxt_int_t res;
+ nxt_port_send_msg_t msg;
msg.link.next = NULL;
msg.link.prev = NULL;
msg.buf = b;
+ msg.share = 0;
msg.fd = fd;
msg.close_fd = (type & NXT_PORT_MSG_CLOSE_FD) != 0;
- msg.share = 0;
+ msg.allocated = 0;
if (tracking != NULL) {
nxt_port_mmap_tracking_write(msg.tracking_msg, tracking);
@@ -270,25 +173,63 @@ nxt_port_socket_twrite(nxt_task_t *task, nxt_port_t *port, nxt_uint_t type,
msg.port_msg.mf = 0;
msg.port_msg.tracking = tracking != NULL;
- msg.work.data = NULL;
-
- if (port->socket.write_ready) {
+ res = nxt_port_msg_chk_insert(task, port, &msg);
+ if (nxt_fast_path(res == NXT_DECLINED)) {
nxt_port_write_handler(task, &port->socket, &msg);
- } else {
- nxt_thread_mutex_lock(&port->write_mutex);
+ res = NXT_OK;
+ }
+
+ return res;
+}
+
- res = nxt_port_msg_insert_tail(task, port, &msg);
+static nxt_int_t
+nxt_port_msg_chk_insert(nxt_task_t *task, nxt_port_t *port,
+ nxt_port_send_msg_t *msg)
+{
+ nxt_int_t res;
+
+ nxt_thread_mutex_lock(&port->write_mutex);
+
+ if (nxt_fast_path(port->socket.write_ready
+ && nxt_queue_is_empty(&port->messages)))
+ {
+ res = NXT_DECLINED;
+
+ } else {
+ msg = nxt_port_msg_alloc(msg);
- nxt_thread_mutex_unlock(&port->write_mutex);
+ if (nxt_fast_path(msg != NULL)) {
+ nxt_queue_insert_tail(&port->messages, &msg->link);
+ nxt_port_use(task, port, 1);
+ res = NXT_OK;
- if (res == NULL) {
- return NXT_ERROR;
+ } else {
+ res = NXT_ERROR;
}
+ }
+
+ nxt_thread_mutex_unlock(&port->write_mutex);
+
+ return res;
+}
+
+
+static nxt_port_send_msg_t *
+nxt_port_msg_alloc(nxt_port_send_msg_t *m)
+{
+ nxt_port_send_msg_t *msg;
- nxt_port_use(task, port, 1);
+ msg = nxt_malloc(sizeof(nxt_port_send_msg_t));
+ if (nxt_slow_path(msg == NULL)) {
+ return NULL;
}
- return NXT_OK;
+ *msg = *m;
+
+ msg->allocated = 1;
+
+ return msg;
}
@@ -312,9 +253,10 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data)
int use_delta;
size_t plain_size;
ssize_t n;
+ uint32_t mmsg_buf[3 * NXT_IOBUF_MAX * 10];
nxt_bool_t block_write, enable_write;
nxt_port_t *port;
- struct iovec *iov;
+ struct iovec iov[NXT_IOBUF_MAX * 10];
nxt_work_queue_t *wq;
nxt_port_method_t m;
nxt_port_send_msg_t *msg;
@@ -326,20 +268,23 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data)
enable_write = 0;
use_delta = 0;
- nxt_thread_mutex_lock(&port->write_mutex);
-
- iov = port->iov;
-
wq = &task->thread->engine->fast_work_queue;
do {
- msg = nxt_port_msg_first(task, port, data);
+ if (data) {
+ msg = data;
- if (msg == NULL) {
- block_write = 1;
- goto unlock_mutex;
+ } else {
+ msg = nxt_port_msg_first(port);
+
+ if (msg == NULL) {
+ block_write = 1;
+ goto cleanup;
+ }
}
+next_fragment:
+
iov[0].iov_base = &msg->port_msg;
iov[0].iov_len = sizeof(nxt_port_msg_t);
@@ -377,7 +322,7 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data)
* is bigger than PORT_MMAP_MIN_SIZE.
*/
if (m == NXT_PORT_METHOD_MMAP && plain_size > PORT_MMAP_MIN_SIZE) {
- nxt_port_mmap_write(task, port, msg, &sb);
+ nxt_port_mmap_write(task, port, msg, &sb, mmsg_buf);
} else {
m = NXT_PORT_METHOD_PLAIN;
@@ -402,7 +347,7 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data)
}
msg->buf = nxt_port_buf_completion(task, wq, msg->buf, plain_size,
- m == NXT_PORT_METHOD_MMAP);
+ m == NXT_PORT_METHOD_MMAP);
if (msg->buf != NULL) {
nxt_debug(task, "port %d: frag stream #%uD", port->socket.fd,
@@ -421,36 +366,58 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data)
msg->share = 0;
if (msg->link.next != NULL) {
+ nxt_thread_mutex_lock(&port->write_mutex);
+
nxt_queue_remove(&msg->link);
- use_delta--;
- }
- data = NULL;
+ nxt_queue_insert_tail(&port->messages, &msg->link);
+
+ nxt_thread_mutex_unlock(&port->write_mutex);
+
+ } else {
+ msg = nxt_port_msg_insert_tail(port, msg);
+ if (nxt_slow_path(msg == NULL)) {
+ goto fail;
+ }
- if (nxt_port_msg_insert_tail(task, port, msg) != NULL) {
use_delta++;
}
+
+ } else {
+ goto next_fragment;
}
} else {
if (msg->link.next != NULL) {
+ nxt_thread_mutex_lock(&port->write_mutex);
+
nxt_queue_remove(&msg->link);
+ msg->link.next = NULL;
+
+ nxt_thread_mutex_unlock(&port->write_mutex);
+
use_delta--;
- nxt_work_queue_add(wq, nxt_port_release_send_msg, task, msg,
- msg->work.data);
}
- data = NULL;
+
+ nxt_port_release_send_msg(msg);
}
- } else {
- if (msg->link.next == NULL) {
- if (nxt_port_msg_insert_head(task, port, msg) != NULL) {
- use_delta++;
- }
+ if (data != NULL) {
+ goto cleanup;
}
+ } else {
if (nxt_slow_path(n == NXT_ERROR)) {
goto fail;
}
+
+ if (msg->link.next == NULL) {
+ msg = nxt_port_msg_insert_tail(port, msg);
+ if (nxt_slow_path(msg == NULL)) {
+ goto fail;
+ }
+
+ use_delta++;
+ }
}
} while (port->socket.write_ready);
@@ -459,7 +426,7 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data)
enable_write = 1;
}
- goto unlock_mutex;
+ goto cleanup;
fail:
@@ -468,8 +435,7 @@ fail:
nxt_work_queue_add(wq, nxt_port_error_handler, task, &port->socket,
&port->socket);
-unlock_mutex:
- nxt_thread_mutex_unlock(&port->write_mutex);
+cleanup:
if (block_write && nxt_fd_event_is_active(port->socket.write)) {
nxt_port_post(task, port, nxt_port_fd_block_write, NULL);
@@ -485,6 +451,29 @@ unlock_mutex:
}
+static nxt_port_send_msg_t *
+nxt_port_msg_first(nxt_port_t *port)
+{
+ nxt_queue_link_t *lnk;
+ nxt_port_send_msg_t *msg;
+
+ nxt_thread_mutex_lock(&port->write_mutex);
+
+ lnk = nxt_queue_first(&port->messages);
+
+ if (lnk == nxt_queue_tail(&port->messages)) {
+ msg = NULL;
+
+ } else {
+ msg = nxt_queue_link_data(lnk, nxt_port_send_msg_t, link);
+ }
+
+ nxt_thread_mutex_unlock(&port->write_mutex);
+
+ return msg;
+}
+
+
static nxt_buf_t *
nxt_port_buf_completion(nxt_task_t *task, nxt_work_queue_t *wq, nxt_buf_t *b,
size_t sent, nxt_bool_t mmap_mode)
@@ -546,6 +535,27 @@ nxt_port_buf_completion(nxt_task_t *task, nxt_work_queue_t *wq, nxt_buf_t *b,
}
+static nxt_port_send_msg_t *
+nxt_port_msg_insert_tail(nxt_port_t *port, nxt_port_send_msg_t *msg)
+{
+ if (msg->allocated == 0) {
+ msg = nxt_port_msg_alloc(msg);
+
+ if (nxt_slow_path(msg == NULL)) {
+ return NULL;
+ }
+ }
+
+ nxt_thread_mutex_lock(&port->write_mutex);
+
+ nxt_queue_insert_tail(&port->messages, &msg->link);
+
+ nxt_thread_mutex_unlock(&port->write_mutex);
+
+ return msg;
+}
+
+
void
nxt_port_read_enable(nxt_task_t *task, nxt_port_t *port)
{
@@ -668,7 +678,7 @@ nxt_port_lvlhsh_frag_test(nxt_lvlhsh_query_t *lhq, void *data)
static void *
nxt_port_lvlhsh_frag_alloc(void *ctx, size_t size)
{
- return nxt_mp_alloc(ctx, size);
+ return nxt_mp_align(ctx, size, size);
}
@@ -986,8 +996,8 @@ nxt_port_error_handler(nxt_task_t *task, void *obj, void *data)
nxt_queue_remove(&msg->link);
use_delta--;
- nxt_work_queue_add(wq, nxt_port_release_send_msg, task, msg,
- msg->work.data);
+
+ nxt_port_release_send_msg(msg);
} nxt_queue_loop;
diff --git a/src/nxt_router.c b/src/nxt_router.c
index 149a0ff3..b87f588f 100644
--- a/src/nxt_router.c
+++ b/src/nxt_router.c
@@ -14,7 +14,7 @@
#include <nxt_port_memory_int.h>
#include <nxt_unit_request.h>
#include <nxt_unit_response.h>
-
+#include <nxt_router_request.h>
typedef struct {
nxt_str_t type;
@@ -48,51 +48,6 @@ typedef struct {
#endif
-typedef struct nxt_msg_info_s {
- nxt_buf_t *buf;
- nxt_port_mmap_tracking_t tracking;
- nxt_work_handler_t completion_handler;
-} nxt_msg_info_t;
-
-
-typedef struct nxt_req_app_link_s nxt_req_app_link_t;
-
-
-typedef struct {
- uint32_t stream;
- nxt_app_t *app;
- nxt_port_t *app_port;
- nxt_http_request_t *request;
- nxt_msg_info_t msg_info;
- nxt_req_app_link_t *ra;
-
- nxt_queue_link_t link; /* for nxt_conn_t.requests */
-} nxt_req_conn_link_t;
-
-
-struct nxt_req_app_link_s {
- uint32_t stream;
- nxt_atomic_t use_count;
- nxt_port_t *app_port;
- nxt_port_t *reply_port;
- nxt_http_request_t *request;
- nxt_msg_info_t msg_info;
- nxt_req_conn_link_t *rc;
-
- nxt_nsec_t res_time;
-
- nxt_queue_link_t link_app_requests; /* for nxt_app_t.requests */
- nxt_queue_link_t link_port_pending; /* for nxt_port_t.pending_requests */
- nxt_queue_link_t link_app_pending; /* for nxt_app_t.pending */
-
- nxt_mp_t *mem_pool;
- nxt_work_t work;
-
- int err_code;
- const char *err_str;
-};
-
-
typedef struct {
nxt_socket_conf_t *socket_conf;
nxt_router_temp_conf_t *temp_conf;
@@ -106,15 +61,15 @@ typedef struct {
struct nxt_port_select_state_s {
- nxt_app_t *app;
- nxt_req_app_link_t *ra;
+ nxt_app_t *app;
+ nxt_request_app_link_t *req_app_link;
- nxt_port_t *failed_port;
- int failed_port_use_delta;
+ nxt_port_t *failed_port;
+ int failed_port_use_delta;
- uint8_t start_process; /* 1 bit */
- nxt_req_app_link_t *shared_ra;
- nxt_port_t *port;
+ uint8_t start_process; /* 1 bit */
+ nxt_request_app_link_t *shared_ra;
+ nxt_port_t *port;
};
typedef struct nxt_port_select_state_s nxt_port_select_state_t;
@@ -129,28 +84,32 @@ static nxt_int_t nxt_router_port_post_select(nxt_task_t *task,
nxt_port_select_state_t *state);
static nxt_int_t nxt_router_start_app_process(nxt_task_t *task, nxt_app_t *app);
+static void nxt_request_app_link_update_peer(nxt_task_t *task,
+ nxt_request_app_link_t *req_app_link);
+
nxt_inline void
-nxt_router_ra_inc_use(nxt_req_app_link_t *ra)
+nxt_request_app_link_inc_use(nxt_request_app_link_t *req_app_link)
{
- nxt_atomic_fetch_add(&ra->use_count, 1);
+ nxt_atomic_fetch_add(&req_app_link->use_count, 1);
}
nxt_inline void
-nxt_router_ra_dec_use(nxt_req_app_link_t *ra)
+nxt_request_app_link_dec_use(nxt_request_app_link_t *req_app_link)
{
#if (NXT_DEBUG)
int c;
- c = nxt_atomic_fetch_add(&ra->use_count, -1);
+ c = nxt_atomic_fetch_add(&req_app_link->use_count, -1);
nxt_assert(c > 1);
#else
- (void) nxt_atomic_fetch_add(&ra->use_count, -1);
+ (void) nxt_atomic_fetch_add(&req_app_link->use_count, -1);
#endif
}
-static void nxt_router_ra_use(nxt_task_t *task, nxt_req_app_link_t *ra, int i);
+static void nxt_request_app_link_use(nxt_task_t *task,
+ nxt_request_app_link_t *req_app_link, int i);
static nxt_router_temp_conf_t *nxt_router_temp_conf(nxt_task_t *task);
static void nxt_router_conf_apply(nxt_task_t *task, void *obj, void *data);
@@ -257,13 +216,14 @@ static void nxt_router_app_port_error(nxt_task_t *task,
nxt_port_recv_msg_t *msg, void *data);
static void nxt_router_app_unlink(nxt_task_t *task, nxt_app_t *app);
+
static void nxt_router_app_port_release(nxt_task_t *task, nxt_port_t *port,
- uint32_t request_failed, uint32_t got_response);
+ nxt_apr_action_t action);
static nxt_int_t nxt_router_app_port(nxt_task_t *task, nxt_app_t *app,
- nxt_req_app_link_t *ra);
+ nxt_request_app_link_t *req_app_link);
static void nxt_router_app_prepare_request(nxt_task_t *task,
- nxt_req_app_link_t *ra);
+ nxt_request_app_link_t *req_app_link);
static nxt_buf_t *nxt_router_prepare_msg(nxt_task_t *task,
nxt_http_request_t *r, nxt_port_t *port, const nxt_str_t *prefix);
@@ -287,6 +247,8 @@ static nxt_int_t nxt_router_http_request_done(nxt_task_t *task,
static void nxt_router_http_request_release(nxt_task_t *task, void *obj,
void *data);
+const nxt_http_request_state_t nxt_http_websocket;
+
static nxt_router_t *nxt_router;
static const nxt_str_t http_prefix = nxt_string("HTTP_");
@@ -493,58 +455,63 @@ nxt_router_start_app_process(nxt_task_t *task, nxt_app_t *app)
nxt_inline void
-nxt_router_ra_init(nxt_task_t *task, nxt_req_app_link_t *ra,
- nxt_req_conn_link_t *rc)
+nxt_request_app_link_init(nxt_task_t *task,
+ nxt_request_app_link_t *req_app_link, nxt_request_rpc_data_t *req_rpc_data)
{
nxt_event_engine_t *engine;
engine = task->thread->engine;
- nxt_memzero(ra, sizeof(nxt_req_app_link_t));
+ nxt_memzero(req_app_link, sizeof(nxt_request_app_link_t));
- ra->stream = rc->stream;
- ra->use_count = 1;
- ra->rc = rc;
- rc->ra = ra;
- ra->reply_port = engine->port;
- ra->request = rc->request;
+ req_app_link->stream = req_rpc_data->stream;
+ req_app_link->use_count = 1;
+ req_app_link->req_rpc_data = req_rpc_data;
+ req_rpc_data->req_app_link = req_app_link;
+ req_app_link->reply_port = engine->port;
+ req_app_link->request = req_rpc_data->request;
+ req_app_link->apr_action = NXT_APR_GOT_RESPONSE;
- ra->work.handler = NULL;
- ra->work.task = &engine->task;
- ra->work.obj = ra;
- ra->work.data = engine;
+ req_app_link->work.handler = NULL;
+ req_app_link->work.task = &engine->task;
+ req_app_link->work.obj = req_app_link;
+ req_app_link->work.data = engine;
}
-nxt_inline nxt_req_app_link_t *
-nxt_router_ra_create(nxt_task_t *task, nxt_req_app_link_t *ra_src)
+nxt_inline nxt_request_app_link_t *
+nxt_request_app_link_alloc(nxt_task_t *task,
+ nxt_request_app_link_t *ra_src, nxt_request_rpc_data_t *req_rpc_data)
{
- nxt_mp_t *mp;
- nxt_req_app_link_t *ra;
+ nxt_mp_t *mp;
+ nxt_request_app_link_t *req_app_link;
- if (ra_src->mem_pool != NULL) {
+ if (ra_src != NULL && ra_src->mem_pool != NULL) {
return ra_src;
}
- mp = ra_src->request->mem_pool;
+ mp = req_rpc_data->request->mem_pool;
- ra = nxt_mp_alloc(mp, sizeof(nxt_req_app_link_t));
+ req_app_link = nxt_mp_alloc(mp, sizeof(nxt_request_app_link_t));
- if (nxt_slow_path(ra == NULL)) {
+ if (nxt_slow_path(req_app_link == NULL)) {
- ra_src->rc->ra = NULL;
- ra_src->rc = NULL;
+ req_rpc_data->req_app_link = NULL;
+
+ if (ra_src != NULL) {
+ ra_src->req_rpc_data = NULL;
+ }
return NULL;
}
nxt_mp_retain(mp);
- nxt_router_ra_init(task, ra, ra_src->rc);
+ nxt_request_app_link_init(task, req_app_link, req_rpc_data);
- ra->mem_pool = mp;
+ req_app_link->mem_pool = mp;
- return ra;
+ return req_app_link;
}
@@ -584,177 +551,189 @@ nxt_router_msg_cancel(nxt_task_t *task, nxt_msg_info_t *msg_info,
static void
-nxt_router_ra_update_peer(nxt_task_t *task, nxt_req_app_link_t *ra);
-
-
-static void
-nxt_router_ra_update_peer_handler(nxt_task_t *task, void *obj, void *data)
+nxt_request_app_link_update_peer_handler(nxt_task_t *task, void *obj,
+ void *data)
{
- nxt_req_app_link_t *ra;
+ nxt_request_app_link_t *req_app_link;
- ra = obj;
+ req_app_link = obj;
- nxt_router_ra_update_peer(task, ra);
+ nxt_request_app_link_update_peer(task, req_app_link);
- nxt_router_ra_use(task, ra, -1);
+ nxt_request_app_link_use(task, req_app_link, -1);
}
static void
-nxt_router_ra_update_peer(nxt_task_t *task, nxt_req_app_link_t *ra)
+nxt_request_app_link_update_peer(nxt_task_t *task,
+ nxt_request_app_link_t *req_app_link)
{
- nxt_event_engine_t *engine;
- nxt_req_conn_link_t *rc;
+ nxt_event_engine_t *engine;
+ nxt_request_rpc_data_t *req_rpc_data;
- engine = ra->work.data;
+ engine = req_app_link->work.data;
if (task->thread->engine != engine) {
- nxt_router_ra_inc_use(ra);
+ nxt_request_app_link_inc_use(req_app_link);
- ra->work.handler = nxt_router_ra_update_peer_handler;
- ra->work.task = &engine->task;
- ra->work.next = NULL;
+ req_app_link->work.handler = nxt_request_app_link_update_peer_handler;
+ req_app_link->work.task = &engine->task;
+ req_app_link->work.next = NULL;
- nxt_debug(task, "ra stream #%uD post update peer to %p",
- ra->stream, engine);
+ nxt_debug(task, "req_app_link stream #%uD post update peer to %p",
+ req_app_link->stream, engine);
- nxt_event_engine_post(engine, &ra->work);
+ nxt_event_engine_post(engine, &req_app_link->work);
return;
}
- nxt_debug(task, "ra stream #%uD update peer", ra->stream);
+ nxt_debug(task, "req_app_link stream #%uD update peer",
+ req_app_link->stream);
- rc = ra->rc;
+ req_rpc_data = req_app_link->req_rpc_data;
- if (rc != NULL && ra->app_port != NULL) {
- nxt_port_rpc_ex_set_peer(task, engine->port, rc, ra->app_port->pid);
+ if (req_rpc_data != NULL && req_app_link->app_port != NULL) {
+ nxt_port_rpc_ex_set_peer(task, engine->port, req_rpc_data,
+ req_app_link->app_port->pid);
}
- nxt_router_ra_use(task, ra, -1);
+ nxt_request_app_link_use(task, req_app_link, -1);
}
static void
-nxt_router_ra_release(nxt_task_t *task, nxt_req_app_link_t *ra)
+nxt_request_app_link_release(nxt_task_t *task,
+ nxt_request_app_link_t *req_app_link)
{
nxt_mp_t *mp;
- nxt_req_conn_link_t *rc;
+ nxt_http_request_t *r;
+ nxt_request_rpc_data_t *req_rpc_data;
- nxt_assert(task->thread->engine == ra->work.data);
- nxt_assert(ra->use_count == 0);
+ nxt_assert(task->thread->engine == req_app_link->work.data);
+ nxt_assert(req_app_link->use_count == 0);
- nxt_debug(task, "ra stream #%uD release", ra->stream);
+ nxt_debug(task, "req_app_link stream #%uD release", req_app_link->stream);
- rc = ra->rc;
+ req_rpc_data = req_app_link->req_rpc_data;
- if (rc != NULL) {
- if (nxt_slow_path(ra->err_code != 0)) {
- nxt_http_request_error(task, rc->request, ra->err_code);
+ if (req_rpc_data != NULL) {
+ if (nxt_slow_path(req_app_link->err_code != 0)) {
+ nxt_http_request_error(task, req_rpc_data->request,
+ req_app_link->err_code);
} else {
- rc->app_port = ra->app_port;
- rc->msg_info = ra->msg_info;
-
- if (rc->app->timeout != 0) {
- rc->request->timer.handler = nxt_router_app_timeout;
- rc->request->timer_data = rc;
- nxt_timer_add(task->thread->engine, &rc->request->timer,
- rc->app->timeout);
+ req_rpc_data->app_port = req_app_link->app_port;
+ req_rpc_data->apr_action = req_app_link->apr_action;
+ req_rpc_data->msg_info = req_app_link->msg_info;
+
+ if (req_rpc_data->app->timeout != 0) {
+ r = req_rpc_data->request;
+
+ r->timer.handler = nxt_router_app_timeout;
+ r->timer_data = req_rpc_data;
+ nxt_timer_add(task->thread->engine, &r->timer,
+ req_rpc_data->app->timeout);
}
- ra->app_port = NULL;
- ra->msg_info.buf = NULL;
+ req_app_link->app_port = NULL;
+ req_app_link->msg_info.buf = NULL;
}
- rc->ra = NULL;
- ra->rc = NULL;
+ req_rpc_data->req_app_link = NULL;
+ req_app_link->req_rpc_data = NULL;
}
- if (ra->app_port != NULL) {
- nxt_router_app_port_release(task, ra->app_port, 0, 1);
+ if (req_app_link->app_port != NULL) {
+ nxt_router_app_port_release(task, req_app_link->app_port,
+ req_app_link->apr_action);
- ra->app_port = NULL;
+ req_app_link->app_port = NULL;
}
- nxt_router_msg_cancel(task, &ra->msg_info, ra->stream);
+ nxt_router_msg_cancel(task, &req_app_link->msg_info, req_app_link->stream);
- mp = ra->mem_pool;
+ mp = req_app_link->mem_pool;
if (mp != NULL) {
- nxt_mp_free(mp, ra);
+ nxt_mp_free(mp, req_app_link);
nxt_mp_release(mp);
}
}
static void
-nxt_router_ra_release_handler(nxt_task_t *task, void *obj, void *data)
+nxt_request_app_link_release_handler(nxt_task_t *task, void *obj, void *data)
{
- nxt_req_app_link_t *ra;
+ nxt_request_app_link_t *req_app_link;
- ra = obj;
+ req_app_link = obj;
- nxt_assert(ra->work.data == data);
+ nxt_assert(req_app_link->work.data == data);
- nxt_atomic_fetch_add(&ra->use_count, -1);
+ nxt_atomic_fetch_add(&req_app_link->use_count, -1);
- nxt_router_ra_release(task, ra);
+ nxt_request_app_link_release(task, req_app_link);
}
static void
-nxt_router_ra_use(nxt_task_t *task, nxt_req_app_link_t *ra, int i)
+nxt_request_app_link_use(nxt_task_t *task, nxt_request_app_link_t *req_app_link,
+ int i)
{
int c;
nxt_event_engine_t *engine;
- c = nxt_atomic_fetch_add(&ra->use_count, i);
+ c = nxt_atomic_fetch_add(&req_app_link->use_count, i);
if (i < 0 && c == -i) {
- engine = ra->work.data;
+ engine = req_app_link->work.data;
if (task->thread->engine == engine) {
- nxt_router_ra_release(task, ra);
+ nxt_request_app_link_release(task, req_app_link);
return;
}
- nxt_router_ra_inc_use(ra);
+ nxt_request_app_link_inc_use(req_app_link);
- ra->work.handler = nxt_router_ra_release_handler;
- ra->work.task = &engine->task;
- ra->work.next = NULL;
+ req_app_link->work.handler = nxt_request_app_link_release_handler;
+ req_app_link->work.task = &engine->task;
+ req_app_link->work.next = NULL;
- nxt_debug(task, "ra stream #%uD post release to %p",
- ra->stream, engine);
+ nxt_debug(task, "req_app_link stream #%uD post release to %p",
+ req_app_link->stream, engine);
- nxt_event_engine_post(engine, &ra->work);
+ nxt_event_engine_post(engine, &req_app_link->work);
}
}
nxt_inline void
-nxt_router_ra_error(nxt_req_app_link_t *ra, int code, const char *str)
+nxt_request_app_link_error(nxt_request_app_link_t *req_app_link, int code,
+ const char *str)
{
- ra->app_port = NULL;
- ra->err_code = code;
- ra->err_str = str;
+ req_app_link->app_port = NULL;
+ req_app_link->err_code = code;
+ req_app_link->err_str = str;
}
nxt_inline void
-nxt_router_ra_pending(nxt_task_t *task, nxt_app_t *app, nxt_req_app_link_t *ra)
+nxt_request_app_link_pending(nxt_task_t *task, nxt_app_t *app,
+ nxt_request_app_link_t *req_app_link)
{
- nxt_queue_insert_tail(&ra->app_port->pending_requests,
- &ra->link_port_pending);
- nxt_queue_insert_tail(&app->pending, &ra->link_app_pending);
+ nxt_queue_insert_tail(&req_app_link->app_port->pending_requests,
+ &req_app_link->link_port_pending);
+ nxt_queue_insert_tail(&app->pending, &req_app_link->link_app_pending);
- nxt_router_ra_inc_use(ra);
+ nxt_request_app_link_inc_use(req_app_link);
- ra->res_time = nxt_thread_monotonic_time(task->thread) + app->res_timeout;
+ req_app_link->res_time = nxt_thread_monotonic_time(task->thread)
+ + app->res_timeout;
- nxt_debug(task, "ra stream #%uD enqueue to pending_requests", ra->stream);
+ nxt_debug(task, "req_app_link stream #%uD enqueue to pending_requests",
+ req_app_link->stream);
}
@@ -774,60 +753,66 @@ nxt_queue_chk_remove(nxt_queue_link_t *lnk)
nxt_inline void
-nxt_router_rc_unlink(nxt_task_t *task, nxt_req_conn_link_t *rc)
+nxt_request_rpc_data_unlink(nxt_task_t *task,
+ nxt_request_rpc_data_t *req_rpc_data)
{
- int ra_use_delta;
- nxt_req_app_link_t *ra;
+ int ra_use_delta;
+ nxt_request_app_link_t *req_app_link;
- if (rc->app_port != NULL) {
- nxt_router_app_port_release(task, rc->app_port, 0, 1);
+ if (req_rpc_data->app_port != NULL) {
+ nxt_router_app_port_release(task, req_rpc_data->app_port,
+ req_rpc_data->apr_action);
- rc->app_port = NULL;
+ req_rpc_data->app_port = NULL;
}
- nxt_router_msg_cancel(task, &rc->msg_info, rc->stream);
-
- ra = rc->ra;
+ nxt_router_msg_cancel(task, &req_rpc_data->msg_info, req_rpc_data->stream);
- if (ra != NULL) {
- rc->ra = NULL;
- ra->rc = NULL;
+ req_app_link = req_rpc_data->req_app_link;
+ if (req_app_link != NULL) {
+ req_rpc_data->req_app_link = NULL;
+ req_app_link->req_rpc_data = NULL;
ra_use_delta = 0;
- nxt_thread_mutex_lock(&rc->app->mutex);
+ nxt_thread_mutex_lock(&req_rpc_data->app->mutex);
- if (ra->link_app_requests.next == NULL
- && ra->link_port_pending.next == NULL
- && ra->link_app_pending.next == NULL)
+ if (req_app_link->link_app_requests.next == NULL
+ && req_app_link->link_port_pending.next == NULL
+ && req_app_link->link_app_pending.next == NULL
+ && req_app_link->link_port_websockets.next == NULL)
{
- ra = NULL;
+ req_app_link = NULL;
} else {
- ra_use_delta -= nxt_queue_chk_remove(&ra->link_app_requests);
- ra_use_delta -= nxt_queue_chk_remove(&ra->link_port_pending);
- nxt_queue_chk_remove(&ra->link_app_pending);
+ ra_use_delta -=
+ nxt_queue_chk_remove(&req_app_link->link_app_requests)
+ + nxt_queue_chk_remove(&req_app_link->link_port_pending)
+ + nxt_queue_chk_remove(&req_app_link->link_port_websockets);
+
+ nxt_queue_chk_remove(&req_app_link->link_app_pending);
}
- nxt_thread_mutex_unlock(&rc->app->mutex);
+ nxt_thread_mutex_unlock(&req_rpc_data->app->mutex);
- if (ra != NULL) {
- nxt_router_ra_use(task, ra, ra_use_delta);
+ if (req_app_link != NULL) {
+ nxt_request_app_link_use(task, req_app_link, ra_use_delta);
}
}
- if (rc->app != NULL) {
- nxt_router_app_use(task, rc->app, -1);
+ if (req_rpc_data->app != NULL) {
+ nxt_router_app_use(task, req_rpc_data->app, -1);
- rc->app = NULL;
+ req_rpc_data->app = NULL;
}
- if (rc->request != NULL) {
- rc->request->timer_data = NULL;
+ if (req_rpc_data->request != NULL) {
+ req_rpc_data->request->timer_data = NULL;
- nxt_router_http_request_done(task, rc->request);
+ nxt_router_http_request_done(task, req_rpc_data->request);
- rc->request = NULL;
+ req_rpc_data->request->req_rpc_data = NULL;
+ req_rpc_data->request = NULL;
}
}
@@ -928,10 +913,6 @@ nxt_router_remove_pid_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg)
nxt_port_remove_pid_handler(task, msg);
- if (msg->port_msg.stream == 0) {
- return;
- }
-
nxt_queue_each(engine, &nxt_router->engines, nxt_event_engine_t, link0)
{
nxt_port_post(task, engine->port, nxt_router_app_process_remove_pid,
@@ -939,6 +920,10 @@ nxt_router_remove_pid_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg)
}
nxt_queue_loop;
+ if (msg->port_msg.stream == 0) {
+ return;
+ }
+
msg->port_msg.type = _NXT_PORT_MSG_RPC_ERROR;
nxt_port_rpc_handler(task, msg);
@@ -1376,6 +1361,28 @@ static nxt_conf_map_t nxt_router_http_conf[] = {
};
+static nxt_conf_map_t nxt_router_websocket_conf[] = {
+ {
+ nxt_string("max_frame_size"),
+ NXT_CONF_MAP_SIZE,
+ offsetof(nxt_websocket_conf_t, max_frame_size),
+ },
+
+ {
+ nxt_string("read_timeout"),
+ NXT_CONF_MAP_MSEC,
+ offsetof(nxt_websocket_conf_t, read_timeout),
+ },
+
+ {
+ nxt_string("keepalive_interval"),
+ NXT_CONF_MAP_MSEC,
+ offsetof(nxt_websocket_conf_t, keepalive_interval),
+ },
+
+};
+
+
static nxt_int_t
nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
u_char *start, u_char *end)
@@ -1389,7 +1396,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
nxt_app_t *app, *prev;
nxt_router_t *router;
nxt_app_joint_t *app_joint;
- nxt_conf_value_t *conf, *http, *value;
+ nxt_conf_value_t *conf, *http, *value, *websocket;
nxt_conf_value_t *applications, *application;
nxt_conf_value_t *listeners, *listener;
nxt_conf_value_t *routes_conf;
@@ -1412,6 +1419,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
#if (NXT_TLS)
static nxt_str_t certificate_path = nxt_string("/tls/certificate");
#endif
+ static nxt_str_t websocket_path = nxt_string("/settings/http/websocket");
conf = nxt_conf_json_parse(tmcf->mem_pool, start, end, NULL);
if (conf == NULL) {
@@ -1432,177 +1440,177 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
tmcf->router_conf->threads = nxt_ncpu;
}
- applications = nxt_conf_get_path(conf, &applications_path);
- if (applications == NULL) {
- nxt_alert(task, "no \"applications\" block");
- return NXT_ERROR;
- }
-
router = tmcf->router_conf->router;
- next = 0;
+ applications = nxt_conf_get_path(conf, &applications_path);
- for ( ;; ) {
- application = nxt_conf_next_object_member(applications, &name, &next);
- if (application == NULL) {
- break;
- }
+ if (applications != NULL) {
+ next = 0;
- nxt_debug(task, "application \"%V\"", &name);
+ for ( ;; ) {
+ application = nxt_conf_next_object_member(applications, &name, &next);
+ if (application == NULL) {
+ break;
+ }
- size = nxt_conf_json_length(application, NULL);
+ nxt_debug(task, "application \"%V\"", &name);
- app = nxt_malloc(sizeof(nxt_app_t) + name.length + size);
- if (app == NULL) {
- goto fail;
- }
+ size = nxt_conf_json_length(application, NULL);
- nxt_memzero(app, sizeof(nxt_app_t));
+ app = nxt_malloc(sizeof(nxt_app_t) + name.length + size);
+ if (app == NULL) {
+ goto fail;
+ }
- app->name.start = nxt_pointer_to(app, sizeof(nxt_app_t));
- app->conf.start = nxt_pointer_to(app, sizeof(nxt_app_t) + name.length);
+ nxt_memzero(app, sizeof(nxt_app_t));
- p = nxt_conf_json_print(app->conf.start, application, NULL);
- app->conf.length = p - app->conf.start;
+ app->name.start = nxt_pointer_to(app, sizeof(nxt_app_t));
+ app->conf.start = nxt_pointer_to(app, sizeof(nxt_app_t)
+ + name.length);
- nxt_assert(app->conf.length <= size);
+ p = nxt_conf_json_print(app->conf.start, application, NULL);
+ app->conf.length = p - app->conf.start;
- nxt_debug(task, "application conf \"%V\"", &app->conf);
+ nxt_assert(app->conf.length <= size);
- prev = nxt_router_app_find(&router->apps, &name);
+ nxt_debug(task, "application conf \"%V\"", &app->conf);
- if (prev != NULL && nxt_strstr_eq(&app->conf, &prev->conf)) {
- nxt_free(app);
+ prev = nxt_router_app_find(&router->apps, &name);
- nxt_queue_remove(&prev->link);
- nxt_queue_insert_tail(&tmcf->previous, &prev->link);
- continue;
- }
+ if (prev != NULL && nxt_strstr_eq(&app->conf, &prev->conf)) {
+ nxt_free(app);
- apcf.processes = 1;
- apcf.max_processes = 1;
- apcf.spare_processes = 0;
- apcf.timeout = 0;
- apcf.res_timeout = 1000;
- apcf.idle_timeout = 15000;
- apcf.requests = 0;
- apcf.limits_value = NULL;
- apcf.processes_value = NULL;
-
- app_joint = nxt_malloc(sizeof(nxt_app_joint_t));
- if (nxt_slow_path(app_joint == NULL)) {
- goto app_fail;
- }
-
- nxt_memzero(app_joint, sizeof(nxt_app_joint_t));
-
- ret = nxt_conf_map_object(mp, application, nxt_router_app_conf,
- nxt_nitems(nxt_router_app_conf), &apcf);
- if (ret != NXT_OK) {
- nxt_alert(task, "application map error");
- goto app_fail;
- }
-
- if (apcf.limits_value != NULL) {
+ nxt_queue_remove(&prev->link);
+ nxt_queue_insert_tail(&tmcf->previous, &prev->link);
+ continue;
+ }
- if (nxt_conf_type(apcf.limits_value) != NXT_CONF_OBJECT) {
- nxt_alert(task, "application limits is not object");
+ apcf.processes = 1;
+ apcf.max_processes = 1;
+ apcf.spare_processes = 0;
+ apcf.timeout = 0;
+ apcf.res_timeout = 1000;
+ apcf.idle_timeout = 15000;
+ apcf.requests = 0;
+ apcf.limits_value = NULL;
+ apcf.processes_value = NULL;
+
+ app_joint = nxt_malloc(sizeof(nxt_app_joint_t));
+ if (nxt_slow_path(app_joint == NULL)) {
goto app_fail;
}
- ret = nxt_conf_map_object(mp, apcf.limits_value,
- nxt_router_app_limits_conf,
- nxt_nitems(nxt_router_app_limits_conf),
- &apcf);
+ nxt_memzero(app_joint, sizeof(nxt_app_joint_t));
+
+ ret = nxt_conf_map_object(mp, application, nxt_router_app_conf,
+ nxt_nitems(nxt_router_app_conf), &apcf);
if (ret != NXT_OK) {
- nxt_alert(task, "application limits map error");
+ nxt_alert(task, "application map error");
goto app_fail;
}
- }
- if (apcf.processes_value != NULL
- && nxt_conf_type(apcf.processes_value) == NXT_CONF_OBJECT)
- {
- ret = nxt_conf_map_object(mp, apcf.processes_value,
- nxt_router_app_processes_conf,
- nxt_nitems(nxt_router_app_processes_conf),
- &apcf);
- if (ret != NXT_OK) {
- nxt_alert(task, "application processes map error");
- goto app_fail;
+ if (apcf.limits_value != NULL) {
+
+ if (nxt_conf_type(apcf.limits_value) != NXT_CONF_OBJECT) {
+ nxt_alert(task, "application limits is not object");
+ goto app_fail;
+ }
+
+ ret = nxt_conf_map_object(mp, apcf.limits_value,
+ nxt_router_app_limits_conf,
+ nxt_nitems(nxt_router_app_limits_conf),
+ &apcf);
+ if (ret != NXT_OK) {
+ nxt_alert(task, "application limits map error");
+ goto app_fail;
+ }
}
- } else {
- apcf.max_processes = apcf.processes;
- apcf.spare_processes = apcf.processes;
- }
+ if (apcf.processes_value != NULL
+ && nxt_conf_type(apcf.processes_value) == NXT_CONF_OBJECT)
+ {
+ ret = nxt_conf_map_object(mp, apcf.processes_value,
+ nxt_router_app_processes_conf,
+ nxt_nitems(nxt_router_app_processes_conf),
+ &apcf);
+ if (ret != NXT_OK) {
+ nxt_alert(task, "application processes map error");
+ goto app_fail;
+ }
- nxt_debug(task, "application type: %V", &apcf.type);
- nxt_debug(task, "application processes: %D", apcf.processes);
- nxt_debug(task, "application request timeout: %M", apcf.timeout);
- nxt_debug(task, "application reschedule timeout: %M", apcf.res_timeout);
- nxt_debug(task, "application requests: %D", apcf.requests);
+ } else {
+ apcf.max_processes = apcf.processes;
+ apcf.spare_processes = apcf.processes;
+ }
- lang = nxt_app_lang_module(task->thread->runtime, &apcf.type);
+ nxt_debug(task, "application type: %V", &apcf.type);
+ nxt_debug(task, "application processes: %D", apcf.processes);
+ nxt_debug(task, "application request timeout: %M", apcf.timeout);
+ nxt_debug(task, "application reschedule timeout: %M",
+ apcf.res_timeout);
+ nxt_debug(task, "application requests: %D", apcf.requests);
- if (lang == NULL) {
- nxt_alert(task, "unknown application type: \"%V\"", &apcf.type);
- goto app_fail;
- }
+ lang = nxt_app_lang_module(task->thread->runtime, &apcf.type);
- nxt_debug(task, "application language module: \"%s\"", lang->file);
+ if (lang == NULL) {
+ nxt_alert(task, "unknown application type: \"%V\"", &apcf.type);
+ goto app_fail;
+ }
- ret = nxt_thread_mutex_create(&app->mutex);
- if (ret != NXT_OK) {
- goto app_fail;
- }
+ nxt_debug(task, "application language module: \"%s\"", lang->file);
- nxt_queue_init(&app->ports);
- nxt_queue_init(&app->spare_ports);
- nxt_queue_init(&app->idle_ports);
- nxt_queue_init(&app->requests);
- nxt_queue_init(&app->pending);
+ ret = nxt_thread_mutex_create(&app->mutex);
+ if (ret != NXT_OK) {
+ goto app_fail;
+ }
- app->name.length = name.length;
- nxt_memcpy(app->name.start, name.start, name.length);
+ nxt_queue_init(&app->ports);
+ nxt_queue_init(&app->spare_ports);
+ nxt_queue_init(&app->idle_ports);
+ nxt_queue_init(&app->requests);
+ nxt_queue_init(&app->pending);
- app->type = lang->type;
- app->max_processes = apcf.max_processes;
- app->spare_processes = apcf.spare_processes;
- app->max_pending_processes = apcf.spare_processes
- ? apcf.spare_processes : 1;
- app->timeout = apcf.timeout;
- app->res_timeout = apcf.res_timeout * 1000000;
- app->idle_timeout = apcf.idle_timeout;
- app->max_pending_responses = 2;
- app->max_requests = apcf.requests;
+ app->name.length = name.length;
+ nxt_memcpy(app->name.start, name.start, name.length);
- engine = task->thread->engine;
+ app->type = lang->type;
+ app->max_processes = apcf.max_processes;
+ app->spare_processes = apcf.spare_processes;
+ app->max_pending_processes = apcf.spare_processes
+ ? apcf.spare_processes : 1;
+ app->timeout = apcf.timeout;
+ app->res_timeout = apcf.res_timeout * 1000000;
+ app->idle_timeout = apcf.idle_timeout;
+ app->max_pending_responses = 2;
+ app->max_requests = apcf.requests;
- app->engine = engine;
+ engine = task->thread->engine;
- app->adjust_idle_work.handler = nxt_router_adjust_idle_timer;
- app->adjust_idle_work.task = &engine->task;
- app->adjust_idle_work.obj = app;
+ app->engine = engine;
- nxt_queue_insert_tail(&tmcf->apps, &app->link);
+ app->adjust_idle_work.handler = nxt_router_adjust_idle_timer;
+ app->adjust_idle_work.task = &engine->task;
+ app->adjust_idle_work.obj = app;
- nxt_router_app_use(task, app, 1);
+ nxt_queue_insert_tail(&tmcf->apps, &app->link);
- app->joint = app_joint;
+ nxt_router_app_use(task, app, 1);
- app_joint->use_count = 1;
- app_joint->app = app;
+ app->joint = app_joint;
- app_joint->idle_timer.bias = NXT_TIMER_DEFAULT_BIAS;
- app_joint->idle_timer.work_queue = &engine->fast_work_queue;
- app_joint->idle_timer.handler = nxt_router_app_idle_timeout;
- app_joint->idle_timer.task = &engine->task;
- app_joint->idle_timer.log = app_joint->idle_timer.task->log;
+ app_joint->use_count = 1;
+ app_joint->app = app;
- app_joint->free_app_work.handler = nxt_router_free_app;
- app_joint->free_app_work.task = &engine->task;
- app_joint->free_app_work.obj = app_joint;
+ app_joint->idle_timer.bias = NXT_TIMER_DEFAULT_BIAS;
+ app_joint->idle_timer.work_queue = &engine->fast_work_queue;
+ app_joint->idle_timer.handler = nxt_router_app_idle_timeout;
+ app_joint->idle_timer.task = &engine->task;
+ app_joint->idle_timer.log = app_joint->idle_timer.task->log;
+
+ app_joint->free_app_work.handler = nxt_router_free_app;
+ app_joint->free_app_work.task = &engine->task;
+ app_joint->free_app_work.obj = app_joint;
+ }
}
routes_conf = nxt_conf_get_path(conf, &routes_path);
@@ -1622,87 +1630,102 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
}
#endif
- listeners = nxt_conf_get_path(conf, &listeners_path);
- if (listeners == NULL) {
- nxt_alert(task, "no \"listeners\" block");
- return NXT_ERROR;
- }
+ websocket = nxt_conf_get_path(conf, &websocket_path);
- next = 0;
+ listeners = nxt_conf_get_path(conf, &listeners_path);
- for ( ;; ) {
- listener = nxt_conf_next_object_member(listeners, &name, &next);
- if (listener == NULL) {
- break;
- }
+ if (listeners != NULL) {
+ next = 0;
- skcf = nxt_router_socket_conf(task, tmcf, &name);
- if (skcf == NULL) {
- goto fail;
- }
+ for ( ;; ) {
+ listener = nxt_conf_next_object_member(listeners, &name, &next);
+ if (listener == NULL) {
+ break;
+ }
- nxt_memzero(&lscf, sizeof(lscf));
+ skcf = nxt_router_socket_conf(task, tmcf, &name);
+ if (skcf == NULL) {
+ goto fail;
+ }
- ret = nxt_conf_map_object(mp, listener, nxt_router_listener_conf,
- nxt_nitems(nxt_router_listener_conf), &lscf);
- if (ret != NXT_OK) {
- nxt_alert(task, "listener map error");
- goto fail;
- }
+ nxt_memzero(&lscf, sizeof(lscf));
- nxt_debug(task, "application: %V", &lscf.application);
-
- // STUB, default values if http block is not defined.
- skcf->header_buffer_size = 2048;
- skcf->large_header_buffer_size = 8192;
- skcf->large_header_buffers = 4;
- skcf->body_buffer_size = 16 * 1024;
- skcf->max_body_size = 8 * 1024 * 1024;
- skcf->idle_timeout = 180 * 1000;
- skcf->header_read_timeout = 30 * 1000;
- skcf->body_read_timeout = 30 * 1000;
- skcf->send_timeout = 30 * 1000;
-
- if (http != NULL) {
- ret = nxt_conf_map_object(mp, http, nxt_router_http_conf,
- nxt_nitems(nxt_router_http_conf), skcf);
+ ret = nxt_conf_map_object(mp, listener, nxt_router_listener_conf,
+ nxt_nitems(nxt_router_listener_conf),
+ &lscf);
if (ret != NXT_OK) {
- nxt_alert(task, "http map error");
+ nxt_alert(task, "listener map error");
goto fail;
}
- }
-#if (NXT_TLS)
+ nxt_debug(task, "application: %V", &lscf.application);
+
+ // STUB, default values if http block is not defined.
+ skcf->header_buffer_size = 2048;
+ skcf->large_header_buffer_size = 8192;
+ skcf->large_header_buffers = 4;
+ skcf->body_buffer_size = 16 * 1024;
+ skcf->max_body_size = 8 * 1024 * 1024;
+ skcf->idle_timeout = 180 * 1000;
+ skcf->header_read_timeout = 30 * 1000;
+ skcf->body_read_timeout = 30 * 1000;
+ skcf->send_timeout = 30 * 1000;
+
+ skcf->websocket_conf.max_frame_size = 1024 * 1024;
+ skcf->websocket_conf.read_timeout = 60 * 1000;
+ skcf->websocket_conf.keepalive_interval = 30 * 1000;
+
+ if (http != NULL) {
+ ret = nxt_conf_map_object(mp, http, nxt_router_http_conf,
+ nxt_nitems(nxt_router_http_conf),
+ skcf);
+ if (ret != NXT_OK) {
+ nxt_alert(task, "http map error");
+ goto fail;
+ }
+ }
- value = nxt_conf_get_path(listener, &certificate_path);
+ 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 (value != NULL) {
- nxt_conf_get_string(value, &name);
+#if (NXT_TLS)
+ value = nxt_conf_get_path(listener, &certificate_path);
- tls = nxt_mp_get(mp, sizeof(nxt_router_tlssock_t));
- if (nxt_slow_path(tls == NULL)) {
- goto fail;
- }
+ if (value != NULL) {
+ nxt_conf_get_string(value, &name);
- tls->name = name;
- tls->conf = skcf;
+ tls = nxt_mp_get(mp, sizeof(nxt_router_tlssock_t));
+ if (nxt_slow_path(tls == NULL)) {
+ goto fail;
+ }
- nxt_queue_insert_tail(&tmcf->tls, &tls->link);
- }
+ tls->name = name;
+ tls->conf = skcf;
+ nxt_queue_insert_tail(&tmcf->tls, &tls->link);
+ }
#endif
- skcf->listen->handler = nxt_http_conn_init;
- skcf->router_conf = tmcf->router_conf;
- skcf->router_conf->count++;
+ skcf->listen->handler = nxt_http_conn_init;
+ skcf->router_conf = tmcf->router_conf;
+ skcf->router_conf->count++;
- if (lscf.pass.length != 0) {
- skcf->pass = nxt_http_pass_create(task, tmcf, &lscf.pass);
+ if (lscf.pass.length != 0) {
+ skcf->pass = nxt_http_pass_create(task, tmcf, &lscf.pass);
- /* COMPATIBILITY: listener application. */
- } else if (lscf.application.length > 0) {
- skcf->pass = nxt_http_pass_application(task, tmcf,
- &lscf.application);
+ /* COMPATIBILITY: listener application. */
+ } else if (lscf.application.length > 0) {
+ skcf->pass = nxt_http_pass_application(task, tmcf,
+ &lscf.application);
+ }
}
}
@@ -3058,7 +3081,7 @@ nxt_router_access_log_writer(nxt_task_t *task, nxt_http_request_t *r,
*p++ = ' ';
- bytes = nxt_http_proto_body_bytes_sent[r->protocol](task, r->proto);
+ bytes = nxt_http_proto[r->protocol].body_bytes_sent(task, r->proto);
p = nxt_sprintf(p, p + NXT_OFF_T_LEN, "%O", bytes);
@@ -3382,28 +3405,30 @@ static void
nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
void *data)
{
- nxt_int_t ret;
- nxt_buf_t *b;
- nxt_unit_field_t *f;
- nxt_http_field_t *field;
- nxt_http_request_t *r;
- nxt_req_conn_link_t *rc;
- nxt_unit_response_t *resp;
+ nxt_int_t ret;
+ nxt_buf_t *b;
+ nxt_port_t *app_port;
+ nxt_unit_field_t *f;
+ nxt_http_field_t *field;
+ nxt_http_request_t *r;
+ nxt_unit_response_t *resp;
+ nxt_request_app_link_t *req_app_link;
+ nxt_request_rpc_data_t *req_rpc_data;
b = msg->buf;
- rc = data;
+ req_rpc_data = data;
if (msg->size == 0) {
b = NULL;
}
- r = rc->request;
+ r = req_rpc_data->request;
if (nxt_slow_path(r == NULL)) {
return;
}
if (r->error) {
- nxt_router_rc_unlink(task, rc);
+ nxt_request_rpc_data_unlink(task, req_rpc_data);
return;
}
@@ -3412,13 +3437,14 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
nxt_buf_chain_add(&b, nxt_http_buf_last(r));
- nxt_router_rc_unlink(task, rc);
+ nxt_request_rpc_data_unlink(task, req_rpc_data);
} else {
- if (rc->app != NULL && rc->app->timeout != 0) {
+ if (req_rpc_data->app != NULL && req_rpc_data->app->timeout != 0) {
r->timer.handler = nxt_router_app_timeout;
- r->timer_data = rc;
- nxt_timer_add(task->thread->engine, &r->timer, rc->app->timeout);
+ r->timer_data = req_rpc_data;
+ nxt_timer_add(task->thread->engine, &r->timer,
+ req_rpc_data->app->timeout);
}
}
@@ -3448,7 +3474,13 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
goto fail;
}
+ field = NULL;
+
for (f = resp->fields; f < resp->fields + resp->fields_count; f++) {
+ if (f->skip) {
+ continue;
+ }
+
field = nxt_list_add(r->resp.fields);
if (nxt_slow_path(field == NULL)) {
@@ -3456,26 +3488,30 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
}
field->hash = f->hash;
- field->skip = f->skip;
+ field->skip = 0;
field->name_length = f->name_length;
field->value_length = f->value_length;
field->name = nxt_unit_sptr_get(&f->name);
field->value = nxt_unit_sptr_get(&f->value);
- nxt_debug(task, "header: %*s: %*s",
+ ret = nxt_http_field_process(field, &nxt_response_fields_hash, r);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ goto fail;
+ }
+
+ nxt_debug(task, "header%s: %*s: %*s",
+ (field->skip ? " skipped" : ""),
(size_t) field->name_length, field->name,
(size_t) field->value_length, field->value);
+
+ if (field->skip) {
+ r->resp.fields->last->nelts--;
+ }
}
r->status = resp->status;
- ret = nxt_http_fields_process(r->resp.fields,
- &nxt_response_fields_hash, r);
- if (nxt_slow_path(ret != NXT_OK)) {
- goto fail;
- }
-
if (resp->piggyback_content_length != 0) {
b->mem.pos = nxt_unit_sptr_get(&resp->piggyback_content);
b->mem.free = b->mem.pos + resp->piggyback_content_length;
@@ -3495,9 +3531,55 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
nxt_buf_chain_add(&r->out, b);
}
- r->state = &nxt_http_request_send_state;
-
nxt_http_request_header_send(task, r);
+
+ if (r->websocket_handshake
+ && r->status == NXT_HTTP_SWITCHING_PROTOCOLS)
+ {
+ req_app_link = nxt_request_app_link_alloc(task,
+ req_rpc_data->req_app_link,
+ req_rpc_data);
+ if (nxt_slow_path(req_app_link == NULL)) {
+ goto fail;
+ }
+
+ app_port = req_app_link->app_port;
+
+ if (app_port == NULL && req_rpc_data->app_port != NULL) {
+ req_app_link->app_port = req_rpc_data->app_port;
+ app_port = req_app_link->app_port;
+ req_app_link->apr_action = req_rpc_data->apr_action;
+
+ req_rpc_data->app_port = NULL;
+ }
+
+ if (nxt_slow_path(app_port == NULL)) {
+ goto fail;
+ }
+
+ nxt_thread_mutex_lock(&req_rpc_data->app->mutex);
+
+ nxt_queue_insert_tail(&app_port->active_websockets,
+ &req_app_link->link_port_websockets);
+
+ nxt_thread_mutex_unlock(&req_rpc_data->app->mutex);
+
+ nxt_router_app_port_release(task, app_port, NXT_APR_UPGRADE);
+ req_app_link->apr_action = NXT_APR_CLOSE;
+
+ nxt_debug(task, "req_app_link stream #%uD upgrade",
+ req_app_link->stream);
+
+ r->state = &nxt_http_websocket;
+
+ } else {
+ r->state = &nxt_http_request_send_state;
+ }
+
+ if (r->out) {
+ nxt_work_queue_add(&task->thread->engine->fast_work_queue,
+ nxt_http_request_send_body, task, r, NULL);
+ }
}
return;
@@ -3506,14 +3588,13 @@ fail:
nxt_http_request_error(task, r, NXT_HTTP_SERVICE_UNAVAILABLE);
- nxt_router_rc_unlink(task, rc);
+ nxt_request_rpc_data_unlink(task, req_rpc_data);
}
static const nxt_http_request_state_t nxt_http_request_send_state
nxt_aligned(64) =
{
- .ready_handler = nxt_http_request_send_body,
.error_handler = nxt_http_request_error_handler,
};
@@ -3539,36 +3620,37 @@ static void
nxt_router_response_error_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
void *data)
{
- nxt_int_t res;
- nxt_port_t *port;
- nxt_bool_t cancelled;
- nxt_req_app_link_t *ra;
- nxt_req_conn_link_t *rc;
-
- rc = data;
+ nxt_int_t res;
+ nxt_port_t *port;
+ nxt_bool_t cancelled;
+ nxt_request_app_link_t *req_app_link;
+ nxt_request_rpc_data_t *req_rpc_data;
- ra = rc->ra;
+ req_rpc_data = data;
- if (ra != NULL) {
- cancelled = nxt_router_msg_cancel(task, &ra->msg_info, ra->stream);
+ req_app_link = req_rpc_data->req_app_link;
+ if (req_app_link != NULL) {
+ cancelled = nxt_router_msg_cancel(task, &req_app_link->msg_info,
+ req_app_link->stream);
if (cancelled) {
- nxt_router_ra_inc_use(ra);
+ nxt_request_app_link_inc_use(req_app_link);
- res = nxt_router_app_port(task, rc->app, ra);
+ res = nxt_router_app_port(task, req_rpc_data->app, req_app_link);
if (res == NXT_OK) {
- port = ra->app_port;
+ port = req_app_link->app_port;
if (nxt_slow_path(port == NULL)) {
- nxt_log(task, NXT_LOG_ERR, "port is NULL in cancelled ra");
+ nxt_log(task, NXT_LOG_ERR,
+ "port is NULL in cancelled req_app_link");
return;
}
- nxt_port_rpc_ex_set_peer(task, task->thread->engine->port, rc,
- port->pid);
+ nxt_port_rpc_ex_set_peer(task, task->thread->engine->port,
+ req_rpc_data, port->pid);
- nxt_router_app_prepare_request(task, ra);
+ nxt_router_app_prepare_request(task, req_app_link);
}
msg->port_msg.last = 0;
@@ -3577,12 +3659,12 @@ nxt_router_response_error_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
}
}
- if (rc->request != NULL) {
- nxt_http_request_error(task, rc->request,
+ if (req_rpc_data->request != NULL) {
+ nxt_http_request_error(task, req_rpc_data->request,
NXT_HTTP_SERVICE_UNAVAILABLE);
}
- nxt_router_rc_unlink(task, rc);
+ nxt_request_rpc_data_unlink(task, req_rpc_data);
}
@@ -3626,7 +3708,7 @@ nxt_router_app_port_ready(nxt_task_t *task, nxt_port_recv_msg_t *msg,
nxt_debug(task, "app '%V' new port ready, pid %PI, %d/%d",
&app->name, port->pid, app->processes, app->pending_processes);
- nxt_router_app_port_release(task, port, 0, 0);
+ nxt_router_app_port_release(task, port, NXT_APR_NEW_PORT);
}
@@ -3634,10 +3716,10 @@ static void
nxt_router_app_port_error(nxt_task_t *task, nxt_port_recv_msg_t *msg,
void *data)
{
- nxt_app_t *app;
- nxt_app_joint_t *app_joint;
- nxt_queue_link_t *lnk;
- nxt_req_app_link_t *ra;
+ nxt_app_t *app;
+ nxt_app_joint_t *app_joint;
+ nxt_queue_link_t *lnk;
+ nxt_request_app_link_t *req_app_link;
app_joint = data;
@@ -3666,20 +3748,22 @@ nxt_router_app_port_error(nxt_task_t *task, nxt_port_recv_msg_t *msg,
nxt_queue_remove(lnk);
lnk->next = NULL;
- ra = nxt_queue_link_data(lnk, nxt_req_app_link_t, link_app_requests);
+ req_app_link = nxt_queue_link_data(lnk, nxt_request_app_link_t,
+ link_app_requests);
} else {
- ra = NULL;
+ req_app_link = NULL;
}
nxt_thread_mutex_unlock(&app->mutex);
- if (ra != NULL) {
+ if (req_app_link != NULL) {
nxt_debug(task, "app '%V' %p abort next stream #%uD",
- &app->name, app, ra->stream);
+ &app->name, app, req_app_link->stream);
- nxt_router_ra_error(ra, 500, "Failed to start application process");
- nxt_router_ra_use(task, ra, -1);
+ nxt_request_app_link_error(req_app_link, 500,
+ "Failed to start application process");
+ nxt_request_app_link_use(task, req_app_link, -1);
}
}
@@ -3813,9 +3897,9 @@ nxt_router_app_unlink(nxt_task_t *task, nxt_app_t *app)
static void
nxt_router_app_process_request(nxt_task_t *task, void *obj, void *data)
{
- nxt_req_app_link_t *ra;
+ nxt_request_app_link_t *req_app_link;
- ra = data;
+ req_app_link = data;
#if (NXT_DEBUG)
{
@@ -3824,39 +3908,66 @@ nxt_router_app_process_request(nxt_task_t *task, void *obj, void *data)
app = obj;
nxt_assert(app != NULL);
- nxt_assert(ra != NULL);
- nxt_assert(ra->app_port != NULL);
+ nxt_assert(req_app_link != NULL);
+ nxt_assert(req_app_link->app_port != NULL);
nxt_debug(task, "app '%V' %p process next stream #%uD",
- &app->name, app, ra->stream);
+ &app->name, app, req_app_link->stream);
}
#endif
- nxt_router_app_prepare_request(task, ra);
+ nxt_router_app_prepare_request(task, req_app_link);
}
static void
nxt_router_app_port_release(nxt_task_t *task, nxt_port_t *port,
- uint32_t request_failed, uint32_t got_response)
+ nxt_apr_action_t action)
{
+ int inc_use;
+ uint32_t dec_pending, got_response;
nxt_app_t *app;
nxt_bool_t port_unchained;
nxt_bool_t send_quit, cancelled, adjust_idle_timer;
nxt_queue_link_t *lnk;
- nxt_req_app_link_t *ra, *pending_ra, *re_ra;
+ nxt_request_app_link_t *req_app_link, *pending_ra, *re_ra;
nxt_port_select_state_t state;
nxt_assert(port != NULL);
nxt_assert(port->app != NULL);
- ra = NULL;
+ req_app_link = NULL;
app = port->app;
+ inc_use = 0;
+ dec_pending = 0;
+ got_response = 0;
+
+ switch (action) {
+ case NXT_APR_NEW_PORT:
+ break;
+ case NXT_APR_REQUEST_FAILED:
+ dec_pending = 1;
+ inc_use = -1;
+ break;
+ case NXT_APR_GOT_RESPONSE:
+ dec_pending = 1;
+ got_response = 1;
+ inc_use = -1;
+ break;
+ case NXT_APR_UPGRADE:
+ dec_pending = 1;
+ got_response = 1;
+ break;
+ case NXT_APR_CLOSE:
+ inc_use = -1;
+ break;
+ }
+
nxt_thread_mutex_lock(&app->mutex);
- port->app_pending_responses -= request_failed + got_response;
+ port->app_pending_responses -= dec_pending;
port->app_responses += got_response;
if (port->pair[1] != -1
@@ -3893,24 +4004,25 @@ nxt_router_app_port_release(nxt_task_t *task, nxt_port_t *port,
nxt_queue_remove(lnk);
lnk->next = NULL;
- ra = nxt_queue_link_data(lnk, nxt_req_app_link_t, link_app_requests);
+ req_app_link = nxt_queue_link_data(lnk, nxt_request_app_link_t,
+ link_app_requests);
- ra->app_port = nxt_router_pop_first_port(app);
+ req_app_link->app_port = nxt_router_pop_first_port(app);
- if (ra->app_port->app_pending_responses > 1) {
- nxt_router_ra_pending(task, app, ra);
+ if (req_app_link->app_port->app_pending_responses > 1) {
+ nxt_request_app_link_pending(task, app, req_app_link);
}
}
/* Pop first pending request for this port. */
- if ((request_failed > 0 || got_response > 0)
+ if (dec_pending > 0
&& !nxt_queue_is_empty(&port->pending_requests))
{
lnk = nxt_queue_first(&port->pending_requests);
nxt_queue_remove(lnk);
lnk->next = NULL;
- pending_ra = nxt_queue_link_data(lnk, nxt_req_app_link_t,
+ pending_ra = nxt_queue_link_data(lnk, nxt_request_app_link_t,
link_port_pending);
nxt_assert(pending_ra->link_app_pending.next != NULL);
@@ -3926,7 +4038,8 @@ nxt_router_app_port_release(nxt_task_t *task, nxt_port_t *port,
if (got_response > 0 && !nxt_queue_is_empty(&app->pending)) {
lnk = nxt_queue_first(&app->pending);
- re_ra = nxt_queue_link_data(lnk, nxt_req_app_link_t, link_app_pending);
+ re_ra = nxt_queue_link_data(lnk, nxt_request_app_link_t,
+ link_app_pending);
if (re_ra->res_time <= nxt_thread_monotonic_time(task->thread)) {
@@ -3937,9 +4050,9 @@ nxt_router_app_port_release(nxt_task_t *task, nxt_port_t *port,
re_ra->stream);
if (cancelled) {
- nxt_router_ra_inc_use(re_ra);
+ nxt_request_app_link_inc_use(re_ra);
- state.ra = re_ra;
+ state.req_app_link = re_ra;
state.app = app;
nxt_router_port_select(task, &state);
@@ -3969,9 +4082,10 @@ re_ra_cancelled:
adjust_idle_timer = 0;
- if (port->pair[1] != -1 && !send_quit && port->app_pending_responses == 0) {
- nxt_assert(port->idle_link.next == NULL);
-
+ if (port->pair[1] != -1 && !send_quit && port->app_pending_responses == 0
+ && nxt_queue_is_empty(&port->active_websockets)
+ && port->idle_link.next == NULL)
+ {
if (app->idle_processes == app->spare_processes
&& app->adjust_idle_work.data == NULL)
{
@@ -4000,7 +4114,7 @@ re_ra_cancelled:
}
if (pending_ra != NULL) {
- nxt_router_ra_use(task, pending_ra, -1);
+ nxt_request_app_link_use(task, pending_ra, -1);
}
if (re_ra != NULL) {
@@ -4011,12 +4125,12 @@ re_ra_cancelled:
}
}
- if (ra != NULL) {
- nxt_router_ra_use(task, ra, -1);
+ if (req_app_link != NULL) {
+ nxt_request_app_link_use(task, req_app_link, -1);
nxt_work_queue_add(&task->thread->engine->fast_work_queue,
nxt_router_app_process_request,
- &task->thread->engine->task, app, ra);
+ &task->thread->engine->task, app, req_app_link);
goto adjust_use;
}
@@ -4048,9 +4162,7 @@ re_ra_cancelled:
adjust_use:
- if (request_failed > 0 || got_response > 0) {
- nxt_port_use(task, port, -1);
- }
+ nxt_port_use(task, port, inc_use);
}
@@ -4267,33 +4379,33 @@ nxt_router_free_app(nxt_task_t *task, void *obj, void *data)
static void
nxt_router_port_select(nxt_task_t *task, nxt_port_select_state_t *state)
{
- nxt_app_t *app;
- nxt_bool_t can_start_process;
- nxt_req_app_link_t *ra;
+ nxt_app_t *app;
+ nxt_bool_t can_start_process;
+ nxt_request_app_link_t *req_app_link;
- ra = state->ra;
+ req_app_link = state->req_app_link;
app = state->app;
state->failed_port_use_delta = 0;
- if (nxt_queue_chk_remove(&ra->link_app_requests))
+ if (nxt_queue_chk_remove(&req_app_link->link_app_requests))
{
- nxt_router_ra_dec_use(ra);
+ nxt_request_app_link_dec_use(req_app_link);
}
- if (nxt_queue_chk_remove(&ra->link_port_pending))
+ if (nxt_queue_chk_remove(&req_app_link->link_port_pending))
{
- nxt_assert(ra->link_app_pending.next != NULL);
+ nxt_assert(req_app_link->link_app_pending.next != NULL);
- nxt_queue_remove(&ra->link_app_pending);
- ra->link_app_pending.next = NULL;
+ nxt_queue_remove(&req_app_link->link_app_pending);
+ req_app_link->link_app_pending.next = NULL;
- nxt_router_ra_dec_use(ra);
+ nxt_request_app_link_dec_use(req_app_link);
}
- state->failed_port = ra->app_port;
+ state->failed_port = req_app_link->app_port;
- if (ra->app_port != NULL) {
+ if (req_app_link->app_port != NULL) {
state->failed_port_use_delta--;
state->failed_port->app_pending_responses--;
@@ -4302,7 +4414,7 @@ nxt_router_port_select(nxt_task_t *task, nxt_port_select_state_t *state)
state->failed_port_use_delta--;
}
- ra->app_port = NULL;
+ req_app_link->app_port = NULL;
}
can_start_process = nxt_router_app_can_start(app);
@@ -4313,22 +4425,25 @@ nxt_router_port_select(nxt_task_t *task, nxt_port_select_state_t *state)
if (nxt_queue_is_empty(&app->ports)
|| (can_start_process && nxt_router_app_first_port_busy(app)) )
{
- ra = nxt_router_ra_create(task, ra);
-
- if (nxt_slow_path(ra == NULL)) {
+ req_app_link = nxt_request_app_link_alloc(task, req_app_link,
+ req_app_link->req_rpc_data);
+ if (nxt_slow_path(req_app_link == NULL)) {
goto fail;
}
if (nxt_slow_path(state->failed_port != NULL)) {
- nxt_queue_insert_head(&app->requests, &ra->link_app_requests);
+ nxt_queue_insert_head(&app->requests,
+ &req_app_link->link_app_requests);
} else {
- nxt_queue_insert_tail(&app->requests, &ra->link_app_requests);
+ nxt_queue_insert_tail(&app->requests,
+ &req_app_link->link_app_requests);
}
- nxt_router_ra_inc_use(ra);
+ nxt_request_app_link_inc_use(req_app_link);
- nxt_debug(task, "ra stream #%uD enqueue to app->requests", ra->stream);
+ nxt_debug(task, "req_app_link stream #%uD enqueue to app->requests",
+ req_app_link->stream);
if (can_start_process) {
app->pending_processes++;
@@ -4339,15 +4454,15 @@ nxt_router_port_select(nxt_task_t *task, nxt_port_select_state_t *state)
state->port = nxt_router_pop_first_port(app);
if (state->port->app_pending_responses > 1) {
- ra = nxt_router_ra_create(task, ra);
-
- if (nxt_slow_path(ra == NULL)) {
+ req_app_link = nxt_request_app_link_alloc(task, req_app_link,
+ req_app_link->req_rpc_data);
+ if (nxt_slow_path(req_app_link == NULL)) {
goto fail;
}
- ra->app_port = state->port;
+ req_app_link->app_port = state->port;
- nxt_router_ra_pending(task, app, ra);
+ nxt_request_app_link_pending(task, app, req_app_link);
}
if (can_start_process && nxt_router_app_need_start(app)) {
@@ -4358,32 +4473,32 @@ nxt_router_port_select(nxt_task_t *task, nxt_port_select_state_t *state)
fail:
- state->shared_ra = ra;
+ state->shared_ra = req_app_link;
}
static nxt_int_t
nxt_router_port_post_select(nxt_task_t *task, nxt_port_select_state_t *state)
{
- nxt_int_t res;
- nxt_app_t *app;
- nxt_req_app_link_t *ra;
+ nxt_int_t res;
+ nxt_app_t *app;
+ nxt_request_app_link_t *req_app_link;
- ra = state->shared_ra;
+ req_app_link = state->shared_ra;
app = state->app;
if (state->failed_port_use_delta != 0) {
nxt_port_use(task, state->failed_port, state->failed_port_use_delta);
}
- if (nxt_slow_path(ra == NULL)) {
+ if (nxt_slow_path(req_app_link == NULL)) {
if (state->port != NULL) {
nxt_port_use(task, state->port, -1);
}
- nxt_router_ra_error(state->ra, 500,
+ nxt_request_app_link_error(state->req_app_link, 500,
"Failed to allocate shared req<->app link");
- nxt_router_ra_use(task, state->ra, -1);
+ nxt_request_app_link_use(task, state->req_app_link, -1);
return NXT_ERROR;
}
@@ -4391,7 +4506,7 @@ nxt_router_port_post_select(nxt_task_t *task, nxt_port_select_state_t *state)
if (state->port != NULL) {
nxt_debug(task, "already have port for app '%V' %p ", &app->name, app);
- ra->app_port = state->port;
+ req_app_link->app_port = state->port;
if (state->start_process) {
nxt_router_start_app_process(task, app);
@@ -4410,8 +4525,9 @@ nxt_router_port_post_select(nxt_task_t *task, nxt_port_select_state_t *state)
res = nxt_router_start_app_process(task, app);
if (nxt_slow_path(res != NXT_OK)) {
- nxt_router_ra_error(ra, 500, "Failed to start app process");
- nxt_router_ra_use(task, ra, -1);
+ nxt_request_app_link_error(req_app_link, 500,
+ "Failed to start app process");
+ nxt_request_app_link_use(task, req_app_link, -1);
return NXT_ERROR;
}
@@ -4421,11 +4537,12 @@ nxt_router_port_post_select(nxt_task_t *task, nxt_port_select_state_t *state)
static nxt_int_t
-nxt_router_app_port(nxt_task_t *task, nxt_app_t *app, nxt_req_app_link_t *ra)
+nxt_router_app_port(nxt_task_t *task, nxt_app_t *app,
+ nxt_request_app_link_t *req_app_link)
{
nxt_port_select_state_t state;
- state.ra = ra;
+ state.req_app_link = req_app_link;
state.app = app;
nxt_thread_mutex_lock(&app->mutex);
@@ -4442,48 +4559,48 @@ void
nxt_router_process_http_request(nxt_task_t *task, nxt_http_request_t *r,
nxt_app_t *app)
{
- nxt_int_t res;
- nxt_port_t *port;
- nxt_event_engine_t *engine;
- nxt_req_app_link_t ra_local, *ra;
- nxt_req_conn_link_t *rc;
+ nxt_int_t res;
+ nxt_port_t *port;
+ nxt_event_engine_t *engine;
+ nxt_request_app_link_t ra_local, *req_app_link;
+ nxt_request_rpc_data_t *req_rpc_data;
engine = task->thread->engine;
- rc = nxt_port_rpc_register_handler_ex(task, engine->port,
+ req_rpc_data = nxt_port_rpc_register_handler_ex(task, engine->port,
nxt_router_response_ready_handler,
nxt_router_response_error_handler,
- sizeof(nxt_req_conn_link_t));
-
- if (nxt_slow_path(rc == NULL)) {
+ sizeof(nxt_request_rpc_data_t));
+ if (nxt_slow_path(req_rpc_data == NULL)) {
nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
return;
}
- rc->stream = nxt_port_rpc_ex_stream(rc);
- rc->app = app;
+ req_rpc_data->stream = nxt_port_rpc_ex_stream(req_rpc_data);
+ req_rpc_data->app = app;
nxt_router_app_use(task, app, 1);
- rc->request = r;
+ req_rpc_data->request = r;
+ r->req_rpc_data = req_rpc_data;
- ra = &ra_local;
- nxt_router_ra_init(task, ra, rc);
+ req_app_link = &ra_local;
+ nxt_request_app_link_init(task, req_app_link, req_rpc_data);
- res = nxt_router_app_port(task, app, ra);
+ res = nxt_router_app_port(task, app, req_app_link);
if (res != NXT_OK) {
return;
}
- ra = rc->ra;
- port = ra->app_port;
+ req_app_link = req_rpc_data->req_app_link;
+ port = req_app_link->app_port;
nxt_assert(port != NULL);
- nxt_port_rpc_ex_set_peer(task, engine->port, rc, port->pid);
+ nxt_port_rpc_ex_set_peer(task, engine->port, req_rpc_data, port->pid);
- nxt_router_app_prepare_request(task, ra);
+ nxt_router_app_prepare_request(task, req_app_link);
}
@@ -4494,19 +4611,20 @@ nxt_router_dummy_buf_completion(nxt_task_t *task, void *obj, void *data)
static void
-nxt_router_app_prepare_request(nxt_task_t *task, nxt_req_app_link_t *ra)
+nxt_router_app_prepare_request(nxt_task_t *task,
+ nxt_request_app_link_t *req_app_link)
{
- uint32_t request_failed;
- nxt_buf_t *buf;
- nxt_int_t res;
- nxt_port_t *port, *c_port, *reply_port;
+ nxt_buf_t *buf;
+ nxt_int_t res;
+ nxt_port_t *port, *c_port, *reply_port;
+ nxt_apr_action_t apr_action;
- nxt_assert(ra->app_port != NULL);
+ nxt_assert(req_app_link->app_port != NULL);
- port = ra->app_port;
- reply_port = ra->reply_port;
+ port = req_app_link->app_port;
+ reply_port = req_app_link->reply_port;
- request_failed = 1;
+ apr_action = NXT_APR_REQUEST_FAILED;
c_port = nxt_process_connected_port_find(port->process, reply_port->pid,
reply_port->id);
@@ -4514,7 +4632,7 @@ nxt_router_app_prepare_request(nxt_task_t *task, nxt_req_app_link_t *ra)
res = nxt_port_send_port(task, port, reply_port, 0);
if (nxt_slow_path(res != NXT_OK)) {
- nxt_router_ra_error(ra, 500,
+ nxt_request_app_link_error(req_app_link, 500,
"Failed to send reply port to application");
goto release_port;
}
@@ -4522,11 +4640,11 @@ nxt_router_app_prepare_request(nxt_task_t *task, nxt_req_app_link_t *ra)
nxt_process_connected_port_add(port->process, reply_port);
}
- buf = nxt_router_prepare_msg(task, ra->request, port,
+ buf = nxt_router_prepare_msg(task, req_app_link->request, port,
nxt_app_msg_prefix[port->app->type]);
if (nxt_slow_path(buf == NULL)) {
- nxt_router_ra_error(ra, 500,
+ nxt_request_app_link_error(req_app_link, 500,
"Failed to prepare message for application");
goto release_port;
}
@@ -4535,40 +4653,41 @@ nxt_router_app_prepare_request(nxt_task_t *task, nxt_req_app_link_t *ra)
nxt_buf_used_size(buf),
port->socket.fd);
- request_failed = 0;
+ apr_action = NXT_APR_NEW_PORT;
- ra->msg_info.buf = buf;
- ra->msg_info.completion_handler = buf->completion_handler;
+ req_app_link->msg_info.buf = buf;
+ req_app_link->msg_info.completion_handler = buf->completion_handler;
for (; buf; buf = buf->next) {
buf->completion_handler = nxt_router_dummy_buf_completion;
}
- buf = ra->msg_info.buf;
+ buf = req_app_link->msg_info.buf;
- res = nxt_port_mmap_get_tracking(task, port, &ra->msg_info.tracking,
- ra->stream);
+ res = nxt_port_mmap_get_tracking(task, port,
+ &req_app_link->msg_info.tracking,
+ req_app_link->stream);
if (nxt_slow_path(res != NXT_OK)) {
- nxt_router_ra_error(ra, 500,
- "Failed to get tracking area");
+ nxt_request_app_link_error(req_app_link, 500,
+ "Failed to get tracking area");
goto release_port;
}
- res = nxt_port_socket_twrite(task, port, NXT_PORT_MSG_DATA,
- -1, ra->stream, reply_port->id, buf,
- &ra->msg_info.tracking);
+ res = nxt_port_socket_twrite(task, port, NXT_PORT_MSG_REQ_HEADERS,
+ -1, req_app_link->stream, reply_port->id, buf,
+ &req_app_link->msg_info.tracking);
if (nxt_slow_path(res != NXT_OK)) {
- nxt_router_ra_error(ra, 500,
- "Failed to send message to application");
+ nxt_request_app_link_error(req_app_link, 500,
+ "Failed to send message to application");
goto release_port;
}
release_port:
- nxt_router_app_port_release(task, port, request_failed, 0);
+ nxt_router_app_port_release(task, port, apr_action);
- nxt_router_ra_update_peer(task, ra);
+ nxt_request_app_link_update_peer(task, req_app_link);
}
@@ -4704,6 +4823,7 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_http_request_t *r,
*p++ = '\0';
req->tls = (r->tls != NULL);
+ req->websocket_handshake = r->websocket_handshake;
req->server_name_length = r->server_name.length;
nxt_unit_sptr_set(&req->server_name, p);
@@ -4899,8 +5019,8 @@ nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data)
nxt_timer_t *timer;
nxt_queue_link_t *lnk;
nxt_http_request_t *r;
- nxt_req_app_link_t *pending_ra;
- nxt_req_conn_link_t *rc;
+ nxt_request_app_link_t *pending_ra;
+ nxt_request_rpc_data_t *req_rpc_data;
nxt_port_select_state_t state;
timer = obj;
@@ -4908,8 +5028,8 @@ nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data)
nxt_debug(task, "router app timeout");
r = nxt_timer_data(timer, nxt_http_request_t, timer);
- rc = r->timer_data;
- app = rc->app;
+ req_rpc_data = r->timer_data;
+ app = req_rpc_data->app;
if (app == NULL) {
goto generate_error;
@@ -4918,14 +5038,16 @@ nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data)
port = NULL;
pending_ra = NULL;
- if (rc->app_port != NULL) {
- port = rc->app_port;
- rc->app_port = NULL;
+ if (req_rpc_data->app_port != NULL) {
+ port = req_rpc_data->app_port;
+ req_rpc_data->app_port = NULL;
}
- if (port == NULL && rc->ra != NULL && rc->ra->app_port != NULL) {
- port = rc->ra->app_port;
- rc->ra->app_port = NULL;
+ if (port == NULL && req_rpc_data->req_app_link != NULL
+ && req_rpc_data->req_app_link->app_port != NULL)
+ {
+ port = req_rpc_data->req_app_link->app_port;
+ req_rpc_data->req_app_link->app_port = NULL;
}
if (port == NULL) {
@@ -4939,7 +5061,7 @@ nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data)
if (!nxt_queue_is_empty(&port->pending_requests)) {
lnk = nxt_queue_first(&port->pending_requests);
- pending_ra = nxt_queue_link_data(lnk, nxt_req_app_link_t,
+ pending_ra = nxt_queue_link_data(lnk, nxt_request_app_link_t,
link_port_pending);
nxt_assert(pending_ra->link_app_pending.next != NULL);
@@ -4951,9 +5073,9 @@ nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data)
pending_ra->stream);
if (cancelled) {
- nxt_router_ra_inc_use(pending_ra);
+ nxt_request_app_link_inc_use(pending_ra);
- state.ra = pending_ra;
+ state.req_app_link = pending_ra;
state.app = app;
nxt_router_port_select(task, &state);
@@ -4981,7 +5103,7 @@ generate_error:
nxt_http_request_error(task, r, NXT_HTTP_SERVICE_UNAVAILABLE);
- nxt_router_rc_unlink(task, rc);
+ nxt_request_rpc_data_unlink(task, req_rpc_data);
}
diff --git a/src/nxt_router.h b/src/nxt_router.h
index d9fbfe05..b55a4de3 100644
--- a/src/nxt_router.h
+++ b/src/nxt_router.h
@@ -108,8 +108,8 @@ struct nxt_app_s {
nxt_work_t adjust_idle_work;
nxt_event_engine_t *engine;
- nxt_queue_t requests; /* of nxt_req_app_link_t */
- nxt_queue_t pending; /* of nxt_req_app_link_t */
+ nxt_queue_t requests; /* of nxt_request_app_link_t */
+ nxt_queue_t pending; /* of nxt_request_app_link_t */
nxt_str_t name;
uint32_t pending_processes;
@@ -139,6 +139,13 @@ struct nxt_app_s {
typedef struct {
+ size_t max_frame_size;
+ nxt_msec_t read_timeout;
+ nxt_msec_t keepalive_interval;
+} nxt_websocket_conf_t;
+
+
+typedef struct {
uint32_t count;
nxt_queue_link_t link;
nxt_router_conf_t *router_conf;
@@ -164,6 +171,8 @@ typedef struct {
nxt_msec_t body_read_timeout;
nxt_msec_t send_timeout;
+ nxt_websocket_conf_t websocket_conf;
+
#if (NXT_TLS)
nxt_tls_conf_t *tls;
#endif
diff --git a/src/nxt_router_request.h b/src/nxt_router_request.h
new file mode 100644
index 00000000..c3d5767e
--- /dev/null
+++ b/src/nxt_router_request.h
@@ -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_ */
diff --git a/src/nxt_sha1.c b/src/nxt_sha1.c
new file mode 100644
index 00000000..407c5933
--- /dev/null
+++ b/src/nxt_sha1.c
@@ -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;
+}
diff --git a/src/nxt_sha1.h b/src/nxt_sha1.h
new file mode 100644
index 00000000..2816982b
--- /dev/null
+++ b/src/nxt_sha1.h
@@ -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_ */
diff --git a/src/nxt_socket.c b/src/nxt_socket.c
index eba53354..95a298d8 100644
--- a/src/nxt_socket.c
+++ b/src/nxt_socket.c
@@ -281,7 +281,7 @@ nxt_socket_shutdown(nxt_task_t *task, nxt_socket_t s, nxt_uint_t how)
switch (err) {
case NXT_ENOTCONN:
- level = NXT_LOG_INFO;
+ level = NXT_LOG_DEBUG;
break;
case NXT_ECONNRESET:
@@ -313,7 +313,7 @@ nxt_socket_error_level(nxt_err_t err)
case NXT_ENETUNREACH:
case NXT_EHOSTDOWN:
case NXT_EHOSTUNREACH:
- return NXT_LOG_ERR;
+ return NXT_LOG_INFO;
default:
return NXT_LOG_ALERT;
diff --git a/src/nxt_unit.c b/src/nxt_unit.c
index 88c7fa6a..28a0de20 100644
--- a/src/nxt_unit.c
+++ b/src/nxt_unit.c
@@ -11,38 +11,60 @@
#include "nxt_unit.h"
#include "nxt_unit_request.h"
#include "nxt_unit_response.h"
+#include "nxt_unit_websocket.h"
+
+#include "nxt_websocket.h"
#if (NXT_HAVE_MEMFD_CREATE)
#include <linux/memfd.h>
#endif
-typedef struct nxt_unit_impl_s nxt_unit_impl_t;
-typedef struct nxt_unit_mmap_s nxt_unit_mmap_t;
-typedef struct nxt_unit_mmaps_s nxt_unit_mmaps_t;
-typedef struct nxt_unit_process_s nxt_unit_process_t;
-typedef struct nxt_unit_mmap_buf_s nxt_unit_mmap_buf_t;
-typedef struct nxt_unit_recv_msg_s nxt_unit_recv_msg_t;
-typedef struct nxt_unit_ctx_impl_s nxt_unit_ctx_impl_t;
-typedef struct nxt_unit_port_impl_s nxt_unit_port_impl_t;
-typedef struct nxt_unit_request_info_impl_s nxt_unit_request_info_impl_t;
+typedef struct nxt_unit_impl_s nxt_unit_impl_t;
+typedef struct nxt_unit_mmap_s nxt_unit_mmap_t;
+typedef struct nxt_unit_mmaps_s nxt_unit_mmaps_t;
+typedef struct nxt_unit_process_s nxt_unit_process_t;
+typedef struct nxt_unit_mmap_buf_s nxt_unit_mmap_buf_t;
+typedef struct nxt_unit_recv_msg_s nxt_unit_recv_msg_t;
+typedef struct nxt_unit_ctx_impl_s nxt_unit_ctx_impl_t;
+typedef struct nxt_unit_port_impl_s nxt_unit_port_impl_t;
+typedef struct nxt_unit_request_info_impl_s nxt_unit_request_info_impl_t;
+typedef struct nxt_unit_websocket_frame_impl_s nxt_unit_websocket_frame_impl_t;
static nxt_unit_impl_t *nxt_unit_create(nxt_unit_init_t *init);
static void nxt_unit_ctx_init(nxt_unit_impl_t *lib,
nxt_unit_ctx_impl_t *ctx_impl, void *data);
+nxt_inline void nxt_unit_mmap_buf_insert(nxt_unit_mmap_buf_t **head,
+ nxt_unit_mmap_buf_t *mmap_buf);
+nxt_inline void nxt_unit_mmap_buf_insert_tail(nxt_unit_mmap_buf_t **prev,
+ nxt_unit_mmap_buf_t *mmap_buf);
+nxt_inline void nxt_unit_mmap_buf_remove(nxt_unit_mmap_buf_t *mmap_buf);
static int nxt_unit_read_env(nxt_unit_port_t *ready_port,
nxt_unit_port_t *read_port, int *log_fd, uint32_t *stream);
static int nxt_unit_ready(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id,
uint32_t stream);
+static int nxt_unit_process_new_port(nxt_unit_ctx_t *ctx,
+ nxt_unit_recv_msg_t *recv_msg);
+static int nxt_unit_process_req_headers(nxt_unit_ctx_t *ctx,
+ nxt_unit_recv_msg_t *recv_msg);
+static int nxt_unit_process_websocket(nxt_unit_ctx_t *ctx,
+ nxt_unit_recv_msg_t *recv_msg);
static nxt_unit_request_info_impl_t *nxt_unit_request_info_get(
nxt_unit_ctx_t *ctx);
static void nxt_unit_request_info_release(nxt_unit_request_info_t *req);
static void nxt_unit_request_info_free(nxt_unit_request_info_impl_t *req);
+static nxt_unit_websocket_frame_impl_t *nxt_unit_websocket_frame_get(
+ nxt_unit_ctx_t *ctx);
+static void nxt_unit_websocket_frame_release(nxt_unit_websocket_frame_t *ws);
+static void nxt_unit_websocket_frame_free(nxt_unit_websocket_frame_impl_t *ws);
static nxt_unit_process_t *nxt_unit_msg_get_process(nxt_unit_ctx_t *ctx,
nxt_unit_recv_msg_t *recv_msg);
static nxt_unit_mmap_buf_t *nxt_unit_mmap_buf_get(nxt_unit_ctx_t *ctx);
static void nxt_unit_mmap_buf_release(nxt_unit_mmap_buf_t *mmap_buf);
static int nxt_unit_mmap_buf_send(nxt_unit_ctx_t *ctx, uint32_t stream,
nxt_unit_mmap_buf_t *mmap_buf, int last);
+static void nxt_unit_mmap_buf_free(nxt_unit_mmap_buf_t *mmap_buf);
+static ssize_t nxt_unit_buf_read(nxt_unit_buf_t **b, uint64_t *len, void *dst,
+ size_t size);
static nxt_port_mmap_header_t *nxt_unit_mmap_get(nxt_unit_ctx_t *ctx,
nxt_unit_process_t *process, nxt_unit_port_id_t *port_id,
nxt_chunk_id_t *c, int n);
@@ -65,7 +87,7 @@ static nxt_port_mmap_header_t *nxt_unit_get_incoming_mmap(nxt_unit_ctx_t *ctx,
static int nxt_unit_tracking_read(nxt_unit_ctx_t *ctx,
nxt_unit_recv_msg_t *recv_msg);
static int nxt_unit_mmap_read(nxt_unit_ctx_t *ctx,
- nxt_unit_recv_msg_t *recv_msg, nxt_queue_t *incoming_buf);
+ nxt_unit_recv_msg_t *recv_msg);
static int nxt_unit_mmap_release(nxt_port_mmap_header_t *hdr, void *start,
uint32_t size);
@@ -98,14 +120,22 @@ static int nxt_unit_port_hash_add(nxt_lvlhsh_t *port_hash,
static nxt_unit_port_impl_t *nxt_unit_port_hash_find(nxt_lvlhsh_t *port_hash,
nxt_unit_port_id_t *port_id, int remove);
+static int nxt_unit_request_hash_add(nxt_lvlhsh_t *request_hash,
+ nxt_unit_request_info_impl_t *req_impl);
+static nxt_unit_request_info_impl_t *nxt_unit_request_hash_find(
+ nxt_lvlhsh_t *request_hash, uint32_t stream, int remove);
+
static char * nxt_unit_snprint_prefix(char *p, char *end, pid_t pid, int level);
struct nxt_unit_mmap_buf_s {
nxt_unit_buf_t buf;
+ nxt_unit_mmap_buf_t *next;
+ nxt_unit_mmap_buf_t **prev;
+
nxt_port_mmap_header_t *hdr;
- nxt_queue_link_t link;
+// nxt_queue_link_t link;
nxt_unit_port_id_t port_id;
nxt_unit_request_info_t *req;
nxt_unit_ctx_impl_t *ctx_impl;
@@ -113,12 +143,20 @@ struct nxt_unit_mmap_buf_s {
struct nxt_unit_recv_msg_s {
- nxt_port_msg_t port_msg;
+ uint32_t stream;
+ nxt_pid_t pid;
+ nxt_port_id_t reply_port;
+
+ uint8_t last; /* 1 bit */
+ uint8_t mmap; /* 1 bit */
void *start;
uint32_t size;
+ int fd;
nxt_unit_process_t *process;
+
+ nxt_unit_mmap_buf_t *incoming_buf;
};
@@ -127,18 +165,22 @@ typedef enum {
NXT_UNIT_RS_RESPONSE_INIT,
NXT_UNIT_RS_RESPONSE_HAS_CONTENT,
NXT_UNIT_RS_RESPONSE_SENT,
- NXT_UNIT_RS_DONE,
+ NXT_UNIT_RS_RELEASED,
} nxt_unit_req_state_t;
struct nxt_unit_request_info_impl_s {
nxt_unit_request_info_t req;
- nxt_unit_recv_msg_t recv_msg;
- nxt_queue_t outgoing_buf; /* of nxt_unit_mmap_buf_t */
- nxt_queue_t incoming_buf; /* of nxt_unit_mmap_buf_t */
+ uint32_t stream;
+
+ nxt_unit_process_t *process;
+
+ nxt_unit_mmap_buf_t *outgoing_buf;
+ nxt_unit_mmap_buf_t *incoming_buf;
nxt_unit_req_state_t state;
+ uint8_t websocket;
nxt_queue_link_t link;
@@ -146,6 +188,19 @@ struct nxt_unit_request_info_impl_s {
};
+struct nxt_unit_websocket_frame_impl_s {
+ nxt_unit_websocket_frame_t ws;
+
+ nxt_unit_mmap_buf_t *buf;
+
+ nxt_queue_link_t link;
+
+ nxt_unit_ctx_impl_t *ctx_impl;
+
+ void *retain_buf;
+};
+
+
struct nxt_unit_ctx_impl_s {
nxt_unit_ctx_t ctx;
@@ -154,14 +209,20 @@ struct nxt_unit_ctx_impl_s {
nxt_queue_link_t link;
- nxt_queue_t free_buf; /* of nxt_unit_mmap_buf_t */
+ nxt_unit_mmap_buf_t *free_buf;
/* of nxt_unit_request_info_impl_t */
nxt_queue_t free_req;
+ /* of nxt_unit_websocket_frame_impl_t */
+ nxt_queue_t free_ws;
+
/* of nxt_unit_request_info_impl_t */
nxt_queue_t active_req;
+ /* of nxt_unit_request_info_impl_t */
+ nxt_lvlhsh_t requests;
+
nxt_unit_mmap_buf_t ctx_buf[2];
nxt_unit_request_info_impl_t req;
@@ -394,18 +455,65 @@ nxt_unit_ctx_init(nxt_unit_impl_t *lib, nxt_unit_ctx_impl_t *ctx_impl,
nxt_queue_insert_tail(&lib->contexts, &ctx_impl->link);
- nxt_queue_init(&ctx_impl->free_buf);
nxt_queue_init(&ctx_impl->free_req);
+ nxt_queue_init(&ctx_impl->free_ws);
nxt_queue_init(&ctx_impl->active_req);
- nxt_queue_insert_tail(&ctx_impl->free_buf, &ctx_impl->ctx_buf[0].link);
- nxt_queue_insert_tail(&ctx_impl->free_buf, &ctx_impl->ctx_buf[1].link);
+ ctx_impl->free_buf = NULL;
+ nxt_unit_mmap_buf_insert(&ctx_impl->free_buf, &ctx_impl->ctx_buf[1]);
+ nxt_unit_mmap_buf_insert(&ctx_impl->free_buf, &ctx_impl->ctx_buf[0]);
+
nxt_queue_insert_tail(&ctx_impl->free_req, &ctx_impl->req.link);
ctx_impl->req.req.ctx = &ctx_impl->ctx;
ctx_impl->req.req.unit = &lib->unit;
ctx_impl->read_port_fd = -1;
+ ctx_impl->requests.slot = 0;
+}
+
+
+nxt_inline void
+nxt_unit_mmap_buf_insert(nxt_unit_mmap_buf_t **head,
+ nxt_unit_mmap_buf_t *mmap_buf)
+{
+ mmap_buf->next = *head;
+
+ if (mmap_buf->next != NULL) {
+ mmap_buf->next->prev = &mmap_buf->next;
+ }
+
+ *head = mmap_buf;
+ mmap_buf->prev = head;
+}
+
+
+nxt_inline void
+nxt_unit_mmap_buf_insert_tail(nxt_unit_mmap_buf_t **prev,
+ nxt_unit_mmap_buf_t *mmap_buf)
+{
+ while (*prev != NULL) {
+ prev = &(*prev)->next;
+ }
+
+ nxt_unit_mmap_buf_insert(prev, mmap_buf);
+}
+
+
+nxt_inline void
+nxt_unit_mmap_buf_remove(nxt_unit_mmap_buf_t *mmap_buf)
+{
+ nxt_unit_mmap_buf_t **prev;
+
+ prev = mmap_buf->prev;
+
+ if (mmap_buf->next != NULL) {
+ mmap_buf->next->prev = prev;
+ }
+
+ if (prev != NULL) {
+ *prev = mmap_buf->next;
+ }
}
@@ -509,26 +617,18 @@ int
nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id,
void *buf, size_t buf_size, void *oob, size_t oob_size)
{
- int fd, rc, nb;
- pid_t pid;
- nxt_queue_t incoming_buf;
- struct cmsghdr *cm;
- nxt_port_msg_t *port_msg;
- nxt_unit_impl_t *lib;
- nxt_unit_port_t new_port;
- nxt_queue_link_t *lnk;
- nxt_unit_request_t *r;
- nxt_unit_mmap_buf_t *b;
- nxt_unit_recv_msg_t recv_msg;
- nxt_unit_callbacks_t *cb;
- nxt_port_msg_new_port_t *new_port_msg;
- nxt_unit_request_info_t *req;
- nxt_unit_request_info_impl_t *req_impl;
+ int rc;
+ pid_t pid;
+ struct cmsghdr *cm;
+ nxt_port_msg_t *port_msg;
+ nxt_unit_impl_t *lib;
+ nxt_unit_recv_msg_t recv_msg;
+ nxt_unit_callbacks_t *cb;
lib = nxt_container_of(ctx->unit, nxt_unit_impl_t, unit);
rc = NXT_UNIT_ERROR;
- fd = -1;
+ recv_msg.fd = -1;
recv_msg.process = NULL;
port_msg = buf;
cm = oob;
@@ -538,17 +638,22 @@ nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id,
&& cm->cmsg_level == SOL_SOCKET
&& cm->cmsg_type == SCM_RIGHTS)
{
- memcpy(&fd, CMSG_DATA(cm), sizeof(int));
+ memcpy(&recv_msg.fd, CMSG_DATA(cm), sizeof(int));
}
- nxt_queue_init(&incoming_buf);
+ recv_msg.incoming_buf = NULL;
if (nxt_slow_path(buf_size < sizeof(nxt_port_msg_t))) {
nxt_unit_warn(ctx, "message too small (%d bytes)", (int) buf_size);
goto fail;
}
- recv_msg.port_msg = *port_msg;
+ recv_msg.stream = port_msg->stream;
+ recv_msg.pid = port_msg->pid;
+ recv_msg.reply_port = port_msg->reply_port;
+ recv_msg.last = port_msg->last;
+ recv_msg.mmap = port_msg->mmap;
+
recv_msg.start = port_msg + 1;
recv_msg.size = buf_size - sizeof(nxt_port_msg_t);
@@ -572,7 +677,7 @@ nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id,
}
if (port_msg->mmap) {
- if (nxt_unit_mmap_read(ctx, &recv_msg, &incoming_buf) != NXT_UNIT_OK) {
+ if (nxt_unit_mmap_read(ctx, &recv_msg) != NXT_UNIT_OK) {
goto fail;
}
}
@@ -589,187 +694,326 @@ nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id,
break;
case _NXT_PORT_MSG_NEW_PORT:
- if (nxt_slow_path(recv_msg.size != sizeof(nxt_port_msg_new_port_t))) {
- nxt_unit_warn(ctx, "#%"PRIu32": new_port: "
- "invalid message size (%d)",
- port_msg->stream, (int) recv_msg.size);
+ rc = nxt_unit_process_new_port(ctx, &recv_msg);
+ break;
- goto fail;
- }
+ case _NXT_PORT_MSG_CHANGE_FILE:
+ nxt_unit_debug(ctx, "#%"PRIu32": change_file: fd %d",
+ port_msg->stream, recv_msg.fd);
+ break;
- if (nxt_slow_path(fd < 0)) {
- nxt_unit_alert(ctx, "#%"PRIu32": invalid fd %d for new port",
- port_msg->stream, fd);
+ case _NXT_PORT_MSG_MMAP:
+ if (nxt_slow_path(recv_msg.fd < 0)) {
+ nxt_unit_alert(ctx, "#%"PRIu32": invalid fd %d for mmap",
+ port_msg->stream, recv_msg.fd);
goto fail;
}
- new_port_msg = recv_msg.start;
+ rc = nxt_unit_incoming_mmap(ctx, port_msg->pid, recv_msg.fd);
+ break;
- nxt_unit_debug(ctx, "#%"PRIu32": new_port: %d,%d fd %d",
- port_msg->stream, (int) new_port_msg->pid,
- (int) new_port_msg->id, fd);
+ case _NXT_PORT_MSG_REQ_HEADERS:
+ rc = nxt_unit_process_req_headers(ctx, &recv_msg);
+ break;
- nb = 0;
+ case _NXT_PORT_MSG_WEBSOCKET:
+ rc = nxt_unit_process_websocket(ctx, &recv_msg);
+ break;
- if (nxt_slow_path(ioctl(fd, FIONBIO, &nb) == -1)) {
- nxt_unit_alert(ctx, "#%"PRIu32": new_port: ioctl(%d, FIONBIO, 0) "
- "failed: %s (%d)", fd, strerror(errno), errno);
+ case _NXT_PORT_MSG_REMOVE_PID:
+ if (nxt_slow_path(recv_msg.size != sizeof(pid))) {
+ nxt_unit_warn(ctx, "#%"PRIu32": remove_pid: invalid message size "
+ "(%d != %d)", port_msg->stream, (int) recv_msg.size,
+ (int) sizeof(pid));
goto fail;
}
- nxt_unit_port_id_init(&new_port.id, new_port_msg->pid,
- new_port_msg->id);
+ memcpy(&pid, recv_msg.start, sizeof(pid));
- new_port.in_fd = -1;
- new_port.out_fd = fd;
- new_port.data = NULL;
+ nxt_unit_debug(ctx, "#%"PRIu32": remove_pid: %d",
+ port_msg->stream, (int) pid);
- fd = -1;
+ cb->remove_pid(ctx, pid);
- rc = cb->add_port(ctx, &new_port);
+ rc = NXT_UNIT_OK;
break;
- case _NXT_PORT_MSG_CHANGE_FILE:
- nxt_unit_debug(ctx, "#%"PRIu32": change_file: fd %d",
- port_msg->stream, fd);
- break;
+ default:
+ nxt_unit_debug(ctx, "#%"PRIu32": ignore message type: %d",
+ port_msg->stream, (int) port_msg->type);
- case _NXT_PORT_MSG_MMAP:
- if (nxt_slow_path(fd < 0)) {
- nxt_unit_alert(ctx, "#%"PRIu32": invalid fd %d for mmap",
- port_msg->stream, fd);
+ goto fail;
+ }
- goto fail;
- }
+fail:
- rc = nxt_unit_incoming_mmap(ctx, port_msg->pid, fd);
- break;
+ if (recv_msg.fd != -1) {
+ close(recv_msg.fd);
+ }
- case _NXT_PORT_MSG_DATA:
- if (nxt_slow_path(port_msg->mmap == 0)) {
- nxt_unit_warn(ctx, "#%"PRIu32": data is not in shared memory",
- port_msg->stream);
+ while (recv_msg.incoming_buf != NULL) {
+ nxt_unit_mmap_buf_free(recv_msg.incoming_buf);
+ }
- goto fail;
- }
+ if (recv_msg.process != NULL) {
+ nxt_unit_process_use(ctx, recv_msg.process, -1);
+ }
- if (nxt_slow_path(recv_msg.size < sizeof(nxt_unit_request_t))) {
- nxt_unit_warn(ctx, "#%"PRIu32": data too short: %d while at least "
- "%d expected", port_msg->stream, (int) recv_msg.size,
- (int) sizeof(nxt_unit_request_t));
+ return rc;
+}
- goto fail;
- }
- req_impl = nxt_unit_request_info_get(ctx);
- if (nxt_slow_path(req_impl == NULL)) {
- nxt_unit_warn(ctx, "#%"PRIu32": request info allocation failed",
- port_msg->stream);
+static int
+nxt_unit_process_new_port(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg)
+{
+ int nb;
+ nxt_unit_impl_t *lib;
+ nxt_unit_port_t new_port;
+ nxt_port_msg_new_port_t *new_port_msg;
- goto fail;
- }
+ if (nxt_slow_path(recv_msg->size != sizeof(nxt_port_msg_new_port_t))) {
+ nxt_unit_warn(ctx, "#%"PRIu32": new_port: "
+ "invalid message size (%d)",
+ recv_msg->stream, (int) recv_msg->size);
- req = &req_impl->req;
+ return NXT_UNIT_ERROR;
+ }
- req->request_port = *port_id;
+ if (nxt_slow_path(recv_msg->fd < 0)) {
+ nxt_unit_alert(ctx, "#%"PRIu32": invalid fd %d for new port",
+ recv_msg->stream, recv_msg->fd);
- nxt_unit_port_id_init(&req->response_port, port_msg->pid,
- port_msg->reply_port);
+ return NXT_UNIT_ERROR;
+ }
- req->request = recv_msg.start;
+ new_port_msg = recv_msg->start;
- lnk = nxt_queue_first(&incoming_buf);
- b = nxt_container_of(lnk, nxt_unit_mmap_buf_t, link);
+ nxt_unit_debug(ctx, "#%"PRIu32": new_port: %d,%d fd %d",
+ recv_msg->stream, (int) new_port_msg->pid,
+ (int) new_port_msg->id, recv_msg->fd);
- req->request_buf = &b->buf;
- req->response = NULL;
- req->response_buf = NULL;
+ nb = 0;
- r = req->request;
+ if (nxt_slow_path(ioctl(recv_msg->fd, FIONBIO, &nb) == -1)) {
+ nxt_unit_alert(ctx, "#%"PRIu32": new_port: ioctl(%d, FIONBIO, 0) "
+ "failed: %s (%d)", recv_msg->fd, strerror(errno), errno);
- req->content_length = r->content_length;
+ return NXT_UNIT_ERROR;
+ }
- req->content_buf = req->request_buf;
- req->content_buf->free = nxt_unit_sptr_get(&r->preread_content);
+ nxt_unit_port_id_init(&new_port.id, new_port_msg->pid,
+ new_port_msg->id);
- /* Move process to req_impl. */
- req_impl->recv_msg = recv_msg;
+ new_port.in_fd = -1;
+ new_port.out_fd = recv_msg->fd;
+ new_port.data = NULL;
- recv_msg.process = NULL;
+ recv_msg->fd = -1;
- nxt_queue_init(&req_impl->outgoing_buf);
- nxt_queue_init(&req_impl->incoming_buf);
+ lib = nxt_container_of(ctx->unit, nxt_unit_impl_t, unit);
- nxt_queue_each(b, &incoming_buf, nxt_unit_mmap_buf_t, link)
- {
- b->req = req;
- } nxt_queue_loop;
+ return lib->callbacks.add_port(ctx, &new_port);
+}
- nxt_queue_add(&req_impl->incoming_buf, &incoming_buf);
- nxt_queue_init(&incoming_buf);
- req->response_max_fields = 0;
- req_impl->state = NXT_UNIT_RS_START;
+static int
+nxt_unit_process_req_headers(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg)
+{
+ nxt_unit_impl_t *lib;
+ nxt_unit_request_t *r;
+ nxt_unit_mmap_buf_t *b;
+ nxt_unit_request_info_t *req;
+ nxt_unit_request_info_impl_t *req_impl;
- nxt_unit_debug(ctx, "#%"PRIu32": %.*s %.*s (%d)", port_msg->stream,
- (int) r->method_length, nxt_unit_sptr_get(&r->method),
- (int) r->target_length, nxt_unit_sptr_get(&r->target),
- (int) r->content_length);
+ if (nxt_slow_path(recv_msg->mmap == 0)) {
+ nxt_unit_warn(ctx, "#%"PRIu32": data is not in shared memory",
+ recv_msg->stream);
- cb->request_handler(req);
+ return NXT_UNIT_ERROR;
+ }
- rc = NXT_UNIT_OK;
- break;
+ if (nxt_slow_path(recv_msg->size < sizeof(nxt_unit_request_t))) {
+ nxt_unit_warn(ctx, "#%"PRIu32": data too short: %d while at least "
+ "%d expected", recv_msg->stream, (int) recv_msg->size,
+ (int) sizeof(nxt_unit_request_t));
- case _NXT_PORT_MSG_REMOVE_PID:
- if (nxt_slow_path(recv_msg.size != sizeof(pid))) {
- nxt_unit_warn(ctx, "#%"PRIu32": remove_pid: invalid message size "
- "(%d != %d)", port_msg->stream, (int) recv_msg.size,
- (int) sizeof(pid));
+ return NXT_UNIT_ERROR;
+ }
- goto fail;
- }
+ req_impl = nxt_unit_request_info_get(ctx);
+ if (nxt_slow_path(req_impl == NULL)) {
+ nxt_unit_warn(ctx, "#%"PRIu32": request info allocation failed",
+ recv_msg->stream);
- memcpy(&pid, recv_msg.start, sizeof(pid));
+ return NXT_UNIT_ERROR;
+ }
- nxt_unit_debug(ctx, "#%"PRIu32": remove_pid: %d",
- port_msg->stream, (int) pid);
+ req = &req_impl->req;
- cb->remove_pid(ctx, pid);
+ nxt_unit_port_id_init(&req->response_port, recv_msg->pid,
+ recv_msg->reply_port);
- rc = NXT_UNIT_OK;
- break;
+ req->request = recv_msg->start;
- default:
- nxt_unit_debug(ctx, "#%"PRIu32": ignore message type: %d",
- port_msg->stream, (int) port_msg->type);
+ b = recv_msg->incoming_buf;
- goto fail;
+ req->request_buf = &b->buf;
+ req->response = NULL;
+ req->response_buf = NULL;
+
+ r = req->request;
+
+ req->content_length = r->content_length;
+
+ req->content_buf = req->request_buf;
+ req->content_buf->free = nxt_unit_sptr_get(&r->preread_content);
+
+ /* "Move" process reference to req_impl. */
+ req_impl->process = nxt_unit_msg_get_process(ctx, recv_msg);
+ if (nxt_slow_path(req_impl->process == NULL)) {
+ return NXT_UNIT_ERROR;
}
-fail:
+ recv_msg->process = NULL;
- if (fd != -1) {
- close(fd);
+ req_impl->stream = recv_msg->stream;
+
+ req_impl->outgoing_buf = NULL;
+
+ for (b = recv_msg->incoming_buf; b != NULL; b = b->next) {
+ b->req = req;
}
- if (port_msg->mmap) {
- nxt_queue_each(b, &incoming_buf, nxt_unit_mmap_buf_t, link)
- {
- nxt_unit_mmap_release(b->hdr, b->buf.start,
- b->buf.end - b->buf.start);
+ /* "Move" incoming buffer list to req_impl. */
+ req_impl->incoming_buf = recv_msg->incoming_buf;
+ req_impl->incoming_buf->prev = &req_impl->incoming_buf;
+ recv_msg->incoming_buf = NULL;
+
+ req->response_max_fields = 0;
+ req_impl->state = NXT_UNIT_RS_START;
+ req_impl->websocket = 0;
+
+ nxt_unit_debug(ctx, "#%"PRIu32": %.*s %.*s (%d)", recv_msg->stream,
+ (int) r->method_length, nxt_unit_sptr_get(&r->method),
+ (int) r->target_length, nxt_unit_sptr_get(&r->target),
+ (int) r->content_length);
+
+ lib = nxt_container_of(ctx->unit, nxt_unit_impl_t, unit);
+
+ lib->callbacks.request_handler(req);
- nxt_unit_mmap_buf_release(b);
- } nxt_queue_loop;
+ return NXT_UNIT_OK;
+}
+
+
+static int
+nxt_unit_process_websocket(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg)
+{
+ size_t hsize;
+ nxt_unit_impl_t *lib;
+ nxt_unit_mmap_buf_t *b;
+ nxt_unit_ctx_impl_t *ctx_impl;
+ nxt_unit_callbacks_t *cb;
+ nxt_unit_request_info_t *req;
+ nxt_unit_request_info_impl_t *req_impl;
+ nxt_unit_websocket_frame_impl_t *ws_impl;
+
+ ctx_impl = nxt_container_of(ctx, nxt_unit_ctx_impl_t, ctx);
+
+ req_impl = nxt_unit_request_hash_find(&ctx_impl->requests, recv_msg->stream,
+ recv_msg->last);
+ if (req_impl == NULL) {
+ return NXT_UNIT_OK;
}
- if (recv_msg.process != NULL) {
- nxt_unit_process_use(ctx, recv_msg.process, -1);
+ req = &req_impl->req;
+
+ lib = nxt_container_of(ctx->unit, nxt_unit_impl_t, unit);
+ cb = &lib->callbacks;
+
+ if (cb->websocket_handler && recv_msg->size >= 2) {
+ ws_impl = nxt_unit_websocket_frame_get(ctx);
+ if (nxt_slow_path(ws_impl == NULL)) {
+ nxt_unit_warn(ctx, "#%"PRIu32": websocket frame allocation failed",
+ req_impl->stream);
+
+ return NXT_UNIT_ERROR;
+ }
+
+ ws_impl->ws.req = req;
+
+ ws_impl->buf = NULL;
+ ws_impl->retain_buf = NULL;
+
+ if (recv_msg->mmap) {
+ for (b = recv_msg->incoming_buf; b != NULL; b = b->next) {
+ b->req = req;
+ }
+
+ /* "Move" incoming buffer list to ws_impl. */
+ ws_impl->buf = recv_msg->incoming_buf;
+ ws_impl->buf->prev = &ws_impl->buf;
+ recv_msg->incoming_buf = NULL;
+
+ b = ws_impl->buf;
+
+ } else {
+ b = nxt_unit_mmap_buf_get(ctx);
+ if (nxt_slow_path(b == NULL)) {
+ return NXT_UNIT_ERROR;
+ }
+
+ b->hdr = NULL;
+ b->req = req;
+ b->buf.start = recv_msg->start;
+ b->buf.free = b->buf.start;
+ b->buf.end = b->buf.start + recv_msg->size;
+
+ nxt_unit_mmap_buf_insert(&ws_impl->buf, b);
+ }
+
+ ws_impl->ws.header = (void *) b->buf.start;
+ ws_impl->ws.payload_len = nxt_websocket_frame_payload_len(
+ ws_impl->ws.header);
+
+ hsize = nxt_websocket_frame_header_size(ws_impl->ws.header);
+
+ if (ws_impl->ws.header->mask) {
+ ws_impl->ws.mask = (uint8_t *) b->buf.start + hsize - 4;
+
+ } else {
+ ws_impl->ws.mask = NULL;
+ }
+
+ b->buf.free += hsize;
+
+ ws_impl->ws.content_buf = &b->buf;
+ ws_impl->ws.content_length = ws_impl->ws.payload_len;
+
+ nxt_unit_req_debug(req, "websocket_handler: opcode=%d, "
+ "payload_len=%"PRIu64,
+ ws_impl->ws.header->opcode,
+ ws_impl->ws.payload_len);
+
+ cb->websocket_handler(&ws_impl->ws);
}
- return rc;
+ if (recv_msg->last) {
+ req_impl->websocket = 0;
+
+ if (cb->close_handler) {
+ nxt_unit_req_debug(req, "close_handler");
+
+ cb->close_handler(req);
+
+ } else {
+ nxt_unit_request_done(req, NXT_UNIT_ERROR);
+ }
+ }
+
+ return NXT_UNIT_OK;
}
@@ -815,9 +1059,7 @@ nxt_unit_request_info_get(nxt_unit_ctx_t *ctx)
static void
nxt_unit_request_info_release(nxt_unit_request_info_t *req)
{
- nxt_unit_mmap_buf_t *b;
nxt_unit_ctx_impl_t *ctx_impl;
- nxt_unit_recv_msg_t *recv_msg;
nxt_unit_request_info_impl_t *req_impl;
ctx_impl = nxt_container_of(req->ctx, nxt_unit_ctx_impl_t, ctx);
@@ -826,30 +1068,31 @@ nxt_unit_request_info_release(nxt_unit_request_info_t *req)
req->response = NULL;
req->response_buf = NULL;
- recv_msg = &req_impl->recv_msg;
+ if (req_impl->process != NULL) {
+ nxt_unit_process_use(req->ctx, req_impl->process, -1);
- if (recv_msg->process != NULL) {
- nxt_unit_process_use(req->ctx, recv_msg->process, -1);
-
- recv_msg->process = NULL;
+ req_impl->process = NULL;
}
- nxt_queue_each(b, &req_impl->outgoing_buf, nxt_unit_mmap_buf_t, link) {
-
- nxt_unit_buf_free(&b->buf);
+ if (req_impl->websocket) {
+ nxt_unit_request_hash_find(&ctx_impl->requests, req_impl->stream, 1);
- } nxt_queue_loop;
-
- nxt_queue_each(b, &req_impl->incoming_buf, nxt_unit_mmap_buf_t, link) {
+ req_impl->websocket = 0;
+ }
- nxt_unit_mmap_release(b->hdr, b->buf.start, b->buf.end - b->buf.start);
- nxt_unit_mmap_buf_release(b);
+ while (req_impl->outgoing_buf != NULL) {
+ nxt_unit_mmap_buf_free(req_impl->outgoing_buf);
+ }
- } nxt_queue_loop;
+ while (req_impl->incoming_buf != NULL) {
+ nxt_unit_mmap_buf_free(req_impl->incoming_buf);
+ }
nxt_queue_remove(&req_impl->link);
nxt_queue_insert_tail(&ctx_impl->free_req, &req_impl->link);
+
+ req_impl->state = NXT_UNIT_RS_RELEASED;
}
@@ -868,6 +1111,68 @@ nxt_unit_request_info_free(nxt_unit_request_info_impl_t *req_impl)
}
+static nxt_unit_websocket_frame_impl_t *
+nxt_unit_websocket_frame_get(nxt_unit_ctx_t *ctx)
+{
+ nxt_queue_link_t *lnk;
+ nxt_unit_ctx_impl_t *ctx_impl;
+ nxt_unit_websocket_frame_impl_t *ws_impl;
+
+ ctx_impl = nxt_container_of(ctx, nxt_unit_ctx_impl_t, ctx);
+
+ if (nxt_queue_is_empty(&ctx_impl->free_ws)) {
+ ws_impl = malloc(sizeof(nxt_unit_websocket_frame_impl_t));
+ if (nxt_slow_path(ws_impl == NULL)) {
+ nxt_unit_warn(ctx, "websocket frame allocation failed");
+
+ return NULL;
+ }
+
+ } else {
+ lnk = nxt_queue_first(&ctx_impl->free_ws);
+ nxt_queue_remove(lnk);
+
+ ws_impl = nxt_container_of(lnk, nxt_unit_websocket_frame_impl_t, link);
+ }
+
+ ws_impl->ctx_impl = ctx_impl;
+
+ return ws_impl;
+}
+
+
+static void
+nxt_unit_websocket_frame_release(nxt_unit_websocket_frame_t *ws)
+{
+ nxt_unit_websocket_frame_impl_t *ws_impl;
+
+ ws_impl = nxt_container_of(ws, nxt_unit_websocket_frame_impl_t, ws);
+
+ while (ws_impl->buf != NULL) {
+ nxt_unit_mmap_buf_free(ws_impl->buf);
+ }
+
+ ws->req = NULL;
+
+ if (ws_impl->retain_buf != NULL) {
+ free(ws_impl->retain_buf);
+
+ ws_impl->retain_buf = NULL;
+ }
+
+ nxt_queue_insert_tail(&ws_impl->ctx_impl->free_ws, &ws_impl->link);
+}
+
+
+static void
+nxt_unit_websocket_frame_free(nxt_unit_websocket_frame_impl_t *ws_impl)
+{
+ nxt_queue_remove(&ws_impl->link);
+
+ free(ws_impl);
+}
+
+
uint16_t
nxt_unit_field_hash(const char *name, size_t name_length)
{
@@ -1275,6 +1580,10 @@ nxt_unit_response_send(nxt_unit_request_info_t *req)
return NXT_UNIT_ERROR;
}
+ if (req->request->websocket_handshake && req->response->status == 101) {
+ nxt_unit_response_upgrade(req);
+ }
+
nxt_unit_req_debug(req, "send: %"PRIu32" fields, %d bytes",
req->response->fields_count,
(int) (req->response_buf->free
@@ -1282,9 +1591,7 @@ nxt_unit_response_send(nxt_unit_request_info_t *req)
mmap_buf = nxt_container_of(req->response_buf, nxt_unit_mmap_buf_t, buf);
- rc = nxt_unit_mmap_buf_send(req->ctx,
- req_impl->recv_msg.port_msg.stream,
- mmap_buf, 0);
+ rc = nxt_unit_mmap_buf_send(req->ctx, req_impl->stream, mmap_buf, 0);
if (nxt_fast_path(rc == NXT_UNIT_OK)) {
req->response = NULL;
req->response_buf = NULL;
@@ -1312,7 +1619,6 @@ nxt_unit_buf_t *
nxt_unit_response_buf_alloc(nxt_unit_request_info_t *req, uint32_t size)
{
int rc;
- nxt_unit_process_t *process;
nxt_unit_mmap_buf_t *mmap_buf;
nxt_unit_request_info_impl_t *req_impl;
@@ -1327,11 +1633,6 @@ nxt_unit_response_buf_alloc(nxt_unit_request_info_t *req, uint32_t size)
req_impl = nxt_container_of(req, nxt_unit_request_info_impl_t, req);
- process = nxt_unit_msg_get_process(req->ctx, &req_impl->recv_msg);
- if (nxt_slow_path(process == NULL)) {
- return NULL;
- }
-
mmap_buf = nxt_unit_mmap_buf_get(req->ctx);
if (nxt_slow_path(mmap_buf == NULL)) {
return NULL;
@@ -1339,10 +1640,10 @@ nxt_unit_response_buf_alloc(nxt_unit_request_info_t *req, uint32_t size)
mmap_buf->req = req;
- nxt_queue_insert_tail(&req_impl->outgoing_buf, &mmap_buf->link);
+ nxt_unit_mmap_buf_insert_tail(&req_impl->outgoing_buf, mmap_buf);
- rc = nxt_unit_get_outgoing_buf(req->ctx, process, &req->response_port,
- size, mmap_buf);
+ rc = nxt_unit_get_outgoing_buf(req->ctx, req_impl->process,
+ &req->response_port, size, mmap_buf);
if (nxt_slow_path(rc != NXT_UNIT_OK)) {
nxt_unit_mmap_buf_release(mmap_buf);
@@ -1366,13 +1667,13 @@ nxt_unit_msg_get_process(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg)
pthread_mutex_lock(&lib->mutex);
- recv_msg->process = nxt_unit_process_find(ctx, recv_msg->port_msg.pid, 0);
+ recv_msg->process = nxt_unit_process_find(ctx, recv_msg->pid, 0);
pthread_mutex_unlock(&lib->mutex);
if (recv_msg->process == NULL) {
nxt_unit_warn(ctx, "#%"PRIu32": process %d not found",
- recv_msg->port_msg.stream, (int) recv_msg->port_msg.pid);
+ recv_msg->stream, (int) recv_msg->pid);
}
return recv_msg->process;
@@ -1382,23 +1683,21 @@ nxt_unit_msg_get_process(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg)
static nxt_unit_mmap_buf_t *
nxt_unit_mmap_buf_get(nxt_unit_ctx_t *ctx)
{
- nxt_queue_link_t *lnk;
nxt_unit_mmap_buf_t *mmap_buf;
nxt_unit_ctx_impl_t *ctx_impl;
ctx_impl = nxt_container_of(ctx, nxt_unit_ctx_impl_t, ctx);
- if (nxt_queue_is_empty(&ctx_impl->free_buf)) {
+ if (ctx_impl->free_buf == NULL) {
mmap_buf = malloc(sizeof(nxt_unit_mmap_buf_t));
if (nxt_slow_path(mmap_buf == NULL)) {
nxt_unit_warn(ctx, "failed to allocate buf");
}
} else {
- lnk = nxt_queue_first(&ctx_impl->free_buf);
- nxt_queue_remove(lnk);
+ mmap_buf = ctx_impl->free_buf;
- mmap_buf = nxt_container_of(lnk, nxt_unit_mmap_buf_t, link);
+ nxt_unit_mmap_buf_remove(mmap_buf);
}
mmap_buf->ctx_impl = ctx_impl;
@@ -1410,9 +1709,91 @@ nxt_unit_mmap_buf_get(nxt_unit_ctx_t *ctx)
static void
nxt_unit_mmap_buf_release(nxt_unit_mmap_buf_t *mmap_buf)
{
- nxt_queue_remove(&mmap_buf->link);
+ nxt_unit_mmap_buf_remove(mmap_buf);
+
+ nxt_unit_mmap_buf_insert(&mmap_buf->ctx_impl->free_buf, mmap_buf);
+}
+
+
+typedef struct {
+ size_t len;
+ const char *str;
+} nxt_unit_str_t;
+
+
+#define nxt_unit_str(str) { nxt_length(str), str }
+
+
+int
+nxt_unit_request_is_websocket_handshake(nxt_unit_request_info_t *req)
+{
+ return req->request->websocket_handshake;
+}
+
+
+int
+nxt_unit_response_upgrade(nxt_unit_request_info_t *req)
+{
+ int rc;
+ nxt_unit_ctx_impl_t *ctx_impl;
+ nxt_unit_request_info_impl_t *req_impl;
+
+ req_impl = nxt_container_of(req, nxt_unit_request_info_impl_t, req);
+
+ if (nxt_slow_path(req_impl->websocket != 0)) {
+ nxt_unit_req_debug(req, "upgrade: already upgraded");
+
+ return NXT_UNIT_OK;
+ }
+
+ if (nxt_slow_path(req_impl->state < NXT_UNIT_RS_RESPONSE_INIT)) {
+ nxt_unit_req_warn(req, "upgrade: response is not initialized yet");
+
+ return NXT_UNIT_ERROR;
+ }
+
+ if (nxt_slow_path(req_impl->state >= NXT_UNIT_RS_RESPONSE_SENT)) {
+ nxt_unit_req_warn(req, "upgrade: response already sent");
+
+ return NXT_UNIT_ERROR;
+ }
+
+ ctx_impl = nxt_container_of(req->ctx, nxt_unit_ctx_impl_t, ctx);
+
+ rc = nxt_unit_request_hash_add(&ctx_impl->requests, req_impl);
+ if (nxt_slow_path(rc != NXT_UNIT_OK)) {
+ nxt_unit_req_warn(req, "upgrade: failed to add request to hash");
+
+ return NXT_UNIT_ERROR;
+ }
+
+ req_impl->websocket = 1;
+
+ req->response->status = 101;
+
+ return NXT_UNIT_OK;
+}
+
+
+int
+nxt_unit_response_is_websocket(nxt_unit_request_info_t *req)
+{
+ nxt_unit_request_info_impl_t *req_impl;
- nxt_queue_insert_tail(&mmap_buf->ctx_impl->free_buf, &mmap_buf->link);
+ req_impl = nxt_container_of(req, nxt_unit_request_info_impl_t, req);
+
+ return req_impl->websocket;
+}
+
+
+nxt_unit_request_info_t *
+nxt_unit_get_request_info_from_data(void *data)
+{
+ nxt_unit_request_info_impl_t *req_impl;
+
+ req_impl = nxt_container_of(data, nxt_unit_request_info_impl_t, extra_data);
+
+ return &req_impl->req;
}
@@ -1445,9 +1826,7 @@ nxt_unit_buf_send(nxt_unit_buf_t *buf)
}
if (nxt_fast_path(buf->free > buf->start)) {
- rc = nxt_unit_mmap_buf_send(req->ctx,
- req_impl->recv_msg.port_msg.stream,
- mmap_buf, 0);
+ rc = nxt_unit_mmap_buf_send(req->ctx, req_impl->stream, mmap_buf, 0);
if (nxt_slow_path(rc != NXT_UNIT_OK)) {
return rc;
}
@@ -1472,10 +1851,7 @@ nxt_unit_buf_send_done(nxt_unit_buf_t *buf)
req = mmap_buf->req;
req_impl = nxt_container_of(req, nxt_unit_request_info_impl_t, req);
- rc = nxt_unit_mmap_buf_send(req->ctx,
- req_impl->recv_msg.port_msg.stream,
- mmap_buf, 1);
-
+ rc = nxt_unit_mmap_buf_send(req->ctx, req_impl->stream, mmap_buf, 1);
if (nxt_slow_path(rc == NXT_UNIT_OK)) {
nxt_unit_mmap_buf_release(mmap_buf);
@@ -1506,6 +1882,7 @@ nxt_unit_mmap_buf_send(nxt_unit_ctx_t *ctx, uint32_t stream,
lib = nxt_container_of(ctx->unit, nxt_unit_impl_t, unit);
buf = &mmap_buf->buf;
+ hdr = mmap_buf->hdr;
m.mmap_msg.size = buf->free - buf->start;
@@ -1514,15 +1891,15 @@ nxt_unit_mmap_buf_send(nxt_unit_ctx_t *ctx, uint32_t stream,
m.msg.reply_port = 0;
m.msg.type = _NXT_PORT_MSG_DATA;
m.msg.last = last != 0;
- m.msg.mmap = m.mmap_msg.size > 0;
+ m.msg.mmap = hdr != NULL && m.mmap_msg.size > 0;
m.msg.nf = 0;
m.msg.mf = 0;
m.msg.tracking = 0;
- hdr = mmap_buf->hdr;
-
- m.mmap_msg.mmap_id = hdr->id;
- m.mmap_msg.chunk_id = nxt_port_mmap_chunk_id(hdr, (u_char *) buf->start);
+ if (hdr != NULL) {
+ m.mmap_msg.mmap_id = hdr->id;
+ m.mmap_msg.chunk_id = nxt_port_mmap_chunk_id(hdr, (u_char *) buf->start);
+ }
nxt_unit_debug(ctx, "#%"PRIu32": send mmap: (%d,%d,%d)",
stream,
@@ -1531,14 +1908,13 @@ nxt_unit_mmap_buf_send(nxt_unit_ctx_t *ctx, uint32_t stream,
(int) m.mmap_msg.size);
res = lib->callbacks.port_send(ctx, &mmap_buf->port_id, &m,
- m.mmap_msg.size > 0 ? sizeof(m)
- : sizeof(m.msg),
+ m.msg.mmap ? sizeof(m) : sizeof(m.msg),
NULL, 0);
if (nxt_slow_path(res != sizeof(m))) {
return NXT_UNIT_ERROR;
}
- if (buf->end - buf->free >= PORT_MMAP_CHUNK_SIZE) {
+ if (buf->end - buf->free >= PORT_MMAP_CHUNK_SIZE && hdr != NULL) {
last_used = (u_char *) buf->free - 1;
first_free_chunk = nxt_port_mmap_chunk_id(hdr, last_used) + 1;
@@ -1557,11 +1933,17 @@ nxt_unit_mmap_buf_send(nxt_unit_ctx_t *ctx, uint32_t stream,
void
nxt_unit_buf_free(nxt_unit_buf_t *buf)
{
- nxt_unit_mmap_buf_t *mmap_buf;
+ nxt_unit_mmap_buf_free(nxt_container_of(buf, nxt_unit_mmap_buf_t, buf));
+}
- mmap_buf = nxt_container_of(buf, nxt_unit_mmap_buf_t, buf);
- nxt_unit_mmap_release(mmap_buf->hdr, buf->start, buf->end - buf->start);
+static void
+nxt_unit_mmap_buf_free(nxt_unit_mmap_buf_t *mmap_buf)
+{
+ if (nxt_fast_path(mmap_buf->hdr != NULL)) {
+ nxt_unit_mmap_release(mmap_buf->hdr, mmap_buf->buf.start,
+ mmap_buf->buf.end - mmap_buf->buf.start);
+ }
nxt_unit_mmap_buf_release(mmap_buf);
}
@@ -1570,26 +1952,15 @@ nxt_unit_buf_free(nxt_unit_buf_t *buf)
nxt_unit_buf_t *
nxt_unit_buf_next(nxt_unit_buf_t *buf)
{
- nxt_queue_link_t *lnk;
- nxt_unit_mmap_buf_t *mmap_buf;
- nxt_unit_request_info_impl_t *req_impl;
+ nxt_unit_mmap_buf_t *mmap_buf;
mmap_buf = nxt_container_of(buf, nxt_unit_mmap_buf_t, buf);
- req_impl = nxt_container_of(mmap_buf->req, nxt_unit_request_info_impl_t,
- req);
- lnk = &mmap_buf->link;
-
- if (lnk == nxt_queue_last(&req_impl->incoming_buf)
- || lnk == nxt_queue_last(&req_impl->outgoing_buf))
- {
+ if (mmap_buf->next == NULL) {
return NULL;
}
- lnk = nxt_queue_next(lnk);
- mmap_buf = nxt_container_of(lnk, nxt_unit_mmap_buf_t, link);
-
- return &mmap_buf->buf;
+ return &mmap_buf->next->buf;
}
@@ -1614,7 +1985,6 @@ nxt_unit_response_write(nxt_unit_request_info_t *req, const void *start,
int rc;
uint32_t part_size;
const char *part_start;
- nxt_unit_process_t *process;
nxt_unit_mmap_buf_t mmap_buf;
nxt_unit_request_info_impl_t *req_impl;
@@ -1641,16 +2011,12 @@ nxt_unit_response_write(nxt_unit_request_info_t *req, const void *start,
part_start += part_size;
}
- process = nxt_unit_msg_get_process(req->ctx, &req_impl->recv_msg);
- if (nxt_slow_path(process == NULL)) {
- return NXT_UNIT_ERROR;
- }
-
while (size > 0) {
part_size = nxt_min(size, PORT_MMAP_DATA_SIZE);
- rc = nxt_unit_get_outgoing_buf(req->ctx, process, &req->response_port,
- part_size, &mmap_buf);
+ rc = nxt_unit_get_outgoing_buf(req->ctx, req_impl->process,
+ &req->response_port, part_size,
+ &mmap_buf);
if (nxt_slow_path(rc != NXT_UNIT_OK)) {
return rc;
}
@@ -1658,9 +2024,7 @@ nxt_unit_response_write(nxt_unit_request_info_t *req, const void *start,
mmap_buf.buf.free = nxt_cpymem(mmap_buf.buf.free,
part_start, part_size);
- rc = nxt_unit_mmap_buf_send(req->ctx,
- req_impl->recv_msg.port_msg.stream,
- &mmap_buf, 0);
+ rc = nxt_unit_mmap_buf_send(req->ctx, req_impl->stream, &mmap_buf, 0);
if (nxt_slow_path(rc != NXT_UNIT_OK)) {
nxt_unit_mmap_release(mmap_buf.hdr, mmap_buf.buf.start,
mmap_buf.buf.end - mmap_buf.buf.start);
@@ -1766,6 +2130,14 @@ nxt_unit_response_write_cb(nxt_unit_request_info_t *req,
ssize_t
nxt_unit_request_read(nxt_unit_request_info_t *req, void *dst, size_t size)
{
+ return nxt_unit_buf_read(&req->content_buf, &req->content_length,
+ dst, size);
+}
+
+
+static ssize_t
+nxt_unit_buf_read(nxt_unit_buf_t **b, uint64_t *len, void *dst, size_t size)
+{
u_char *p;
size_t rest, copy, read;
nxt_unit_buf_t *buf;
@@ -1773,7 +2145,7 @@ nxt_unit_request_read(nxt_unit_request_info_t *req, void *dst, size_t size)
p = dst;
rest = size;
- buf = req->content_buf;
+ buf = *b;
while (buf != NULL) {
copy = buf->end - buf->free;
@@ -1795,11 +2167,11 @@ nxt_unit_request_read(nxt_unit_request_info_t *req, void *dst, size_t size)
buf = nxt_unit_buf_next(buf);
}
- req->content_buf = buf;
+ *b = buf;
read = size - rest;
- req->content_length -= read;
+ *len -= read;
return read;
}
@@ -1852,7 +2224,7 @@ skip_response_send:
lib = nxt_container_of(req->unit, nxt_unit_impl_t, unit);
- msg.stream = req_impl->recv_msg.port_msg.stream;
+ msg.stream = req_impl->stream;
msg.pid = lib->pid;
msg.reply_port = 0;
msg.type = (rc == NXT_UNIT_OK) ? _NXT_PORT_MSG_DATA
@@ -1874,6 +2246,162 @@ skip_response_send:
}
+int
+nxt_unit_websocket_send(nxt_unit_request_info_t *req, uint8_t opcode,
+ uint8_t last, const void *start, size_t size)
+{
+ const struct iovec iov = { (void *) start, size };
+
+ return nxt_unit_websocket_sendv(req, opcode, last, &iov, 1);
+}
+
+
+int
+nxt_unit_websocket_sendv(nxt_unit_request_info_t *req, uint8_t opcode,
+ uint8_t last, const struct iovec *iov, int iovcnt)
+{
+ int i, rc;
+ size_t l, copy;
+ uint32_t payload_len, buf_size;
+ const uint8_t *b;
+ nxt_unit_buf_t *buf;
+ nxt_websocket_header_t *wh;
+
+ payload_len = 0;
+
+ for (i = 0; i < iovcnt; i++) {
+ payload_len += iov[i].iov_len;
+ }
+
+ buf_size = 10 + payload_len;
+
+ buf = nxt_unit_response_buf_alloc(req, nxt_min(buf_size,
+ PORT_MMAP_DATA_SIZE));
+ if (nxt_slow_path(buf == NULL)) {
+ nxt_unit_req_error(req, "Failed to allocate buf for content");
+
+ return NXT_UNIT_ERROR;
+ }
+
+ buf->start[0] = 0;
+ buf->start[1] = 0;
+
+ wh = (void *) buf->free;
+
+ buf->free = nxt_websocket_frame_init(wh, payload_len);
+ wh->fin = last;
+ wh->opcode = opcode;
+
+ for (i = 0; i < iovcnt; i++) {
+ b = iov[i].iov_base;
+ l = iov[i].iov_len;
+
+ while (l > 0) {
+ copy = buf->end - buf->free;
+ copy = nxt_min(l, copy);
+
+ buf->free = nxt_cpymem(buf->free, b, copy);
+ b += copy;
+ l -= copy;
+
+ if (l > 0) {
+ buf_size -= buf->end - buf->start;
+
+ rc = nxt_unit_buf_send(buf);
+ if (nxt_slow_path(rc != NXT_UNIT_OK)) {
+ nxt_unit_req_error(req, "Failed to send content");
+
+ return NXT_UNIT_ERROR;
+ }
+
+ buf = nxt_unit_response_buf_alloc(req, nxt_min(buf_size,
+ PORT_MMAP_DATA_SIZE));
+ if (nxt_slow_path(buf == NULL)) {
+ nxt_unit_req_error(req,
+ "Failed to allocate buf for content");
+
+ return NXT_UNIT_ERROR;
+ }
+ }
+ }
+ }
+
+ if (buf->free > buf->start) {
+ rc = nxt_unit_buf_send(buf);
+ if (nxt_slow_path(rc != NXT_UNIT_OK)) {
+ nxt_unit_req_error(req, "Failed to send content");
+ }
+ }
+
+ return rc;
+}
+
+
+ssize_t
+nxt_unit_websocket_read(nxt_unit_websocket_frame_t *ws, void *dst,
+ size_t size)
+{
+ ssize_t res;
+ uint8_t *b;
+ uint64_t i, d;
+
+ res = nxt_unit_buf_read(&ws->content_buf, &ws->content_length,
+ dst, size);
+
+ if (ws->mask == NULL) {
+ return res;
+ }
+
+ b = dst;
+ d = (ws->payload_len - ws->content_length - res) % 4;
+
+ for (i = 0; i < (uint64_t) res; i++) {
+ b[i] ^= ws->mask[ (i + d) % 4 ];
+ }
+
+ return res;
+}
+
+
+int
+nxt_unit_websocket_retain(nxt_unit_websocket_frame_t *ws)
+{
+ char *b;
+ size_t size;
+ nxt_unit_websocket_frame_impl_t *ws_impl;
+
+ ws_impl = nxt_container_of(ws, nxt_unit_websocket_frame_impl_t, ws);
+
+ if (ws_impl->retain_buf != NULL || ws_impl->buf->hdr != NULL) {
+ return NXT_UNIT_OK;
+ }
+
+ size = ws_impl->buf->buf.end - ws_impl->buf->buf.start;
+
+ b = malloc(size);
+ if (nxt_slow_path(b == NULL)) {
+ return NXT_UNIT_ERROR;
+ }
+
+ memcpy(b, ws_impl->buf->buf.start, size);
+
+ ws_impl->buf->buf.start = b;
+ ws_impl->buf->buf.free = b;
+ ws_impl->buf->buf.end = b + size;
+
+ ws_impl->retain_buf = b;
+
+ return NXT_UNIT_OK;
+}
+
+
+void
+nxt_unit_websocket_done(nxt_unit_websocket_frame_t *ws)
+{
+ nxt_unit_websocket_frame_release(ws);
+}
+
+
static nxt_port_mmap_header_t *
nxt_unit_mmap_get(nxt_unit_ctx_t *ctx, nxt_unit_process_t *process,
nxt_unit_port_id_t *port_id, nxt_chunk_id_t *c, int n)
@@ -2355,7 +2883,7 @@ nxt_unit_tracking_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg)
if (recv_msg->size < (int) sizeof(nxt_port_mmap_tracking_msg_t)) {
nxt_unit_warn(ctx, "#%"PRIu32": tracking_read: too small message (%d)",
- recv_msg->port_msg.stream, (int) recv_msg->size);
+ recv_msg->stream, (int) recv_msg->size);
return 0;
}
@@ -2378,18 +2906,18 @@ nxt_unit_tracking_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg)
nxt_unit_warn(ctx, "#%"PRIu32": tracking_read: "
"invalid mmap id %d,%"PRIu32,
- recv_msg->port_msg.stream,
- (int) process->pid, tracking_msg->mmap_id);
+ recv_msg->stream, (int) process->pid,
+ tracking_msg->mmap_id);
return 0;
}
c = tracking_msg->tracking_id;
- rc = nxt_atomic_cmp_set(hdr->tracking + c, recv_msg->port_msg.stream, 0);
+ rc = nxt_atomic_cmp_set(hdr->tracking + c, recv_msg->stream, 0);
if (rc == 0) {
nxt_unit_debug(ctx, "#%"PRIu32": tracking cancelled",
- recv_msg->port_msg.stream);
+ recv_msg->stream);
nxt_port_mmap_set_chunk_free(hdr->free_tracking_map, c);
}
@@ -2401,19 +2929,18 @@ nxt_unit_tracking_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg)
static int
-nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg,
- nxt_queue_t *incoming_buf)
+nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg)
{
void *start;
uint32_t size;
nxt_unit_process_t *process;
- nxt_unit_mmap_buf_t *b;
+ nxt_unit_mmap_buf_t *b, **incoming_tail;
nxt_port_mmap_msg_t *mmap_msg, *end;
nxt_port_mmap_header_t *hdr;
if (nxt_slow_path(recv_msg->size < sizeof(nxt_port_mmap_msg_t))) {
nxt_unit_warn(ctx, "#%"PRIu32": mmap_read: too small message (%d)",
- recv_msg->port_msg.stream, (int) recv_msg->size);
+ recv_msg->stream, (int) recv_msg->size);
return NXT_UNIT_ERROR;
}
@@ -2426,6 +2953,8 @@ nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg,
mmap_msg = recv_msg->start;
end = nxt_pointer_to(recv_msg->start, recv_msg->size);
+ incoming_tail = &recv_msg->incoming_buf;
+
pthread_mutex_lock(&process->incoming.mutex);
for (; mmap_msg < end; mmap_msg++) {
@@ -2435,8 +2964,8 @@ nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg,
nxt_unit_warn(ctx, "#%"PRIu32": mmap_read: "
"invalid mmap id %d,%"PRIu32,
- recv_msg->port_msg.stream,
- (int) process->pid, mmap_msg->mmap_id);
+ recv_msg->stream, (int) process->pid,
+ mmap_msg->mmap_id);
return NXT_UNIT_ERROR;
}
@@ -2453,16 +2982,16 @@ nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg,
if (nxt_slow_path(b == NULL)) {
pthread_mutex_unlock(&process->incoming.mutex);
- nxt_unit_warn(ctx, "#%"PRIu32": mmap_read: "
- "failed to allocate buf",
- recv_msg->port_msg.stream);
+ nxt_unit_warn(ctx, "#%"PRIu32": mmap_read: failed to allocate buf",
+ recv_msg->stream);
nxt_unit_mmap_release(hdr, start, size);
return NXT_UNIT_ERROR;
}
- nxt_queue_insert_tail(incoming_buf, &b->link);
+ nxt_unit_mmap_buf_insert(incoming_tail, b);
+ incoming_tail = &b->next;
b->buf.start = start;
b->buf.free = start;
@@ -2470,7 +2999,7 @@ nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg,
b->hdr = hdr;
nxt_unit_debug(ctx, "#%"PRIu32": mmap_read: [%p,%d] %d->%d,(%d,%d,%d)",
- recv_msg->port_msg.stream,
+ recv_msg->stream,
start, (int) size,
(int) hdr->src_pid, (int) hdr->dst_pid,
(int) hdr->id, (int) mmap_msg->chunk_id,
@@ -2685,6 +3214,11 @@ nxt_unit_run_once(nxt_unit_ctx_t *ctx)
if (nxt_fast_path(rsize > 0)) {
rc = nxt_unit_process_msg(ctx, &ctx_impl->read_port_id, buf, rsize,
oob, sizeof(oob));
+
+#if (NXT_DEBUG)
+ memset(buf, 0xAC, rsize);
+#endif
+
} else {
rc = NXT_UNIT_ERROR;
}
@@ -2775,10 +3309,11 @@ nxt_unit_ctx_alloc(nxt_unit_ctx_t *ctx, void *data)
void
nxt_unit_ctx_free(nxt_unit_ctx_t *ctx)
{
- nxt_unit_impl_t *lib;
- nxt_unit_ctx_impl_t *ctx_impl;
- nxt_unit_mmap_buf_t *mmap_buf;
- nxt_unit_request_info_impl_t *req_impl;
+ nxt_unit_impl_t *lib;
+ nxt_unit_ctx_impl_t *ctx_impl;
+ nxt_unit_mmap_buf_t *mmap_buf;
+ nxt_unit_request_info_impl_t *req_impl;
+ nxt_unit_websocket_frame_impl_t *ws_impl;
ctx_impl = nxt_container_of(ctx, nxt_unit_ctx_impl_t, ctx);
lib = nxt_container_of(ctx->unit, nxt_unit_impl_t, unit);
@@ -2792,15 +3327,14 @@ nxt_unit_ctx_free(nxt_unit_ctx_t *ctx)
} nxt_queue_loop;
- nxt_queue_remove(&ctx_impl->ctx_buf[0].link);
- nxt_queue_remove(&ctx_impl->ctx_buf[1].link);
-
- nxt_queue_each(mmap_buf, &ctx_impl->free_buf, nxt_unit_mmap_buf_t, link) {
+ nxt_unit_mmap_buf_remove(&ctx_impl->ctx_buf[0]);
+ nxt_unit_mmap_buf_remove(&ctx_impl->ctx_buf[1]);
- nxt_queue_remove(&mmap_buf->link);
+ while (ctx_impl->free_buf != NULL) {
+ mmap_buf = ctx_impl->free_buf;
+ nxt_unit_mmap_buf_remove(mmap_buf);
free(mmap_buf);
-
- } nxt_queue_loop;
+ }
nxt_queue_each(req_impl, &ctx_impl->free_req,
nxt_unit_request_info_impl_t, link)
@@ -2809,6 +3343,13 @@ nxt_unit_ctx_free(nxt_unit_ctx_t *ctx)
} nxt_queue_loop;
+ nxt_queue_each(ws_impl, &ctx_impl->free_ws,
+ nxt_unit_websocket_frame_impl_t, link)
+ {
+ nxt_unit_websocket_frame_free(ws_impl);
+
+ } nxt_queue_loop;
+
nxt_queue_remove(&ctx_impl->link);
if (ctx_impl != &lib->main_ctx) {
@@ -3454,6 +3995,83 @@ nxt_unit_port_hash_find(nxt_lvlhsh_t *port_hash, nxt_unit_port_id_t *port_id,
}
+static nxt_int_t
+nxt_unit_request_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
+{
+ return NXT_OK;
+}
+
+
+static const nxt_lvlhsh_proto_t lvlhsh_requests_proto nxt_aligned(64) = {
+ NXT_LVLHSH_DEFAULT,
+ nxt_unit_request_hash_test,
+ nxt_lvlhsh_alloc,
+ nxt_lvlhsh_free,
+};
+
+
+static int
+nxt_unit_request_hash_add(nxt_lvlhsh_t *request_hash,
+ nxt_unit_request_info_impl_t *req_impl)
+{
+ uint32_t *stream;
+ nxt_int_t res;
+ nxt_lvlhsh_query_t lhq;
+
+ stream = &req_impl->stream;
+
+ lhq.key_hash = nxt_murmur_hash2(stream, sizeof(*stream));
+ lhq.key.length = sizeof(*stream);
+ lhq.key.start = (u_char *) stream;
+ lhq.proto = &lvlhsh_requests_proto;
+ lhq.pool = NULL;
+ lhq.replace = 0;
+ lhq.value = req_impl;
+
+ res = nxt_lvlhsh_insert(request_hash, &lhq);
+
+ switch (res) {
+
+ case NXT_OK:
+ return NXT_UNIT_OK;
+
+ default:
+ return NXT_UNIT_ERROR;
+ }
+}
+
+
+static nxt_unit_request_info_impl_t *
+nxt_unit_request_hash_find(nxt_lvlhsh_t *request_hash, uint32_t stream,
+ int remove)
+{
+ nxt_int_t res;
+ nxt_lvlhsh_query_t lhq;
+
+ lhq.key_hash = nxt_murmur_hash2(&stream, sizeof(stream));
+ lhq.key.length = sizeof(stream);
+ lhq.key.start = (u_char *) &stream;
+ lhq.proto = &lvlhsh_requests_proto;
+ lhq.pool = NULL;
+
+ if (remove) {
+ res = nxt_lvlhsh_delete(request_hash, &lhq);
+
+ } else {
+ res = nxt_lvlhsh_find(request_hash, &lhq);
+ }
+
+ switch (res) {
+
+ case NXT_OK:
+ return lhq.value;
+
+ default:
+ return NULL;
+ }
+}
+
+
void
nxt_unit_log(nxt_unit_ctx_t *ctx, int level, const char *fmt, ...)
{
@@ -3526,8 +4144,7 @@ nxt_unit_req_log(nxt_unit_request_info_t *req, int level, const char *fmt, ...)
if (nxt_fast_path(req != NULL)) {
req_impl = nxt_container_of(req, nxt_unit_request_info_impl_t, req);
- p += snprintf(p, end - p,
- "#%"PRIu32": ", req_impl->recv_msg.port_msg.stream);
+ p += snprintf(p, end - p, "#%"PRIu32": ", req_impl->stream);
}
va_start(ap, fmt);
diff --git a/src/nxt_unit.h b/src/nxt_unit.h
index 532de20d..3471a758 100644
--- a/src/nxt_unit.h
+++ b/src/nxt_unit.h
@@ -9,6 +9,7 @@
#include <inttypes.h>
#include <sys/types.h>
+#include <sys/uio.h>
#include <string.h>
#include "nxt_version.h"
@@ -106,17 +107,24 @@ struct nxt_unit_request_info_s {
void *data;
};
+
/*
* Set of application-specific callbacks. Application may leave all optional
* callbacks as NULL.
*/
struct nxt_unit_callbacks_s {
/*
- * Process request data. Unlike all other callback, this callback
+ * Process request. Unlike all other callback, this callback
* need to be defined by application.
*/
void (*request_handler)(nxt_unit_request_info_t *req);
+ /* Process websocket frame. */
+ void (*websocket_handler)(nxt_unit_websocket_frame_t *ws);
+
+ /* Connection closed. */
+ void (*close_handler)(nxt_unit_request_info_t *req);
+
/* Add new Unit port to communicate with process pid. Optional. */
int (*add_port)(nxt_unit_ctx_t *, nxt_unit_port_t *port);
@@ -293,6 +301,14 @@ int nxt_unit_response_is_sent(nxt_unit_request_info_t *req);
nxt_unit_buf_t *nxt_unit_response_buf_alloc(nxt_unit_request_info_t *req,
uint32_t size);
+int nxt_unit_request_is_websocket_handshake(nxt_unit_request_info_t *req);
+
+int nxt_unit_response_upgrade(nxt_unit_request_info_t *req);
+
+int nxt_unit_response_is_websocket(nxt_unit_request_info_t *req);
+
+nxt_unit_request_info_t *nxt_unit_get_request_info_from_data(void *data);
+
int nxt_unit_buf_send(nxt_unit_buf_t *buf);
void nxt_unit_buf_free(nxt_unit_buf_t *buf);
@@ -315,6 +331,20 @@ ssize_t nxt_unit_request_read(nxt_unit_request_info_t *req, void *dst,
void nxt_unit_request_done(nxt_unit_request_info_t *req, int rc);
+int nxt_unit_websocket_send(nxt_unit_request_info_t *req, uint8_t opcode,
+ uint8_t last, const void *start, size_t size);
+
+int nxt_unit_websocket_sendv(nxt_unit_request_info_t *req, uint8_t opcode,
+ uint8_t last, const struct iovec *iov, int iovcnt);
+
+ssize_t nxt_unit_websocket_read(nxt_unit_websocket_frame_t *ws, void *dst,
+ size_t size);
+
+int nxt_unit_websocket_retain(nxt_unit_websocket_frame_t *ws);
+
+void nxt_unit_websocket_done(nxt_unit_websocket_frame_t *ws);
+
+
void nxt_unit_log(nxt_unit_ctx_t *ctx, int level, const char* fmt, ...);
void nxt_unit_req_log(nxt_unit_request_info_t *req, int level,
diff --git a/src/nxt_unit_request.h b/src/nxt_unit_request.h
index 2207cefa..52017a42 100644
--- a/src/nxt_unit_request.h
+++ b/src/nxt_unit_request.h
@@ -20,6 +20,7 @@ struct nxt_unit_request_s {
uint8_t remote_length;
uint8_t local_length;
uint8_t tls;
+ uint8_t websocket_handshake;
uint32_t server_name_length;
uint32_t target_length;
uint32_t path_length;
diff --git a/src/nxt_unit_typedefs.h b/src/nxt_unit_typedefs.h
index 871ce25b..26e54f91 100644
--- a/src/nxt_unit_typedefs.h
+++ b/src/nxt_unit_typedefs.h
@@ -7,19 +7,20 @@
#define _NXT_UNIT_TYPEDEFS_H_INCLUDED_
-typedef struct nxt_unit_s nxt_unit_t;
-typedef struct nxt_unit_ctx_s nxt_unit_ctx_t;
-typedef struct nxt_unit_port_id_s nxt_unit_port_id_t;
-typedef struct nxt_unit_port_s nxt_unit_port_t;
-typedef struct nxt_unit_buf_s nxt_unit_buf_t;
-typedef struct nxt_unit_request_info_s nxt_unit_request_info_t;
-typedef struct nxt_unit_callbacks_s nxt_unit_callbacks_t;
-typedef struct nxt_unit_init_s nxt_unit_init_t;
-typedef union nxt_unit_sptr_u nxt_unit_sptr_t;
-typedef struct nxt_unit_field_s nxt_unit_field_t;
-typedef struct nxt_unit_request_s nxt_unit_request_t;
-typedef struct nxt_unit_response_s nxt_unit_response_t;
-typedef struct nxt_unit_read_info_s nxt_unit_read_info_t;
+typedef struct nxt_unit_s nxt_unit_t;
+typedef struct nxt_unit_ctx_s nxt_unit_ctx_t;
+typedef struct nxt_unit_port_id_s nxt_unit_port_id_t;
+typedef struct nxt_unit_port_s nxt_unit_port_t;
+typedef struct nxt_unit_buf_s nxt_unit_buf_t;
+typedef struct nxt_unit_request_info_s nxt_unit_request_info_t;
+typedef struct nxt_unit_callbacks_s nxt_unit_callbacks_t;
+typedef struct nxt_unit_init_s nxt_unit_init_t;
+typedef union nxt_unit_sptr_u nxt_unit_sptr_t;
+typedef struct nxt_unit_field_s nxt_unit_field_t;
+typedef struct nxt_unit_request_s nxt_unit_request_t;
+typedef struct nxt_unit_response_s nxt_unit_response_t;
+typedef struct nxt_unit_read_info_s nxt_unit_read_info_t;
+typedef struct nxt_unit_websocket_frame_s nxt_unit_websocket_frame_t;
#endif /* _NXT_UNIT_TYPEDEFS_H_INCLUDED_ */
diff --git a/src/nxt_unit_websocket.h b/src/nxt_unit_websocket.h
new file mode 100644
index 00000000..beb2536e
--- /dev/null
+++ b/src/nxt_unit_websocket.h
@@ -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_ */
diff --git a/src/nxt_websocket.c b/src/nxt_websocket.c
new file mode 100644
index 00000000..9a099760
--- /dev/null
+++ b/src/nxt_websocket.c
@@ -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;
+}
diff --git a/src/nxt_websocket.h b/src/nxt_websocket.h
new file mode 100644
index 00000000..499a3268
--- /dev/null
+++ b/src/nxt_websocket.h
@@ -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_ */
diff --git a/src/nxt_websocket_accept.c b/src/nxt_websocket_accept.c
new file mode 100644
index 00000000..05cbcb56
--- /dev/null
+++ b/src/nxt_websocket_accept.c
@@ -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));
+}
+
+
diff --git a/src/nxt_websocket_header.h b/src/nxt_websocket_header.h
new file mode 100644
index 00000000..f75dfacd
--- /dev/null
+++ b/src/nxt_websocket_header.h
@@ -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_ */
diff --git a/src/perl/nxt_perl_psgi_layer.h b/src/perl/nxt_perl_psgi_layer.h
index 561d5153..3fa349c0 100644
--- a/src/perl/nxt_perl_psgi_layer.h
+++ b/src/perl/nxt_perl_psgi_layer.h
@@ -8,7 +8,6 @@
#define _NXT_PERL_PSGI_LAYER_H_INCLUDED_
-#define _GNU_SOURCE
#include <EXTERN.h>
#include <XSUB.h>
#include <perl.h>
diff --git a/src/test/nxt_unit_websocket_chat.c b/src/test/nxt_unit_websocket_chat.c
new file mode 100644
index 00000000..ecc9a243
--- /dev/null
+++ b/src/test/nxt_unit_websocket_chat.c
@@ -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);
diff --git a/src/test/nxt_unit_websocket_echo.c b/src/test/nxt_unit_websocket_echo.c
new file mode 100644
index 00000000..2a89cdc0
--- /dev/null
+++ b/src/test/nxt_unit_websocket_echo.c
@@ -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;
+}
diff --git a/test/go/404/app.go b/test/go/404/app.go
index abb33066..08fe56c9 100644
--- a/test/go/404/app.go
+++ b/test/go/404/app.go
@@ -1,22 +1,22 @@
package main
import (
- "io"
- "io/ioutil"
- "net/http"
- "nginx/unit"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "nginx/unit"
)
func handler(w http.ResponseWriter, r *http.Request) {
- b, e := ioutil.ReadFile("404.html")
+ b, e := ioutil.ReadFile("404.html")
- if e == nil {
- w.WriteHeader(http.StatusNotFound)
- io.WriteString(w, string(b))
- }
+ if e == nil {
+ w.WriteHeader(http.StatusNotFound)
+ io.WriteString(w, string(b))
+ }
}
func main() {
- http.HandleFunc("/", handler)
- unit.ListenAndServe(":7080", nil)
+ http.HandleFunc("/", handler)
+ unit.ListenAndServe(":7080", nil)
}
diff --git a/test/go/command_line_arguments/app.go b/test/go/command_line_arguments/app.go
index 228e07c0..234e565e 100644
--- a/test/go/command_line_arguments/app.go
+++ b/test/go/command_line_arguments/app.go
@@ -1,23 +1,23 @@
package main
import (
- "io"
- "os"
- "fmt"
- "strings"
- "net/http"
- "nginx/unit"
+ "fmt"
+ "io"
+ "net/http"
+ "nginx/unit"
+ "os"
+ "strings"
)
func handler(w http.ResponseWriter, r *http.Request) {
- args := strings.Join(os.Args[1:], ",")
+ args := strings.Join(os.Args[1:], ",")
- w.Header().Add("X-Arg-0", fmt.Sprintf("%v", os.Args[0]))
- w.Header().Add("Content-Length", fmt.Sprintf("%v", len(args)))
- io.WriteString(w, args)
+ w.Header().Add("X-Arg-0", fmt.Sprintf("%v", os.Args[0]))
+ w.Header().Add("Content-Length", fmt.Sprintf("%v", len(args)))
+ io.WriteString(w, args)
}
func main() {
- http.HandleFunc("/", handler)
- unit.ListenAndServe(":7080", nil)
+ http.HandleFunc("/", handler)
+ unit.ListenAndServe(":7080", nil)
}
diff --git a/test/go/cookies/app.go b/test/go/cookies/app.go
index 6fb9def0..e6647ea8 100644
--- a/test/go/cookies/app.go
+++ b/test/go/cookies/app.go
@@ -1,19 +1,19 @@
package main
import (
- "net/http"
- "nginx/unit"
+ "net/http"
+ "nginx/unit"
)
func handler(w http.ResponseWriter, r *http.Request) {
- cookie1, _ := r.Cookie("var1")
- cookie2, _ := r.Cookie("var2")
+ cookie1, _ := r.Cookie("var1")
+ cookie2, _ := r.Cookie("var2")
- w.Header().Set("X-Cookie-1", cookie1.Value)
- w.Header().Set("X-Cookie-2", cookie2.Value)
+ w.Header().Set("X-Cookie-1", cookie1.Value)
+ w.Header().Set("X-Cookie-2", cookie2.Value)
}
func main() {
- http.HandleFunc("/", handler)
- unit.ListenAndServe(":7080", nil)
+ http.HandleFunc("/", handler)
+ unit.ListenAndServe(":7080", nil)
}
diff --git a/test/go/empty/app.go b/test/go/empty/app.go
index 2e07405f..6e0fce1b 100644
--- a/test/go/empty/app.go
+++ b/test/go/empty/app.go
@@ -1,13 +1,13 @@
package main
import (
- "net/http"
- "nginx/unit"
+ "net/http"
+ "nginx/unit"
)
func handler(w http.ResponseWriter, r *http.Request) {}
func main() {
- http.HandleFunc("/", handler)
- unit.ListenAndServe(":7080", nil)
+ http.HandleFunc("/", handler)
+ unit.ListenAndServe(":7080", nil)
}
diff --git a/test/go/get_variables/app.go b/test/go/get_variables/app.go
index 563febc8..4dcc0e7b 100644
--- a/test/go/get_variables/app.go
+++ b/test/go/get_variables/app.go
@@ -1,17 +1,17 @@
package main
import (
- "net/http"
- "nginx/unit"
+ "net/http"
+ "nginx/unit"
)
func handler(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("X-Var-1", r.URL.Query().Get("var1"))
- w.Header().Set("X-Var-2", r.URL.Query().Get("var2"))
- w.Header().Set("X-Var-3", r.URL.Query().Get("var3"))
+ w.Header().Set("X-Var-1", r.URL.Query().Get("var1"))
+ w.Header().Set("X-Var-2", r.URL.Query().Get("var2"))
+ w.Header().Set("X-Var-3", r.URL.Query().Get("var3"))
}
func main() {
- http.HandleFunc("/", handler)
- unit.ListenAndServe(":7080", nil)
+ http.HandleFunc("/", handler)
+ unit.ListenAndServe(":7080", nil)
}
diff --git a/test/go/mirror/app.go b/test/go/mirror/app.go
index 82b1c92d..748aa7ee 100644
--- a/test/go/mirror/app.go
+++ b/test/go/mirror/app.go
@@ -1,21 +1,21 @@
package main
import (
- "io"
- "fmt"
- "net/http"
- "nginx/unit"
+ "fmt"
+ "io"
+ "net/http"
+ "nginx/unit"
)
func handler(w http.ResponseWriter, r *http.Request) {
- var buf [32768]byte;
- len, _ := r.Body.Read(buf[:])
+ var buf [32768]byte
+ len, _ := r.Body.Read(buf[:])
- w.Header().Add("Content-Length", fmt.Sprintf("%v", len))
- io.WriteString(w, string(buf[:len]))
+ w.Header().Add("Content-Length", fmt.Sprintf("%v", len))
+ io.WriteString(w, string(buf[:len]))
}
func main() {
- http.HandleFunc("/", handler)
- unit.ListenAndServe(":7080", nil)
+ http.HandleFunc("/", handler)
+ unit.ListenAndServe(":7080", nil)
}
diff --git a/test/go/post_variables/app.go b/test/go/post_variables/app.go
index 433afc62..947976d2 100644
--- a/test/go/post_variables/app.go
+++ b/test/go/post_variables/app.go
@@ -1,19 +1,19 @@
package main
import (
- "net/http"
- "nginx/unit"
+ "net/http"
+ "nginx/unit"
)
func handler(w http.ResponseWriter, r *http.Request) {
- r.ParseForm()
+ r.ParseForm()
- w.Header().Set("X-Var-1", r.Form.Get("var1"))
- w.Header().Set("X-Var-2", r.Form.Get("var2"))
- w.Header().Set("X-Var-3", r.Form.Get("var3"))
+ w.Header().Set("X-Var-1", r.Form.Get("var1"))
+ w.Header().Set("X-Var-2", r.Form.Get("var2"))
+ w.Header().Set("X-Var-3", r.Form.Get("var3"))
}
func main() {
- http.HandleFunc("/", handler)
- unit.ListenAndServe(":7080", nil)
+ http.HandleFunc("/", handler)
+ unit.ListenAndServe(":7080", nil)
}
diff --git a/test/go/variables/app.go b/test/go/variables/app.go
index 5db4ac67..fdcbf7e8 100644
--- a/test/go/variables/app.go
+++ b/test/go/variables/app.go
@@ -1,30 +1,30 @@
package main
import (
- "io"
- "fmt"
- "net/http"
- "nginx/unit"
+ "fmt"
+ "io"
+ "net/http"
+ "nginx/unit"
)
func handler(w http.ResponseWriter, r *http.Request) {
- var buf [4096]byte;
- len, _ := r.Body.Read(buf[:])
+ var buf [4096]byte
+ len, _ := r.Body.Read(buf[:])
- w.Header().Set("Request-Method", r.Method)
- w.Header().Set("Request-Uri", r.RequestURI)
- w.Header().Set("Server-Protocol", r.Proto)
- w.Header().Set("Server-Protocol-Major", fmt.Sprintf("%v", r.ProtoMajor))
- w.Header().Set("Server-Protocol-Minor", fmt.Sprintf("%v", r.ProtoMinor))
- w.Header().Set("Content-Length", fmt.Sprintf("%v", len))
- w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
- w.Header().Set("Custom-Header", r.Header.Get("Custom-Header"))
- w.Header().Set("Http-Host", r.Header.Get("Host"))
+ w.Header().Set("Request-Method", r.Method)
+ w.Header().Set("Request-Uri", r.RequestURI)
+ w.Header().Set("Server-Protocol", r.Proto)
+ w.Header().Set("Server-Protocol-Major", fmt.Sprintf("%v", r.ProtoMajor))
+ w.Header().Set("Server-Protocol-Minor", fmt.Sprintf("%v", r.ProtoMinor))
+ w.Header().Set("Content-Length", fmt.Sprintf("%v", len))
+ w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
+ w.Header().Set("Custom-Header", r.Header.Get("Custom-Header"))
+ w.Header().Set("Http-Host", r.Header.Get("Host"))
- io.WriteString(w, string(buf[:len]))
+ io.WriteString(w, string(buf[:len]))
}
func main() {
- http.HandleFunc("/", handler)
- unit.ListenAndServe(":7080", nil)
+ http.HandleFunc("/", handler)
+ unit.ListenAndServe(":7080", nil)
}
diff --git a/test/java/empty_war/empty.war b/test/java/empty_war/empty.war
new file mode 100644
index 00000000..4985e804
--- /dev/null
+++ b/test/java/empty_war/empty.war
Binary files differ
diff --git a/test/java/multipart/app.java b/test/java/multipart/app.java
new file mode 100644
index 00000000..c4c89ffb
--- /dev/null
+++ b/test/java/multipart/app.java
@@ -0,0 +1,93 @@
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import java.util.Map;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.annotation.MultipartConfig;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import javax.servlet.http.Part;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet("/")
+@MultipartConfig(
+ fileSizeThreshold = 1024 * 1024 * 1, // 1 MB
+ maxFileSize = 1024 * 1024 * 10, // 10 MB
+ maxRequestSize = 1024 * 1024 * 15 // 15 MB
+)
+public class app extends HttpServlet
+{
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ response.setContentType("text/html;charset=UTF-8");
+
+ // Create path components to save the file
+ final String path = request.getParameter("destination");
+ final Part filePart = request.getPart("file");
+ final String fileName = getFileName(filePart);
+
+ OutputStream out = null;
+ InputStream filecontent = null;
+ final PrintWriter writer = response.getWriter();
+
+ try {
+ out = new FileOutputStream(new File(path + File.separator
+ + fileName));
+ filecontent = filePart.getInputStream();
+
+ int read = 0;
+ final byte[] bytes = new byte[1024];
+
+ while ((read = filecontent.read(bytes)) != -1) {
+ out.write(bytes, 0, read);
+ }
+ writer.println(fileName + " created at " + path);
+
+ } catch (FileNotFoundException fne) {
+ writer.println("You either did not specify a file to upload or are "
+ + "trying to upload a file to a protected or nonexistent "
+ + "location.");
+ writer.println("<br/> ERROR: " + fne.getMessage());
+
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ if (filecontent != null) {
+ filecontent.close();
+ }
+ if (writer != null) {
+ writer.close();
+ }
+ }
+
+ return;
+ }
+
+ private String getFileName(final Part part) {
+ final String partHeader = part.getHeader("content-disposition");
+
+ for (String content : part.getHeader("content-disposition").split(";"))
+ {
+ if (content.trim().startsWith("filename")) {
+ return content.substring(
+ content.indexOf("=") + 1).trim().replace("\"", "");
+ }
+ }
+ return null;
+ }
+}
diff --git a/test/java/session_inactive/app.java b/test/java/session_inactive/app.java
index f338fc89..618e4d67 100644
--- a/test/java/session_inactive/app.java
+++ b/test/java/session_inactive/app.java
@@ -17,7 +17,13 @@ public class app extends HttpServlet
HttpSession s = request.getSession();
if (s.isNew()) {
- s.setMaxInactiveInterval(2);
+ String interval = request.getHeader("X-Interval");
+
+ if (interval == null) {
+ s.setMaxInactiveInterval(0);
+ } else {
+ s.setMaxInactiveInterval(Integer.parseInt(interval));
+ }
}
response.addHeader("X-Session-Id", s.getId());
diff --git a/test/node/404/app.js b/test/node/404/app.js
index 9600d486..587c432d 100755
--- a/test/node/404/app.js
+++ b/test/node/404/app.js
@@ -3,6 +3,5 @@
var fs = require('fs');
require('unit-http').createServer(function (req, res) {
- res.writeHead(404, {});
- res.end(fs.readFileSync('404.html'));
+ res.writeHead(404, {}).end(fs.readFileSync('404.html'));
}).listen(7080);
diff --git a/test/node/basic/app.js b/test/node/basic/app.js
index bc8d570a..7820c474 100755
--- a/test/node/basic/app.js
+++ b/test/node/basic/app.js
@@ -1,6 +1,6 @@
#!/usr/bin/env node
require('unit-http').createServer(function (req, res) {
- res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'});
- res.end('Hello World\n');
+ res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'})
+ .end('Hello World\n');
}).listen(7080);
diff --git a/test/node/double_end/app.js b/test/node/double_end/app.js
index d8280917..63912097 100755
--- a/test/node/double_end/app.js
+++ b/test/node/double_end/app.js
@@ -1,6 +1,5 @@
#!/usr/bin/env node
require('unit-http').createServer(function (req, res) {
- res.end();
- res.end();
+ res.end().end();
}).listen(7080);
diff --git a/test/node/mirror/app.js b/test/node/mirror/app.js
index abcb87cb..1488917e 100755
--- a/test/node/mirror/app.js
+++ b/test/node/mirror/app.js
@@ -6,7 +6,7 @@ require('unit-http').createServer(function (req, res) {
body += chunk.toString();
});
req.on('end', () => {
- res.writeHead(200, {'Content-Length': Buffer.byteLength(body)});
- res.end(body);
+ res.writeHead(200, {'Content-Length': Buffer.byteLength(body)})
+ .end(body);
});
}).listen(7080);
diff --git a/test/node/promise_handler/app.js b/test/node/promise_handler/app.js
index 60b0c3bb..51c3666b 100755
--- a/test/node/promise_handler/app.js
+++ b/test/node/promise_handler/app.js
@@ -6,8 +6,7 @@ require('unit-http').createServer(function (req, res) {
res.end();
if (req.headers['x-write-call']) {
- res.writeHead(200, {'Content-Type': 'text/plain'});
- res.write('blah');
+ res.writeHead(200, {'Content-Type': 'text/plain'}).write('blah');
}
Promise.resolve().then(() => {
diff --git a/test/node/status_message/app.js b/test/node/status_message/app.js
index 4f3b064a..e8a798dd 100755
--- a/test/node/status_message/app.js
+++ b/test/node/status_message/app.js
@@ -1,6 +1,5 @@
#!/usr/bin/env node
require('unit-http').createServer(function (req, res) {
- res.writeHead(200, 'blah', {'Content-Type': 'text/plain'});
- res.end();
+ res.writeHead(200, 'blah', {'Content-Type': 'text/plain'}).end();
}).listen(7080);
diff --git a/test/node/variables/app.js b/test/node/variables/app.js
index 4ed94d09..d8cdc20c 100755
--- a/test/node/variables/app.js
+++ b/test/node/variables/app.js
@@ -14,7 +14,6 @@ require('unit-http').createServer(function (req, res) {
res.setHeader('Content-Type', req.headers['content-type']);
res.setHeader('Custom-Header', req.headers['custom-header']);
res.setHeader('Http-Host', req.headers['host']);
- res.writeHead(200, {});
- res.end(body);
+ res.writeHead(200, {}).end(body);
});
}).listen(7080);
diff --git a/test/node/websockets/mirror/app.js b/test/node/websockets/mirror/app.js
new file mode 100755
index 00000000..23746465
--- /dev/null
+++ b/test/node/websockets/mirror/app.js
@@ -0,0 +1,31 @@
+#!/usr/bin/env node
+
+server = require('unit-http').createServer(function() {});
+webSocketServer = require('unit-http/websocket').server;
+//server = require('http').createServer(function() {});
+//webSocketServer = require('websocket').server;
+
+server.listen(7080, function() {});
+
+var wsServer = new webSocketServer({
+ maxReceivedMessageSize: 0x1000000000,
+ maxReceivedFrameSize: 0x1000000000,
+ fragmentOutgoingMessages: false,
+ fragmentationThreshold: 0x1000000000,
+ httpServer: server,
+});
+
+wsServer.on('request', function(request) {
+ var connection = request.accept(null);
+
+ connection.on('message', function(message) {
+ if (message.type === 'utf8') {
+ connection.send(message.utf8Data);
+ } else if (message.type === 'binary') {
+ connection.send(message.binaryData);
+ }
+
+ });
+
+ connection.on('close', function(r) {});
+});
diff --git a/test/node/websockets/mirror_fragmentation/app.js b/test/node/websockets/mirror_fragmentation/app.js
new file mode 100755
index 00000000..7024252a
--- /dev/null
+++ b/test/node/websockets/mirror_fragmentation/app.js
@@ -0,0 +1,26 @@
+#!/usr/bin/env node
+
+server = require('unit-http').createServer(function() {});
+webSocketServer = require('unit-http/websocket').server;
+//server = require('http').createServer(function() {});
+//webSocketServer = require('websocket').server;
+
+server.listen(7080, function() {});
+
+var wsServer = new webSocketServer({
+ httpServer: server
+});
+
+wsServer.on('request', function(request) {
+ //console.log('request');
+ var connection = request.accept(null);
+
+ connection.on('message', function(message) {
+ //console.log('message');
+ connection.send(message.utf8Data);
+ });
+
+ connection.on('close', function(r) {
+ //console.log('close');
+ });
+});
diff --git a/test/node/write_before_write_head/app.js b/test/node/write_before_write_head/app.js
index 6e3fb9a9..724b0efb 100755
--- a/test/node/write_before_write_head/app.js
+++ b/test/node/write_before_write_head/app.js
@@ -2,6 +2,5 @@
require('unit-http').createServer(function (req, res) {
res.write('blah');
- res.writeHead(200, {'Content-Type': 'text/plain'});
- res.end();
+ res.writeHead(200, {'Content-Type': 'text/plain'}).end();
}).listen(7080);
diff --git a/test/node/write_buffer/app.js b/test/node/write_buffer/app.js
index f41de2a1..a7623523 100755
--- a/test/node/write_buffer/app.js
+++ b/test/node/write_buffer/app.js
@@ -1,6 +1,6 @@
#!/usr/bin/env node
require('unit-http').createServer(function (req, res) {
- res.writeHead(200, {'Content-Type': 'text/plain'});
- res.end(new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]));
+ res.writeHead(200, {'Content-Type': 'text/plain'})
+ .end(new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]));
}).listen(7080);
diff --git a/test/node/write_return/app.js b/test/node/write_return/app.js
index 3ae967c6..82dfbc6e 100755
--- a/test/node/write_return/app.js
+++ b/test/node/write_return/app.js
@@ -1,6 +1,6 @@
#!/usr/bin/env node
require('unit-http').createServer(function (req, res) {
- res.writeHead(200, {'Content-Type': 'text/plain'});
- res.end(res.write('body').toString());
+ res.writeHead(200, {'Content-Type': 'text/plain'})
+ .end(res.write('body').toString());
}).listen(7080);
diff --git a/test/php/header/index.php b/test/php/header/index.php
new file mode 100644
index 00000000..1aa5ca04
--- /dev/null
+++ b/test/php/header/index.php
@@ -0,0 +1,4 @@
+<?php
+header($_SERVER['HTTP_X_HEADER']);
+header('Content-Length: 0');
+?>
diff --git a/test/php/script/phpinfo.php b/test/php/script/phpinfo.php
new file mode 100644
index 00000000..cf608608
--- /dev/null
+++ b/test/php/script/phpinfo.php
@@ -0,0 +1,3 @@
+<?php
+phpinfo();
+?>
diff --git a/test/php/variables/index.php b/test/php/variables/index.php
index 8f2e3bfc..279efc79 100644
--- a/test/php/variables/index.php
+++ b/test/php/variables/index.php
@@ -4,6 +4,7 @@ $body = file_get_contents('php://input');
header('Content-Length: ' . strlen($body));
header('Request-Method: ' . $_SERVER['REQUEST_METHOD']);
header('Request-Uri: ' . $_SERVER['REQUEST_URI']);
+header('Path-Info: ' . $_SERVER['PATH_INFO']);
header('Http-Host: ' . $_SERVER['HTTP_HOST']);
header('Server-Protocol: ' . $_SERVER['SERVER_PROTOCOL']);
header('Server-Software: ' . $_SERVER['SERVER_SOFTWARE']);
diff --git a/test/test_access_log.py b/test/test_access_log.py
index 49497ad2..fbcc131f 100644
--- a/test/test_access_log.py
+++ b/test/test_access_log.py
@@ -180,7 +180,9 @@ Connection: close
self.assertEqual(self.post()['status'], 200, 'init')
- resp = self.http(b"""GE""", raw=True, read_timeout=5)
+ resp = self.http(b"""GE""", raw=True, read_timeout=1)
+
+ time.sleep(1)
self.stop()
@@ -206,7 +208,9 @@ Connection: close
self.assertEqual(self.post()['status'], 200, 'init')
- resp = self.http(b"""GET / HTTP/1.1""", raw=True, read_timeout=5)
+ resp = self.http(b"""GET / HTTP/1.1""", raw=True, read_timeout=1)
+
+ time.sleep(1)
self.stop()
@@ -219,7 +223,9 @@ Connection: close
self.assertEqual(self.post()['status'], 200, 'init')
- resp = self.http(b"""GET / HTTP/1.1\n""", raw=True, read_timeout=5)
+ resp = self.http(b"""GET / HTTP/1.1\n""", raw=True, read_timeout=1)
+
+ time.sleep(1)
self.stop()
diff --git a/test/test_java_application.py b/test/test_java_application.py
index 5d0350fa..526be565 100644
--- a/test/test_java_application.py
+++ b/test/test_java_application.py
@@ -1,10 +1,52 @@
import time
+import unittest
from unit.applications.lang.java import TestApplicationJava
class TestJavaApplication(TestApplicationJava):
prerequisites = ['java']
+ def test_java_conf_error(self):
+ self.skip_alerts.extend(
+ [
+ r'realpath.*failed',
+ r'failed to apply new conf',
+ ]
+ )
+ self.assertIn(
+ 'error',
+ self.conf(
+ {
+ "listeners": {"*:7080": {"pass": "applications/app"}},
+ "applications": {
+ "app": {
+ "type": "java",
+ "processes": 1,
+ "working_directory": self.current_dir
+ + "/java/empty",
+ "webapp": self.testdir + "/java",
+ "unit_jars": self.testdir + "/no_such_dir",
+ }
+ },
+ }
+ ),
+ 'conf error',
+ )
+
+ def test_java_war(self):
+ self.load('empty_war')
+
+ self.assertIn(
+ 'success',
+ self.conf(
+ '"' + self.testdir + '/java/empty.war"',
+ '/config/applications/empty_war/webapp',
+ ),
+ 'configure war',
+ )
+
+ self.assertEqual(self.get()['status'], 200, 'war')
+
def test_java_application_cookies(self):
self.load('cookies')
@@ -99,12 +141,16 @@ class TestJavaApplication(TestApplicationJava):
def test_java_application_session_active(self):
self.load('session_inactive')
- resp = self.get()
+ resp = self.get(headers={
+ 'X-Interval': '4',
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ })
session_id = resp['headers']['X-Session-Id']
self.assertEqual(resp['status'], 200, 'session init')
self.assertEqual(
- resp['headers']['X-Session-Interval'], '2', 'session interval'
+ resp['headers']['X-Session-Interval'], '4', 'session interval'
)
self.assertLess(
abs(
@@ -147,7 +193,7 @@ class TestJavaApplication(TestApplicationJava):
resp['headers']['X-Session-Id'], session_id, 'session active 2'
)
- time.sleep(1)
+ time.sleep(2)
resp = self.get(
headers={
@@ -164,7 +210,11 @@ class TestJavaApplication(TestApplicationJava):
def test_java_application_session_inactive(self):
self.load('session_inactive')
- resp = self.get()
+ resp = self.get(headers={
+ 'X-Interval': '1',
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ })
session_id = resp['headers']['X-Session-Id']
time.sleep(3)
@@ -1164,6 +1214,43 @@ class TestJavaApplication(TestApplicationJava):
)
self.assertEqual(headers['X-Get-Date'], date, 'get date header')
+ def test_java_application_multipart(self):
+ self.load('multipart')
+
+ body = """Preamble. Should be ignored.\r
+\r
+--12345\r
+Content-Disposition: form-data; name="file"; filename="sample.txt"\r
+Content-Type: text/plain\r
+\r
+Data from sample file\r
+--12345\r
+Content-Disposition: form-data; name="destination"\r
+\r
+%s\r
+--12345\r
+Content-Disposition: form-data; name="upload"\r
+\r
+Upload\r
+--12345--\r
+\r
+Epilogue. Should be ignored.""" % self.testdir
+
+ resp = self.post(
+ headers={
+ 'Content-Type': 'multipart/form-data; boundary=12345',
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ },
+ body=body,
+ )
+
+ self.assertEqual(resp['status'], 200, 'multipart status')
+ self.assertRegex(resp['body'], r'sample\.txt created', 'multipart body')
+ self.assertIsNotNone(
+ self.search_in_log(r'^Data from sample file$', name='sample.txt'),
+ 'file created',
+ )
if __name__ == '__main__':
TestJavaApplication.main()
diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py
new file mode 100644
index 00000000..6652d8c5
--- /dev/null
+++ b/test/test_node_websockets.py
@@ -0,0 +1,1585 @@
+import time
+import struct
+import unittest
+from unit.applications.lang.node import TestApplicationNode
+from unit.applications.websockets import TestApplicationWebsocket
+
+class TestNodeWebsockets(TestApplicationNode):
+ prerequisites = ['node']
+
+ ws = TestApplicationWebsocket()
+
+ def setUp(self):
+ super().setUp()
+
+ self.assertIn(
+ 'success',
+ self.conf(
+ {'http': {'websocket': {'keepalive_interval': 0}}}, 'settings'
+ ),
+ 'clear keepalive_interval',
+ )
+
+ self.skip_alerts.extend(
+ [
+ r'last message send failed',
+ r'socket close\(\d+\) failed',
+ ]
+ )
+
+ def close_connection(self, sock):
+ self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock')
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close())
+
+ self.check_close(sock)
+
+ def check_close(self, sock, code = 1000, no_close = False):
+ frame = self.ws.frame_read(sock)
+
+ self.assertEqual(frame['fin'], True, 'close fin')
+ self.assertEqual(frame['opcode'], self.ws.OP_CLOSE, 'close opcode')
+ self.assertEqual(frame['code'], code, 'close code')
+
+ if not no_close:
+ sock.close()
+
+ def check_frame(self, frame, fin, opcode, payload, decode=True):
+ if opcode == self.ws.OP_BINARY or not decode:
+ data = frame['data']
+ else:
+ data = frame['data'].decode('utf-8')
+
+ self.assertEqual(frame['fin'], fin, 'fin')
+ self.assertEqual(frame['opcode'], opcode, 'opcode')
+ self.assertEqual(data, payload, 'payload')
+
+ def test_node_websockets_handshake(self):
+ self.load('websockets/mirror')
+
+ resp, sock, key = self.ws.upgrade()
+ sock.close()
+
+ self.assertEqual(resp['status'], 101, 'status')
+ self.assertEqual(
+ resp['headers']['Upgrade'], 'websocket', 'upgrade'
+ )
+ self.assertEqual(
+ resp['headers']['Connection'], 'Upgrade', 'connection'
+ )
+ self.assertEqual(
+ resp['headers']['Sec-WebSocket-Accept'], self.ws.accept(key), 'key'
+ )
+
+ def test_node_websockets_mirror(self):
+ self.load('websockets/mirror')
+
+ message = 'blah'
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, message)
+ frame = self.ws.frame_read(sock)
+
+ self.assertEqual(
+ message, frame['data'].decode('utf-8'), 'mirror'
+ )
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, message)
+ frame = self.ws.frame_read(sock)
+
+ self.assertEqual(
+ message, frame['data'].decode('utf-8'), 'mirror 2'
+ )
+
+ sock.close()
+
+ def test_node_websockets_no_mask(self):
+ self.load('websockets/mirror')
+
+ message = 'blah'
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, message, mask=False)
+
+ frame = self.ws.frame_read(sock)
+
+ self.assertEqual(frame['opcode'], self.ws.OP_CLOSE, 'no mask opcode')
+ self.assertEqual(frame['code'], 1002, 'no mask close code')
+
+ sock.close()
+
+ def test_node_websockets_fragmentation(self):
+ self.load('websockets/mirror')
+
+ message = 'blah'
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, message, fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, ' ', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, message)
+
+ frame = self.ws.frame_read(sock)
+
+ self.assertEqual(
+ message + ' ' + message,
+ frame['data'].decode('utf-8'),
+ 'mirror framing',
+ )
+
+ sock.close()
+
+ def test_node_websockets_frame_fragmentation_invalid(self):
+ self.load('websockets/mirror')
+
+ message = 'blah'
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_PING, message, fin=False)
+
+ frame = self.ws.frame_read(sock)
+
+ frame.pop('data')
+ self.assertDictEqual(
+ frame,
+ {
+ 'fin': True,
+ 'rsv1': False,
+ 'rsv2': False,
+ 'rsv3': False,
+ 'opcode': self.ws.OP_CLOSE,
+ 'mask': 0,
+ 'code': 1002,
+ 'reason': 'Fragmented control frame',
+ },
+ 'close frame',
+ )
+
+ sock.close()
+
+ def test_node_websockets_partial_send(self):
+ self.load('websockets/mirror')
+
+ message = 'blah'
+
+ _, sock, _ = self.ws.upgrade()
+
+ frame = self.ws.frame_to_send(self.ws.OP_TEXT, message)
+ sock.sendall(frame[:1])
+ sock.sendall(frame[1:2])
+ sock.sendall(frame[2:3])
+ sock.sendall(frame[3:])
+
+ frame = self.ws.frame_read(sock)
+
+ self.assertEqual(
+ message,
+ frame['data'].decode('utf-8'),
+ 'partial send',
+ )
+
+ sock.close()
+
+ def test_node_websockets_large(self):
+ self.load('websockets/mirror_fragmentation')
+
+ message = '0123456789' * 3000
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, message)
+
+ frame = self.ws.frame_read(sock)
+ data = frame['data'].decode('utf-8')
+
+ frame = self.ws.frame_read(sock)
+ data += frame['data'].decode('utf-8')
+
+ self.assertEqual(message, data, 'large')
+
+ sock.close()
+
+ def test_node_websockets_frame_invalid_opcode(self):
+ self.load('websockets/mirror')
+
+ message = 'blah'
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, message, fin=False)
+ self.ws.frame_write(sock, self.ws.OP_TEXT, message)
+
+ frame = self.ws.frame_read(sock)
+
+ frame.pop('data')
+ frame.pop('reason')
+ self.assertDictEqual(
+ frame,
+ {
+ 'fin': True,
+ 'rsv1': False,
+ 'rsv2': False,
+ 'rsv3': False,
+ 'opcode': self.ws.OP_CLOSE,
+ 'mask': 0,
+ 'code': 1002,
+ },
+ 'close frame',
+ )
+
+ sock.close()
+
+ def test_node_websockets_frame_invalid_opcode_2(self):
+ self.load('websockets/mirror')
+
+ message = 'blah'
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_CONT, message)
+
+ frame = self.ws.frame_read(sock)
+
+ frame.pop('data')
+ self.assertDictEqual(
+ frame,
+ {
+ 'fin': True,
+ 'rsv1': False,
+ 'rsv2': False,
+ 'rsv3': False,
+ 'opcode': self.ws.OP_CLOSE,
+ 'mask': 0,
+ 'code': 1002,
+ 'reason': 'Unrecognized opcode 0',
+ },
+ 'close frame',
+ )
+
+ sock.close()
+
+ def test_node_websockets_two_clients(self):
+ self.load('websockets/mirror')
+
+ message1 = 'blah1'
+ message2 = 'blah2'
+
+ _, sock1, _ = self.ws.upgrade()
+ _, sock2, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock1, self.ws.OP_TEXT, message1)
+ self.ws.frame_write(sock2, self.ws.OP_TEXT, message2)
+
+ frame1 = self.ws.frame_read(sock1)
+ frame2 = self.ws.frame_read(sock2)
+
+ self.assertEqual(
+ message1, frame1['data'].decode('utf-8'), 'client 1'
+ )
+ self.assertEqual(
+ message2, frame2['data'].decode('utf-8'), 'client 2'
+ )
+
+ sock1.close()
+ sock2.close()
+
+ @unittest.skip('not yet')
+ def test_node_websockets_handshake_upgrade_absent(self): # FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1
+ self.load('websockets/mirror')
+
+ key = self.ws.key()
+ resp = self.get(headers={
+ 'Host': 'localhost',
+ 'Connection': 'Upgrade',
+ 'Sec-WebSocket-Key': key,
+ 'Sec-WebSocket-Protocol': 'chat',
+ 'Sec-WebSocket-Version': 13,
+ }, read_timeout=1)
+
+ self.assertEqual(resp['status'], 400, 'upgrade absent')
+
+ def test_node_websockets_handshake_case_insensitive(self):
+ self.load('websockets/mirror')
+
+ key = self.ws.key()
+ resp = self.get(headers={
+ 'Host': 'localhost',
+ 'Upgrade': 'WEBSOCKET',
+ 'Connection': 'UPGRADE',
+ 'Sec-WebSocket-Key': key,
+ 'Sec-WebSocket-Protocol': 'chat',
+ 'Sec-WebSocket-Version': 13,
+ }, read_timeout=1)
+
+ self.assertEqual(resp['status'], 101, 'status')
+
+ @unittest.skip('not yet')
+ def test_node_websockets_handshake_connection_absent(self): # FAIL
+ self.load('websockets/mirror')
+
+ key = self.ws.key()
+ resp = self.get(headers={
+ 'Host': 'localhost',
+ 'Upgrade': 'websocket',
+ 'Sec-WebSocket-Key': key,
+ 'Sec-WebSocket-Protocol': 'chat',
+ 'Sec-WebSocket-Version': 13,
+ }, read_timeout=1)
+
+ self.assertEqual(resp['status'], 400, 'status')
+
+ def test_node_websockets_handshake_version_absent(self):
+ self.load('websockets/mirror')
+
+ key = self.ws.key()
+ resp = self.get(headers={
+ 'Host': 'localhost',
+ 'Upgrade': 'websocket',
+ 'Connection': 'Upgrade',
+ 'Sec-WebSocket-Key': key,
+ 'Sec-WebSocket-Protocol': 'chat'
+ }, read_timeout=1)
+
+ self.assertEqual(resp['status'], 426, 'status')
+
+ @unittest.skip('not yet')
+ def test_node_websockets_handshake_key_invalid(self):
+ self.load('websockets/mirror')
+
+ resp = self.get(headers={
+ 'Host': 'localhost',
+ 'Upgrade': 'websocket',
+ 'Connection': 'Upgrade',
+ 'Sec-WebSocket-Key': '!',
+ 'Sec-WebSocket-Protocol': 'chat',
+ 'Sec-WebSocket-Version': 13
+ }, read_timeout=1)
+
+ self.assertEqual(resp['status'], 400, 'key length')
+
+ key = self.ws.key()
+ resp = self.get(headers={
+ 'Host': 'localhost',
+ 'Upgrade': 'websocket',
+ 'Connection': 'Upgrade',
+ 'Sec-WebSocket-Key': [key, key],
+ 'Sec-WebSocket-Protocol': 'chat',
+ 'Sec-WebSocket-Version': 13
+ }, read_timeout=1)
+
+ self.assertEqual(resp['status'], 400, 'key double') # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1
+
+ def test_node_websockets_handshake_method_invalid(self):
+ self.load('websockets/mirror')
+
+ key = self.ws.key()
+ resp = self.post(headers={
+ 'Host': 'localhost',
+ 'Upgrade': 'websocket',
+ 'Connection': 'Upgrade',
+ 'Sec-WebSocket-Key': key,
+ 'Sec-WebSocket-Protocol': 'chat',
+ 'Sec-WebSocket-Version': 13
+ }, read_timeout=1)
+
+ self.assertEqual(resp['status'], 400, 'status')
+
+ def test_node_websockets_handshake_http_10(self):
+ self.load('websockets/mirror')
+
+ key = self.ws.key()
+ resp = self.get(headers={
+ 'Host': 'localhost',
+ 'Upgrade': 'websocket',
+ 'Connection': 'Upgrade',
+ 'Sec-WebSocket-Key': key,
+ 'Sec-WebSocket-Protocol': 'chat',
+ 'Sec-WebSocket-Version': 13
+ }, http_10=True, read_timeout=1)
+
+ self.assertEqual(resp['status'], 400, 'status')
+
+ def test_node_websockets_handshake_uri_invalid(self):
+ self.load('websockets/mirror')
+
+ key = self.ws.key()
+ resp = self.get(headers={
+ 'Host': 'localhost',
+ 'Upgrade': 'websocket',
+ 'Connection': 'Upgrade',
+ 'Sec-WebSocket-Key': key,
+ 'Sec-WebSocket-Protocol': 'chat',
+ 'Sec-WebSocket-Version': 13
+ }, url='!', read_timeout=1)
+
+ self.assertEqual(resp['status'], 400, 'status')
+
+ def test_node_websockets_protocol_absent(self):
+ self.load('websockets/mirror')
+
+ key = self.ws.key()
+ resp = self.get(headers={
+ 'Host': 'localhost',
+ 'Upgrade': 'websocket',
+ 'Connection': 'Upgrade',
+ 'Sec-WebSocket-Key': key,
+ 'Sec-WebSocket-Version': 13
+ }, read_timeout=1)
+
+ self.assertEqual(resp['status'], 101, 'status')
+ self.assertEqual(
+ resp['headers']['Upgrade'], 'websocket', 'upgrade'
+ )
+ self.assertEqual(
+ resp['headers']['Connection'], 'Upgrade', 'connection'
+ )
+ self.assertEqual(
+ resp['headers']['Sec-WebSocket-Accept'], self.ws.accept(key), 'key'
+ )
+
+ # autobahn-testsuite
+
+ # Some following tests fail because of Unit does not support UTF-8
+ # validation for websocket frames. It should be implemented
+ # by application, if necessary.
+
+ @unittest.skip('not yet')
+ def test_node_websockets_1_1_1__1_1_8(self):
+ self.load('websockets/mirror')
+
+ opcode = self.ws.OP_TEXT
+
+ _, sock, _ = self.ws.upgrade()
+
+ def check_length(length, chopsize=None):
+ payload = '*' * length
+
+ self.ws.frame_write(sock, opcode, payload, chopsize=chopsize)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, opcode, payload)
+
+ check_length(0) # 1_1_1
+ check_length(125) # 1_1_2
+ check_length(126) # 1_1_3
+ check_length(127) # 1_1_4
+ check_length(128) # 1_1_5
+ check_length(65535) # 1_1_6
+ check_length(65536) # 1_1_7
+ check_length(65536, chopsize = 997) # 1_1_8
+
+ self.close_connection(sock)
+
+ @unittest.skip('not yet')
+ def test_node_websockets_1_2_1__1_2_8(self):
+ self.load('websockets/mirror')
+
+ opcode = self.ws.OP_BINARY
+
+ _, sock, _ = self.ws.upgrade()
+
+ def check_length(length, chopsize=None):
+ payload = b'\xfe' * length
+
+ self.ws.frame_write(sock, opcode, payload, chopsize=chopsize)
+ frame = self.ws.frame_read(sock)
+
+ self.check_frame(frame, True, opcode, payload)
+
+ check_length(0) # 1_2_1
+ check_length(125) # 1_2_2
+ check_length(126) # 1_2_3
+ check_length(127) # 1_2_4
+ check_length(128) # 1_2_5
+ check_length(65535) # 1_2_6
+ check_length(65536) # 1_2_7
+ check_length(65536, chopsize = 997) # 1_2_8
+
+ self.close_connection(sock)
+
+ def test_node_websockets_2_1__2_6(self):
+ self.load('websockets/mirror')
+
+ op_ping = self.ws.OP_PING
+ op_pong = self.ws.OP_PONG
+
+ _, sock, _ = self.ws.upgrade()
+
+ def check_ping(payload, chopsize=None, decode=True):
+ self.ws.frame_write(sock, op_ping, payload, chopsize=chopsize)
+ frame = self.ws.frame_read(sock)
+
+ self.check_frame(frame, True, op_pong, payload, decode=decode)
+
+ check_ping('') # 2_1
+ check_ping('Hello, world!') # 2_2
+ check_ping(b'\x00\xff\xfe\xfd\xfc\xfb\x00\xff', decode=False) # 2_3
+ check_ping(b'\xfe' * 125, decode=False) # 2_4
+ check_ping(b'\xfe' * 125, chopsize=1, decode=False) # 2_6
+
+ self.close_connection(sock)
+
+ # 2_5
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_PING, b'\xfe' * 126)
+ self.check_close(sock, 1002)
+
+ def test_node_websockets_2_7__2_9(self):
+ self.load('websockets/mirror')
+
+ # 2_7
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_PONG, '')
+ self.assertEqual(self.recvall(sock, read_timeout=1), b'', '2_7')
+
+ # 2_8
+
+ self.ws.frame_write(sock, self.ws.OP_PONG, 'unsolicited pong payload')
+ self.assertEqual(self.recvall(sock, read_timeout=1), b'', '2_8')
+
+ # 2_9
+
+ payload = 'ping payload'
+
+ self.ws.frame_write(sock, self.ws.OP_PONG, 'unsolicited pong payload')
+ self.ws.frame_write(sock, self.ws.OP_PING, payload)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_PONG, payload)
+
+ self.close_connection(sock)
+
+ def test_node_websockets_2_10__2_11(self):
+ self.load('websockets/mirror')
+
+ # 2_10
+
+ _, sock, _ = self.ws.upgrade()
+
+ for i in range(0, 10):
+ self.ws.frame_write(sock, self.ws.OP_PING, 'payload-%d' % i)
+
+ for i in range(0, 10):
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_PONG, 'payload-%d' % i)
+
+ # 2_11
+
+ for i in range(0, 10):
+ opcode = self.ws.OP_PING
+ self.ws.frame_write(sock, opcode, 'payload-%d' % i, chopsize=1)
+
+ for i in range(0, 10):
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_PONG, 'payload-%d' % i)
+
+ self.close_connection(sock)
+
+ @unittest.skip('not yet')
+ def test_node_websockets_3_1__3_7(self):
+ self.load('websockets/mirror')
+
+ payload = 'Hello, world!'
+
+ # 3_1
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload, rsv1=True)
+ self.check_close(sock, 1002)
+
+ # 3_2
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload)
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload, rsv2=True)
+ self.ws.frame_write(sock, self.ws.OP_PING, '')
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ self.check_close(sock, 1002, no_close = True)
+
+ self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_2')
+ sock.close()
+
+ # 3_3
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_TEXT,
+ payload,
+ rsv1=True,
+ rsv2=True,
+ )
+
+ self.check_close(sock, 1002, no_close = True)
+
+ self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_3')
+ sock.close()
+
+ # 3_4
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload, chopsize=1)
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_TEXT,
+ payload,
+ rsv3=True,
+ chopsize=1
+ )
+ self.ws.frame_write(sock, self.ws.OP_PING, '')
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ self.check_close(sock, 1002, no_close = True)
+
+ self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty 3_4')
+ sock.close()
+
+ # 3_5
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_BINARY,
+ b'\x00\xff\xfe\xfd\xfc\xfb\x00\xff',
+ rsv1=True,
+ rsv3=True,
+ )
+
+ self.check_close(sock, 1002)
+
+ # 3_6
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_PING,
+ payload,
+ rsv2=True,
+ rsv3=True,
+ )
+
+ self.check_close(sock, 1002)
+
+ # 3_7
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_CLOSE,
+ payload,
+ rsv1=True,
+ rsv2=True,
+ rsv3=True,
+ )
+
+ self.check_close(sock, 1002)
+
+ def test_node_websockets_4_1_1__4_2_5(self):
+ self.load('websockets/mirror')
+
+ payload = 'Hello, world!'
+
+ # 4_1_1
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, 0x03, '')
+ self.check_close(sock, 1002)
+
+ # 4_1_2
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, 0x04, 'reserved opcode payload')
+ self.check_close(sock, 1002)
+
+ # 4_1_3
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ self.ws.frame_write(sock, 0x05, '')
+ self.ws.frame_write(sock, self.ws.OP_PING, '')
+
+ self.check_close(sock, 1002)
+
+ # 4_1_4
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ self.ws.frame_write(sock, 0x06, payload)
+ self.ws.frame_write(sock, self.ws.OP_PING, '')
+
+ self.check_close(sock, 1002)
+
+ # 4_1_5
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload, chopsize=1)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ self.ws.frame_write(sock, 0x07, payload, chopsize=1)
+ self.ws.frame_write(sock, self.ws.OP_PING, '')
+
+ self.check_close(sock, 1002)
+
+ # 4_2_1
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, 0x0B, '')
+ self.check_close(sock, 1002)
+
+ # 4_2_2
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, 0x0C, 'reserved opcode payload')
+ self.check_close(sock, 1002)
+
+ # 4_2_3
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ self.ws.frame_write(sock, 0x0D, '')
+ self.ws.frame_write(sock, self.ws.OP_PING, '')
+
+ self.check_close(sock, 1002)
+
+ # 4_2_4
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ self.ws.frame_write(sock, 0x0E, payload)
+ self.ws.frame_write(sock, self.ws.OP_PING, '')
+
+ self.check_close(sock, 1002)
+
+ # 4_2_5
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload, chopsize=1)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ self.ws.frame_write(sock, 0x0F, payload, chopsize=1)
+ self.ws.frame_write(sock, self.ws.OP_PING, '')
+
+ self.check_close(sock, 1002)
+
+ @unittest.skip('not yet')
+ def test_node_websockets_5_1__5_20(self):
+ self.load('websockets/mirror')
+
+ # 5_1
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_PING, 'fragment1', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True)
+ self.check_close(sock, 1002)
+
+ # 5_2
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_PONG, 'fragment1', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True)
+ self.check_close(sock, 1002)
+
+ # 5_3
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2')
+
+ # 5_4
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False)
+ self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_4')
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2')
+
+ # 5_5
+
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_TEXT,
+ 'fragment1',
+ fin=False,
+ chopsize=1,
+ )
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_CONT,
+ 'fragment2',
+ fin=True,
+ chopsize=1,
+ )
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2')
+
+ # 5_6
+
+ ping_payload = 'ping payload'
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_PING, ping_payload)
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_PONG, ping_payload)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2')
+
+ # 5_7
+
+ ping_payload = 'ping payload'
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False)
+ self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_7')
+
+ self.ws.frame_write(sock, self.ws.OP_PING, ping_payload)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_PONG, ping_payload)
+
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2')
+
+ # 5_8
+
+ ping_payload = 'ping payload'
+
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_TEXT,
+ 'fragment1',
+ fin=False,
+ chopsize=1,
+ )
+ self.ws.frame_write(sock, self.ws.OP_PING, ping_payload, chopsize=1)
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_CONT,
+ 'fragment2',
+ fin=True,
+ chopsize=1,
+ )
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_PONG, ping_payload)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2')
+
+ # 5_9
+
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_CONT,
+ 'non-continuation payload',
+ fin=True,
+ )
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True)
+ self.check_close(sock, 1002)
+
+ # 5_10
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_CONT,
+ 'non-continuation payload',
+ fin=True,
+ )
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True)
+ self.check_close(sock, 1002)
+
+ # 5_11
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_CONT,
+ 'non-continuation payload',
+ fin=True,
+ chopsize=1,
+ )
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_TEXT,
+ 'Hello, world!',
+ fin=True,
+ chopsize=1,
+ )
+ self.check_close(sock, 1002)
+
+ # 5_12
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_CONT,
+ 'non-continuation payload',
+ fin=False,
+ )
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True)
+ self.check_close(sock, 1002)
+
+ # 5_13
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_CONT,
+ 'non-continuation payload',
+ fin=False,
+ )
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'Hello, world!', fin=True)
+ self.check_close(sock, 1002)
+
+ # 5_14
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_CONT,
+ 'non-continuation payload',
+ fin=False,
+ chopsize=1,
+ )
+ self.ws.frame_write(
+ sock,
+ self.ws.OP_TEXT,
+ 'Hello, world!',
+ fin=True,
+ chopsize=1,
+ )
+ self.check_close(sock, 1002)
+
+ # 5_15
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=True)
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment4', fin=True)
+ self.check_close(sock, 1002)
+
+ # 5_16
+
+ _, sock, _ = self.ws.upgrade()
+
+ for i in range(0, 2):
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment1', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment2', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=True)
+ self.check_close(sock, 1002)
+
+ # 5_17
+
+ _, sock, _ = self.ws.upgrade()
+
+ for i in range(0, 2):
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment1', fin=True)
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment2', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=True)
+ self.check_close(sock, 1002)
+
+ # 5_18
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment2')
+ self.check_close(sock, 1002)
+
+ # 5_19
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_PING, 'pongme 1!')
+
+ time.sleep(1)
+
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment4', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_PING, 'pongme 2!')
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment5')
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 1!')
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 2!')
+
+ self.check_frame(
+ self.ws.frame_read(sock),
+ True,
+ self.ws.OP_TEXT,
+ 'fragment1fragment2fragment3fragment4fragment5',
+ )
+
+ # 5_20
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_PING, 'pongme 1!')
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 1!')
+
+ time.sleep(1)
+
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment3', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment4', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_PING, 'pongme 2!')
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_PONG, 'pongme 2!')
+
+ self.assertEqual(self.recvall(sock, read_timeout=1), b'', '5_20')
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment5')
+
+ self.check_frame(
+ self.ws.frame_read(sock),
+ True,
+ self.ws.OP_TEXT,
+ 'fragment1fragment2fragment3fragment4fragment5',
+ )
+
+ self.close_connection(sock)
+
+ def test_node_websockets_6_1_1__6_4_4(self):
+ self.load('websockets/mirror')
+
+ # 6_1_1
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, '')
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, '')
+
+ # 6_1_2
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, '', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, '', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, '')
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, '')
+
+ # 6_1_3
+
+ payload = 'middle frame payload'
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, '', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, payload, fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, '')
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ # 6_2_1
+
+ payload = 'Hello-µ@ßöäüàá-UTF-8!!'
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ # 6_2_2
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload[:12], fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CONT, payload[12:])
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ # 6_2_3
+
+ self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ # 6_2_4
+
+ payload = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5'
+
+ self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ self.close_connection(sock)
+
+ # Unit does not support UTF-8 validation
+
+# # 6_3_1 FAIL
+#
+# payload_1 = '\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5'
+# payload_2 = '\xed\xa0\x80'
+# payload_3 = '\x65\x64\x69\x74\x65\x64'
+#
+# payload = payload_1 + payload_2 + payload_3
+#
+# self.ws.message(sock, self.ws.OP_TEXT, payload)
+# self.check_close(sock, 1007)
+#
+# # 6_3_2 FAIL
+#
+# _, sock, _ = self.ws.upgrade()
+#
+# self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1)
+# self.check_close(sock, 1007)
+#
+# # 6_4_1 ... 6_4_4 FAIL
+
+ def test_node_websockets_7_1_1__7_5_1(self):
+ self.load('websockets/mirror')
+
+ # 7_1_1
+
+ _, sock, _ = self.ws.upgrade()
+
+ payload = "Hello World!"
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ self.close_connection(sock)
+
+ # 7_1_2
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close())
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close())
+
+ self.check_close(sock)
+
+ # 7_1_3
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close())
+ self.check_close(sock, no_close = True)
+
+ self.ws.frame_write(sock, self.ws.OP_PING, '')
+ self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock')
+
+ sock.close()
+
+ # 7_1_4
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close())
+ self.check_close(sock, no_close = True)
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload)
+ self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock')
+
+ sock.close()
+
+ # 7_1_5
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False)
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close())
+ self.check_close(sock, no_close = True)
+
+ self.ws.frame_write(sock, self.ws.OP_CONT, 'fragment2')
+ self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock')
+
+ sock.close()
+
+ # 7_1_6
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, 'BAsd7&jh23' * 26 * 2**10)
+ self.ws.frame_write(sock, self.ws.OP_TEXT, payload)
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close())
+
+ self.recvall(sock, read_timeout=1)
+
+ self.ws.frame_write(sock, self.ws.OP_PING, '')
+ self.assertEqual(self.recvall(sock, read_timeout=1), b'', 'empty sock')
+
+ sock.close()
+
+ # 7_3_1 # FAIL
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, '')
+ self.check_close(sock)
+
+ # 7_3_2
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, 'a')
+ self.check_close(sock, 1002)
+
+ # 7_3_3
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, self.ws.serialize_close())
+ self.check_close(sock)
+
+ # 7_3_4
+
+ _, sock, _ = self.ws.upgrade()
+
+ payload = self.ws.serialize_close(reason = 'Hello World!')
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, payload)
+ self.check_close(sock)
+
+ # 7_3_5
+
+ _, sock, _ = self.ws.upgrade()
+
+ payload = self.ws.serialize_close(reason = '*' * 123)
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, payload)
+ self.check_close(sock)
+
+ # 7_3_6
+
+ _, sock, _ = self.ws.upgrade()
+
+ payload = self.ws.serialize_close(reason = '*' * 124)
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, payload)
+ self.check_close(sock, 1002)
+
+ # 7_5_1 FAIL Unit does not support UTF-8 validation
+
+# _, sock, _ = self.ws.upgrade()
+#
+# payload = self.ws.serialize_close(reason = '\xce\xba\xe1\xbd\xb9\xcf' \
+# '\x83\xce\xbc\xce\xb5\xed\xa0\x80\x65\x64\x69\x74\x65\x64')
+#
+# self.ws.frame_write(sock, self.ws.OP_CLOSE, payload)
+# self.check_close(sock, 1007)
+
+ def test_node_websockets_7_7_X__7_9_X(self):
+ self.load('websockets/mirror')
+
+ valid_codes = [
+ 1000,
+ 1001,
+ 1002,
+ 1003,
+ 1007,
+ 1008,
+ 1009,
+ 1010,
+ 1011,
+ 3000,
+ 3999,
+ 4000,
+ 4999,
+ ]
+
+ invalid_codes = [0, 999, 1004, 1005, 1006, 1016, 1100, 2000, 2999]
+
+ for code in valid_codes:
+ _, sock, _ = self.ws.upgrade()
+
+ payload = self.ws.serialize_close(code = code)
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, payload)
+ self.check_close(sock)
+
+ for code in invalid_codes:
+ _, sock, _ = self.ws.upgrade()
+
+ payload = self.ws.serialize_close(code = code)
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, payload)
+ self.check_close(sock, 1002)
+
+ def test_node_websockets_7_13_1__7_13_2(self):
+ self.load('websockets/mirror')
+
+ # 7_13_1
+
+ _, sock, _ = self.ws.upgrade()
+
+ payload = self.ws.serialize_close(code = 5000)
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, payload)
+ self.check_close(sock, 1002)
+
+ # 7_13_2
+
+ _, sock, _ = self.ws.upgrade()
+
+ payload = struct.pack('!I', 65536) + ''.encode('utf-8')
+
+ self.ws.frame_write(sock, self.ws.OP_CLOSE, payload)
+ self.check_close(sock, 1002)
+
+ def test_node_websockets_9_1_1__9_6_6(self):
+ if not self.unsafe:
+ self.skipTest("unsafe, long run")
+
+ self.load('websockets/mirror')
+
+ self.assertIn(
+ 'success',
+ self.conf(
+ {
+ 'http': {
+ 'websocket': {
+ 'max_frame_size': 33554432,
+ 'keepalive_interval': 0,
+ }
+ }
+ },
+ 'settings',
+ ),
+ 'increase max_frame_size and keepalive_interval',
+ )
+
+ _, sock, _ = self.ws.upgrade()
+
+ op_text = self.ws.OP_TEXT
+ op_binary = self.ws.OP_BINARY
+
+ def check_payload(opcode, length, chopsize=None):
+ if opcode == self.ws.OP_TEXT:
+ payload = '*' * length
+ else:
+ payload = b'*' * length
+
+ self.ws.frame_write(sock, opcode, payload, chopsize=chopsize)
+ frame = self.ws.frame_read(sock, read_timeout=5)
+ self.check_frame(frame, True, opcode, payload)
+
+ def check_message(opcode, f_size):
+ if opcode == self.ws.OP_TEXT:
+ payload = '*' * 4 * 2**20
+ else:
+ payload = b'*' * 4 * 2**20
+
+ self.ws.message(sock, opcode, payload, fragmention_size=f_size)
+ frame = self.ws.frame_read(sock, read_timeout=5)
+ self.check_frame(frame, True, opcode, payload)
+
+ check_payload(op_text, 64 * 2**10) # 9_1_1
+ check_payload(op_text, 256 * 2**10) # 9_1_2
+ check_payload(op_text, 2**20) # 9_1_3
+ check_payload(op_text, 4 * 2**20) # 9_1_4
+ check_payload(op_text, 8 * 2**20) # 9_1_5
+ check_payload(op_text, 16 * 2**20) # 9_1_6
+
+ check_payload(op_binary, 64 * 2**10) # 9_2_1
+ check_payload(op_binary, 256 * 2**10) # 9_2_2
+ check_payload(op_binary, 2**20) # 9_2_3
+ check_payload(op_binary, 4 * 2**20) # 9_2_4
+ check_payload(op_binary, 8 * 2**20) # 9_2_5
+ check_payload(op_binary, 16 * 2**20) # 9_2_6
+
+ if self.system != 'Darwin' and self.system != 'FreeBSD':
+ check_message(op_text, 64) # 9_3_1
+ check_message(op_text, 256) # 9_3_2
+ check_message(op_text, 2**10) # 9_3_3
+ check_message(op_text, 4 * 2**10) # 9_3_4
+ check_message(op_text, 16 * 2**10) # 9_3_5
+ check_message(op_text, 64 * 2**10) # 9_3_6
+ check_message(op_text, 256 * 2**10) # 9_3_7
+ check_message(op_text, 2**20) # 9_3_8
+ check_message(op_text, 4 * 2**20) # 9_3_9
+
+ check_message(op_binary, 64) # 9_4_1
+ check_message(op_binary, 256) # 9_4_2
+ check_message(op_binary, 2**10) # 9_4_3
+ check_message(op_binary, 4 * 2**10) # 9_4_4
+ check_message(op_binary, 16 * 2**10) # 9_4_5
+ check_message(op_binary, 64 * 2**10) # 9_4_6
+ check_message(op_binary, 256 * 2**10) # 9_4_7
+ check_message(op_binary, 2**20) # 9_4_8
+ check_message(op_binary, 4 * 2**20) # 9_4_9
+
+ check_payload(op_text, 2**20, chopsize=64) # 9_5_1
+ check_payload(op_text, 2**20, chopsize=128) # 9_5_2
+ check_payload(op_text, 2**20, chopsize=256) # 9_5_3
+ check_payload(op_text, 2**20, chopsize=512) # 9_5_4
+ check_payload(op_text, 2**20, chopsize=1024) # 9_5_5
+ check_payload(op_text, 2**20, chopsize=2048) # 9_5_6
+
+ check_payload(op_binary, 2**20, chopsize=64) # 9_6_1
+ check_payload(op_binary, 2**20, chopsize=128) # 9_6_2
+ check_payload(op_binary, 2**20, chopsize=256) # 9_6_3
+ check_payload(op_binary, 2**20, chopsize=512) # 9_6_4
+ check_payload(op_binary, 2**20, chopsize=1024) # 9_6_5
+ check_payload(op_binary, 2**20, chopsize=2048) # 9_6_6
+
+ self.close_connection(sock)
+
+ def test_node_websockets_10_1_1(self):
+ self.load('websockets/mirror')
+
+ _, sock, _ = self.ws.upgrade()
+
+ payload = '*' * 65536
+
+ self.ws.message(sock, self.ws.OP_TEXT, payload, fragmention_size=1300)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_TEXT, payload)
+
+ self.close_connection(sock)
+
+ # settings
+
+ def test_node_websockets_max_frame_size(self):
+ self.load('websockets/mirror')
+
+ self.assertIn(
+ 'success',
+ self.conf(
+ {'http': {'websocket': {'max_frame_size': 100}}}, 'settings'
+ ),
+ 'configure max_frame_size',
+ )
+
+ _, sock, _ = self.ws.upgrade()
+
+ payload = '*' * 94
+ opcode = self.ws.OP_TEXT
+
+ self.ws.frame_write(sock, opcode, payload) # frame length is 100
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, opcode, payload)
+
+ payload = '*' * 95
+
+ self.ws.frame_write(sock, opcode, payload) # frame length is 101
+ self.check_close(sock, 1009) # 1009 - CLOSE_TOO_LARGE
+
+ def test_node_websockets_read_timeout(self):
+ self.load('websockets/mirror')
+
+ self.assertIn(
+ 'success',
+ self.conf(
+ {'http': {'websocket': {'read_timeout': 5}}}, 'settings'
+ ),
+ 'configure read_timeout',
+ )
+
+ _, sock, _ = self.ws.upgrade()
+
+ frame = self.ws.frame_to_send(self.ws.OP_TEXT, 'blah')
+ sock.sendall(frame[:2])
+
+ time.sleep(2)
+
+ self.check_close(sock, 1001) # 1001 - CLOSE_GOING_AWAY
+
+ def test_node_websockets_keepalive_interval(self):
+ self.load('websockets/mirror')
+
+ self.assertIn(
+ 'success',
+ self.conf(
+ {'http': {'websocket': {'keepalive_interval': 5}}}, 'settings'
+ ),
+ 'configure keepalive_interval',
+ )
+
+ _, sock, _ = self.ws.upgrade()
+
+ frame = self.ws.frame_to_send(self.ws.OP_TEXT, 'blah')
+ sock.sendall(frame[:2])
+
+ time.sleep(2)
+
+ frame = self.ws.frame_read(sock)
+ self.check_frame(frame, True, self.ws.OP_PING, '') # PING frame
+
+ sock.close()
+
+if __name__ == '__main__':
+ TestNodeWebsockets.main()
diff --git a/test/test_php_application.py b/test/test_php_application.py
index 8032e96e..ee2048b5 100644
--- a/test/test_php_application.py
+++ b/test/test_php_application.py
@@ -24,6 +24,7 @@ class TestPHPApplication(TestApplicationPHP):
'Connection': 'close',
},
body=body,
+ url='/index.php/blah?var=val'
)
self.assertEqual(resp['status'], 200, 'status')
@@ -54,7 +55,8 @@ class TestPHPApplication(TestApplicationPHP):
'Connection': 'close',
'Content-Length': str(len(body)),
'Request-Method': 'POST',
- 'Request-Uri': '/',
+ 'Path-Info': '/blah',
+ 'Request-Uri': '/index.php/blah?var=val',
'Http-Host': 'localhost',
'Server-Protocol': 'HTTP/1.1',
'Custom-Header': 'blah',
@@ -102,6 +104,46 @@ class TestPHPApplication(TestApplicationPHP):
self.assertEqual(resp['status'], 200, 'status')
self.assertNotEqual(resp['body'], '', 'body not empty')
+ def test_php_application_header_status(self):
+ self.load('header')
+
+ self.assertEqual(
+ self.get(
+ headers={
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ 'X-Header': 'HTTP/1.1 404 Not Found',
+ }
+ )['status'],
+ 404,
+ 'status',
+ )
+
+ self.assertEqual(
+ self.get(
+ headers={
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ 'X-Header': 'http/1.1 404 Not Found',
+ }
+ )['status'],
+ 404,
+ 'status case insensitive',
+ )
+
+ self.assertEqual(
+ self.get(
+ headers={
+ 'Host': 'localhost',
+ 'Connection': 'close',
+ 'X-Header': 'HTTP/ 404 Not Found',
+ }
+ )['status'],
+ 404,
+ 'status version empty',
+ )
+
+
def test_php_application_404(self):
self.load('404')
@@ -420,6 +462,45 @@ class TestPHPApplication(TestApplicationPHP):
self.get()['body'], r'012345', 'disable_classes before'
)
+ def test_php_application_script(self):
+ self.assertIn(
+ 'success', self.conf(
+ {
+ "listeners": {"*:7080": {"pass": "applications/script"}},
+ "applications": {
+ "script": {
+ "type": "php",
+ "processes": {"spare": 0},
+ "root": self.current_dir + "/php/script",
+ "script": "phpinfo.php",
+ }
+ },
+ }
+ ), 'configure script'
+ )
+
+ resp = self.get()
+
+ self.assertEqual(resp['status'], 200, 'status')
+ self.assertNotEqual(resp['body'], '', 'body not empty')
+
+ def test_php_application_index_default(self):
+ self.assertIn(
+ 'success', self.conf(
+ {
+ "listeners": {"*:7080": {"pass": "applications/phpinfo"}},
+ "applications": {
+ "phpinfo": {
+ "type": "php",
+ "processes": {"spare": 0},
+ "root": self.current_dir + "/php/phpinfo",
+ }
+ },
+ }
+ ), 'configure index default'
+ )
+
+ self.assertEqual(self.get()['status'], 200, 'status')
if __name__ == '__main__':
TestPHPApplication.main()
diff --git a/test/test_php_basic.py b/test/test_php_basic.py
index 02ff81de..0c84f206 100644
--- a/test/test_php_basic.py
+++ b/test/test_php_basic.py
@@ -164,6 +164,32 @@ class TestPHPBasic(TestControl):
'error', self.conf_delete('applications/app'), 'delete app again'
)
+ def test_php_delete_blocks(self):
+ self.conf(self.conf_basic)
+
+ self.assertIn(
+ 'success',
+ self.conf_delete('listeners'),
+ 'listeners delete',
+ )
+
+ self.assertIn(
+ 'success',
+ self.conf_delete('applications'),
+ 'applications delete',
+ )
+
+ self.assertIn(
+ 'success',
+ self.conf(self.conf_app, 'applications'),
+ 'listeners restore',
+ )
+
+ self.assertIn(
+ 'success',
+ self.conf({"*:7081": {"pass": "applications/app"}}, 'listeners'),
+ 'applications restore',
+ )
if __name__ == '__main__':
TestPHPBasic.main()
diff --git a/test/test_python_basic.py b/test/test_python_basic.py
index 9987e886..e63158e5 100644
--- a/test/test_python_basic.py
+++ b/test/test_python_basic.py
@@ -177,6 +177,33 @@ class TestPythonBasic(TestControl):
'error', self.conf_delete('applications/app'), 'delete app again'
)
+ def test_python_delete_blocks(self):
+ self.conf(self.conf_basic)
+
+ self.assertIn(
+ 'success',
+ self.conf_delete('listeners'),
+ 'listeners delete',
+ )
+
+ self.assertIn(
+ 'success',
+ self.conf_delete('applications'),
+ 'applications delete',
+ )
+
+ self.assertIn(
+ 'success',
+ self.conf(self.conf_app, 'applications'),
+ 'listeners restore',
+ )
+
+ self.assertIn(
+ 'success',
+ self.conf({"*:7081": {"pass": "applications/app"}}, 'listeners'),
+ 'applications restore',
+ )
+
if __name__ == '__main__':
TestPythonBasic.main()
diff --git a/test/test_routing.py b/test/test_routing.py
index ac2e0de8..6073877d 100644
--- a/test/test_routing.py
+++ b/test/test_routing.py
@@ -38,6 +38,9 @@ class TestRouting(TestApplicationProto):
}
)
+ def route(self, route):
+ return self.conf([route], 'routes')
+
def test_routes_match_method_positive(self):
self.assertEqual(self.get()['status'], 200, 'method positive GET')
self.assertEqual(self.post()['status'], 404, 'method positive POST')
@@ -45,14 +48,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_method_positive_many(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": ["GET", "POST"]},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": ["GET", "POST"]},
+ "action": {"pass": "applications/empty"},
+ }
),
'method positive many configure',
)
@@ -68,14 +68,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_method_negative(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": "!GET"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": "!GET"},
+ "action": {"pass": "applications/empty"},
+ }
),
'method negative configure',
)
@@ -86,14 +83,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_method_negative_many(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": ["!GET", "!POST"]},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": ["!GET", "!POST"]},
+ "action": {"pass": "applications/empty"},
+ }
),
'method negative many configure',
)
@@ -109,14 +103,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_method_wildcard_left(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": "*ET"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": "*ET"},
+ "action": {"pass": "applications/empty"},
+ }
),
'method wildcard left configure',
)
@@ -129,14 +120,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_method_wildcard_right(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": "GE*"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": "GE*"},
+ "action": {"pass": "applications/empty"},
+ }
),
'method wildcard right configure',
)
@@ -151,14 +139,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_method_wildcard_left_right(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": "*GET*"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": "*GET*"},
+ "action": {"pass": "applications/empty"},
+ }
),
'method wildcard left right configure',
)
@@ -173,14 +158,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_method_wildcard(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": "*"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": "*"},
+ "action": {"pass": "applications/empty"},
+ }
),
'method wildcard configure',
)
@@ -190,70 +172,55 @@ class TestRouting(TestApplicationProto):
def test_routes_match_invalid(self):
self.assertIn(
'error',
- self.conf(
- [
- {
- "match": {"method": "**"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": "**"},
+ "action": {"pass": "applications/empty"},
+ }
),
'wildcard invalid',
)
self.assertIn(
'error',
- self.conf(
- [
- {
- "match": {"method": "blah**"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": "blah**"},
+ "action": {"pass": "applications/empty"},
+ }
),
'wildcard invalid 2',
)
self.assertIn(
'error',
- self.conf(
- [
- {
- "match": {"host": "*blah*blah"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": "*blah*blah"},
+ "action": {"pass": "applications/empty"},
+ }
),
'wildcard invalid 3',
)
self.assertIn(
'error',
- self.conf(
- [
- {
- "match": {"host": "blah*blah*blah"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": "blah*blah*blah"},
+ "action": {"pass": "applications/empty"},
+ }
),
'wildcard invalid 4',
)
self.assertIn(
'error',
- self.conf(
- [
- {
- "match": {"host": "blah*blah*"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": "blah*blah*"},
+ "action": {"pass": "applications/empty"},
+ }
),
'wildcard invalid 5',
)
@@ -261,14 +228,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_wildcard_middle(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"host": "ex*le"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": "ex*le"},
+ "action": {"pass": "applications/empty"},
+ }
),
'host wildcard middle configure',
)
@@ -308,14 +272,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_method_case_insensitive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": "get"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": "get"},
+ "action": {"pass": "applications/empty"},
+ }
),
'method case insensitive configure',
)
@@ -325,14 +286,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_wildcard_left_case_insensitive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": "*et"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": "*et"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match wildcard case insensitive configure',
)
@@ -344,14 +302,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_wildcard_middle_case_insensitive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": "g*t"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": "g*t"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match wildcard case insensitive configure',
)
@@ -363,14 +318,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_wildcard_right_case_insensitive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": "get*"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": "get*"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match wildcard case insensitive configure',
)
@@ -382,14 +334,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_wildcard_substring_case_insensitive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": "*et*"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": "*et*"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match wildcard substring case insensitive configure',
)
@@ -403,14 +352,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_wildcard_left_case_sensitive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"uri": "*blah"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"uri": "*blah"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match wildcard left case sensitive configure',
)
@@ -430,14 +376,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_wildcard_middle_case_sensitive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"uri": "/b*h"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"uri": "/b*h"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match wildcard middle case sensitive configure',
)
@@ -457,14 +400,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_wildcard_right_case_sensitive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"uri": "/bla*"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"uri": "/bla*"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match wildcard right case sensitive configure',
)
@@ -484,14 +424,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_wildcard_substring_case_sensitive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"uri": "*bla*"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"uri": "*bla*"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match wildcard substring case sensitive configure',
)
@@ -677,14 +614,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_host_positive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"host": "localhost"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": "localhost"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match host positive configure',
)
@@ -729,14 +663,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_host_absent(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"host": "localhost"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": "localhost"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match host absent configure',
)
@@ -750,14 +681,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_host_ipv4(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"host": "127.0.0.1"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": "127.0.0.1"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match host ipv4 configure',
)
@@ -773,14 +701,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_host_ipv6(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"host": "[::1]"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": "[::1]"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match host ipv6 configure',
)
@@ -804,14 +729,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_host_positive_many(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"host": ["localhost", "example.com"]},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": ["localhost", "example.com"]},
+ "action": {"pass": "applications/empty"},
+ }
),
'match host positive many configure',
)
@@ -831,16 +753,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_host_positive_and_negative(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {
- "host": ["*example.com", "!www.example.com"]
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": ["*example.com", "!www.example.com"]},
+ "action": {"pass": "applications/empty"},
+ }
),
'match host positive and negative configure',
)
@@ -878,14 +795,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_host_positive_and_negative_wildcard(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"host": ["*example*", "!www.example*"]},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": ["*example*", "!www.example*"]},
+ "action": {"pass": "applications/empty"},
+ }
),
'match host positive and negative wildcard configure',
)
@@ -909,14 +823,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_host_case_insensitive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"host": "Example.com"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": "Example.com"},
+ "action": {"pass": "applications/empty"},
+ }
),
'host case insensitive configure',
)
@@ -940,14 +851,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_host_port(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"host": "example.com"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": "example.com"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match host port configure',
)
@@ -963,14 +871,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_host_empty(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"host": ""},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"host": ""},
+ "action": {"pass": "applications/empty"},
+ }
),
'match host empty configure',
)
@@ -990,14 +895,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_uri_positive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"uri": "/"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"uri": "/"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match uri positive configure',
)
@@ -1025,14 +927,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_uri_case_sensitive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"uri": "/BLAH"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"uri": "/BLAH"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match uri case sensitive configure',
)
@@ -1056,14 +955,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_uri_normalize(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"uri": "/blah"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"uri": "/blah"},
+ "action": {"pass": "applications/empty"},
+ }
),
'match uri normalize configure',
)
@@ -1075,14 +971,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_empty_array(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"uri": []},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"uri": []},
+ "action": {"pass": "applications/empty"},
+ }
),
'match empty array configure',
)
@@ -1180,14 +1073,11 @@ class TestRouting(TestApplicationProto):
def test_routes_edit(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": "GET"},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": "GET"},
+ "action": {"pass": "applications/empty"},
+ }
),
'routes edit configure',
)
@@ -1324,14 +1214,11 @@ class TestRouting(TestApplicationProto):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"method": ["GET", "POST"]},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"method": ["GET", "POST"]},
+ "action": {"pass": "applications/empty"},
+ }
),
'match edit configure',
)
@@ -1457,18 +1344,15 @@ class TestRouting(TestApplicationProto):
def test_routes_match_rules(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {
- "method": "GET",
- "host": "localhost",
- "uri": "/",
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {
+ "method": "GET",
+ "host": "localhost",
+ "uri": "/",
+ },
+ "action": {"pass": "applications/empty"},
+ }
),
'routes match rules configure',
)
@@ -1478,10 +1362,7 @@ class TestRouting(TestApplicationProto):
def test_routes_loop(self):
self.assertIn(
'success',
- self.conf(
- [{"match": {"uri": "/"}, "action": {"pass": "routes"}}],
- 'routes',
- ),
+ self.route({"match": {"uri": "/"}, "action": {"pass": "routes"}}),
'routes loop configure',
)
@@ -1490,14 +1371,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_headers(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"headers": {"host": "localhost"}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"headers": {"host": "localhost"}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match headers configure',
)
@@ -1547,16 +1425,13 @@ class TestRouting(TestApplicationProto):
def test_routes_match_headers_multiple(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {
- "headers": {"host": "localhost", "x-blah": "test"}
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {
+ "headers": {"host": "localhost", "x-blah": "test"}
+ },
+ "action": {"pass": "applications/empty"},
+ }
),
'match headers multiple configure',
)
@@ -1590,14 +1465,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_headers_multiple_values(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"headers": {"x-blah": "test"}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"headers": {"x-blah": "test"}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match headers multiple values configure',
)
@@ -1639,14 +1511,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_headers_multiple_rules(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"headers": {"x-blah": ["test", "blah"]}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"headers": {"x-blah": ["test", "blah"]}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match headers multiple rules configure',
)
@@ -1706,14 +1575,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_headers_case_insensitive(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"headers": {"X-BLAH": "TEST"}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"headers": {"X-BLAH": "TEST"}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match headers case insensitive configure',
)
@@ -1733,28 +1599,22 @@ class TestRouting(TestApplicationProto):
def test_routes_match_headers_invalid(self):
self.assertIn(
'error',
- self.conf(
- [
- {
- "match": {"headers": ["blah"]},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"headers": ["blah"]},
+ "action": {"pass": "applications/empty"},
+ }
),
'match headers invalid',
)
self.assertIn(
'error',
- self.conf(
- [
- {
- "match": {"headers": {"foo": ["bar", {}]}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"headers": {"foo": ["bar", {}]}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match headers invalid 2',
)
@@ -1762,14 +1622,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_headers_empty_rule(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"headers": {"host": ""}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"headers": {"host": ""}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match headers empty rule configure',
)
@@ -1785,14 +1642,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_headers_rule_field_empty(self):
self.assertIn(
'error',
- self.conf(
- [
- {
- "match": {"headers": {"": "blah"}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"headers": {"": "blah"}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match headers rule field empty configure',
)
@@ -1800,14 +1654,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_headers_empty(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"headers": {}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"headers": {}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match headers empty configure',
)
@@ -1816,14 +1667,11 @@ class TestRouting(TestApplicationProto):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"headers": []},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"headers": []},
+ "action": {"pass": "applications/empty"},
+ }
),
'match headers array empty configure 2',
)
@@ -1835,14 +1683,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_headers_rule_array_empty(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"headers": {"blah": []}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"headers": {"blah": []}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match headers rule array empty configure',
)
@@ -1863,21 +1708,18 @@ class TestRouting(TestApplicationProto):
def test_routes_match_headers_array(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {
- "headers": [
- {"x-header1": "foo*"},
- {"x-header2": "bar"},
- {"x-header3": ["foo", "bar"]},
- {"x-header1": "bar", "x-header4": "foo"},
- ]
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {
+ "headers": [
+ {"x-header1": "foo*"},
+ {"x-header2": "bar"},
+ {"x-header3": ["foo", "bar"]},
+ {"x-header1": "bar", "x-header4": "foo"},
+ ]
+ },
+ "action": {"pass": "applications/empty"},
+ }
),
'match headers array configure',
)
@@ -1972,14 +1814,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_arguments(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"arguments": {"foo": "bar"}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"arguments": {"foo": "bar"}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments configure',
)
@@ -1993,12 +1832,12 @@ class TestRouting(TestApplicationProto):
self.get(url='/?Foo=bar')['status'],
404,
'match arguments case sensitive',
- ) # FAIL
+ )
self.assertEqual(
self.get(url='/?foo=Bar')['status'],
404,
'match arguments case sensitive 2',
- ) # FAIL
+ )
self.assertEqual(
self.get(url='/?foo=bar1')['status'],
404,
@@ -2013,14 +1852,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_arguments_empty(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"arguments": {}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"arguments": {}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments empty configure',
)
@@ -2029,14 +1865,11 @@ class TestRouting(TestApplicationProto):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"arguments": []},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"arguments": []},
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments empty configure 2',
)
@@ -2046,46 +1879,33 @@ class TestRouting(TestApplicationProto):
def test_routes_match_arguments_invalid(self):
self.assertIn(
'error',
- self.conf(
- [
- {
- "match": {"arguments": ["var"]},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"arguments": ["var"]},
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments invalid',
)
self.assertIn(
'error',
- self.conf(
- [
- {
- "match": {"arguments": [{"var1": {}}]},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"arguments": [{"var1": {}}]},
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments invalid 2',
)
self.assertIn(
'error',
- self.conf(
- [
- {
- "match": {
- "arguments": {
- "": "bar"
- }
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"arguments": {"": "bar"}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments invalid 3',
)
@@ -2094,18 +1914,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_arguments_space(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {
- "arguments": {
- "foo": "bar "
- }
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"arguments": {"foo": "bar "}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments space configure',
)
@@ -2130,18 +1943,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_arguments_plus(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {
- "arguments": [
- {"foo": "bar+"}
- ]
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"arguments": [{"foo": "bar+"}]},
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments plus configure',
)
@@ -2161,18 +1967,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_arguments_hex(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {
- "arguments": [
- {"foo": "bar"}
- ]
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"arguments": [{"foo": "bar"}]},
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments hex configure',
)
@@ -2186,18 +1985,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_arguments_chars(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {
- "arguments": {
- "foo": "-._()[],;"
- }
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"arguments": {"foo": "-._()[],;"}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments chars configure',
)
@@ -2211,18 +2003,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_arguments_complex(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {
- "arguments": {
- "foo": ""
- }
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"arguments": {"foo": ""}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments complex configure',
)
@@ -2266,16 +2051,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_arguments_multiple(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {
- "arguments": {"foo": "bar", "blah": "test"}
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"arguments": {"foo": "bar", "blah": "test"}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments multiple configure',
)
@@ -2297,14 +2077,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_arguments_multiple_rules(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"arguments": {"foo": ["bar", "blah"]}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"arguments": {"foo": ["bar", "blah"]}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments multiple rules configure',
)
@@ -2340,21 +2117,18 @@ class TestRouting(TestApplicationProto):
def test_routes_match_arguments_array(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {
- "arguments": [
- {"var1": "val1*"},
- {"var2": "val2"},
- {"var3": ["foo", "bar"]},
- {"var1": "bar", "var4": "foo"},
- ]
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {
+ "arguments": [
+ {"var1": "val1*"},
+ {"var2": "val2"},
+ {"var3": ["foo", "bar"]},
+ {"var1": "bar", "var4": "foo"},
+ ]
+ },
+ "action": {"pass": "applications/empty"},
+ }
),
'match arguments array configure',
)
@@ -2406,14 +2180,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_cookies(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"cookies": {"foO": "bar"}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"cookies": {"foO": "bar"}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match cookie configure',
)
@@ -2423,7 +2194,7 @@ class TestRouting(TestApplicationProto):
self.get(
headers={
'Host': 'localhost',
- 'Cookie': 'foo=bar',
+ 'Cookie': 'foO=bar',
'Connection': 'close',
},
)['status'],
@@ -2434,7 +2205,7 @@ class TestRouting(TestApplicationProto):
self.get(
headers={
'Host': 'localhost',
- 'Cookie': ['foo=bar', 'blah=blah'],
+ 'Cookie': ['foO=bar', 'blah=blah'],
'Connection': 'close',
},
)['status'],
@@ -2445,7 +2216,7 @@ class TestRouting(TestApplicationProto):
self.get(
headers={
'Host': 'localhost',
- 'Cookie': 'foo=bar; blah=blah',
+ 'Cookie': 'foO=bar; blah=blah',
'Connection': 'close',
},
)['status'],
@@ -2461,25 +2232,25 @@ class TestRouting(TestApplicationProto):
'Connection': 'close',
},
)['status'],
- 200,
- 'match cookies case insensitive',
+ 404,
+ 'match cookies case sensitive',
)
self.assertEqual(
self.get(
headers={
'Host': 'localhost',
- 'Cookie': 'foo=Bar',
+ 'Cookie': 'foO=Bar',
'Connection': 'close',
},
)['status'],
- 200,
- 'match cookies case insensitive 2',
+ 404,
+ 'match cookies case sensitive 2',
)
self.assertEqual(
self.get(
headers={
'Host': 'localhost',
- 'Cookie': 'foo=bar1',
+ 'Cookie': 'foO=bar1',
'Connection': 'close',
},
)['status'],
@@ -2490,25 +2261,33 @@ class TestRouting(TestApplicationProto):
self.get(
headers={
'Host': 'localhost',
- 'Cookie': 'foo=bar;',
+ 'Cookie': '1foO=bar;',
'Connection': 'close',
},
)['status'],
- 200,
+ 404,
'match cookies exact 2',
)
+ self.assertEqual(
+ self.get(
+ headers={
+ 'Host': 'localhost',
+ 'Cookie': 'foO=bar;1',
+ 'Connection': 'close',
+ },
+ )['status'],
+ 200,
+ 'match cookies exact 3',
+ )
def test_routes_match_cookies_empty(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"cookies": {}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"cookies": {}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match cookies empty configure',
)
@@ -2517,14 +2296,11 @@ class TestRouting(TestApplicationProto):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"cookies": []},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"cookies": []},
+ "action": {"pass": "applications/empty"},
+ }
),
'match cookies empty configure 2',
)
@@ -2534,28 +2310,22 @@ class TestRouting(TestApplicationProto):
def test_routes_match_cookies_invalid(self):
self.assertIn(
'error',
- self.conf(
- [
- {
- "match": {"cookies": ["var"]},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"cookies": ["var"]},
+ "action": {"pass": "applications/empty"},
+ }
),
'match cookies invalid',
)
self.assertIn(
'error',
- self.conf(
- [
- {
- "match": {"cookies": [{"foo": {}}]},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"cookies": [{"foo": {}}]},
+ "action": {"pass": "applications/empty"},
+ }
),
'match cookies invalid 2',
)
@@ -2563,16 +2333,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_cookies_multiple(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {
- "cookies": {"foo": "bar", "blah": "blah"}
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"cookies": {"foo": "bar", "blah": "blah"}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match cookies multiple configure',
)
@@ -2630,14 +2395,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_cookies_multiple_values(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"cookies": {"blah": "blah"}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"cookies": {"blah": "blah"}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match cookies multiple values configure',
)
@@ -2679,14 +2441,11 @@ class TestRouting(TestApplicationProto):
def test_routes_match_cookies_multiple_rules(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {"cookies": {"blah": ["test", "blah"]}},
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {"cookies": {"blah": ["test", "blah"]}},
+ "action": {"pass": "applications/empty"},
+ }
),
'match cookies multiple rules configure',
)
@@ -2758,21 +2517,18 @@ class TestRouting(TestApplicationProto):
def test_routes_match_cookies_array(self):
self.assertIn(
'success',
- self.conf(
- [
- {
- "match": {
- "cookies": [
- {"var1": "val1*"},
- {"var2": "val2"},
- {"var3": ["foo", "bar"]},
- {"var1": "bar", "var4": "foo"},
- ]
- },
- "action": {"pass": "applications/empty"},
- }
- ],
- 'routes',
+ self.route(
+ {
+ "match": {
+ "cookies": [
+ {"var1": "val1*"},
+ {"var2": "val2"},
+ {"var3": ["foo", "bar"]},
+ {"var1": "bar", "var4": "foo"},
+ ]
+ },
+ "action": {"pass": "applications/empty"},
+ }
),
'match cookies array configure',
)
@@ -2885,5 +2641,99 @@ class TestRouting(TestApplicationProto):
'match cookies array 10',
)
+ def test_routes_match_scheme(self):
+ self.assertIn(
+ 'success',
+ self.route(
+ {
+ "match": {"scheme": "http"},
+ "action": {"pass": "applications/empty"},
+ }
+ ),
+ 'match scheme http configure',
+ )
+ self.assertIn(
+ 'success',
+ self.route(
+ {
+ "match": {"scheme": "https"},
+ "action": {"pass": "applications/empty"},
+ }
+ ),
+ 'match scheme https configure',
+ )
+ self.assertIn(
+ 'success',
+ self.route(
+ {
+ "match": {"scheme": "HtTp"},
+ "action": {"pass": "applications/empty"},
+ }
+ ),
+ 'match scheme http case insensitive configure',
+ )
+ self.assertIn(
+ 'success',
+ self.route(
+ {
+ "match": {"scheme": "HtTpS"},
+ "action": {"pass": "applications/empty"},
+ }
+ ),
+ 'match scheme https case insensitive configure',
+ )
+
+ def test_routes_match_scheme_invalid(self):
+ self.assertIn(
+ 'error',
+ self.route(
+ {
+ "match": {"scheme": ["http"]},
+ "action": {"pass": "applications/empty"},
+ }
+ ),
+ 'scheme invalid type no arrays allowed',
+ )
+ self.assertIn(
+ 'error',
+ self.route(
+ {
+ "match": {"scheme": "ftp"},
+ "action": {"pass": "applications/empty"},
+ }
+ ),
+ 'scheme invalid protocol 1',
+ )
+ self.assertIn(
+ 'error',
+ self.route(
+ {
+ "match": {"scheme": "ws"},
+ "action": {"pass": "applications/empty"},
+ }
+ ),
+ 'scheme invalid protocol 2',
+ )
+ self.assertIn(
+ 'error',
+ self.route(
+ {
+ "match": {"scheme": "*"},
+ "action": {"pass": "applications/empty"},
+ }
+ ),
+ 'scheme invalid no wildcard allowed',
+ )
+ self.assertIn(
+ 'error',
+ self.route(
+ {
+ "match": {"scheme": ""},
+ "action": {"pass": "applications/empty"},
+ }
+ ),
+ 'scheme invalid empty',
+ )
+
if __name__ == '__main__':
TestRouting.main()
diff --git a/test/test_routing_tls.py b/test/test_routing_tls.py
new file mode 100644
index 00000000..433a303e
--- /dev/null
+++ b/test/test_routing_tls.py
@@ -0,0 +1,58 @@
+from unit.applications.tls import TestApplicationTLS
+
+
+class TestRoutingTLS(TestApplicationTLS):
+ prerequisites = ['python', 'openssl']
+
+ def test_routes_match_scheme(self):
+ self.certificate()
+
+ self.assertIn(
+ 'success',
+ self.conf(
+ {
+ "listeners": {
+ "*:7080": {"pass": "routes"},
+ "*:7081": {
+ "pass": "routes",
+ "tls": {"certificate": 'default'},
+ },
+ },
+ "routes": [
+ {
+ "match": {"scheme": "http"},
+ "action": {"pass": "applications/empty"},
+ },
+ {
+ "match": {"scheme": "https"},
+ "action": {"pass": "applications/204_no_content"},
+ },
+ ],
+ "applications": {
+ "empty": {
+ "type": "python",
+ "processes": {"spare": 0},
+ "path": self.current_dir + "/python/empty",
+ "module": "wsgi",
+ },
+ "204_no_content": {
+ "type": "python",
+ "processes": {"spare": 0},
+ "path": self.current_dir
+ + "/python/204_no_content",
+ "module": "wsgi",
+ },
+ },
+ }
+ ),
+ 'scheme configure',
+ )
+
+ self.assertEqual(self.get()['status'], 200, 'scheme http')
+ self.assertEqual(
+ self.get_ssl(port=7081)['status'], 204, 'scheme https'
+ )
+
+
+if __name__ == '__main__':
+ TestRoutingTLS.main()
diff --git a/test/test_tls.py b/test/test_tls.py
index 14efb3a7..076a2c38 100644
--- a/test/test_tls.py
+++ b/test/test_tls.py
@@ -1,6 +1,5 @@
import re
import ssl
-import time
import subprocess
import unittest
from unit.applications.tls import TestApplicationTLS
@@ -146,6 +145,8 @@ class TestTLS(TestApplicationTLS):
def test_tls_certificate_key_ec(self):
self.load('empty')
+ self.openssl_conf()
+
subprocess.call(
[
'openssl',
@@ -515,8 +516,6 @@ basicConstraints = critical,CA:TRUE"""
self.skip_alerts.append(r'process \d+ exited on signal 9')
self.load('mirror')
- self.assertEqual(self.get()['status'], 200, 'init')
-
self.certificate()
self.conf('1', 'applications/mirror/processes')
diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py
index c4390f15..ec1c95d9 100644
--- a/test/unit/applications/lang/java.py
+++ b/test/unit/applications/lang/java.py
@@ -64,7 +64,7 @@ class TestApplicationJava(TestApplicationProto):
"applications": {
script: {
"unit_jars": self.pardir + '/build',
- "type": "java",
+ "type": 'java',
"processes": {"spare": 0},
"working_directory": script_path,
"webapp": app_path,
diff --git a/test/unit/applications/lang/perl.py b/test/unit/applications/lang/perl.py
index 8aaf33a4..79df2cfa 100644
--- a/test/unit/applications/lang/perl.py
+++ b/test/unit/applications/lang/perl.py
@@ -2,6 +2,8 @@ from unit.applications.proto import TestApplicationProto
class TestApplicationPerl(TestApplicationProto):
+ application_type = "perl"
+
def load(self, script, name='psgi.pl'):
script_path = self.current_dir + '/perl/' + script
@@ -10,7 +12,7 @@ class TestApplicationPerl(TestApplicationProto):
"listeners": {"*:7080": {"pass": "applications/" + script}},
"applications": {
script: {
- "type": "perl",
+ "type": self.application_type,
"processes": {"spare": 0},
"working_directory": script_path,
"script": script_path + '/' + name,
diff --git a/test/unit/applications/lang/php.py b/test/unit/applications/lang/php.py
index 99d84164..9c54368d 100644
--- a/test/unit/applications/lang/php.py
+++ b/test/unit/applications/lang/php.py
@@ -2,6 +2,8 @@ from unit.applications.proto import TestApplicationProto
class TestApplicationPHP(TestApplicationProto):
+ application_type = "php"
+
def load(self, script, name='index.php'):
script_path = self.current_dir + '/php/' + script
@@ -10,7 +12,7 @@ class TestApplicationPHP(TestApplicationProto):
"listeners": {"*:7080": {"pass": "applications/" + script}},
"applications": {
script: {
- "type": "php",
+ "type": self.application_type,
"processes": {"spare": 0},
"root": script_path,
"working_directory": script_path,
diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py
index d1b5b839..ded76cb6 100644
--- a/test/unit/applications/lang/python.py
+++ b/test/unit/applications/lang/python.py
@@ -2,6 +2,8 @@ from unit.applications.proto import TestApplicationProto
class TestApplicationPython(TestApplicationProto):
+ application_type = "python"
+
def load(self, script, name=None):
if name is None:
name = script
@@ -13,7 +15,7 @@ class TestApplicationPython(TestApplicationProto):
"listeners": {"*:7080": {"pass": "applications/" + name}},
"applications": {
name: {
- "type": "python",
+ "type": self.application_type,
"processes": {"spare": 0},
"path": script_path,
"working_directory": script_path,
diff --git a/test/unit/applications/lang/ruby.py b/test/unit/applications/lang/ruby.py
index c2d8633e..d30735ad 100644
--- a/test/unit/applications/lang/ruby.py
+++ b/test/unit/applications/lang/ruby.py
@@ -2,6 +2,8 @@ from unit.applications.proto import TestApplicationProto
class TestApplicationRuby(TestApplicationProto):
+ application_type = "ruby"
+
def load(self, script, name='config.ru'):
script_path = self.current_dir + '/ruby/' + script
@@ -10,7 +12,7 @@ class TestApplicationRuby(TestApplicationProto):
"listeners": {"*:7080": {"pass": "applications/" + script}},
"applications": {
script: {
- "type": "ruby",
+ "type": self.application_type,
"processes": {"spare": 0},
"working_directory": script_path,
"script": script_path + '/' + name,
diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py
index c8287ac5..6e8deefb 100644
--- a/test/unit/applications/tls.py
+++ b/test/unit/applications/tls.py
@@ -1,3 +1,4 @@
+import os
import ssl
import subprocess
from unit.applications.proto import TestApplicationProto
@@ -12,6 +13,8 @@ class TestApplicationTLS(TestApplicationProto):
self.context.verify_mode = ssl.CERT_NONE
def certificate(self, name='default', load=True):
+ self.openssl_conf()
+
subprocess.call(
[
'openssl',
@@ -59,13 +62,13 @@ class TestApplicationTLS(TestApplicationProto):
return ssl.get_server_certificate(addr, ssl_version=ssl_version)
- def load(self, script, name=None):
- if name is None:
- name = script
+ def openssl_conf(self):
+ conf_path = self.testdir + '/openssl.conf'
- # create default openssl configuration
+ if os.path.exists(conf_path):
+ return
- with open(self.testdir + '/openssl.conf', 'w') as f:
+ with open(conf_path, 'w') as f:
f.write(
"""[ req ]
default_bits = 2048
@@ -74,9 +77,13 @@ distinguished_name = req_distinguished_name
[ req_distinguished_name ]"""
)
+ def load(self, script, name=None):
+ if name is None:
+ name = script
+
script_path = self.current_dir + '/python/' + script
- self.conf(
+ self._load_conf(
{
"listeners": {"*:7080": {"pass": "applications/" + name}},
"applications": {
diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py
new file mode 100644
index 00000000..417e9504
--- /dev/null
+++ b/test/unit/applications/websockets.py
@@ -0,0 +1,215 @@
+import random
+import base64
+import struct
+import select
+import hashlib
+import itertools
+from unit.applications.proto import TestApplicationProto
+
+GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+
+class TestApplicationWebsocket(TestApplicationProto):
+
+ OP_CONT = 0x00
+ OP_TEXT = 0x01
+ OP_BINARY = 0x02
+ OP_CLOSE = 0x08
+ OP_PING = 0x09
+ OP_PONG = 0x0A
+ CLOSE_CODES = [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011]
+
+ def __init__(self, preinit=False):
+ self.preinit = preinit
+
+ def key(self):
+ raw_key = bytes(random.getrandbits(8) for _ in range(16))
+ return base64.b64encode(raw_key).decode()
+
+ def accept(self, key):
+ sha1 = hashlib.sha1((key + GUID).encode()).digest()
+ return base64.b64encode(sha1).decode()
+
+ def upgrade(self):
+ key = self.key()
+
+ if self.preinit:
+ self.get()
+
+ resp, sock = self.get(
+ headers={
+ 'Host': 'localhost',
+ 'Upgrade': 'websocket',
+ 'Connection': 'Upgrade',
+ 'Sec-WebSocket-Key': key,
+ 'Sec-WebSocket-Protocol': 'chat',
+ 'Sec-WebSocket-Version': 13,
+ },
+ read_timeout=1,
+ start=True,
+ )
+
+ return (resp, sock, key)
+
+ def apply_mask(self, data, mask):
+ return bytes(b ^ m for b, m in zip(data, itertools.cycle(mask)))
+
+ def serialize_close(self, code = 1000, reason = ''):
+ return struct.pack('!H', code) + reason.encode('utf-8')
+
+ def frame_read(self, sock, read_timeout=10):
+ def recv_bytes(sock, bytes):
+ data = b''
+ while select.select([sock], [], [], read_timeout)[0]:
+ try:
+ if bytes < 65536:
+ data = sock.recv(bytes)
+ else:
+ data = self.recvall(
+ sock,
+ read_timeout=read_timeout,
+ buff_size=bytes,
+ )
+ break
+ except:
+ break
+
+ return data
+
+ frame = {}
+
+ head1, = struct.unpack('!B', recv_bytes(sock, 1))
+ head2, = struct.unpack('!B', recv_bytes(sock, 1))
+
+ frame['fin'] = bool(head1 & 0b10000000)
+ frame['rsv1'] = bool(head1 & 0b01000000)
+ frame['rsv2'] = bool(head1 & 0b00100000)
+ frame['rsv3'] = bool(head1 & 0b00010000)
+ frame['opcode'] = head1 & 0b00001111
+ frame['mask'] = head2 & 0b10000000
+
+ length = head2 & 0b01111111
+ if length == 126:
+ data = recv_bytes(sock, 2)
+ length, = struct.unpack('!H', data)
+ elif length == 127:
+ data = recv_bytes(sock, 8)
+ length, = struct.unpack('!Q', data)
+
+ if frame['mask']:
+ mask_bits = recv_bytes(sock, 4)
+
+ data = recv_bytes(sock, length)
+ if frame['mask']:
+ data = self.apply_mask(data, mask_bits)
+
+ if frame['opcode'] == self.OP_CLOSE:
+ if length >= 2:
+ code, = struct.unpack('!H', data[:2])
+ reason = data[2:].decode('utf-8')
+ if not (code in self.CLOSE_CODES or 3000 <= code < 5000):
+ self.fail('Invalid status code')
+ frame['code'] = code
+ frame['reason'] = reason
+ elif length == 0:
+ frame['code'] = 1005
+ frame['reason'] = ''
+ else:
+ self.fail('Close frame too short')
+
+ frame['data'] = data
+
+ if frame['mask']:
+ self.fail('Received frame with mask')
+
+ return frame
+
+ def frame_to_send(
+ self,
+ opcode,
+ data,
+ fin=True,
+ length=None,
+ rsv1=False,
+ rsv2=False,
+ rsv3=False,
+ mask=True,
+ ):
+ frame = b''
+
+ if isinstance(data, str):
+ data = data.encode('utf-8')
+
+ head1 = (
+ (0b10000000 if fin else 0)
+ | (0b01000000 if rsv1 else 0)
+ | (0b00100000 if rsv2 else 0)
+ | (0b00010000 if rsv3 else 0)
+ | opcode
+ )
+
+ head2 = 0b10000000 if mask else 0
+
+ data_length = len(data) if length is None else length
+ if data_length < 126:
+ frame += struct.pack('!BB', head1, head2 | data_length)
+ elif data_length < 65536:
+ frame += struct.pack('!BBH', head1, head2 | 126, data_length)
+ else:
+ frame += struct.pack('!BBQ', head1, head2 | 127, data_length)
+
+ if mask:
+ mask_bits = struct.pack('!I', random.getrandbits(32))
+ frame += mask_bits
+
+ if mask:
+ frame += self.apply_mask(data, mask_bits)
+ else:
+ frame += data
+
+ return frame
+
+ def frame_write(self, sock, *args, **kwargs):
+ chopsize = kwargs.pop('chopsize') if 'chopsize' in kwargs else None
+
+ frame = self.frame_to_send(*args, **kwargs)
+
+ if chopsize is None:
+ sock.sendall(frame)
+
+ else:
+ pos = 0
+ frame_len = len(frame)
+ while (pos < frame_len):
+ end = min(pos + chopsize, frame_len)
+ sock.sendall(frame[pos:end])
+ pos = end
+
+ def message(self, sock, type, message, fragmention_size=None, **kwargs):
+ message_len = len(message)
+
+ if fragmention_size is None:
+ fragmention_size = message_len
+
+ if message_len <= fragmention_size:
+ self.frame_write(sock, type, message, **kwargs)
+ return
+
+ pos = 0
+ op_code = type
+ while(pos < message_len):
+ end = min(pos + fragmention_size, message_len)
+ fin = (end == message_len)
+ self.frame_write(sock, op_code, message[pos:end], fin=fin, **kwargs)
+ op_code = self.OP_CONT
+ pos = end
+
+ def message_read(self, sock, read_timeout=10):
+ frame = self.frame_read(sock, read_timeout=read_timeout)
+
+ while(not frame['fin']):
+ temp = self.frame_read(sock, read_timeout=read_timeout)
+ frame['data'] += temp['data']
+ frame['fin'] = temp['fin']
+
+ return frame
diff --git a/test/unit/http.py b/test/unit/http.py
index 1ce86e5a..c0af8a9e 100644
--- a/test/unit/http.py
+++ b/test/unit/http.py
@@ -81,7 +81,11 @@ class TestHTTP(TestUnit):
sock.sendall(req)
if TestUnit.detailed:
- print('>>>', req, sep='\n')
+ print('>>>')
+ try:
+ print(req.decode('utf-8', 'ignore'))
+ except UnicodeEncodeError:
+ print(req)
resp = ''
@@ -93,7 +97,11 @@ class TestHTTP(TestUnit):
resp = self.recvall(sock, read_timeout=read_timeout).decode(enc)
if TestUnit.detailed:
- print('<<<', resp.encode('utf-8'), sep='\n')
+ print('<<<')
+ try:
+ print(resp)
+ except UnicodeEncodeError:
+ print(resp.encode())
if 'raw_resp' not in kwargs:
resp = self._resp_to_dict(resp)
diff --git a/test/unit/main.py b/test/unit/main.py
index 49806fe7..6a167a9e 100644
--- a/test/unit/main.py
+++ b/test/unit/main.py
@@ -12,6 +12,8 @@ import subprocess
from multiprocessing import Process
+available_modules = {}
+
class TestUnit(unittest.TestCase):
current_dir = os.path.abspath(
@@ -21,6 +23,7 @@ class TestUnit(unittest.TestCase):
os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
)
architecture = platform.architecture()[0]
+ system = platform.system()
maxDiff = None
detailed = False
@@ -34,6 +37,17 @@ class TestUnit(unittest.TestCase):
TestUnit._set_args(args)
+ def run(self, result=None):
+ if not hasattr(self, 'application_type'):
+ return super().run(result)
+
+ type = self.application_type
+ for prerequisite in self.prerequisites:
+ if prerequisite in available_modules:
+ for version in available_modules[prerequisite]:
+ self.application_type = type + ' ' + version
+ super().run(result)
+
@classmethod
def main(cls):
args, rest = TestUnit._parse_args()
@@ -108,6 +122,16 @@ class TestUnit(unittest.TestCase):
self.stop()
exit("Unit is writing log too long")
+ # discover all available modules
+
+ global available_modules
+ available_modules = {}
+ for module in re.findall(r'module: ([a-zA-Z]+) (.*) ".*"$', log, re.M):
+ if module[0] not in available_modules:
+ available_modules[module[0]] = [module[1]]
+ else:
+ available_modules[module[0]].append(module[1])
+
missed_module = ''
for module in modules:
if module == 'go':
@@ -153,7 +177,8 @@ class TestUnit(unittest.TestCase):
m = None
else:
- m = re.search('module: ' + module, log)
+ if module not in available_modules:
+ m = None
if m is None:
missed_module = module
@@ -309,6 +334,13 @@ class TestUnit(unittest.TestCase):
action='store_true',
help='Save unit.log after the test execution',
)
+ parser.add_argument(
+ '-u',
+ '--unsafe',
+ dest='unsafe',
+ action='store_true',
+ help='Run unsafe tests',
+ )
return parser.parse_known_args()
@@ -316,6 +348,7 @@ class TestUnit(unittest.TestCase):
def _set_args(args):
TestUnit.detailed = args.detailed
TestUnit.save_log = args.save_log
+ TestUnit.unsafe = args.unsafe
if TestUnit.detailed:
fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, 0)
diff --git a/version b/version
index 540e9134..271e354a 100644
--- a/version
+++ b/version
@@ -1,5 +1,5 @@
# Copyright (C) NGINX, Inc.
-NXT_VERSION=1.9.0
-NXT_VERNUM=10900
+NXT_VERSION=1.10.0
+NXT_VERNUM=11000