diff options
124 files changed, 10199 insertions, 2354 deletions
@@ -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 @@ -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. @@ -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 <defan@nginx.com>"> + +<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 <defan@nginx.com>"> + +<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 <defan@nginx.com>"> 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 Binary files differnew file mode 100644 index 00000000..4985e804 --- /dev/null +++ b/test/java/empty_war/empty.war 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) @@ -1,5 +1,5 @@ # Copyright (C) NGINX, Inc. -NXT_VERSION=1.9.0 -NXT_VERNUM=10900 +NXT_VERSION=1.10.0 +NXT_VERNUM=11000 |