summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrei Belov <defan@nginx.com>2021-05-27 17:03:24 +0300
committerAndrei Belov <defan@nginx.com>2021-05-27 17:03:24 +0300
commit0afb4b5790c5a37ba6b880eb351a65fe00521fbe (patch)
treec7e0b6bed92ee62a5e8b13c945c4134e68554cec
parent21ff5e086ece7188df3b7338d228fa4fb7f886af (diff)
parentd06e55dfa3692e27a92ff6c2534bb083416bc0c8 (diff)
downloadunit-0afb4b5790c5a37ba6b880eb351a65fe00521fbe.tar.gz
unit-0afb4b5790c5a37ba6b880eb351a65fe00521fbe.tar.bz2
Merged with the default branch.1.24.0-1
-rw-r--r--.hgtags1
-rw-r--r--CHANGES28
-rw-r--r--auto/files32
-rw-r--r--auto/make2
-rw-r--r--auto/modules/go2
-rw-r--r--auto/ssltls14
-rw-r--r--docs/Makefile5
-rw-r--r--docs/changes.xml112
-rw-r--r--pkg/Makefile2
-rw-r--r--pkg/deb/Makefile21
-rw-r--r--pkg/deb/Makefile.jsc-common2
-rw-r--r--pkg/deb/Makefile.jsc1671
-rw-r--r--pkg/deb/Makefile.jsc1771
-rw-r--r--pkg/deb/Makefile.python3946
-rw-r--r--pkg/deb/debian.module/control-noarch.in2
-rw-r--r--pkg/deb/debian.module/control.in2
-rw-r--r--pkg/deb/debian.module/unit.example-jsc16-config15
-rw-r--r--pkg/deb/debian.module/unit.example-jsc17-config15
-rw-r--r--pkg/deb/debian.module/unit.example-python3.9-config16
-rw-r--r--pkg/deb/debian/control.in (renamed from pkg/deb/debian/control)2
-rw-r--r--pkg/docker/Dockerfile.go1.152
-rw-r--r--pkg/docker/Dockerfile.jsc112
-rw-r--r--pkg/docker/Dockerfile.minimal2
-rw-r--r--pkg/docker/Dockerfile.node152
-rw-r--r--pkg/docker/Dockerfile.perl5.322
-rw-r--r--pkg/docker/Dockerfile.php8.02
-rw-r--r--pkg/docker/Dockerfile.python3.92
-rw-r--r--pkg/docker/Dockerfile.ruby2.72
-rw-r--r--pkg/rpm/Makefile8
-rw-r--r--pkg/rpm/unit.module.spec.in7
-rw-r--r--pkg/rpm/unit.spec.in9
-rw-r--r--src/nodejs/unit-http/http.js15
-rw-r--r--src/nodejs/unit-http/http_server.js24
-rw-r--r--src/nodejs/unit-http/loader.js27
-rw-r--r--src/nodejs/unit-http/loader.mjs18
-rw-r--r--src/nxt_application.h1
-rw-r--r--src/nxt_cert.c19
-rw-r--r--src/nxt_conf_validation.c284
-rw-r--r--src/nxt_errno.h1
-rw-r--r--src/nxt_file.c44
-rw-r--r--src/nxt_file.h32
-rwxr-xr-x[-rw-r--r--]src/nxt_h1proto.c0
-rw-r--r--src/nxt_http.h20
-rw-r--r--src/nxt_http_request.c4
-rw-r--r--src/nxt_http_route.c144
-rw-r--r--src/nxt_http_static.c178
-rw-r--r--src/nxt_main_process.c6
-rw-r--r--src/nxt_openssl.c146
-rw-r--r--src/nxt_pcre2.c2
-rw-r--r--src/nxt_php_sapi.c37
-rw-r--r--src/nxt_router.c123
-rw-r--r--src/nxt_router.h2
-rw-r--r--src/nxt_tls.h8
-rw-r--r--src/nxt_unix.h4
-rw-r--r--src/python/nxt_python.c164
-rw-r--r--src/python/nxt_python.h17
-rw-r--r--src/python/nxt_python_asgi.c37
-rw-r--r--src/python/nxt_python_asgi.h4
-rw-r--r--src/python/nxt_python_asgi_lifespan.c93
-rw-r--r--src/python/nxt_python_wsgi.c5
-rw-r--r--src/ruby/nxt_ruby_stream_io.c8
-rw-r--r--test/conftest.py213
-rw-r--r--[-rwxr-xr-x]test/node/404/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/basic/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/double_end/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/get_header_names/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/get_header_type/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/get_variables/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/has_header/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/header_name_case/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/header_name_valid/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/header_value_object/app.js3
-rw-r--r--test/node/loader/es_modules_http/app.mjs6
-rw-r--r--test/node/loader/es_modules_http_indirect/app.js1
-rw-r--r--test/node/loader/es_modules_http_indirect/module.mjs6
-rw-r--r--test/node/loader/es_modules_websocket/app.mjs30
-rw-r--r--test/node/loader/es_modules_websocket_indirect/app.js1
-rw-r--r--test/node/loader/es_modules_websocket_indirect/module.mjs30
-rw-r--r--test/node/loader/transitive_dependency/app.js1
-rw-r--r--test/node/loader/transitive_dependency/transitive_http.js8
-rw-r--r--test/node/loader/unit_http/app.js4
-rw-r--r--[-rwxr-xr-x]test/node/mirror/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/post_variables/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/promise_end/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/promise_handler/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/remove_header/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/set_header_array/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/status_message/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/update_header/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/variables/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/websockets/mirror/app.js7
-rw-r--r--[-rwxr-xr-x]test/node/websockets/mirror_fragmentation/app.js7
-rw-r--r--[-rwxr-xr-x]test/node/write_before_write_head/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/write_buffer/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/write_callback/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/write_multiple/app.js3
-rw-r--r--[-rwxr-xr-x]test/node/write_return/app.js3
-rw-r--r--test/php/fastcgi_finish_request/index.php2
-rw-r--r--test/php/fastcgi_finish_request/server.php4
-rw-r--r--test/python/atexit/wsgi.py1
-rw-r--r--test/python/body_generate/wsgi.py6
-rw-r--r--test/python/body_io/wsgi.py1
-rw-r--r--test/python/callable/wsgi.py1
-rw-r--r--test/python/ctx_iter_atexit/wsgi.py16
-rw-r--r--test/python/custom_header/wsgi.py11
-rw-r--r--test/python/delayed/asgi.py27
-rw-r--r--test/python/empty/asgi.py14
-rw-r--r--test/python/environment/wsgi.py1
-rw-r--r--test/python/header_fields/wsgi.py7
-rw-r--r--test/python/host/wsgi.py13
-rw-r--r--test/python/iter_exception/wsgi.py4
-rw-r--r--test/python/legacy/asgi.py15
-rw-r--r--test/python/legacy_force/asgi.py15
-rw-r--r--test/python/lifespan/empty/asgi.py16
-rw-r--r--test/python/mirror/asgi.py19
-rw-r--r--test/python/mirror/wsgi.py4
-rw-r--r--test/python/path/wsgi.py1
-rw-r--r--test/python/query_string/asgi.py18
-rw-r--r--test/python/query_string/wsgi.py11
-rw-r--r--test/python/server_port/asgi.py18
-rw-r--r--test/python/server_port/wsgi.py8
-rw-r--r--test/python/targets/asgi.py54
-rw-r--r--test/python/targets/wsgi.py8
-rw-r--r--test/python/threading/asgi.py14
-rw-r--r--test/python/threads/asgi.py19
-rw-r--r--test/python/threads/wsgi.py14
-rw-r--r--test/python/upload/wsgi.py10
-rw-r--r--test/python/user_group/wsgi.py17
-rw-r--r--test/python/variables/asgi.py39
-rw-r--r--test/python/variables/wsgi.py33
-rw-r--r--test/python/websockets/mirror/asgi.py18
-rw-r--r--test/python/websockets/subprotocol/asgi.py30
-rw-r--r--test/ruby/encoding/config.ru8
-rw-r--r--test/test_access_log.py3
-rw-r--r--test/test_asgi_application.py17
-rw-r--r--test/test_asgi_lifespan.py96
-rw-r--r--test/test_asgi_targets.py92
-rw-r--r--test/test_asgi_websockets.py208
-rw-r--r--test/test_configuration.py29
-rw-r--r--test/test_go_application.py8
-rw-r--r--test/test_go_isolation.py14
-rw-r--r--test/test_go_isolation_rootfs.py1
-rw-r--r--test/test_http_header.py1
-rw-r--r--test/test_java_application.py11
-rw-r--r--test/test_java_isolation_rootfs.py1
-rw-r--r--test/test_java_websockets.py197
-rw-r--r--test/test_node_application.py24
-rw-r--r--test/test_node_es_modules.py50
-rw-r--r--test/test_node_websockets.py197
-rw-r--r--test/test_perl_application.py1
-rw-r--r--test/test_php_application.py63
-rw-r--r--test/test_php_isolation.py5
-rw-r--r--test/test_proxy.py10
-rw-r--r--test/test_python_application.py15
-rw-r--r--test/test_python_isolation.py18
-rw-r--r--test/test_python_isolation_chroot.py4
-rw-r--r--test/test_python_procman.py1
-rw-r--r--test/test_python_targets.py51
-rw-r--r--test/test_respawn.py13
-rw-r--r--test/test_routing.py106
-rw-r--r--test/test_ruby_application.py54
-rw-r--r--test/test_ruby_isolation.py1
-rw-r--r--test/test_settings.py199
-rw-r--r--test/test_share_chroot.py108
-rw-r--r--test/test_share_fallback.py6
-rw-r--r--test/test_share_mount.py142
-rw-r--r--test/test_share_symlink.py96
-rw-r--r--test/test_share_types.py170
-rw-r--r--test/test_static.py19
-rw-r--r--test/test_tls.py291
-rw-r--r--test/test_tls_conf_command.py112
-rw-r--r--test/test_tls_sni.py276
-rw-r--r--test/test_usr1.py55
-rw-r--r--test/test_variables.py12
-rw-r--r--test/unit/applications/lang/go.py1
-rw-r--r--test/unit/applications/lang/node.py21
-rw-r--r--test/unit/applications/lang/python.py11
-rw-r--r--test/unit/applications/proto.py18
-rw-r--r--test/unit/applications/tls.py42
-rw-r--r--test/unit/applications/websockets.py18
-rw-r--r--test/unit/check/chroot.py32
-rw-r--r--test/unit/check/go.py1
-rw-r--r--test/unit/check/isolation.py4
-rw-r--r--test/unit/check/node.py13
-rw-r--r--test/unit/http.py26
-rw-r--r--test/unit/log.py23
-rw-r--r--test/unit/option.py3
-rw-r--r--test/unit/utils.py11
-rw-r--r--version4
189 files changed, 4466 insertions, 1390 deletions
diff --git a/.hgtags b/.hgtags
index 455af2cb..bc28dc7a 100644
--- a/.hgtags
+++ b/.hgtags
@@ -53,3 +53,4 @@ e3f504b6082ee97ed0d6c8660890585ef6a5796f 1.21.0-1
86b359acc93fe53f1f21b22abc8d1b40ca26158c 1.22.0-1
49ee24c03f5749f8a1b69dc1c600ad48517d9d7a 1.23.0
ad6aad2450c256d4f1a3c32f7091a78dbbc4a6d1 1.23.0-1
+847c88d10f26765b45149c14f88c2274adfc3f42 1.24.0
diff --git a/CHANGES b/CHANGES
index b411e816..cbc6678e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,4 +1,32 @@
+Changes with Unit 1.24.0 27 May 2021
+
+ *) Change: PHP added to the default MIME type list.
+
+ *) Feature: arbitrary configuration of TLS connections via OpenSSL
+ commands.
+
+ *) Feature: the ability to limit static file serving by MIME types.
+
+ *) Feature: support for chrooting, rejecting symlinks, and rejecting
+ mount point traversal on a per-request basis when serving static
+ files.
+
+ *) Feature: a loader for automatically overriding the "http" and
+ "websocket" modules in Node.js.
+
+ *) Feature: multiple "targets" in Python applications.
+
+ *) Feature: compatibility with Ruby 3.0.
+
+ *) Bugfix: the router process could crash while closing a TLS
+ connection.
+
+ *) Bugfix: a segmentation fault might have occurred in the PHP module if
+ fastcgi_finish_request() was used with the "auto_globals_jit" option
+ enabled.
+
+
Changes with Unit 1.23.0 25 Mar 2021
*) Feature: support for multiple certificate bundles on a listener via
diff --git a/auto/files b/auto/files
index d99e93d7..591c5ee1 100644
--- a/auto/files
+++ b/auto/files
@@ -49,3 +49,35 @@ nxt_feature_test="#include <fcntl.h>
return 0;
}"
. auto/feature
+
+
+nxt_feature="openat2()"
+nxt_feature_name=NXT_HAVE_OPENAT2
+nxt_feature_run=
+nxt_feature_incs=
+nxt_feature_libs=
+nxt_feature_test="#include <fcntl.h>
+ #include <unistd.h>
+ #include <sys/syscall.h>
+ #include <linux/openat2.h>
+ #include <string.h>
+
+ int main() {
+ struct open_how how;
+
+ memset(&how, 0, sizeof(how));
+
+ how.flags = O_RDONLY;
+ how.mode = O_NONBLOCK;
+ how.resolve = RESOLVE_IN_ROOT
+ | RESOLVE_NO_SYMLINKS
+ | RESOLVE_NO_XDEV;
+
+ int fd = syscall(SYS_openat2, AT_FDCWD, \".\",
+ &how, sizeof(how));
+ if (fd == -1)
+ return 1;
+
+ return 0;
+ }"
+. auto/feature
diff --git a/auto/make b/auto/make
index 18d23917..757e24db 100644
--- a/auto/make
+++ b/auto/make
@@ -368,7 +368,7 @@ ${NXT_DAEMON}-install: $NXT_DAEMON install-check
manpage-install: manpage install-check
install -d \$(DESTDIR)$NXT_MANDIR/man8
- install -p $NXT_BUILD_DIR/unitd.8 \$(DESTDIR)$NXT_MANDIR/man8/
+ install -p -m644 $NXT_BUILD_DIR/unitd.8 \$(DESTDIR)$NXT_MANDIR/man8/
.PHONY: uninstall ${NXT_DAEMON}-uninstall manpage-uninstall
diff --git a/auto/modules/go b/auto/modules/go
index 8bb9216e..7324ffbe 100644
--- a/auto/modules/go
+++ b/auto/modules/go
@@ -111,7 +111,7 @@ install: ${NXT_GO}-install
${NXT_GO}:
${NXT_GO}-install: ${NXT_GO}-install-src ${NXT_GO}-install-env
- GOPATH=\$(DESTDIR)\$(GOPATH) ${NXT_GO} build ${NXT_GO_PKG}
+ GOPATH=\$(DESTDIR)\$(GOPATH) GO111MODULE=auto ${NXT_GO} build ${NXT_GO_PKG}
${NXT_GO}-install-src:
install -d \$(DESTDIR)\$(NXT_GO_DST)/src/${NXT_GO_PKG}
diff --git a/auto/ssltls b/auto/ssltls
index f034b758..f9363dde 100644
--- a/auto/ssltls
+++ b/auto/ssltls
@@ -52,6 +52,20 @@ if [ $NXT_OPENSSL = YES ]; then
$echo
exit 1;
fi
+
+
+ nxt_feature="OpenSSL SSL_CONF_cmd()"
+ nxt_feature_name=NXT_HAVE_OPENSSL_CONF_CMD
+ nxt_feature_run=
+ nxt_feature_incs=
+ nxt_feature_libs="$NXT_OPENSSL_LIBS"
+ nxt_feature_test="#include <openssl/ssl.h>
+
+ int main() {
+ SSL_CONF_cmd(NULL, NULL, NULL);
+ return 0;
+ }"
+ . auto/feature
fi
diff --git a/docs/Makefile b/docs/Makefile
index db63eec4..d27e69be 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -7,12 +7,13 @@ PACKAGES= unit \
unit-php \
unit-python unit-python2.7 unit-python3.4 \
unit-python3.5 unit-python3.6 unit-python3.7 \
- unit-python3.8 \
+ unit-python3.8 unit-python3.9 \
unit-go unit-go1.7 unit-go1.8 unit-go1.9 unit-go1.10 \
unit-go1.12 unit-go1.13 \
unit-perl \
unit-ruby \
- unit-jsc-common unit-jsc8 unit-jsc10 unit-jsc11
+ unit-jsc-common unit-jsc8 unit-jsc10 unit-jsc11 \
+ unit-jsc13 unit-jsc14 unit-jsc15 unit-jsc16 unit-jsc17
CURDATE:=$(shell date +"%Y-%m-%d")
CURTIME:=$(shell date +"%H:%M:%S %z")
diff --git a/docs/changes.xml b/docs/changes.xml
index 6c79b560..e3711d0c 100644
--- a/docs/changes.xml
+++ b/docs/changes.xml
@@ -5,6 +5,118 @@
<change_log title="unit">
+<changes apply="unit-jsc17" ver="1.24.0" rev="1"
+ date="2021-05-27" time="18:00:00 +0300"
+ packager="Andrei Belov &lt;defan@nginx.com&gt;">
+
+<change>
+<para>
+Initial release of Java 17 module for NGINX Unit.
+</para>
+</change>
+
+</changes>
+
+
+<changes apply="unit-jsc16" ver="1.24.0" rev="1"
+ date="2021-05-27" time="18:00:00 +0300"
+ packager="Andrei Belov &lt;defan@nginx.com&gt;">
+
+<change>
+<para>
+Initial release of Java 16 module for NGINX Unit.
+</para>
+</change>
+
+</changes>
+
+
+<changes apply="unit-php
+ unit-python unit-python2.7
+ unit-python3.4 unit-python3.5 unit-python3.6 unit-python3.7
+ unit-python3.8 unit-python3.9
+ unit-go
+ unit-perl
+ unit-ruby
+ unit-jsc-common unit-jsc8 unit-jsc10 unit-jsc11 unit-jsc13
+ unit-jsc14 unit-jsc15"
+ ver="1.24.0" rev="1"
+ date="2021-05-27" time="18:00:00 +0300"
+ packager="Andrei Belov &lt;defan@nginx.com&gt;">
+
+<change>
+<para>
+NGINX Unit updated to 1.24.0.
+</para>
+</change>
+
+</changes>
+
+
+<changes apply="unit" ver="1.24.0" rev="1"
+ date="2021-05-27" time="18:00:00 +0300"
+ packager="Andrei Belov &lt;defan@nginx.com&gt;">
+
+<change type="change">
+<para>
+PHP added to the default MIME type list.
+</para>
+</change>
+
+<change type="feature">
+<para>
+arbitrary configuration of TLS connections via OpenSSL commands.
+</para>
+</change>
+
+<change type="feature">
+<para>
+the ability to limit static file serving by MIME types.
+</para>
+</change>
+
+<change type="feature">
+<para>
+support for chrooting, rejecting symlinks, and rejecting mount
+point traversal on a per-request basis when serving static files.
+</para>
+</change>
+
+<change type="feature">
+<para>
+a loader for automatically overriding the "http" and "websocket" modules in
+Node.js.
+</para>
+</change>
+
+<change type="feature">
+<para>
+multiple "targets" in Python applications.
+</para>
+</change>
+
+<change type="feature">
+<para>
+compatibility with Ruby 3.0.
+</para>
+</change>
+
+<change type="bugfix">
+<para>
+the router process could crash while closing a TLS connection.
+</para>
+</change>
+
+<change type="bugfix">
+<para>
+a segmentation fault might have occurred in the PHP module if
+fastcgi_finish_request() was used with the "auto_globals_jit" option enabled.
+</para>
+</change>
+
+</changes>
+
+
<changes apply="unit-php
unit-python unit-python2.7
unit-python3.4 unit-python3.5 unit-python3.6 unit-python3.7
diff --git a/pkg/Makefile b/pkg/Makefile
index 15ff075d..4cf9ff80 100644
--- a/pkg/Makefile
+++ b/pkg/Makefile
@@ -14,7 +14,7 @@ dist:
hg archive unit-$(VERSION).tar.gz \
-r $(VERSION) \
-p unit-$(VERSION) \
- -X "../.hg*" -X "../pkg/" -X "../docs/"
+ -X "../.hg*" -X "../pkg/" -X "../docs/*.*" -X "../docs/Makefile"
$(SHA512SUM) unit-$(VERSION).tar.gz > unit-$(VERSION).tar.gz.sha512
rpm:
diff --git a/pkg/deb/Makefile b/pkg/deb/Makefile
index 8c33fc53..c343eb53 100644
--- a/pkg/deb/Makefile
+++ b/pkg/deb/Makefile
@@ -8,6 +8,8 @@ DEFAULT_RELEASE := 1
VERSION ?= $(DEFAULT_VERSION)
RELEASE ?= $(DEFAULT_RELEASE)
+PACKAGE_VENDOR = NGINX Packaging <nginx-packaging@f5.com>
+
SRCDIR= unit-$(VERSION)
CODENAME = $(shell lsb_release -cs)
@@ -17,6 +19,21 @@ BUILD_DEPENDS = $(BUILD_DEPENDS_unit)
MODULES=
+# Ubuntu 21.04
+ifeq ($(CODENAME),hirsute)
+include Makefile.php
+include Makefile.python27
+include Makefile.python39
+include Makefile.go
+include Makefile.perl
+include Makefile.ruby
+include Makefile.jsc-common
+include Makefile.jsc11
+include Makefile.jsc15
+include Makefile.jsc16
+include Makefile.jsc17
+endif
+
# Ubuntu 20.10
ifeq ($(CODENAME),groovy)
include Makefile.php
@@ -205,6 +222,9 @@ debuild/$(SRCDIR)/debian:
echo '9' > debuild/$(SRCDIR)/debian/compat ; \
mkdir -p debuild/$(SRCDIR)/debian/source ; \
echo '3.0 (quilt)' > debuild/$(SRCDIR)/debian/source/format ; \
+ cat debian/control.in | sed \
+ -e "s#%%PACKAGE_VENDOR%%#$(PACKAGE_VENDOR)#g" \
+ > debuild/$(SRCDIR)/debian/control ; \
cat debian/rules.in | sed \
-e "s#%%CONFIGURE_ARGS%%#$(CONFIGURE_ARGS)#g" \
> debuild/$(SRCDIR)/debian/rules ; \
@@ -280,6 +300,7 @@ endif
-e "s#%%UNIT_RELEASE%%#$(RELEASE)#g" \
-e "s#%%VERSION%%#$(MODULE_VERSION_$*)#g" \
-e "s#%%RELEASE%%#$(MODULE_RELEASE_$*)#g" \
+ -e "s#%%PACKAGE_VENDOR%%#$(PACKAGE_VENDOR)#g" \
-e "s#%%MODULE_BUILD_DEPENDS%%#$(MODULE_BUILD_DEPENDS_$*)#g" \
-e "s#%%MODULE_DEPENDS%%#$(MODULE_DEPENDS_$*)#g" \
> $@/$(SRCDIR)/debian/control ; \
diff --git a/pkg/deb/Makefile.jsc-common b/pkg/deb/Makefile.jsc-common
index f7a6010b..1c4a77b5 100644
--- a/pkg/deb/Makefile.jsc-common
+++ b/pkg/deb/Makefile.jsc-common
@@ -6,7 +6,7 @@ MODULE_SUMMARY_jsc_common= Java shared packages for NGINX Unit
MODULE_VERSION_jsc_common= $(VERSION)
MODULE_RELEASE_jsc_common= 1
-ifneq (,$(findstring $(CODENAME),groovy focal eoan disco buster))
+ifneq (,$(findstring $(CODENAME),hirsute groovy focal eoan disco buster))
JAVA_MINVERSION= 11
else
JAVA_MINVERSION= 8
diff --git a/pkg/deb/Makefile.jsc16 b/pkg/deb/Makefile.jsc16
new file mode 100644
index 00000000..f45e1299
--- /dev/null
+++ b/pkg/deb/Makefile.jsc16
@@ -0,0 +1,71 @@
+MODULES+= jsc16
+MODULE_SUFFIX_jsc16= jsc16
+
+MODULE_SUMMARY_jsc16= Java 16 module for NGINX Unit
+
+MODULE_VERSION_jsc16= $(VERSION)
+MODULE_RELEASE_jsc16= 1
+
+MODULE_CONFARGS_jsc16= java --module=java16 --home=/usr/lib/jvm/java-16-openjdk-$$\(DEB_HOST_ARCH\) --jars=/usr/share/unit-jsc-common/
+MODULE_MAKEARGS_jsc16= java16
+MODULE_INSTARGS_jsc16= java16-install
+
+MODULE_SOURCES_jsc16= unit.example-jsc-app \
+ unit.example-jsc16-config
+
+BUILD_DEPENDS_jsc16= openjdk-16-jdk-headless openjdk-16-jre-headless
+BUILD_DEPENDS+= $(BUILD_DEPENDS_jsc16)
+
+MODULE_BUILD_DEPENDS_jsc16=,openjdk-16-jdk-headless
+MODULE_DEPENDS_jsc16=,openjdk-16-jre-headless,unit-jsc-common (= $(MODULE_VERSION_jsc_common)-$(MODULE_RELEASE_jsc_common)~$(CODENAME))
+
+define MODULE_PREINSTALL_jsc16
+ mkdir -p debian/unit-jsc16/usr/share/doc/unit-jsc16/examples/jsc-app
+ install -m 644 -p debian/unit.example-jsc-app debian/unit-jsc16/usr/share/doc/unit-jsc16/examples/jsc-app/index.jsp
+ install -m 644 -p debian/unit.example-jsc16-config debian/unit-jsc16/usr/share/doc/unit-jsc16/examples/unit.config
+ install -m 644 -p src/java/README.JSR-340 debian/unit-jsc16/usr/share/doc/unit-jsc16/
+endef
+export MODULE_PREINSTALL_jsc16
+
+define MODULE_POSTINSTALL_jsc16
+ cd $$\(BUILDDIR_unit\) \&\& \
+ DESTDIR=$$\(INSTALLDIR\) make java-shared-uninstall
+endef
+export MODULE_POSTINSTALL_jsc16
+
+define MODULE_POST_jsc16
+cat <<BANNER
+----------------------------------------------------------------------
+
+The $(MODULE_SUMMARY_jsc16) has been installed.
+
+To check out the sample app, run these commands:
+
+ sudo service unit restart
+ cd /usr/share/doc/unit-$(MODULE_SUFFIX_jsc16)/examples
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ curl http://localhost:8800/
+
+Online documentation is available at https://unit.nginx.org
+
+NOTICE:
+
+This version of Unit code is made available in support of the open source
+development process. This is an intermediate build made available for
+testing purposes only. This Unit code is untested and presumed incompatible
+with the JSR 340 Java Servlet 3.1 specification. You should not deploy or
+write to this code. You should instead deploy and write production
+applications on pre-built binaries that have been tested and certified
+to meet the JSR-340 compatibility requirements such as certified binaries
+published for the JSR-340 reference implementation available at
+https://javaee.github.io/glassfish/.
+
+Redistribution of any Intermediate Build must retain this notice.
+
+Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+Other names may be trademarks of their respective owners.
+
+----------------------------------------------------------------------
+BANNER
+endef
+export MODULE_POST_jsc16
diff --git a/pkg/deb/Makefile.jsc17 b/pkg/deb/Makefile.jsc17
new file mode 100644
index 00000000..16f840da
--- /dev/null
+++ b/pkg/deb/Makefile.jsc17
@@ -0,0 +1,71 @@
+MODULES+= jsc17
+MODULE_SUFFIX_jsc17= jsc17
+
+MODULE_SUMMARY_jsc17= Java 17 module for NGINX Unit
+
+MODULE_VERSION_jsc17= $(VERSION)
+MODULE_RELEASE_jsc17= 1
+
+MODULE_CONFARGS_jsc17= java --module=java17 --home=/usr/lib/jvm/java-17-openjdk-$$\(DEB_HOST_ARCH\) --jars=/usr/share/unit-jsc-common/
+MODULE_MAKEARGS_jsc17= java17
+MODULE_INSTARGS_jsc17= java17-install
+
+MODULE_SOURCES_jsc17= unit.example-jsc-app \
+ unit.example-jsc17-config
+
+BUILD_DEPENDS_jsc17= openjdk-17-jdk-headless openjdk-17-jre-headless
+BUILD_DEPENDS+= $(BUILD_DEPENDS_jsc17)
+
+MODULE_BUILD_DEPENDS_jsc17=,openjdk-17-jdk-headless
+MODULE_DEPENDS_jsc17=,openjdk-17-jre-headless,unit-jsc-common (= $(MODULE_VERSION_jsc_common)-$(MODULE_RELEASE_jsc_common)~$(CODENAME))
+
+define MODULE_PREINSTALL_jsc17
+ mkdir -p debian/unit-jsc17/usr/share/doc/unit-jsc17/examples/jsc-app
+ install -m 644 -p debian/unit.example-jsc-app debian/unit-jsc17/usr/share/doc/unit-jsc17/examples/jsc-app/index.jsp
+ install -m 644 -p debian/unit.example-jsc17-config debian/unit-jsc17/usr/share/doc/unit-jsc17/examples/unit.config
+ install -m 644 -p src/java/README.JSR-340 debian/unit-jsc17/usr/share/doc/unit-jsc17/
+endef
+export MODULE_PREINSTALL_jsc17
+
+define MODULE_POSTINSTALL_jsc17
+ cd $$\(BUILDDIR_unit\) \&\& \
+ DESTDIR=$$\(INSTALLDIR\) make java-shared-uninstall
+endef
+export MODULE_POSTINSTALL_jsc17
+
+define MODULE_POST_jsc17
+cat <<BANNER
+----------------------------------------------------------------------
+
+The $(MODULE_SUMMARY_jsc17) has been installed.
+
+To check out the sample app, run these commands:
+
+ sudo service unit restart
+ cd /usr/share/doc/unit-$(MODULE_SUFFIX_jsc17)/examples
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ curl http://localhost:8800/
+
+Online documentation is available at https://unit.nginx.org
+
+NOTICE:
+
+This version of Unit code is made available in support of the open source
+development process. This is an intermediate build made available for
+testing purposes only. This Unit code is untested and presumed incompatible
+with the JSR 340 Java Servlet 3.1 specification. You should not deploy or
+write to this code. You should instead deploy and write production
+applications on pre-built binaries that have been tested and certified
+to meet the JSR-340 compatibility requirements such as certified binaries
+published for the JSR-340 reference implementation available at
+https://javaee.github.io/glassfish/.
+
+Redistribution of any Intermediate Build must retain this notice.
+
+Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+Other names may be trademarks of their respective owners.
+
+----------------------------------------------------------------------
+BANNER
+endef
+export MODULE_POST_jsc17
diff --git a/pkg/deb/Makefile.python39 b/pkg/deb/Makefile.python39
new file mode 100644
index 00000000..11ce65f0
--- /dev/null
+++ b/pkg/deb/Makefile.python39
@@ -0,0 +1,46 @@
+MODULES+= python39
+MODULE_SUFFIX_python39= python3.9
+
+MODULE_SUMMARY_python39= Python 3.9 module for NGINX Unit
+
+MODULE_VERSION_python39= $(VERSION)
+MODULE_RELEASE_python39= 1
+
+MODULE_CONFARGS_python39= python --config=python3.9-config
+MODULE_MAKEARGS_python39= python3.9
+MODULE_INSTARGS_python39= python3.9-install
+
+MODULE_SOURCES_python39= unit.example-python-app \
+ unit.example-python3.9-config
+
+BUILD_DEPENDS_python39= python3.9-dev
+BUILD_DEPENDS+= $(BUILD_DEPENDS_python39)
+
+MODULE_BUILD_DEPENDS_python39=,python3.9-dev
+
+define MODULE_PREINSTALL_python39
+ mkdir -p debian/unit-python3.9/usr/share/doc/unit-python3.9/examples/python-app
+ install -m 644 -p debian/unit.example-python-app debian/unit-python3.9/usr/share/doc/unit-python3.9/examples/python-app/wsgi.py
+ install -m 644 -p debian/unit.example-python3.9-config debian/unit-python3.9/usr/share/doc/unit-python3.9/examples/unit.config
+endef
+export MODULE_PREINSTALL_python39
+
+define MODULE_POST_python39
+cat <<BANNER
+----------------------------------------------------------------------
+
+The $(MODULE_SUMMARY_python39) has been installed.
+
+To check out the sample app, run these commands:
+
+ sudo service unit restart
+ cd /usr/share/doc/unit-$(MODULE_SUFFIX_python39)/examples
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ curl http://localhost:8400/
+
+Online documentation is available at https://unit.nginx.org
+
+----------------------------------------------------------------------
+BANNER
+endef
+export MODULE_POST_python39
diff --git a/pkg/deb/debian.module/control-noarch.in b/pkg/deb/debian.module/control-noarch.in
index e22bb49a..d9d9e5e1 100644
--- a/pkg/deb/debian.module/control-noarch.in
+++ b/pkg/deb/debian.module/control-noarch.in
@@ -1,7 +1,7 @@
Source: %%NAME%%
Section: admin
Priority: extra
-Maintainer: Andrei Belov <defan@nginx.com>
+Maintainer: %%PACKAGE_VENDOR%%
Build-Depends: debhelper (>= 9),
linux-libc-dev%%MODULE_BUILD_DEPENDS%%
Standards-Version: 3.9.5
diff --git a/pkg/deb/debian.module/control.in b/pkg/deb/debian.module/control.in
index 7e28f5e9..9a6fa797 100644
--- a/pkg/deb/debian.module/control.in
+++ b/pkg/deb/debian.module/control.in
@@ -1,7 +1,7 @@
Source: %%NAME%%
Section: admin
Priority: extra
-Maintainer: Andrei Belov <defan@nginx.com>
+Maintainer: %%PACKAGE_VENDOR%%
Build-Depends: debhelper (>= 9),
linux-libc-dev,
libssl-dev,
diff --git a/pkg/deb/debian.module/unit.example-jsc16-config b/pkg/deb/debian.module/unit.example-jsc16-config
new file mode 100644
index 00000000..0b10a44d
--- /dev/null
+++ b/pkg/deb/debian.module/unit.example-jsc16-config
@@ -0,0 +1,15 @@
+{
+ "applications": {
+ "example_java16": {
+ "processes": 1,
+ "type": "java 16",
+ "webapp": "/usr/share/doc/unit-jsc16/examples/jsc-app"
+ }
+ },
+
+ "listeners": {
+ "*:8800": {
+ "pass": "applications/example_java16"
+ }
+ }
+}
diff --git a/pkg/deb/debian.module/unit.example-jsc17-config b/pkg/deb/debian.module/unit.example-jsc17-config
new file mode 100644
index 00000000..28b13e4d
--- /dev/null
+++ b/pkg/deb/debian.module/unit.example-jsc17-config
@@ -0,0 +1,15 @@
+{
+ "applications": {
+ "example_java17": {
+ "processes": 1,
+ "type": "java 17",
+ "webapp": "/usr/share/doc/unit-jsc17/examples/jsc-app"
+ }
+ },
+
+ "listeners": {
+ "*:8800": {
+ "pass": "applications/example_java17"
+ }
+ }
+}
diff --git a/pkg/deb/debian.module/unit.example-python3.9-config b/pkg/deb/debian.module/unit.example-python3.9-config
new file mode 100644
index 00000000..fdb7e9db
--- /dev/null
+++ b/pkg/deb/debian.module/unit.example-python3.9-config
@@ -0,0 +1,16 @@
+{
+ "applications": {
+ "example_python": {
+ "type": "python 3.9",
+ "processes": 2,
+ "path": "/usr/share/doc/unit-python3.9/examples/python-app",
+ "module": "wsgi"
+ }
+ },
+
+ "listeners": {
+ "*:8400": {
+ "pass": "applications/example_python"
+ }
+ }
+}
diff --git a/pkg/deb/debian/control b/pkg/deb/debian/control.in
index a8e8cdc4..4d59520e 100644
--- a/pkg/deb/debian/control
+++ b/pkg/deb/debian/control.in
@@ -1,7 +1,7 @@
Source: unit
Section: admin
Priority: extra
-Maintainer: Andrei Belov <defan@nginx.com>
+Maintainer: %%PACKAGE_VENDOR%%
Build-Depends: debhelper (>= 9),
linux-libc-dev,
libssl-dev,
diff --git a/pkg/docker/Dockerfile.go1.15 b/pkg/docker/Dockerfile.go1.15
index 45642662..d446a934 100644
--- a/pkg/docker/Dockerfile.go1.15
+++ b/pkg/docker/Dockerfile.go1.15
@@ -8,7 +8,7 @@ RUN set -ex \
&& mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \
&& hg clone https://hg.nginx.org/unit \
&& cd unit \
- && hg up 1.23.0 \
+ && hg up 1.24.0 \
&& NCPU="$(getconf _NPROCESSORS_ONLN)" \
&& DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \
&& CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \
diff --git a/pkg/docker/Dockerfile.jsc11 b/pkg/docker/Dockerfile.jsc11
index 4cfc541d..b66ebe73 100644
--- a/pkg/docker/Dockerfile.jsc11
+++ b/pkg/docker/Dockerfile.jsc11
@@ -8,7 +8,7 @@ RUN set -ex \
&& mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \
&& hg clone https://hg.nginx.org/unit \
&& cd unit \
- && hg up 1.23.0 \
+ && hg up 1.24.0 \
&& NCPU="$(getconf _NPROCESSORS_ONLN)" \
&& DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \
&& CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \
diff --git a/pkg/docker/Dockerfile.minimal b/pkg/docker/Dockerfile.minimal
index 59aaa6a3..69a70e33 100644
--- a/pkg/docker/Dockerfile.minimal
+++ b/pkg/docker/Dockerfile.minimal
@@ -8,7 +8,7 @@ RUN set -ex \
&& mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \
&& hg clone https://hg.nginx.org/unit \
&& cd unit \
- && hg up 1.23.0 \
+ && hg up 1.24.0 \
&& NCPU="$(getconf _NPROCESSORS_ONLN)" \
&& DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \
&& CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \
diff --git a/pkg/docker/Dockerfile.node15 b/pkg/docker/Dockerfile.node15
index 7eddfb26..1e3846a3 100644
--- a/pkg/docker/Dockerfile.node15
+++ b/pkg/docker/Dockerfile.node15
@@ -8,7 +8,7 @@ RUN set -ex \
&& mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \
&& hg clone https://hg.nginx.org/unit \
&& cd unit \
- && hg up 1.23.0 \
+ && hg up 1.24.0 \
&& NCPU="$(getconf _NPROCESSORS_ONLN)" \
&& DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \
&& CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \
diff --git a/pkg/docker/Dockerfile.perl5.32 b/pkg/docker/Dockerfile.perl5.32
index aa000f63..2fccbf63 100644
--- a/pkg/docker/Dockerfile.perl5.32
+++ b/pkg/docker/Dockerfile.perl5.32
@@ -8,7 +8,7 @@ RUN set -ex \
&& mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \
&& hg clone https://hg.nginx.org/unit \
&& cd unit \
- && hg up 1.23.0 \
+ && hg up 1.24.0 \
&& NCPU="$(getconf _NPROCESSORS_ONLN)" \
&& DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \
&& CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \
diff --git a/pkg/docker/Dockerfile.php8.0 b/pkg/docker/Dockerfile.php8.0
index 4534f9b5..02db27cf 100644
--- a/pkg/docker/Dockerfile.php8.0
+++ b/pkg/docker/Dockerfile.php8.0
@@ -8,7 +8,7 @@ RUN set -ex \
&& mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \
&& hg clone https://hg.nginx.org/unit \
&& cd unit \
- && hg up 1.23.0 \
+ && hg up 1.24.0 \
&& NCPU="$(getconf _NPROCESSORS_ONLN)" \
&& DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \
&& CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \
diff --git a/pkg/docker/Dockerfile.python3.9 b/pkg/docker/Dockerfile.python3.9
index e8650d44..44472a12 100644
--- a/pkg/docker/Dockerfile.python3.9
+++ b/pkg/docker/Dockerfile.python3.9
@@ -8,7 +8,7 @@ RUN set -ex \
&& mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \
&& hg clone https://hg.nginx.org/unit \
&& cd unit \
- && hg up 1.23.0 \
+ && hg up 1.24.0 \
&& NCPU="$(getconf _NPROCESSORS_ONLN)" \
&& DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \
&& CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \
diff --git a/pkg/docker/Dockerfile.ruby2.7 b/pkg/docker/Dockerfile.ruby2.7
index aa8cdb3f..7875c470 100644
--- a/pkg/docker/Dockerfile.ruby2.7
+++ b/pkg/docker/Dockerfile.ruby2.7
@@ -8,7 +8,7 @@ RUN set -ex \
&& mkdir -p /usr/lib/unit/modules /usr/lib/unit/debug-modules \
&& hg clone https://hg.nginx.org/unit \
&& cd unit \
- && hg up 1.23.0 \
+ && hg up 1.24.0 \
&& NCPU="$(getconf _NPROCESSORS_ONLN)" \
&& DEB_HOST_MULTIARCH="$(dpkg-architecture -q DEB_HOST_MULTIARCH)" \
&& CC_OPT="$(DEB_BUILD_MAINT_OPTIONS="hardening=+all,-pie" DEB_CFLAGS_MAINT_APPEND="-Wp,-D_FORTIFY_SOURCE=2 -fPIC" dpkg-buildflags --get CFLAGS)" \
diff --git a/pkg/rpm/Makefile b/pkg/rpm/Makefile
index 75327c49..e67846cf 100644
--- a/pkg/rpm/Makefile
+++ b/pkg/rpm/Makefile
@@ -8,6 +8,8 @@ DEFAULT_RELEASE := 1
VERSION ?= $(DEFAULT_VERSION)
RELEASE ?= $(DEFAULT_RELEASE)
+PACKAGE_VENDOR = NGINX Packaging <nginx-packaging@f5.com>
+
ifeq ($(shell test `rpm --eval '0%{?rhel} -eq 6 -a 0%{?amzn} -eq 0'`; echo $$?), 0)
OSVER = centos6
else ifeq ($(shell test `rpm --eval '0%{?rhel} -eq 7 -a 0%{?amzn} -eq 0'`; echo $$?), 0)
@@ -41,8 +43,12 @@ endif
ifneq (,$(findstring $(OSVER),opensuse-leap opensuse-tumbleweed sles))
BUILD_DEPENDS_unit += libxml2-tools libxslt1 libopenssl-devel
else
+ifneq (,$(findstring $(OSVER),amazonlinux2))
+BUILD_DEPENDS_unit += libxml2 libxslt openssl11-devel
+else
BUILD_DEPENDS_unit += libxml2 libxslt openssl-devel
endif
+endif
BUILD_DEPENDS = $(BUILD_DEPENDS_unit)
@@ -201,6 +207,7 @@ rpmbuild/SPECS/unit.spec: unit.spec.in ../../docs/changes.xml | rpmbuild/SPECS
sed -e "s#%%VERSION%%#$(VERSION)#g" \
-e "s#%%RELEASE%%#$(RELEASE)#g" \
-e "s#%%CONFIGURE_ARGS%%#$(CONFIGURE_ARGS)#g" \
+ -e "s#%%PACKAGE_VENDOR%%#$(PACKAGE_VENDOR)#g" \
> rpmbuild/SPECS/unit.spec
cd ../../docs && make ../build/unit.rpm-changelog
ifneq ($(DEFAULT_VERSION)$(DEFAULT_RELEASE), $(VERSION)$(RELEASE))
@@ -248,6 +255,7 @@ rpmbuild/SPECS/unit-%.spec: unit.module.spec.in ../../docs/changes.xml | rpmbuil
-e "s#%%RELEASE%%#$(MODULE_RELEASE_$*)#g" \
-e "s#%%UNIT_VERSION%%#$(VERSION)#g" \
-e "s#%%UNIT_RELEASE%%#$(RELEASE)#g" \
+ -e "s#%%PACKAGE_VENDOR%%#$(PACKAGE_VENDOR)#g" \
-e "s#%%MODULE_SOURCES%%#$${sources}#g" \
-e "s#%%CONFIGURE_ARGS%%#$(CONFIGURE_ARGS)#g" \
-e "s#%%MODULE_CONFARGS%%#$(MODULE_CONFARGS_$*)#g" \
diff --git a/pkg/rpm/unit.module.spec.in b/pkg/rpm/unit.module.spec.in
index 39083e66..4f096c73 100644
--- a/pkg/rpm/unit.module.spec.in
+++ b/pkg/rpm/unit.module.spec.in
@@ -9,8 +9,12 @@
%if 0%{?rhel}%{?fedora}
BuildRequires: gcc
+%if 0%{?amzn2}
+BuildRequires: openssl11-devel
+%else
BuildRequires: openssl-devel
%endif
+%endif
%if 0%{?suse_version} >= 1315
BuildRequires: libopenssl-devel
@@ -28,9 +32,8 @@ Summary: %%SUMMARY%%
Version: %%VERSION%%
Release: %%RELEASE%%%{?dist}.ngx
License: ASL 2.0
-Vendor: Nginx Software, Inc.
+Vendor: %%PACKAGE_VENDOR%%
URL: https://unit.nginx.org/
-Packager: Nginx Software, Inc. <https://www.nginx.com>
Group: System Environment/Daemons
Source0: unit-%{version}.tar.gz
diff --git a/pkg/rpm/unit.spec.in b/pkg/rpm/unit.spec.in
index 3a148b9d..b35b8998 100644
--- a/pkg/rpm/unit.spec.in
+++ b/pkg/rpm/unit.spec.in
@@ -4,8 +4,12 @@
%if 0%{?rhel}%{?fedora}
BuildRequires: gcc
+%if 0%{?amzn2}
+BuildRequires: openssl11-devel
+%else
BuildRequires: openssl-devel
%endif
+%endif
%if 0%{?rhel}
%if 0%{?amzn} == 0
@@ -29,9 +33,8 @@ Summary: NGINX Unit
Version: %%VERSION%%
Release: %%RELEASE%%%{?dist}.ngx
License: ASL 2.0
-Vendor: Nginx Software, Inc.
+Vendor: %%PACKAGE_VENDOR%%
URL: https://unit.nginx.org/
-Packager: Nginx Software, Inc. <https://www.nginx.com>
Group: System Environment/Daemons
Source0: unit-%{version}.tar.gz
@@ -203,7 +206,7 @@ BANNER
%dir %{_libdir}/unit/modules
%dir %{_libdir}/unit/debug-modules
%dir %{_sharedstatedir}/unit
-%dir %attr(0700,root,root) %{_localstatedir}/log/unit
+%dir %attr(0755,root,root) %{_localstatedir}/log/unit
%config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
%{_mandir}/man8/unitd.8*
diff --git a/src/nodejs/unit-http/http.js b/src/nodejs/unit-http/http.js
index 3a25fa2f..d298a35f 100644
--- a/src/nodejs/unit-http/http.js
+++ b/src/nodejs/unit-http/http.js
@@ -5,19 +5,22 @@
'use strict';
-const server = require('unit-http/http_server');
-
-const { Server } = server;
+const {
+ Server,
+ ServerRequest,
+ ServerResponse,
+} = require('./http_server');
function createServer (requestHandler) {
return new Server(requestHandler);
}
+const http = require("http")
module.exports = {
+ ...http,
Server,
- STATUS_CODES: server.STATUS_CODES,
createServer,
- IncomingMessage: server.ServerRequest,
- ServerResponse: server.ServerResponse
+ IncomingMessage: ServerRequest,
+ ServerResponse,
};
diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js
index e59296ae..89964ec3 100644
--- a/src/nodejs/unit-http/http_server.js
+++ b/src/nodejs/unit-http/http_server.js
@@ -444,17 +444,30 @@ Server.prototype.setTimeout = function setTimeout(msecs, callback) {
Server.prototype.listen = function (...args) {
this.unit.listen();
- const cb = args.pop();
-
- if (typeof cb === 'function') {
- this.once('listening', cb);
+ if (typeof args[args.length - 1] === 'function') {
+ this.once('listening', args[args.length - 1]);
}
- this.emit('listening');
+ /*
+ * Some express.js apps use the returned server object inside the listening
+ * callback, so we timeout the listening event to occur after this function
+ * returns.
+ */
+ setImmediate(function() {
+ this.emit('listening')
+ }.bind(this))
return this;
};
+Server.prototype.address = function () {
+ return {
+ family: "IPv4",
+ address: "127.0.0.1",
+ port: 80
+ }
+}
+
Server.prototype.emit_request = function (req, res) {
if (req._websocket_handshake && this._upgradeListenerCount > 0) {
this.emit('upgrade', req, req.socket);
@@ -530,7 +543,6 @@ function connectionListener(socket) {
}
module.exports = {
- STATUS_CODES: http.STATUS_CODES,
Server,
ServerResponse,
ServerRequest,
diff --git a/src/nodejs/unit-http/loader.js b/src/nodejs/unit-http/loader.js
new file mode 100644
index 00000000..e5aa3558
--- /dev/null
+++ b/src/nodejs/unit-http/loader.js
@@ -0,0 +1,27 @@
+// can only be ran as part of a --require param on the node process
+if (module.parent && module.parent.id === "internal/preload") {
+ const { Module } = require("module")
+
+ if (!Module.prototype.require.__unit_loader) {
+ const http = require("./http")
+ const websocket = require("./websocket")
+
+ const original = Module.prototype.require;
+
+ Module.prototype.require = function (id) {
+ switch(id) {
+ case "http":
+ case "unit-http":
+ return http
+
+ case "websocket":
+ case "unit-http/websocket":
+ return websocket
+ }
+
+ return original.apply(this, arguments);
+ }
+
+ Module.prototype.require.__unit_loader = true;
+ }
+}
diff --git a/src/nodejs/unit-http/loader.mjs b/src/nodejs/unit-http/loader.mjs
new file mode 100644
index 00000000..067d63d4
--- /dev/null
+++ b/src/nodejs/unit-http/loader.mjs
@@ -0,0 +1,18 @@
+// must be ran as part of a --loader or --experimental-loader param
+export async function resolve(specifier, context, defaultResolver) {
+ switch (specifier) {
+ case "websocket":
+ return {
+ url: new URL("./websocket.js", import.meta.url).href,
+ format: "cjs"
+ }
+
+ case "http":
+ return {
+ url: new URL("./http.js", import.meta.url).href,
+ format: "cjs"
+ }
+ }
+
+ return defaultResolver(specifier, context, defaultResolver)
+}
diff --git a/src/nxt_application.h b/src/nxt_application.h
index 632c5632..45e7fa48 100644
--- a/src/nxt_application.h
+++ b/src/nxt_application.h
@@ -54,6 +54,7 @@ typedef struct {
nxt_str_t protocol;
uint32_t threads;
uint32_t thread_stack_size;
+ nxt_conf_value_t *targets;
} nxt_python_app_conf_t;
diff --git a/src/nxt_cert.c b/src/nxt_cert.c
index 3cdb69c1..1806bc19 100644
--- a/src/nxt_cert.c
+++ b/src/nxt_cert.c
@@ -48,6 +48,7 @@ static nxt_conf_value_t *nxt_cert_name_details(nxt_mp_t *mp, X509 *x509,
nxt_bool_t issuer);
static nxt_conf_value_t *nxt_cert_alt_names_details(nxt_mp_t *mp,
STACK_OF(GENERAL_NAME) *alt_names);
+static void nxt_cert_buf_completion(nxt_task_t *task, void *obj, void *data);
static nxt_lvlhsh_t nxt_cert_info;
@@ -1073,6 +1074,9 @@ nxt_cert_store_get(nxt_task_t *task, nxt_str_t *name, nxt_mp_t *mp,
goto fail;
}
+ nxt_mp_retain(mp);
+ b->completion_handler = nxt_cert_buf_completion;
+
nxt_buf_cpystr(b, name);
*b->mem.free++ = '\0';
@@ -1102,6 +1106,21 @@ fail:
}
+static void
+nxt_cert_buf_completion(nxt_task_t *task, void *obj, void *data)
+{
+ nxt_mp_t *mp;
+ nxt_buf_t *b;
+
+ b = obj;
+ mp = b->data;
+ nxt_assert(b->next == NULL);
+
+ nxt_mp_free(mp, b);
+ nxt_mp_release(mp);
+}
+
+
void
nxt_cert_store_get_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg)
{
diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c
index 8c5d1ec7..06ae2847 100644
--- a/src/nxt_conf_validation.c
+++ b/src/nxt_conf_validation.c
@@ -75,6 +75,8 @@ static nxt_int_t nxt_conf_vldt_error(nxt_conf_validation_t *vldt,
const char *fmt, ...);
static nxt_int_t nxt_conf_vldt_var(nxt_conf_validation_t *vldt,
const char *option, nxt_str_t *value);
+nxt_inline nxt_int_t nxt_conf_vldt_unsupported(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_mtypes(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
@@ -87,6 +89,10 @@ static nxt_int_t nxt_conf_vldt_listener(nxt_conf_validation_t *vldt,
#if (NXT_TLS)
static nxt_int_t nxt_conf_vldt_certificate(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
+#if (NXT_HAVE_OPENSSL_CONF_CMD)
+static nxt_int_t nxt_conf_vldt_object_conf_commands(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value, void *data);
+#endif
static nxt_int_t nxt_conf_vldt_certificate_element(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value);
#endif
@@ -98,6 +104,8 @@ static nxt_int_t nxt_conf_vldt_return(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_proxy(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
+static nxt_int_t nxt_conf_vldt_python(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_python_path(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_python_path_element(nxt_conf_validation_t *vldt,
@@ -154,16 +162,16 @@ static nxt_int_t nxt_conf_vldt_array_iterator(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_environment(nxt_conf_validation_t *vldt,
nxt_str_t *name, nxt_conf_value_t *value);
+static nxt_int_t nxt_conf_vldt_targets_exclusive(
+ nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data);
+static nxt_int_t nxt_conf_vldt_targets(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value, void *data);
+static nxt_int_t nxt_conf_vldt_target(nxt_conf_validation_t *vldt,
+ nxt_str_t *name, nxt_conf_value_t *value);
static nxt_int_t nxt_conf_vldt_argument(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value);
static nxt_int_t nxt_conf_vldt_php(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
-static nxt_int_t nxt_conf_vldt_php_targets_exclusive(
- nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data);
-static nxt_int_t nxt_conf_vldt_php_targets(nxt_conf_validation_t *vldt,
- nxt_conf_value_t *value, void *data);
-static nxt_int_t nxt_conf_vldt_php_target(nxt_conf_validation_t *vldt,
- nxt_str_t *name, nxt_conf_value_t *value);
static nxt_int_t nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt,
nxt_str_t *name, nxt_conf_value_t *value);
static nxt_int_t nxt_conf_vldt_java_classpath(nxt_conf_validation_t *vldt,
@@ -200,8 +208,10 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_static_members[];
static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[];
#endif
static nxt_conf_vldt_object_t nxt_conf_vldt_match_members[];
+static nxt_conf_vldt_object_t nxt_conf_vldt_python_target_members[];
static nxt_conf_vldt_object_t nxt_conf_vldt_php_common_members[];
static nxt_conf_vldt_object_t nxt_conf_vldt_php_options_members[];
+static nxt_conf_vldt_object_t nxt_conf_vldt_php_target_members[];
static nxt_conf_vldt_object_t nxt_conf_vldt_common_members[];
static nxt_conf_vldt_object_t nxt_conf_vldt_app_limits_members[];
static nxt_conf_vldt_object_t nxt_conf_vldt_app_processes_members[];
@@ -357,7 +367,17 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[] = {
{
.name = nxt_string("certificate"),
.type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY,
+ .flags = NXT_CONF_VLDT_REQUIRED,
.validator = nxt_conf_vldt_certificate,
+ }, {
+ .name = nxt_string("conf_commands"),
+ .type = NXT_CONF_VLDT_OBJECT,
+#if (NXT_HAVE_OPENSSL_CONF_CMD)
+ .validator = nxt_conf_vldt_object_conf_commands,
+#else
+ .validator = nxt_conf_vldt_unsupported,
+ .u.string = "conf_commands",
+#endif
},
NXT_CONF_VLDT_END
@@ -455,9 +475,34 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_share_action_members[] = {
.name = nxt_string("share"),
.type = NXT_CONF_VLDT_STRING,
}, {
+ .name = nxt_string("types"),
+ .type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY,
+ .validator = nxt_conf_vldt_match_patterns,
+ }, {
.name = nxt_string("fallback"),
.type = NXT_CONF_VLDT_OBJECT,
.validator = nxt_conf_vldt_action,
+ }, {
+ .name = nxt_string("chroot"),
+ .type = NXT_CONF_VLDT_STRING,
+#if !(NXT_HAVE_OPENAT2)
+ .validator = nxt_conf_vldt_unsupported,
+ .u.string = "chroot",
+#endif
+ }, {
+ .name = nxt_string("follow_symlinks"),
+ .type = NXT_CONF_VLDT_BOOLEAN,
+#if !(NXT_HAVE_OPENAT2)
+ .validator = nxt_conf_vldt_unsupported,
+ .u.string = "follow_symlinks",
+#endif
+ }, {
+ .name = nxt_string("traverse_mounts"),
+ .type = NXT_CONF_VLDT_BOOLEAN,
+#if !(NXT_HAVE_OPENAT2)
+ .validator = nxt_conf_vldt_unsupported,
+ .u.string = "traverse_mounts",
+#endif
},
NXT_CONF_VLDT_END
@@ -491,7 +536,7 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_external_members[] = {
};
-static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = {
+static nxt_conf_vldt_object_t nxt_conf_vldt_python_common_members[] = {
{
.name = nxt_string("home"),
.type = NXT_CONF_VLDT_STRING,
@@ -500,13 +545,6 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = {
.type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY,
.validator = nxt_conf_vldt_python_path,
}, {
- .name = nxt_string("module"),
- .type = NXT_CONF_VLDT_STRING,
- .flags = NXT_CONF_VLDT_REQUIRED,
- }, {
- .name = nxt_string("callable"),
- .type = NXT_CONF_VLDT_STRING,
- }, {
.name = nxt_string("protocol"),
.type = NXT_CONF_VLDT_STRING,
.validator = nxt_conf_vldt_python_protocol,
@@ -523,27 +561,77 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = {
NXT_CONF_VLDT_NEXT(nxt_conf_vldt_common_members)
};
+static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = {
+ {
+ .name = nxt_string("module"),
+ .type = NXT_CONF_VLDT_STRING,
+ .validator = nxt_conf_vldt_targets_exclusive,
+ .u.string = "module",
+ }, {
+ .name = nxt_string("callable"),
+ .type = NXT_CONF_VLDT_STRING,
+ .validator = nxt_conf_vldt_targets_exclusive,
+ .u.string = "callable",
+ }, {
+ .name = nxt_string("targets"),
+ .type = NXT_CONF_VLDT_OBJECT,
+ .validator = nxt_conf_vldt_targets,
+ .u.members = nxt_conf_vldt_python_target_members
+ },
+
+ NXT_CONF_VLDT_NEXT(nxt_conf_vldt_python_common_members)
+};
+
+
+static nxt_conf_vldt_object_t nxt_conf_vldt_python_target_members[] = {
+ {
+ .name = nxt_string("module"),
+ .type = NXT_CONF_VLDT_STRING,
+ .flags = NXT_CONF_VLDT_REQUIRED,
+ }, {
+ .name = nxt_string("callable"),
+ .type = NXT_CONF_VLDT_STRING,
+ },
+
+ NXT_CONF_VLDT_END
+};
+
+
+static nxt_conf_vldt_object_t nxt_conf_vldt_python_notargets_members[] = {
+ {
+ .name = nxt_string("module"),
+ .type = NXT_CONF_VLDT_STRING,
+ .flags = NXT_CONF_VLDT_REQUIRED,
+ }, {
+ .name = nxt_string("callable"),
+ .type = NXT_CONF_VLDT_STRING,
+ },
+
+ NXT_CONF_VLDT_NEXT(nxt_conf_vldt_python_common_members)
+};
+
static nxt_conf_vldt_object_t nxt_conf_vldt_php_members[] = {
{
.name = nxt_string("root"),
.type = NXT_CONF_VLDT_ANY_TYPE,
- .validator = nxt_conf_vldt_php_targets_exclusive,
+ .validator = nxt_conf_vldt_targets_exclusive,
.u.string = "root",
}, {
.name = nxt_string("script"),
.type = NXT_CONF_VLDT_ANY_TYPE,
- .validator = nxt_conf_vldt_php_targets_exclusive,
+ .validator = nxt_conf_vldt_targets_exclusive,
.u.string = "script",
}, {
.name = nxt_string("index"),
.type = NXT_CONF_VLDT_ANY_TYPE,
- .validator = nxt_conf_vldt_php_targets_exclusive,
+ .validator = nxt_conf_vldt_targets_exclusive,
.u.string = "index",
}, {
.name = nxt_string("targets"),
.type = NXT_CONF_VLDT_OBJECT,
- .validator = nxt_conf_vldt_php_targets,
+ .validator = nxt_conf_vldt_targets,
+ .u.members = nxt_conf_vldt_php_target_members
},
NXT_CONF_VLDT_NEXT(nxt_conf_vldt_php_common_members)
@@ -1032,6 +1120,15 @@ nxt_conf_vldt_error(nxt_conf_validation_t *vldt, const char *fmt, ...)
}
+nxt_inline nxt_int_t
+nxt_conf_vldt_unsupported(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
+ void *data)
+{
+ return nxt_conf_vldt_error(vldt, "Unit is built without the \"%s\" "
+ "option support.", data);
+}
+
+
static nxt_int_t
nxt_conf_vldt_var(nxt_conf_validation_t *vldt, const char *option,
nxt_str_t *value)
@@ -1137,7 +1234,7 @@ nxt_conf_vldt_mtypes_extension(nxt_conf_validation_t *vldt,
dup_type = nxt_http_static_mtypes_hash_find(&ctx->hash, &ext);
- if (dup_type != NULL) {
+ if (dup_type->length != 0) {
return nxt_conf_vldt_error(vldt, "The \"%V\" file extension has been "
"declared for \"%V\" and \"%V\" "
"MIME types at the same time.",
@@ -1384,6 +1481,25 @@ nxt_conf_vldt_proxy(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
static nxt_int_t
+nxt_conf_vldt_python(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
+ void *data)
+{
+ nxt_conf_value_t *targets;
+
+ static nxt_str_t targets_str = nxt_string("targets");
+
+ targets = nxt_conf_get_object_member(value, &targets_str, NULL);
+
+ if (targets != NULL) {
+ return nxt_conf_vldt_object(vldt, value, nxt_conf_vldt_python_members);
+ }
+
+ return nxt_conf_vldt_object(vldt, value,
+ nxt_conf_vldt_python_notargets_members);
+}
+
+
+static nxt_int_t
nxt_conf_vldt_python_path(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data)
{
@@ -1869,6 +1985,38 @@ nxt_conf_vldt_certificate_element(nxt_conf_validation_t *vldt,
return NXT_OK;
}
+
+#if (NXT_HAVE_OPENSSL_CONF_CMD)
+
+static nxt_int_t
+nxt_conf_vldt_object_conf_commands(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value, void *data)
+{
+ uint32_t index;
+ nxt_int_t ret;
+ nxt_str_t name;
+ nxt_conf_value_t *member;
+
+ index = 0;
+
+ for ( ;; ) {
+ member = nxt_conf_next_object_member(value, &name, &index);
+
+ if (member == NULL) {
+ break;
+ }
+
+ ret = nxt_conf_vldt_type(vldt, &name, member, NXT_CONF_VLDT_STRING);
+ if (ret != NXT_OK) {
+ return ret;
+ }
+ }
+
+ return NXT_OK;
+}
+
+#endif
+
#endif
@@ -1923,7 +2071,7 @@ nxt_conf_vldt_app(nxt_conf_validation_t *vldt, nxt_str_t *name,
} types[] = {
{ nxt_conf_vldt_object, nxt_conf_vldt_external_members },
- { nxt_conf_vldt_object, nxt_conf_vldt_python_members },
+ { nxt_conf_vldt_python, NULL },
{ nxt_conf_vldt_php, NULL },
{ nxt_conf_vldt_object, nxt_conf_vldt_perl_members },
{ nxt_conf_vldt_object, nxt_conf_vldt_ruby_members },
@@ -2250,6 +2398,57 @@ nxt_conf_vldt_environment(nxt_conf_validation_t *vldt, nxt_str_t *name,
static nxt_int_t
+nxt_conf_vldt_targets_exclusive(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value, void *data)
+{
+ return nxt_conf_vldt_error(vldt, "The \"%s\" option is mutually exclusive "
+ "with the \"targets\" object.", data);
+}
+
+
+static nxt_int_t
+nxt_conf_vldt_targets(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
+ void *data)
+{
+ nxt_int_t ret;
+ nxt_uint_t n;
+
+ n = nxt_conf_object_members_count(value);
+
+ if (n > 254) {
+ return nxt_conf_vldt_error(vldt, "The \"targets\" object must not "
+ "contain more than 254 members.");
+ }
+
+ vldt->ctx = data;
+
+ ret = nxt_conf_vldt_object_iterator(vldt, value, &nxt_conf_vldt_target);
+
+ vldt->ctx = NULL;
+
+ return ret;
+}
+
+
+static nxt_int_t
+nxt_conf_vldt_target(nxt_conf_validation_t *vldt, nxt_str_t *name,
+ nxt_conf_value_t *value)
+{
+ if (name->length == 0) {
+ return nxt_conf_vldt_error(vldt,
+ "The target name must not be empty.");
+ }
+
+ if (nxt_conf_type(value) != NXT_CONF_OBJECT) {
+ return nxt_conf_vldt_error(vldt, "The \"%V\" target must be "
+ "an object.", name);
+ }
+
+ return nxt_conf_vldt_object(vldt, value, vldt->ctx);
+}
+
+
+static nxt_int_t
nxt_conf_vldt_clone_namespaces(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data)
{
@@ -2417,51 +2616,6 @@ nxt_conf_vldt_php(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
static nxt_int_t
-nxt_conf_vldt_php_targets_exclusive(nxt_conf_validation_t *vldt,
- nxt_conf_value_t *value, void *data)
-{
- return nxt_conf_vldt_error(vldt, "The \"%s\" option is mutually exclusive "
- "with the \"targets\" object.", data);
-}
-
-
-static nxt_int_t
-nxt_conf_vldt_php_targets(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
- void *data)
-{
- nxt_uint_t n;
-
- n = nxt_conf_object_members_count(value);
-
- if (n > 254) {
- return nxt_conf_vldt_error(vldt, "The \"targets\" object must not "
- "contain more than 254 members.");
- }
-
- return nxt_conf_vldt_object_iterator(vldt, value,
- &nxt_conf_vldt_php_target);
-}
-
-
-static nxt_int_t
-nxt_conf_vldt_php_target(nxt_conf_validation_t *vldt, nxt_str_t *name,
- nxt_conf_value_t *value)
-{
- if (name->length == 0) {
- return nxt_conf_vldt_error(vldt,
- "The PHP target name must not be empty.");
- }
-
- if (nxt_conf_type(value) != NXT_CONF_OBJECT) {
- return nxt_conf_vldt_error(vldt, "The \"%V\" PHP target must be "
- "an object.", name);
- }
-
- return nxt_conf_vldt_object(vldt, value, &nxt_conf_vldt_php_target_members);
-}
-
-
-static nxt_int_t
nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name,
nxt_conf_value_t *value)
{
diff --git a/src/nxt_errno.h b/src/nxt_errno.h
index 40bcfa3f..ec700537 100644
--- a/src/nxt_errno.h
+++ b/src/nxt_errno.h
@@ -22,6 +22,7 @@ typedef int nxt_err_t;
#define NXT_EACCES EACCES
#define NXT_EBUSY EBUSY
#define NXT_EEXIST EEXIST
+#define NXT_ELOOP ELOOP
#define NXT_EXDEV EXDEV
#define NXT_ENOTDIR ENOTDIR
#define NXT_EISDIR EISDIR
diff --git a/src/nxt_file.c b/src/nxt_file.c
index a9595dd9..5d38d57e 100644
--- a/src/nxt_file.c
+++ b/src/nxt_file.c
@@ -42,6 +42,50 @@ nxt_file_open(nxt_task_t *task, nxt_file_t *file, nxt_uint_t mode,
}
+#if (NXT_HAVE_OPENAT2)
+
+nxt_int_t
+nxt_file_openat2(nxt_task_t *task, nxt_file_t *file, nxt_uint_t mode,
+ nxt_uint_t create, nxt_file_access_t access, nxt_fd_t dfd,
+ nxt_uint_t resolve)
+{
+ struct open_how how;
+
+ nxt_memzero(&how, sizeof(how));
+
+ /* O_NONBLOCK is to prevent blocking on FIFOs, special devices, etc. */
+ mode |= (O_NONBLOCK | create);
+
+ how.flags = mode;
+ how.mode = access;
+ how.resolve = resolve;
+
+ file->fd = syscall(SYS_openat2, dfd, file->name, &how, sizeof(how));
+
+ file->error = (file->fd == -1) ? nxt_errno : 0;
+
+#if (NXT_DEBUG)
+ nxt_thread_time_update(task->thread);
+#endif
+
+ nxt_debug(task, "openat2(%FD, \"%FN\"): %FD err:%d", dfd, file->name,
+ file->fd, file->error);
+
+ if (file->fd != -1) {
+ return NXT_OK;
+ }
+
+ if (file->log_level != 0) {
+ nxt_log(task, file->log_level, "openat2(%FD, \"%FN\") failed %E", dfd,
+ file->name, file->error);
+ }
+
+ return NXT_ERROR;
+}
+
+#endif
+
+
void
nxt_file_close(nxt_task_t *task, nxt_file_t *file)
{
diff --git a/src/nxt_file.h b/src/nxt_file.h
index 4f56e746..4846305b 100644
--- a/src/nxt_file.h
+++ b/src/nxt_file.h
@@ -109,6 +109,12 @@ typedef struct {
NXT_EXPORT nxt_int_t nxt_file_open(nxt_task_t *task, nxt_file_t *file,
nxt_uint_t mode, nxt_uint_t create, nxt_file_access_t access);
+#if (NXT_HAVE_OPENAT2)
+NXT_EXPORT nxt_int_t nxt_file_openat2(nxt_task_t *task, nxt_file_t *file,
+ nxt_uint_t mode, nxt_uint_t create, nxt_file_access_t access, nxt_fd_t dfd,
+ nxt_uint_t resolve);
+#endif
+
/* The file open access modes. */
#define NXT_FILE_RDONLY O_RDONLY
@@ -116,6 +122,32 @@ NXT_EXPORT nxt_int_t nxt_file_open(nxt_task_t *task, nxt_file_t *file,
#define NXT_FILE_RDWR O_RDWR
#define NXT_FILE_APPEND (O_WRONLY | O_APPEND)
+#if (NXT_HAVE_OPENAT2)
+
+#if defined(O_DIRECTORY)
+#define NXT_FILE_DIRECTORY O_DIRECTORY
+#else
+#define NXT_FILE_DIRECTORY 0
+#endif
+
+#if defined(O_SEARCH)
+#define NXT_FILE_SEARCH (O_SEARCH|NXT_FILE_DIRECTORY)
+
+#elif defined(O_EXEC)
+#define NXT_FILE_SEARCH (O_EXEC|NXT_FILE_DIRECTORY)
+
+#else
+/*
+ * O_PATH is used in combination with O_RDONLY. The last one is ignored
+ * if O_PATH is used, but it allows Unit to not fail when it was built on
+ * modern system (i.e. glibc 2.14+) and run with a kernel older than 2.6.39.
+ * Then O_PATH is unknown to the kernel and ignored, while O_RDONLY is used.
+ */
+#define NXT_FILE_SEARCH (O_PATH|O_RDONLY|NXT_FILE_DIRECTORY)
+#endif
+
+#endif /* NXT_HAVE_OPENAT2 */
+
/* The file creation modes. */
#define NXT_FILE_CREATE_OR_OPEN O_CREAT
#define NXT_FILE_OPEN 0
diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c
index d3da6942..d3da6942 100644..100755
--- a/src/nxt_h1proto.c
+++ b/src/nxt_h1proto.c
diff --git a/src/nxt_http.h b/src/nxt_http.h
index e30bfeb4..f82d837e 100644
--- a/src/nxt_http.h
+++ b/src/nxt_http.h
@@ -197,7 +197,8 @@ struct nxt_http_request_s {
};
-typedef struct nxt_http_route_s nxt_http_route_t;
+typedef struct nxt_http_route_s nxt_http_route_t;
+typedef struct nxt_http_route_rule_s nxt_http_route_rule_t;
struct nxt_http_action_s {
@@ -206,16 +207,25 @@ struct nxt_http_action_s {
nxt_http_action_t *action);
union {
nxt_http_route_t *route;
- nxt_app_t *application;
- nxt_http_action_t *fallback;
nxt_upstream_t *upstream;
uint32_t upstream_number;
nxt_http_status_t return_code;
nxt_var_t *var;
+
+ struct {
+ nxt_app_t *application;
+ nxt_int_t target;
+ } app;
+
+ struct {
+ nxt_str_t chroot;
+ nxt_uint_t resolve;
+ nxt_http_route_rule_t *types;
+ nxt_http_action_t *fallback;
+ } share;
} u;
nxt_str_t name;
- nxt_int_t target;
};
@@ -298,6 +308,8 @@ nxt_int_t nxt_http_pass_segments(nxt_mp_t *mp, nxt_str_t *pass,
nxt_str_t *segments, nxt_uint_t n);
nxt_http_action_t *nxt_http_pass_application(nxt_task_t *task,
nxt_router_conf_t *rtcf, nxt_str_t *name);
+nxt_int_t nxt_http_route_test_rule(nxt_http_request_t *r,
+ nxt_http_route_rule_t *rule, u_char *start, size_t length);
nxt_int_t nxt_upstreams_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
nxt_conf_value_t *conf);
diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c
index 650c1a89..779cfcf8 100644
--- a/src/nxt_http_request.c
+++ b/src/nxt_http_request.c
@@ -348,9 +348,9 @@ nxt_http_application_handler(nxt_task_t *task, nxt_http_request_t *r,
nxt_str_set(&r->server_name, "localhost");
}
- r->app_target = action->target;
+ r->app_target = action->u.app.target;
- nxt_router_process_http_request(task, r, action->u.application);
+ nxt_router_process_http_request(task, r, action->u.app.application);
return NULL;
}
diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c
index 28545fc9..15b85544 100644
--- a/src/nxt_http_route.c
+++ b/src/nxt_http_route.c
@@ -50,8 +50,12 @@ typedef struct {
nxt_conf_value_t *pass;
nxt_conf_value_t *ret;
nxt_str_t location;
- nxt_conf_value_t *share;
nxt_conf_value_t *proxy;
+ nxt_conf_value_t *share;
+ nxt_str_t chroot;
+ nxt_conf_value_t *follow_symlinks;
+ nxt_conf_value_t *traverse_mounts;
+ nxt_conf_value_t *types;
nxt_conf_value_t *fallback;
} nxt_http_route_action_conf_t;
@@ -112,7 +116,7 @@ typedef struct {
} nxt_http_cookie_t;
-typedef struct {
+struct nxt_http_route_rule_s {
/* The object must be the first field. */
nxt_http_route_object_t object:8;
uint32_t items;
@@ -128,7 +132,7 @@ typedef struct {
} u;
nxt_http_route_pattern_t pattern[0];
-} nxt_http_route_rule_t;
+};
typedef struct {
@@ -195,8 +199,9 @@ static nxt_http_route_t *nxt_http_route_create(nxt_task_t *task,
nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv);
static nxt_http_route_match_t *nxt_http_route_match_create(nxt_task_t *task,
nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv);
-static nxt_int_t nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf,
- nxt_conf_value_t *cv, nxt_http_action_t *action);
+static nxt_int_t nxt_http_route_action_create(nxt_task_t *task,
+ nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv,
+ nxt_http_action_t *action);
static nxt_http_route_table_t *nxt_http_route_table_create(nxt_task_t *task,
nxt_mp_t *mp, nxt_conf_value_t *table_cv, nxt_http_route_object_t object,
nxt_bool_t case_sensitive, nxt_http_route_encoding_t encoding);
@@ -274,8 +279,6 @@ static nxt_http_name_value_t *nxt_http_route_cookie(nxt_array_t *array,
u_char *name, size_t name_length, u_char *start, u_char *end);
static nxt_int_t nxt_http_route_test_cookie(nxt_http_request_t *r,
nxt_http_route_rule_t *rule, nxt_array_t *array);
-static nxt_int_t nxt_http_route_test_rule(nxt_http_request_t *r,
- nxt_http_route_rule_t *rule, u_char *start, size_t length);
static nxt_int_t nxt_http_route_pattern(nxt_http_request_t *r,
nxt_http_route_pattern_t *pattern, u_char *start, size_t length);
static nxt_int_t nxt_http_route_memcmp(u_char *start, u_char *test,
@@ -457,7 +460,7 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
match_conf = nxt_conf_get_path(cv, &match_path);
n = (match_conf != NULL) ? nxt_conf_object_members_count(match_conf) : 0;
- size = sizeof(nxt_http_route_match_t) + n * sizeof(nxt_http_route_rule_t *);
+ size = sizeof(nxt_http_route_match_t) + n * sizeof(nxt_http_route_test_t *);
mp = tmcf->router_conf->mem_pool;
@@ -473,7 +476,7 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
return NULL;
}
- ret = nxt_http_route_action_create(tmcf, action_conf, &match->action);
+ ret = nxt_http_route_action_create(task, tmcf, action_conf, &match->action);
if (nxt_slow_path(ret != NXT_OK)) {
return NULL;
}
@@ -627,14 +630,34 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = {
offsetof(nxt_http_route_action_conf_t, location)
},
{
+ nxt_string("proxy"),
+ NXT_CONF_MAP_PTR,
+ offsetof(nxt_http_route_action_conf_t, proxy)
+ },
+ {
nxt_string("share"),
NXT_CONF_MAP_PTR,
offsetof(nxt_http_route_action_conf_t, share)
},
{
- nxt_string("proxy"),
+ nxt_string("chroot"),
+ NXT_CONF_MAP_STR,
+ offsetof(nxt_http_route_action_conf_t, chroot)
+ },
+ {
+ nxt_string("follow_symlinks"),
NXT_CONF_MAP_PTR,
- offsetof(nxt_http_route_action_conf_t, proxy)
+ offsetof(nxt_http_route_action_conf_t, follow_symlinks)
+ },
+ {
+ nxt_string("traverse_mounts"),
+ NXT_CONF_MAP_PTR,
+ offsetof(nxt_http_route_action_conf_t, traverse_mounts)
+ },
+ {
+ nxt_string("types"),
+ NXT_CONF_MAP_PTR,
+ offsetof(nxt_http_route_action_conf_t, types)
},
{
nxt_string("fallback"),
@@ -645,14 +668,20 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = {
static nxt_int_t
-nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv,
- nxt_http_action_t *action)
+nxt_http_route_action_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
+ nxt_conf_value_t *cv, nxt_http_action_t *action)
{
+#if (NXT_HAVE_OPENAT2)
+ u_char *p;
+ uint8_t slash;
+ nxt_str_t *chroot;
+#endif
nxt_mp_t *mp;
nxt_int_t ret;
nxt_str_t name, *string;
nxt_uint_t encode;
nxt_conf_value_t *conf;
+ nxt_http_route_rule_t *rule;
nxt_http_route_action_conf_t accf;
nxt_memzero(&accf, sizeof(accf));
@@ -700,14 +729,14 @@ nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv,
return NXT_OK;
}
- conf = accf.pass;
-
if (accf.share != NULL) {
conf = accf.share;
- action->handler = nxt_http_static_handler;
} else if (accf.proxy != NULL) {
conf = accf.proxy;
+
+ } else {
+ conf = accf.pass;
}
nxt_conf_get_string(conf, &name);
@@ -717,14 +746,70 @@ nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv,
return NXT_ERROR;
}
- if (accf.fallback != NULL) {
- action->u.fallback = nxt_mp_alloc(mp, sizeof(nxt_http_action_t));
- if (nxt_slow_path(action->u.fallback == NULL)) {
- return NXT_ERROR;
+ if (accf.share != NULL) {
+ action->handler = nxt_http_static_handler;
+
+#if (NXT_HAVE_OPENAT2)
+ string = &accf.chroot;
+ chroot = &action->u.share.chroot;
+
+ if (string->length > 0) {
+ action->u.share.resolve |= RESOLVE_IN_ROOT;
+
+ slash = (string->start[string->length - 1] != '/');
+
+ chroot->length = string->length + (slash ? 1 : 0);
+
+ chroot->start = nxt_mp_alloc(mp, chroot->length + 1);
+ if (nxt_slow_path(chroot->start == NULL)) {
+ return NXT_ERROR;
+ }
+
+ p = nxt_cpymem(chroot->start, string->start, string->length);
+
+ if (slash) {
+ *p++ = '/';
+ }
+
+ *p = '\0';
+ }
+
+ if (accf.follow_symlinks != NULL
+ && !nxt_conf_get_boolean(accf.follow_symlinks))
+ {
+ action->u.share.resolve |= RESOLVE_NO_SYMLINKS;
+ }
+
+ if (accf.traverse_mounts != NULL
+ && !nxt_conf_get_boolean(accf.traverse_mounts))
+ {
+ action->u.share.resolve |= RESOLVE_NO_XDEV;
+ }
+#endif
+
+ if (accf.types != NULL) {
+ rule = nxt_http_route_rule_create(task, mp, accf.types, 0,
+ NXT_HTTP_ROUTE_PATTERN_LOWCASE,
+ NXT_HTTP_ROUTE_ENCODING_NONE);
+ if (nxt_slow_path(rule == NULL)) {
+ return NXT_ERROR;
+ }
+
+ action->u.share.types = rule;
}
- return nxt_http_route_action_create(tmcf, accf.fallback,
- action->u.fallback);
+ if (accf.fallback != NULL) {
+ action->u.share.fallback = nxt_mp_alloc(mp,
+ sizeof(nxt_http_action_t));
+ if (nxt_slow_path(action->u.share.fallback == NULL)) {
+ return NXT_ERROR;
+ }
+
+ return nxt_http_route_action_create(task, tmcf, accf.fallback,
+ action->u.share.fallback);
+ }
+
+ return NXT_OK;
}
if (accf.proxy != NULL) {
@@ -1411,9 +1496,10 @@ nxt_http_action_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
if (action->handler != NULL) {
if (action->handler == nxt_http_static_handler
- && action->u.fallback != NULL)
+ && action->u.share.fallback != NULL)
{
- return nxt_http_action_resolve(task, tmcf, action->u.fallback);
+ return nxt_http_action_resolve(task, tmcf,
+ action->u.share.fallback);
}
return NXT_OK;
@@ -1533,14 +1619,14 @@ nxt_http_pass_find(nxt_task_t *task, nxt_mp_t *mp, nxt_router_conf_t *rtcf,
}
if (segments[2].length != 0) {
- targets = action->u.application->targets;
+ targets = action->u.app.application->targets;
for (i = 0; !nxt_strstr_eq(&segments[2], &targets[i]); i++);
- action->target = i;
+ action->u.app.target = i;
} else {
- action->target = 0;
+ action->u.app.target = 0;
}
return NXT_OK;
@@ -1678,7 +1764,7 @@ nxt_http_pass_application(nxt_task_t *task, nxt_router_conf_t *rtcf,
(void) nxt_router_listener_application(rtcf, name, action);
- action->target = 0;
+ action->u.app.target = 0;
return action;
}
@@ -2426,7 +2512,7 @@ nxt_http_route_test_cookie(nxt_http_request_t *r,
}
-static nxt_int_t
+nxt_int_t
nxt_http_route_test_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule,
u_char *start, size_t length)
{
diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c
index df2655fc..c8b73fac 100644
--- a/src/nxt_http_static.c
+++ b/src/nxt_http_static.c
@@ -31,15 +31,15 @@ nxt_http_action_t *
nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
nxt_http_action_t *action)
{
- size_t alloc, encode;
- u_char *p;
+ size_t length, encode;
+ u_char *p, *fname;
struct tm tm;
nxt_buf_t *fb;
nxt_int_t ret;
- nxt_str_t index, extension, *mtype;
+ nxt_str_t index, extension, *mtype, *chroot;
nxt_uint_t level;
nxt_bool_t need_body;
- nxt_file_t *f;
+ nxt_file_t *f, file;
nxt_file_info_t fi;
nxt_http_field_t *field;
nxt_http_status_t status;
@@ -49,8 +49,8 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
if (nxt_slow_path(!nxt_str_eq(r->method, "GET", 3))) {
if (!nxt_str_eq(r->method, "HEAD", 4)) {
- if (action->u.fallback != NULL) {
- return action->u.fallback;
+ if (action->u.share.fallback != NULL) {
+ return action->u.share.fallback;
}
nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED);
@@ -63,13 +63,6 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
need_body = 1;
}
- f = nxt_mp_zget(r->mem_pool, sizeof(nxt_file_t));
- if (nxt_slow_path(f == NULL)) {
- goto fail;
- }
-
- f->fd = NXT_FILE_INVALID;
-
if (r->path->start[r->path->length - 1] == '/') {
/* TODO: dynamic index setting. */
nxt_str_set(&index, "index.html");
@@ -80,23 +73,110 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
nxt_str_null(&extension);
}
- alloc = action->name.length + r->path->length + index.length + 1;
+ f = NULL;
+
+ rtcf = r->conf->socket_conf->router_conf;
+
+ mtype = NULL;
+
+ if (action->u.share.types != NULL && extension.start == NULL) {
+ nxt_http_static_extract_extension(r->path, &extension);
+ mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash,
+ &extension);
+
+ ret = nxt_http_route_test_rule(r, action->u.share.types,
+ mtype->start, mtype->length);
+ if (nxt_slow_path(ret == NXT_ERROR)) {
+ goto fail;
+ }
+
+ if (ret == 0) {
+ if (action->u.share.fallback != NULL) {
+ return action->u.share.fallback;
+ }
- f->name = nxt_mp_nget(r->mem_pool, alloc);
- if (nxt_slow_path(f->name == NULL)) {
+ nxt_http_request_error(task, r, NXT_HTTP_FORBIDDEN);
+ return NULL;
+ }
+ }
+
+ length = action->name.length + r->path->length + index.length;
+
+ fname = nxt_mp_nget(r->mem_pool, length + 1);
+ if (nxt_slow_path(fname == NULL)) {
goto fail;
}
- p = f->name;
+ p = fname;
p = nxt_cpymem(p, action->name.start, action->name.length);
p = nxt_cpymem(p, r->path->start, r->path->length);
p = nxt_cpymem(p, index.start, index.length);
*p = '\0';
- ret = nxt_file_open(task, f, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
+ nxt_memzero(&file, sizeof(nxt_file_t));
+
+ file.name = fname;
+
+ chroot = &action->u.share.chroot;
+
+#if (NXT_HAVE_OPENAT2)
+
+ if (action->u.share.resolve != 0) {
+
+ if (chroot->length > 0) {
+ file.name = chroot->start;
+
+ if (length > chroot->length
+ && nxt_memcmp(fname, chroot->start, chroot->length) == 0)
+ {
+ fname += chroot->length;
+ ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN,
+ 0);
+
+ } else {
+ file.error = NXT_EACCES;
+ ret = NXT_ERROR;
+ }
+
+ } else if (fname[0] == '/') {
+ file.name = (u_char *) "/";
+ ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, 0);
+
+ } else {
+ file.name = (u_char *) ".";
+ file.fd = AT_FDCWD;
+ ret = NXT_OK;
+ }
+
+ if (nxt_fast_path(ret == NXT_OK)) {
+ nxt_file_t af;
+
+ af = file;
+ nxt_memzero(&file, sizeof(nxt_file_t));
+ file.name = fname;
+
+ ret = nxt_file_openat2(task, &file, NXT_FILE_RDONLY,
+ NXT_FILE_OPEN, 0, af.fd,
+ action->u.share.resolve);
+
+ if (af.fd != AT_FDCWD) {
+ nxt_file_close(task, &af);
+ }
+ }
+
+ } else {
+ ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
+ }
+
+#else
+
+ ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
+
+#endif
if (nxt_slow_path(ret != NXT_OK)) {
- switch (f->error) {
+
+ switch (file.error) {
/*
* For Unix domain sockets "errno" is set to:
@@ -117,6 +197,10 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
break;
case NXT_EACCES:
+#if (NXT_HAVE_OPENAT2)
+ case NXT_ELOOP:
+ case NXT_EXDEV:
+#endif
level = NXT_LOG_ERR;
status = NXT_HTTP_FORBIDDEN;
break;
@@ -127,18 +211,32 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
break;
}
- if (level == NXT_LOG_ERR && action->u.fallback != NULL) {
- return action->u.fallback;
+ if (level == NXT_LOG_ERR && action->u.share.fallback != NULL) {
+ return action->u.share.fallback;
}
if (status != NXT_HTTP_NOT_FOUND) {
- nxt_log(task, level, "open(\"%FN\") failed %E", f->name, f->error);
+ if (chroot->length > 0) {
+ nxt_log(task, level, "opening \"%s\" at \"%V\" failed %E",
+ fname, chroot, file.error);
+
+ } else {
+ nxt_log(task, level, "opening \"%s\" failed %E",
+ fname, file.error);
+ }
}
nxt_http_request_error(task, r, status);
return NULL;
}
+ f = nxt_mp_get(r->mem_pool, sizeof(nxt_file_t));
+ if (nxt_slow_path(f == NULL)) {
+ goto fail;
+ }
+
+ *f = file;
+
ret = nxt_file_info(f, &fi);
if (nxt_slow_path(ret != NXT_OK)) {
goto fail;
@@ -172,15 +270,15 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
nxt_http_field_name_set(field, "ETag");
- alloc = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3;
+ length = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3;
- p = nxt_mp_nget(r->mem_pool, alloc);
+ p = nxt_mp_nget(r->mem_pool, length);
if (nxt_slow_path(p == NULL)) {
goto fail;
}
field->value = p;
- field->value_length = nxt_sprintf(p, p + alloc, "\"%xT-%xO\"",
+ field->value_length = nxt_sprintf(p, p + length, "\"%xT-%xO\"",
nxt_file_mtime(&fi),
nxt_file_size(&fi))
- p;
@@ -189,12 +287,12 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
nxt_http_static_extract_extension(r->path, &extension);
}
- rtcf = r->conf->socket_conf->router_conf;
-
- mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash,
- &extension);
+ if (mtype == NULL) {
+ mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash,
+ &extension);
+ }
- if (mtype != NULL) {
+ if (mtype->length != 0) {
field = nxt_list_zero_add(r->resp.fields);
if (nxt_slow_path(field == NULL)) {
goto fail;
@@ -230,8 +328,8 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
nxt_file_close(task, f);
if (nxt_slow_path(!nxt_is_dir(&fi))) {
- if (action->u.fallback != NULL) {
- return action->u.fallback;
+ if (action->u.share.fallback != NULL) {
+ return action->u.share.fallback;
}
nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file",
@@ -254,19 +352,19 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
nxt_http_field_name_set(field, "Location");
encode = nxt_encode_uri(NULL, r->path->start, r->path->length);
- alloc = r->path->length + encode * 2 + 1;
+ length = r->path->length + encode * 2 + 1;
if (r->args->length > 0) {
- alloc += 1 + r->args->length;
+ length += 1 + r->args->length;
}
- p = nxt_mp_nget(r->mem_pool, alloc);
+ p = nxt_mp_nget(r->mem_pool, length);
if (nxt_slow_path(p == NULL)) {
goto fail;
}
field->value = p;
- field->value_length = alloc;
+ field->value_length = length;
if (encode > 0) {
p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length);
@@ -294,7 +392,7 @@ fail:
nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
- if (f != NULL && f->fd != NXT_FILE_INVALID) {
+ if (f != NULL) {
nxt_file_close(task, f);
}
@@ -539,6 +637,8 @@ nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash)
{ nxt_string("application/octet-stream"), ".deb" },
{ nxt_string("application/octet-stream"), ".rpm" },
+
+ { nxt_string("application/x-httpd-php"), ".php" },
};
for (i = 0; i < nxt_nitems(default_types); i++) {
@@ -605,6 +705,8 @@ nxt_http_static_mtypes_hash_find(nxt_lvlhsh_t *hash, nxt_str_t *extension)
nxt_lvlhsh_query_t lhq;
nxt_http_static_mtype_t *mtype;
+ static nxt_str_t empty = nxt_string("");
+
lhq.key = *extension;
lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
lhq.proto = &nxt_http_static_mtypes_hash_proto;
@@ -614,7 +716,7 @@ nxt_http_static_mtypes_hash_find(nxt_lvlhsh_t *hash, nxt_str_t *extension)
return mtype->type;
}
- return NULL;
+ return &empty;
}
diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c
index f20f2c2c..00f336f6 100644
--- a/src/nxt_main_process.c
+++ b/src/nxt_main_process.c
@@ -211,6 +211,12 @@ static nxt_conf_map_t nxt_python_app_conf[] = {
},
{
+ nxt_string("targets"),
+ NXT_CONF_MAP_PTR,
+ offsetof(nxt_common_app_conf_t, u.python.targets),
+ },
+
+ {
nxt_string("thread_stack_size"),
NXT_CONF_MAP_INT32,
offsetof(nxt_common_app_conf_t, u.python.thread_stack_size),
diff --git a/src/nxt_openssl.c b/src/nxt_openssl.c
index 9b86150f..2fd5d1bf 100644
--- a/src/nxt_openssl.c
+++ b/src/nxt_openssl.c
@@ -5,6 +5,7 @@
*/
#include <nxt_main.h>
+#include <nxt_conf.h>
#include <openssl/ssl.h>
#include <openssl/conf.h>
#include <openssl/err.h>
@@ -42,9 +43,14 @@ static unsigned long nxt_openssl_thread_id(void);
static void nxt_openssl_locks_free(void);
#endif
static nxt_int_t nxt_openssl_server_init(nxt_task_t *task,
- nxt_tls_conf_t *conf, nxt_mp_t *mp, nxt_bool_t last);
+ nxt_tls_conf_t *conf, nxt_mp_t *mp, nxt_conf_value_t *conf_cmds,
+ nxt_bool_t last);
static nxt_int_t nxt_openssl_chain_file(nxt_task_t *task, SSL_CTX *ctx,
nxt_tls_conf_t *conf, nxt_mp_t *mp, nxt_bool_t single);
+#if (NXT_HAVE_OPENSSL_CONF_CMD)
+static nxt_int_t nxt_ssl_conf_commands(nxt_task_t *task, SSL_CTX *ctx,
+ nxt_conf_value_t *value, nxt_mp_t *mp);
+#endif
static nxt_uint_t nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert,
nxt_tls_conf_t *conf, nxt_mp_t *mp);
static nxt_int_t nxt_openssl_bundle_hash_test(nxt_lvlhsh_query_t *lhq,
@@ -66,6 +72,8 @@ static void nxt_openssl_conn_io_shutdown(nxt_task_t *task, void *obj,
void *data);
static nxt_int_t nxt_openssl_conn_test_error(nxt_task_t *task, nxt_conn_t *c,
int ret, nxt_err_t sys_err, nxt_openssl_io_t io);
+static void nxt_openssl_conn_io_shutdown_timeout(nxt_task_t *task, void *obj,
+ void *data);
static void nxt_cdecl nxt_openssl_conn_error(nxt_task_t *task,
nxt_err_t err, const char *fmt, ...);
static nxt_uint_t nxt_openssl_log_error_level(nxt_err_t err);
@@ -258,7 +266,7 @@ nxt_openssl_locks_free(void)
static nxt_int_t
nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf,
- nxt_mp_t *mp, nxt_bool_t last)
+ nxt_mp_t *mp, nxt_conf_value_t *conf_cmds, nxt_bool_t last)
{
SSL_CTX *ctx;
const char *ciphers, *ca_certificate;
@@ -318,6 +326,7 @@ nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf,
goto fail;
}
*/
+
ciphers = (conf->ciphers != NULL) ? conf->ciphers : "HIGH:!aNULL:!MD5";
if (SSL_CTX_set_cipher_list(ctx, ciphers) == 0) {
@@ -327,6 +336,14 @@ nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf,
goto fail;
}
+#if (NXT_HAVE_OPENSSL_CONF_CMD)
+ if (conf_cmds != NULL
+ && nxt_ssl_conf_commands(task, ctx, conf_cmds, mp) != NXT_OK)
+ {
+ goto fail;
+ }
+#endif
+
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
if (conf->ca_certificate != NULL) {
@@ -482,6 +499,89 @@ clean:
}
+#if (NXT_HAVE_OPENSSL_CONF_CMD)
+
+static nxt_int_t
+nxt_ssl_conf_commands(nxt_task_t *task, SSL_CTX *ctx, nxt_conf_value_t *conf,
+ nxt_mp_t *mp)
+{
+ int ret;
+ char *zcmd, *zvalue;
+ uint32_t index;
+ nxt_str_t cmd, value;
+ SSL_CONF_CTX *cctx;
+ nxt_conf_value_t *member;
+
+ cctx = SSL_CONF_CTX_new();
+ if (nxt_slow_path(cctx == NULL)) {
+ nxt_openssl_log_error(task, NXT_LOG_ALERT,
+ "SSL_CONF_CTX_new() failed");
+ return NXT_ERROR;
+ }
+
+ SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE
+ | SSL_CONF_FLAG_SERVER
+ | SSL_CONF_FLAG_CERTIFICATE
+ | SSL_CONF_FLAG_SHOW_ERRORS);
+
+ SSL_CONF_CTX_set_ssl_ctx(cctx, ctx);
+
+ index = 0;
+
+ for ( ;; ) {
+ member = nxt_conf_next_object_member(conf, &cmd, &index);
+ if (nxt_slow_path(member == NULL)) {
+ break;
+ }
+
+ nxt_conf_get_string(member, &value);
+
+ zcmd = nxt_str_cstrz(mp, &cmd);
+ zvalue = nxt_str_cstrz(mp, &value);
+
+ if (nxt_slow_path(zcmd == NULL || zvalue == NULL)) {
+ goto fail;
+ }
+
+ ret = SSL_CONF_cmd(cctx, zcmd, zvalue);
+ if (ret == -2) {
+ nxt_openssl_log_error(task, NXT_LOG_ERR,
+ "unknown command \"%s\" in "
+ "\"conf_commands\" option", zcmd);
+ goto fail;
+ }
+
+ if (ret <= 0) {
+ nxt_openssl_log_error(task, NXT_LOG_ERR,
+ "invalid value \"%s\" for command \"%s\" "
+ "in \"conf_commands\" option",
+ zvalue, zcmd);
+ goto fail;
+ }
+
+ nxt_debug(task, "SSL_CONF_cmd(\"%s\", \"%s\")", zcmd, zvalue);
+ }
+
+ if (SSL_CONF_CTX_finish(cctx) != 1) {
+ nxt_openssl_log_error(task, NXT_LOG_ALERT,
+ "SSL_CONF_finish() failed");
+ goto fail;
+ }
+
+ SSL_CONF_CTX_free(cctx);
+
+ return NXT_OK;
+
+fail:
+
+ SSL_CONF_CTX_free(cctx);
+
+ return NXT_ERROR;
+}
+
+#endif
+
+
static nxt_uint_t
nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert, nxt_tls_conf_t *conf,
nxt_mp_t *mp)
@@ -548,7 +648,7 @@ nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert, nxt_tls_conf_t *conf,
NULL, 0);
if (len <= 0) {
nxt_log(task, NXT_LOG_WARN, "certificate \"%V\" has neither "
- "Subject Alternative Name nor Common Name", bundle->name);
+ "Subject Alternative Name nor Common Name", &bundle->name);
return NXT_OK;
}
@@ -627,7 +727,7 @@ nxt_openssl_bundle_hash_insert(nxt_task_t *task, nxt_lvlhsh_t *lvlhsh,
if (item->name.length == 0 || item->name.start[0] != '.') {
nxt_log(task, NXT_LOG_WARN, "ignored invalid name \"%V\" "
"in certificate \"%V\": missing \".\" "
- "after wildcard symbol", &str, item->bundle->name);
+ "after wildcard symbol", &str, &item->bundle->name);
return NXT_OK;
}
}
@@ -642,7 +742,7 @@ nxt_openssl_bundle_hash_insert(nxt_task_t *task, nxt_lvlhsh_t *lvlhsh,
ret = nxt_lvlhsh_insert(lvlhsh, &lhq);
if (nxt_fast_path(ret == NXT_OK)) {
nxt_debug(task, "name \"%V\" for certificate \"%V\" is inserted",
- &str, item->bundle->name);
+ &str, &item->bundle->name);
return NXT_OK;
}
@@ -651,7 +751,7 @@ nxt_openssl_bundle_hash_insert(nxt_task_t *task, nxt_lvlhsh_t *lvlhsh,
if (old->bundle != item->bundle) {
nxt_log(task, NXT_LOG_WARN, "ignored duplicate name \"%V\" "
"in certificate \"%V\", identical name appears in \"%V\"",
- &str, old->bundle->name, item->bundle->name);
+ &str, &old->bundle->name, &item->bundle->name);
old->bundle = item->bundle;
}
@@ -728,8 +828,8 @@ nxt_openssl_servername(SSL *s, int *ad, void *arg)
if (bundle != NULL) {
nxt_debug(c->socket.task, "new tls context found for \"%V\": \"%V\" "
- "(old: \"%V\")", &str, bundle->name,
- conf->bundle->name);
+ "(old: \"%V\")", &str, &bundle->name,
+ &conf->bundle->name);
if (bundle != conf->bundle) {
if (SSL_set_SSL_CTX(s, bundle->ctx) == NULL) {
@@ -839,11 +939,7 @@ nxt_openssl_conn_init(nxt_task_t *task, nxt_tls_conf_t *conf, nxt_conn_t *c)
c->sendfile = NXT_CONN_SENDFILE_OFF;
nxt_openssl_conn_handshake(task, c, c->socket.data);
- /*
- * TLS configuration might be destroyed after the TLS connection
- * is established.
- */
- tls->conf = NULL;
+
return;
fail:
@@ -1099,6 +1195,10 @@ nxt_openssl_conn_io_shutdown(nxt_task_t *task, void *obj, void *data)
SSL_set_quiet_shutdown(s, quiet);
+ if (tls->conf->no_wait_shutdown) {
+ mode |= SSL_RECEIVED_SHUTDOWN;
+ }
+
once = 1;
for ( ;; ) {
@@ -1153,7 +1253,8 @@ nxt_openssl_conn_io_shutdown(nxt_task_t *task, void *obj, void *data)
break;
case NXT_AGAIN:
- nxt_timer_add(task->thread->engine, &c->read_timer, 5000);
+ c->write_timer.handler = nxt_openssl_conn_io_shutdown_timeout;
+ nxt_timer_add(task->thread->engine, &c->write_timer, 5000);
return;
default:
@@ -1237,6 +1338,23 @@ nxt_openssl_conn_test_error(nxt_task_t *task, nxt_conn_t *c, int ret,
}
+static void
+nxt_openssl_conn_io_shutdown_timeout(nxt_task_t *task, void *obj, void *data)
+{
+ nxt_conn_t *c;
+ nxt_timer_t *timer;
+
+ timer = obj;
+
+ nxt_debug(task, "openssl conn shutdown timeout");
+
+ c = nxt_write_timer_conn(timer);
+
+ c->socket.timedout = 1;
+ nxt_openssl_conn_io_shutdown(task, c, NULL);
+}
+
+
static void nxt_cdecl
nxt_openssl_conn_error(nxt_task_t *task, nxt_err_t err, const char *fmt, ...)
{
diff --git a/src/nxt_pcre2.c b/src/nxt_pcre2.c
index 22c4d2d4..cb51062c 100644
--- a/src/nxt_pcre2.c
+++ b/src/nxt_pcre2.c
@@ -144,7 +144,7 @@ nxt_regex_match(nxt_regex_t *re, u_char *subject, size_t length,
if (pcre2_get_error_message(ret, errptr, ERR_BUF_SIZE) < 0) {
nxt_thread_log_error(NXT_LOG_ERR,
"pcre2_match() failed: %d on \"%*s\" "
- "using \"%V\"", ret, subject, length, subject,
+ "using \"%V\"", ret, length, subject,
&re->pattern);
} else {
diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c
index 8fbe7f65..3fb3b0db 100644
--- a/src/nxt_php_sapi.c
+++ b/src/nxt_php_sapi.c
@@ -163,7 +163,8 @@ static const zend_function_entry nxt_php_ext_functions[] = {
};
-zif_handler nxt_php_chdir_handler;
+zif_handler nxt_php_chdir_handler;
+zend_auto_global *nxt_php_server_ag;
static zend_module_entry nxt_php_unit_module = {
@@ -211,6 +212,7 @@ ZEND_NAMED_FUNCTION(nxt_php_chdir)
PHP_FUNCTION(fastcgi_finish_request)
{
+ zend_auto_global *ag;
nxt_php_run_ctx_t *ctx;
if (nxt_slow_path(zend_parse_parameters_none() == FAILURE)) {
@@ -240,6 +242,16 @@ PHP_FUNCTION(fastcgi_finish_request)
php_header(TSRMLS_C);
#endif
+ ag = nxt_php_server_ag;
+
+ if (ag->armed) {
+#ifdef NXT_PHP7
+ ag->armed = ag->auto_global_callback(ag->name);
+#else
+ ag->armed = ag->auto_global_callback(ag->name, ag->name_len TSRMLS_CC);
+#endif
+ }
+
nxt_unit_request_done(ctx->req, NXT_UNIT_OK);
ctx->req = NULL;
@@ -411,6 +423,19 @@ nxt_php_setup(nxt_task_t *task, nxt_process_t *process,
nxt_php_set_options(task, value, ZEND_INI_USER);
}
+#ifdef NXT_PHP7
+ nxt_php_server_ag = zend_hash_str_find_ptr(CG(auto_globals), "_SERVER",
+ nxt_length("_SERVER"));
+#else
+ zend_hash_quick_find(CG(auto_globals), "_SERVER", sizeof("_SERVER"),
+ zend_hash_func("_SERVER", sizeof("_SERVER")),
+ (void **) &nxt_php_server_ag);
+#endif
+ if (nxt_slow_path(nxt_php_server_ag == NULL)) {
+ nxt_alert(task, "failed to find $_SERVER auto global");
+ return NXT_ERROR;
+ }
+
return NXT_OK;
}
@@ -1076,10 +1101,20 @@ nxt_php_execute(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r)
nxt_memzero(&file_handle, sizeof(file_handle));
file_handle.type = ZEND_HANDLE_FILENAME;
+#if (PHP_VERSION_ID >= 80100)
+ file_handle.filename = zend_string_init((char *) ctx->script_filename.start,
+ ctx->script_filename.length, 0);
+ file_handle.primary_script = 1;
+#else
file_handle.filename = (char *) ctx->script_filename.start;
+#endif
php_execute_script(&file_handle TSRMLS_CC);
+#if (PHP_VERSION_ID >= 80100)
+ zend_destroy_file_handle(&file_handle);
+#endif
+
/* Prevention of consuming possible unread request body. */
#if (PHP_VERSION_ID < 50600)
read_post = sapi_module.read_post;
diff --git a/src/nxt_router.c b/src/nxt_router.c
index 8524b358..015ae226 100644
--- a/src/nxt_router.c
+++ b/src/nxt_router.c
@@ -41,8 +41,11 @@ typedef struct {
#if (NXT_TLS)
typedef struct {
- nxt_str_t name;
- nxt_socket_conf_t *conf;
+ nxt_str_t name;
+ nxt_socket_conf_t *socket_conf;
+ nxt_router_temp_conf_t *temp_conf;
+ nxt_conf_value_t *conf_cmds;
+ nxt_bool_t last;
nxt_queue_link_t link; /* for nxt_socket_conf_t.tls */
} nxt_router_tlssock_t;
@@ -117,12 +120,11 @@ static void nxt_router_listen_socket_ready(nxt_task_t *task,
static void nxt_router_listen_socket_error(nxt_task_t *task,
nxt_port_recv_msg_t *msg, void *data);
#if (NXT_TLS)
-static void nxt_router_tls_rpc_create(nxt_task_t *task,
- nxt_router_temp_conf_t *tmcf, nxt_router_tlssock_t *tls, nxt_bool_t last);
static void nxt_router_tls_rpc_handler(nxt_task_t *task,
nxt_port_recv_msg_t *msg, void *data);
static nxt_int_t nxt_router_conf_tls_insert(nxt_router_temp_conf_t *tmcf,
- nxt_conf_value_t *value, nxt_socket_conf_t *skcf);
+ nxt_conf_value_t *value, nxt_socket_conf_t *skcf,
+ nxt_conf_value_t * conf_cmds);
#endif
static void nxt_router_app_rpc_create(nxt_task_t *task,
nxt_router_temp_conf_t *tmcf, nxt_app_t *app);
@@ -773,7 +775,7 @@ fail:
msg->port_msg.stream, 0, NULL);
if (tmcf != NULL) {
- nxt_mp_destroy(tmcf->mem_pool);
+ nxt_mp_release(tmcf->mem_pool);
}
cleanup:
@@ -954,8 +956,10 @@ nxt_router_conf_apply(nxt_task_t *task, void *obj, void *data)
tls = nxt_queue_link_data(qlk, nxt_router_tlssock_t, link);
- nxt_router_tls_rpc_create(task, tmcf, tls,
- nxt_queue_is_empty(&tmcf->tls));
+ tls->last = nxt_queue_is_empty(&tmcf->tls);
+
+ nxt_cert_store_get(task, &tls->name, tmcf->mem_pool,
+ nxt_router_tls_rpc_handler, tls);
return;
}
#endif
@@ -1061,7 +1065,7 @@ nxt_router_conf_ready(nxt_task_t *task, nxt_router_temp_conf_t *tmcf)
nxt_mp_destroy(rtcf->mem_pool);
}
- nxt_mp_destroy(tmcf->mem_pool);
+ nxt_mp_release(tmcf->mem_pool);
}
@@ -1120,7 +1124,7 @@ nxt_router_conf_error(nxt_task_t *task, nxt_router_temp_conf_t *tmcf)
nxt_router_conf_send(task, tmcf, NXT_PORT_MSG_RPC_ERROR);
- nxt_mp_destroy(tmcf->mem_pool);
+ nxt_mp_release(tmcf->mem_pool);
}
@@ -1337,7 +1341,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
nxt_router_t *router;
nxt_app_joint_t *app_joint;
#if (NXT_TLS)
- nxt_conf_value_t *certificate;
+ nxt_conf_value_t *certificate, *conf_cmds;
#endif
nxt_conf_value_t *conf, *http, *value, *websocket;
nxt_conf_value_t *applications, *application;
@@ -1358,6 +1362,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
static nxt_str_t access_log_path = nxt_string("/access_log");
#if (NXT_TLS)
static nxt_str_t certificate_path = nxt_string("/tls/certificate");
+ static nxt_str_t conf_commands_path = nxt_string("/tls/conf_commands");
#endif
static nxt_str_t static_path = nxt_string("/settings/http/static");
static nxt_str_t websocket_path = nxt_string("/settings/http/websocket");
@@ -1736,6 +1741,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
certificate = nxt_conf_get_path(listener, &certificate_path);
if (certificate != NULL) {
+ conf_cmds = nxt_conf_get_path(listener, &conf_commands_path);
+
if (nxt_conf_type(certificate) == NXT_CONF_ARRAY) {
n = nxt_conf_array_elements_count(certificate);
@@ -1744,7 +1751,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
nxt_assert(value != NULL);
- ret = nxt_router_conf_tls_insert(tmcf, value, skcf);
+ ret = nxt_router_conf_tls_insert(tmcf, value, skcf,
+ conf_cmds);
if (nxt_slow_path(ret != NXT_OK)) {
goto fail;
}
@@ -1752,7 +1760,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
} else {
/* NXT_CONF_STRING */
- ret = nxt_router_conf_tls_insert(tmcf, certificate, skcf);
+ ret = nxt_router_conf_tls_insert(tmcf, certificate, skcf,
+ conf_cmds);
if (nxt_slow_path(ret != NXT_OK)) {
goto fail;
}
@@ -1846,25 +1855,20 @@ fail:
static nxt_int_t
nxt_router_conf_tls_insert(nxt_router_temp_conf_t *tmcf,
- nxt_conf_value_t *value, nxt_socket_conf_t *skcf)
+ nxt_conf_value_t *value, nxt_socket_conf_t *skcf,
+ nxt_conf_value_t *conf_cmds)
{
- nxt_mp_t *mp;
- nxt_str_t str;
nxt_router_tlssock_t *tls;
- mp = tmcf->router_conf->mem_pool;
-
- tls = nxt_mp_get(mp, sizeof(nxt_router_tlssock_t));
+ tls = nxt_mp_get(tmcf->mem_pool, sizeof(nxt_router_tlssock_t));
if (nxt_slow_path(tls == NULL)) {
return NXT_ERROR;
}
- tls->conf = skcf;
- nxt_conf_get_string(value, &str);
-
- if (nxt_slow_path(nxt_str_dup(mp, &tls->name, &str) == NULL)) {
- return NXT_ERROR;
- }
+ tls->socket_conf = skcf;
+ tls->conf_cmds = conf_cmds;
+ tls->temp_conf = tmcf;
+ nxt_conf_get_string(value, &tls->name);
nxt_queue_insert_tail(&tmcf->tls, &tls->link);
@@ -2144,7 +2148,7 @@ nxt_router_listener_application(nxt_router_conf_t *rtcf, nxt_str_t *name,
return NXT_DECLINED;
}
- action->u.application = app;
+ action->u.app.application = app;
action->handler = nxt_http_application_handler;
return NXT_OK;
@@ -2428,42 +2432,20 @@ nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg,
#if (NXT_TLS)
static void
-nxt_router_tls_rpc_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
- nxt_router_tlssock_t *tls, nxt_bool_t last)
-{
- nxt_socket_rpc_t *rpc;
-
- rpc = nxt_mp_alloc(tmcf->mem_pool, sizeof(nxt_socket_rpc_t));
- if (rpc == NULL) {
- nxt_router_conf_error(task, tmcf);
- return;
- }
-
- rpc->name = &tls->name;
- rpc->socket_conf = tls->conf;
- rpc->temp_conf = tmcf;
- rpc->last = last;
-
- nxt_cert_store_get(task, &tls->name, tmcf->mem_pool,
- nxt_router_tls_rpc_handler, rpc);
-}
-
-
-static void
nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
void *data)
{
nxt_mp_t *mp;
nxt_int_t ret;
nxt_tls_conf_t *tlscf;
- nxt_socket_rpc_t *rpc;
+ nxt_router_tlssock_t *tls;
nxt_tls_bundle_conf_t *bundle;
nxt_router_temp_conf_t *tmcf;
nxt_debug(task, "tls rpc handler");
- rpc = data;
- tmcf = rpc->temp_conf;
+ tls = data;
+ tmcf = tls->temp_conf;
if (msg == NULL || msg->port_msg.type == _NXT_PORT_MSG_RPC_ERROR) {
goto fail;
@@ -2471,16 +2453,17 @@ nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
mp = tmcf->router_conf->mem_pool;
- if (rpc->socket_conf->tls == NULL){
+ if (tls->socket_conf->tls == NULL){
tlscf = nxt_mp_zget(mp, sizeof(nxt_tls_conf_t));
if (nxt_slow_path(tlscf == NULL)) {
goto fail;
}
- rpc->socket_conf->tls = tlscf;
+ tlscf->no_wait_shutdown = 1;
+ tls->socket_conf->tls = tlscf;
} else {
- tlscf = rpc->socket_conf->tls;
+ tlscf = tls->socket_conf->tls;
}
bundle = nxt_mp_get(mp, sizeof(nxt_tls_bundle_conf_t));
@@ -2488,12 +2471,16 @@ nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
goto fail;
}
- bundle->name = rpc->name;
+ if (nxt_slow_path(nxt_str_dup(mp, &bundle->name, &tls->name) == NULL)) {
+ goto fail;
+ }
+
bundle->chain_file = msg->fd[0];
bundle->next = tlscf->bundle;
tlscf->bundle = bundle;
- ret = task->thread->runtime->tls->server_init(task, tlscf, mp, rpc->last);
+ ret = task->thread->runtime->tls->server_init(task, tlscf, mp,
+ tls->conf_cmds, tls->last);
if (nxt_slow_path(ret != NXT_OK)) {
goto fail;
}
@@ -3223,12 +3210,11 @@ nxt_router_listen_socket_update(nxt_task_t *task, void *obj, void *data)
static void
nxt_router_listen_socket_delete(nxt_task_t *task, void *obj, void *data)
{
- nxt_joint_job_t *job;
- nxt_socket_conf_t *skcf;
- nxt_listen_event_t *lev;
- nxt_event_engine_t *engine;
+ nxt_socket_conf_t *skcf;
+ nxt_listen_event_t *lev;
+ nxt_event_engine_t *engine;
+ nxt_socket_conf_joint_t *joint;
- job = obj;
skcf = data;
engine = task->thread->engine;
@@ -3240,15 +3226,13 @@ nxt_router_listen_socket_delete(nxt_task_t *task, void *obj, void *data)
nxt_debug(task, "engine %p: listen socket delete: %d", engine,
lev->socket.fd);
+ joint = lev->socket.data;
+ joint->close_job = obj;
+
lev->timer.handler = nxt_router_listen_socket_close;
lev->timer.work_queue = &engine->fast_work_queue;
nxt_timer_add(engine, &lev->timer, 0);
-
- job->work.next = NULL;
- job->work.handler = nxt_router_conf_wait;
-
- nxt_event_engine_post(job->tmcf->engine, &job->work);
}
@@ -3273,6 +3257,7 @@ static void
nxt_router_listen_socket_close(nxt_task_t *task, void *obj, void *data)
{
nxt_timer_t *timer;
+ nxt_joint_job_t *job;
nxt_listen_event_t *lev;
nxt_socket_conf_joint_t *joint;
@@ -3292,6 +3277,12 @@ nxt_router_listen_socket_close(nxt_task_t *task, void *obj, void *data)
nxt_router_listen_socket_release(task, joint->socket_conf);
+ job = joint->close_job;
+ job->work.next = NULL;
+ job->work.handler = nxt_router_conf_wait;
+
+ nxt_event_engine_post(job->tmcf->engine, &job->work);
+
nxt_router_listen_event_release(task, lev, joint);
}
diff --git a/src/nxt_router.h b/src/nxt_router.h
index 5804840f..b1ccdf51 100644
--- a/src/nxt_router.h
+++ b/src/nxt_router.h
@@ -205,6 +205,8 @@ typedef struct {
nxt_event_engine_t *engine;
nxt_socket_conf_t *socket_conf;
+ nxt_joint_job_t *close_job;
+
nxt_upstream_t **upstreams;
/* Modules configuraitons. */
diff --git a/src/nxt_tls.h b/src/nxt_tls.h
index c44bfe56..63c49ee4 100644
--- a/src/nxt_tls.h
+++ b/src/nxt_tls.h
@@ -8,6 +8,9 @@
#define _NXT_TLS_H_INCLUDED_
+#include <nxt_conf.h>
+
+
/*
* The SSL/TLS libraries lack vector I/O interface yet add noticeable
* overhead to each SSL/TLS record so buffering allows to decrease the
@@ -32,6 +35,7 @@ typedef struct {
nxt_int_t (*server_init)(nxt_task_t *task,
nxt_tls_conf_t *conf, nxt_mp_t *mp,
+ nxt_conf_value_t *conf_cmds,
nxt_bool_t last);
void (*server_free)(nxt_task_t *task,
nxt_tls_conf_t *conf);
@@ -49,7 +53,7 @@ struct nxt_tls_bundle_conf_s {
void *ctx;
nxt_fd_t chain_file;
- nxt_str_t *name;
+ nxt_str_t name;
nxt_tls_bundle_conf_t *next;
};
@@ -69,6 +73,8 @@ struct nxt_tls_conf_s {
char *ca_certificate;
size_t buffer_size;
+
+ uint8_t no_wait_shutdown; /* 1 bit */
};
diff --git a/src/nxt_unix.h b/src/nxt_unix.h
index 609f7e95..393f61d9 100644
--- a/src/nxt_unix.h
+++ b/src/nxt_unix.h
@@ -242,6 +242,10 @@
#include <sys/mount.h>
#endif
+#if (NXT_HAVE_OPENAT2)
+#include <linux/openat2.h>
+#endif
+
#if (NXT_TEST_BUILD)
#include <nxt_test_build.h>
#endif
diff --git a/src/python/nxt_python.c b/src/python/nxt_python.c
index d8204937..588a147a 100644
--- a/src/python/nxt_python.c
+++ b/src/python/nxt_python.c
@@ -24,6 +24,8 @@ typedef struct {
static nxt_int_t nxt_python_start(nxt_task_t *task,
nxt_process_data_t *data);
+static nxt_int_t nxt_python_set_target(nxt_task_t *task,
+ nxt_python_target_t *target, nxt_conf_value_t *conf);
static nxt_int_t nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value);
static int nxt_python_init_threads(nxt_python_app_conf_t *c);
static int nxt_python_ready_handler(nxt_unit_ctx_t *ctx);
@@ -49,7 +51,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = {
};
static PyObject *nxt_py_stderr_flush;
-PyObject *nxt_py_application;
+nxt_python_targets_t *nxt_py_targets;
#if PY_MAJOR_VERSION == 3
static wchar_t *nxt_py_home;
@@ -66,18 +68,19 @@ static nxt_int_t
nxt_python_start(nxt_task_t *task, nxt_process_data_t *data)
{
int rc;
- char *nxt_py_module;
- size_t len;
+ size_t len, size;
+ uint32_t next;
PyObject *obj, *module;
- nxt_str_t proto;
- const char *callable;
+ nxt_str_t proto, probe_proto, name;
+ nxt_int_t ret, n, i;
nxt_unit_ctx_t *unit_ctx;
nxt_unit_init_t python_init;
+ nxt_conf_value_t *cv;
+ nxt_python_targets_t *targets;
nxt_common_app_conf_t *app_conf;
nxt_python_app_conf_t *c;
#if PY_MAJOR_VERSION == 3
char *path;
- size_t size;
nxt_int_t pep405;
static const char pyvenv[] = "/pyvenv.cfg";
@@ -190,38 +193,42 @@ nxt_python_start(nxt_task_t *task, nxt_process_data_t *data)
Py_CLEAR(obj);
- nxt_py_module = nxt_alloca(c->module.length + 1);
- nxt_memcpy(nxt_py_module, c->module.start, c->module.length);
- nxt_py_module[c->module.length] = '\0';
+ n = (c->targets != NULL ? nxt_conf_object_members_count(c->targets) : 1);
- module = PyImport_ImportModule(nxt_py_module);
- if (nxt_slow_path(module == NULL)) {
- nxt_alert(task, "Python failed to import module \"%s\"", nxt_py_module);
- nxt_python_print_exception();
+ size = sizeof(nxt_python_targets_t) + n * sizeof(nxt_python_target_t);
+
+ targets = nxt_unit_malloc(NULL, size);
+ if (nxt_slow_path(targets == NULL)) {
+ nxt_alert(task, "Could not allocate targets");
goto fail;
}
- callable = (c->callable != NULL) ? c->callable : "application";
+ memset(targets, 0, size);
- obj = PyDict_GetItemString(PyModule_GetDict(module), callable);
- if (nxt_slow_path(obj == NULL)) {
- nxt_alert(task, "Python failed to get \"%s\" "
- "from module \"%s\"", callable, nxt_py_module);
- goto fail;
- }
+ targets->count = n;
+ nxt_py_targets = targets;
- if (nxt_slow_path(PyCallable_Check(obj) == 0)) {
- nxt_alert(task, "\"%s\" in module \"%s\" "
- "is not a callable object", callable, nxt_py_module);
- goto fail;
- }
+ if (c->targets != NULL) {
+ next = 0;
- nxt_py_application = obj;
- obj = NULL;
+ for (i = 0; /* void */; i++) {
+ cv = nxt_conf_next_object_member(c->targets, &name, &next);
+ if (cv == NULL) {
+ break;
+ }
- Py_INCREF(nxt_py_application);
+ ret = nxt_python_set_target(task, &targets->target[i], cv);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ goto fail;
+ }
+ }
- Py_CLEAR(module);
+ } else {
+ ret = nxt_python_set_target(task, &targets->target[0], app_conf->self);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ goto fail;
+ }
+ }
nxt_unit_default_init(task, &python_init);
@@ -232,7 +239,18 @@ nxt_python_start(nxt_task_t *task, nxt_process_data_t *data)
proto = c->protocol;
if (proto.length == 0) {
- proto = nxt_python_asgi_check(nxt_py_application) ? asgi : wsgi;
+ proto = nxt_python_asgi_check(targets->target[0].application)
+ ? asgi : wsgi;
+
+ for (i = 1; i < targets->count; i++) {
+ probe_proto = nxt_python_asgi_check(targets->target[i].application)
+ ? asgi : wsgi;
+ if (probe_proto.start != proto.start) {
+ nxt_alert(task, "A mix of ASGI & WSGI targets is forbidden, "
+ "specify protocol in config if incorrect");
+ goto fail;
+ }
+ }
}
if (nxt_strstr_eq(&proto, &asgi)) {
@@ -299,6 +317,81 @@ fail:
static nxt_int_t
+nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target,
+ nxt_conf_value_t *conf)
+{
+ char *callable, *module_name;
+ PyObject *module, *obj;
+ nxt_str_t str;
+ nxt_conf_value_t *value;
+
+ static nxt_str_t module_str = nxt_string("module");
+ static nxt_str_t callable_str = nxt_string("callable");
+
+ module = obj = NULL;
+
+ value = nxt_conf_get_object_member(conf, &module_str, NULL);
+ if (nxt_slow_path(value == NULL)) {
+ goto fail;
+ }
+
+ nxt_conf_get_string(value, &str);
+
+ module_name = nxt_alloca(str.length + 1);
+ nxt_memcpy(module_name, str.start, str.length);
+ module_name[str.length] = '\0';
+
+ module = PyImport_ImportModule(module_name);
+ if (nxt_slow_path(module == NULL)) {
+ nxt_alert(task, "Python failed to import module \"%s\"", module_name);
+ nxt_python_print_exception();
+ goto fail;
+ }
+
+ value = nxt_conf_get_object_member(conf, &callable_str, NULL);
+ if (value == NULL) {
+ callable = nxt_alloca(12);
+ nxt_memcpy(callable, "application", 12);
+
+ } else {
+ nxt_conf_get_string(value, &str);
+
+ callable = nxt_alloca(str.length + 1);
+ nxt_memcpy(callable, str.start, str.length);
+ callable[str.length] = '\0';
+ }
+
+ obj = PyDict_GetItemString(PyModule_GetDict(module), callable);
+ if (nxt_slow_path(obj == NULL)) {
+ nxt_alert(task, "Python failed to get \"%s\" from module \"%s\"",
+ callable, module);
+ goto fail;
+ }
+
+ if (nxt_slow_path(PyCallable_Check(obj) == 0)) {
+ nxt_alert(task, "\"%s\" in module \"%s\" is not a callable object",
+ callable, module);
+ goto fail;
+ }
+
+ target->application = obj;
+ obj = NULL;
+
+ Py_INCREF(target->application);
+ Py_CLEAR(module);
+
+ return NXT_OK;
+
+fail:
+
+ Py_XDECREF(obj);
+ Py_XDECREF(module);
+
+ return NXT_ERROR;
+}
+
+
+static nxt_int_t
nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value)
{
int ret;
@@ -596,12 +689,21 @@ nxt_python_done_strings(nxt_python_string_t *pstr)
static void
nxt_python_atexit(void)
{
+ nxt_int_t i;
+
if (nxt_py_proto.done != NULL) {
nxt_py_proto.done();
}
Py_XDECREF(nxt_py_stderr_flush);
- Py_XDECREF(nxt_py_application);
+
+ if (nxt_py_targets != NULL) {
+ for (i = 0; i < nxt_py_targets->count; i++) {
+ Py_XDECREF(nxt_py_targets->target[i].application);
+ }
+
+ nxt_unit_free(NULL, nxt_py_targets);
+ }
Py_Finalize();
diff --git a/src/python/nxt_python.h b/src/python/nxt_python.h
index b581dd46..a5c1d9a6 100644
--- a/src/python/nxt_python.h
+++ b/src/python/nxt_python.h
@@ -37,13 +37,28 @@
#define NXT_HAVE_ASGI 1
#endif
-extern PyObject *nxt_py_application;
+
+typedef struct {
+ PyObject *application;
+ nxt_bool_t asgi_legacy;
+} nxt_python_target_t;
+
+
+typedef struct {
+ nxt_int_t count;
+ nxt_python_target_t target[0];
+} nxt_python_targets_t;
+
+
+extern nxt_python_targets_t *nxt_py_targets;
+
typedef struct {
nxt_str_t string;
PyObject **object_p;
} nxt_python_string_t;
+
typedef struct {
int (*ctx_data_alloc)(void **pdata);
void (*ctx_data_free)(void *data);
diff --git a/src/python/nxt_python_asgi.c b/src/python/nxt_python_asgi.c
index a6f94507..1d220678 100644
--- a/src/python/nxt_python_asgi.c
+++ b/src/python/nxt_python_asgi.c
@@ -43,8 +43,6 @@ static void nxt_py_asgi_shm_ack_handler(nxt_unit_ctx_t *ctx);
static PyObject *nxt_py_asgi_port_read(PyObject *self, PyObject *args);
static void nxt_python_asgi_done(void);
-
-int nxt_py_asgi_legacy;
static PyObject *nxt_py_port_read;
static nxt_unit_port_t *nxt_py_shared_port;
@@ -137,6 +135,7 @@ int
nxt_python_asgi_init(nxt_unit_init_t *init, nxt_python_proto_t *proto)
{
PyObject *func;
+ nxt_int_t i;
PyCodeObject *code;
nxt_unit_debug(NULL, "asgi_init");
@@ -161,21 +160,23 @@ nxt_python_asgi_init(nxt_unit_init_t *init, nxt_python_proto_t *proto)
return NXT_UNIT_ERROR;
}
- func = nxt_python_asgi_get_func(nxt_py_application);
- if (nxt_slow_path(func == NULL)) {
- nxt_unit_alert(NULL, "Python cannot find function for callable");
- return NXT_UNIT_ERROR;
- }
+ for (i = 0; i < nxt_py_targets->count; i++) {
+ func = nxt_python_asgi_get_func(nxt_py_targets->target[i].application);
+ if (nxt_slow_path(func == NULL)) {
+ nxt_unit_alert(NULL, "Python cannot find function for callable");
+ return NXT_UNIT_ERROR;
+ }
- code = (PyCodeObject *) PyFunction_GET_CODE(func);
+ code = (PyCodeObject *) PyFunction_GET_CODE(func);
- if ((code->co_flags & CO_COROUTINE) == 0) {
- nxt_unit_debug(NULL, "asgi: callable is not a coroutine function "
- "switching to legacy mode");
- nxt_py_asgi_legacy = 1;
- }
+ if ((code->co_flags & CO_COROUTINE) == 0) {
+ nxt_unit_debug(NULL, "asgi: callable is not a coroutine function "
+ "switching to legacy mode");
+ nxt_py_targets->target[i].asgi_legacy = 1;
+ }
- Py_DECREF(func);
+ Py_DECREF(func);
+ }
init->callbacks.request_handler = nxt_py_asgi_request_handler;
init->callbacks.data_handler = nxt_py_asgi_http_data_handler;
@@ -408,6 +409,7 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req)
{
PyObject *scope, *res, *task, *receive, *send, *done, *asgi;
PyObject *stage2;
+ nxt_python_target_t *target;
nxt_py_asgi_ctx_data_t *ctx_data;
if (req->request->websocket_handshake) {
@@ -456,17 +458,18 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req)
}
req->data = asgi;
+ target = &nxt_py_targets->target[req->request->app_target];
- if (!nxt_py_asgi_legacy) {
+ if (!target->asgi_legacy) {
nxt_unit_req_debug(req, "Python call ASGI 3.0 application");
- res = PyObject_CallFunctionObjArgs(nxt_py_application,
+ res = PyObject_CallFunctionObjArgs(target->application,
scope, receive, send, NULL);
} else {
nxt_unit_req_debug(req, "Python call legacy application");
- res = PyObject_CallFunctionObjArgs(nxt_py_application, scope, NULL);
+ res = PyObject_CallFunctionObjArgs(target->application, scope, NULL);
if (nxt_slow_path(res == NULL)) {
nxt_unit_req_error(req, "Python failed to call legacy app stage1");
diff --git a/src/python/nxt_python_asgi.h b/src/python/nxt_python_asgi.h
index 37f2a099..20702065 100644
--- a/src/python/nxt_python_asgi.h
+++ b/src/python/nxt_python_asgi.h
@@ -33,7 +33,7 @@ typedef struct {
PyObject *loop_remove_reader;
PyObject *quit_future;
PyObject *quit_future_set_result;
- PyObject *lifespan;
+ PyObject **target_lifespans;
nxt_unit_port_t *port;
} nxt_py_asgi_ctx_data_t;
@@ -69,6 +69,4 @@ int nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data);
int nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t *ctx);
-extern int nxt_py_asgi_legacy;
-
#endif /* _NXT_PYTHON_ASGI_H_INCLUDED_ */
diff --git a/src/python/nxt_python_asgi_lifespan.c b/src/python/nxt_python_asgi_lifespan.c
index 506eaf4d..1fc0e6b7 100644
--- a/src/python/nxt_python_asgi_lifespan.c
+++ b/src/python/nxt_python_asgi_lifespan.c
@@ -27,7 +27,10 @@ typedef struct {
PyObject *receive_future;
} nxt_py_asgi_lifespan_t;
-
+static PyObject *nxt_py_asgi_lifespan_target_startup(
+ nxt_py_asgi_ctx_data_t *ctx_data, nxt_python_target_t *target);
+static int nxt_py_asgi_lifespan_target_shutdown(
+ nxt_py_asgi_lifespan_t *lifespan);
static PyObject *nxt_py_asgi_lifespan_receive(PyObject *self, PyObject *none);
static PyObject *nxt_py_asgi_lifespan_send(PyObject *self, PyObject *dict);
static PyObject *nxt_py_asgi_lifespan_send_startup(
@@ -69,24 +72,60 @@ static PyTypeObject nxt_py_asgi_lifespan_type = {
int
nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data)
{
- int rc;
+ size_t size;
+ PyObject *lifespan;
+ PyObject **target_lifespans;
+ nxt_int_t i;
+ nxt_python_target_t *target;
+
+ size = nxt_py_targets->count * sizeof(PyObject*);
+
+ target_lifespans = nxt_unit_malloc(NULL, size);
+ if (nxt_slow_path(target_lifespans == NULL)) {
+ nxt_unit_alert(NULL, "Failed to allocate lifespan data");
+ return NXT_UNIT_ERROR;
+ }
+
+ memset(target_lifespans, 0, size);
+
+ for (i = 0; i < nxt_py_targets->count; i++) {
+ target = &nxt_py_targets->target[i];
+
+ lifespan = nxt_py_asgi_lifespan_target_startup(ctx_data, target);
+ if (nxt_slow_path(lifespan == NULL)) {
+ return NXT_UNIT_ERROR;
+ }
+
+ target_lifespans[i] = lifespan;
+ }
+
+ ctx_data->target_lifespans = target_lifespans;
+
+ return NXT_UNIT_OK;
+}
+
+
+static PyObject *
+nxt_py_asgi_lifespan_target_startup(nxt_py_asgi_ctx_data_t *ctx_data,
+ nxt_python_target_t *target)
+{
PyObject *scope, *res, *py_task, *receive, *send, *done;
PyObject *stage2;
- nxt_py_asgi_lifespan_t *lifespan;
+ nxt_py_asgi_lifespan_t *lifespan, *ret;
if (nxt_slow_path(PyType_Ready(&nxt_py_asgi_lifespan_type) != 0)) {
nxt_unit_alert(NULL,
"Python failed to initialize the 'asgi_lifespan' type object");
- return NXT_UNIT_ERROR;
+ return NULL;
}
lifespan = PyObject_New(nxt_py_asgi_lifespan_t, &nxt_py_asgi_lifespan_type);
if (nxt_slow_path(lifespan == NULL)) {
nxt_unit_alert(NULL, "Python failed to create lifespan object");
- return NXT_UNIT_ERROR;
+ return NULL;
}
- rc = NXT_UNIT_ERROR;
+ ret = NULL;
receive = PyObject_GetAttrString((PyObject *) lifespan, "receive");
if (nxt_slow_path(receive == NULL)) {
@@ -130,23 +169,25 @@ nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data)
goto release_future;
}
- if (!nxt_py_asgi_legacy) {
+ if (!target->asgi_legacy) {
nxt_unit_req_debug(NULL, "Python call ASGI 3.0 application");
- res = PyObject_CallFunctionObjArgs(nxt_py_application,
+ res = PyObject_CallFunctionObjArgs(target->application,
scope, receive, send, NULL);
} else {
nxt_unit_req_debug(NULL, "Python call legacy application");
- res = PyObject_CallFunctionObjArgs(nxt_py_application, scope, NULL);
+ res = PyObject_CallFunctionObjArgs(target->application, scope, NULL);
if (nxt_slow_path(res == NULL)) {
nxt_unit_log(NULL, NXT_UNIT_LOG_INFO,
"ASGI Lifespan processing exception");
nxt_python_print_exception();
lifespan->disabled = 1;
- rc = NXT_UNIT_OK;
+
+ Py_INCREF(lifespan);
+ ret = lifespan;
goto release_scope;
}
@@ -211,10 +252,9 @@ nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data)
Py_DECREF(res);
if (lifespan->startup_sent == 1 || lifespan->disabled) {
- ctx_data->lifespan = (PyObject *) lifespan;
- Py_INCREF(ctx_data->lifespan);
+ Py_INCREF(lifespan);
- rc = NXT_UNIT_OK;
+ ret = lifespan;
}
release_task:
@@ -232,20 +272,41 @@ release_receive:
release_lifespan:
Py_DECREF(lifespan);
- return rc;
+ return (PyObject *) ret;
}
int
nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t *ctx)
{
- PyObject *msg, *future, *res;
+ nxt_int_t i, ret;
nxt_py_asgi_lifespan_t *lifespan;
nxt_py_asgi_ctx_data_t *ctx_data;
ctx_data = ctx->data;
- lifespan = (nxt_py_asgi_lifespan_t *) ctx_data->lifespan;
+ for (i = 0; i < nxt_py_targets->count; i++) {
+ lifespan = (nxt_py_asgi_lifespan_t *)ctx_data->target_lifespans[i];
+
+ ret = nxt_py_asgi_lifespan_target_shutdown(lifespan);
+ if (nxt_slow_path(ret != NXT_UNIT_OK)) {
+ return NXT_UNIT_ERROR;
+ }
+ }
+
+ nxt_unit_free(NULL, ctx_data->target_lifespans);
+
+ return NXT_UNIT_OK;
+}
+
+
+static int
+nxt_py_asgi_lifespan_target_shutdown(nxt_py_asgi_lifespan_t *lifespan)
+{
+ PyObject *msg, *future, *res;
+ nxt_py_asgi_ctx_data_t *ctx_data;
+
+ ctx_data = lifespan->ctx_data;
if (nxt_slow_path(lifespan == NULL || lifespan->disabled)) {
return NXT_UNIT_OK;
diff --git a/src/python/nxt_python_wsgi.c b/src/python/nxt_python_wsgi.c
index 77c45af5..b80d10fa 100644
--- a/src/python/nxt_python_wsgi.c
+++ b/src/python/nxt_python_wsgi.c
@@ -302,7 +302,7 @@ nxt_python_request_handler(nxt_unit_request_info_t *req)
{
int rc;
PyObject *environ, *args, *response, *iterator, *item;
- PyObject *close, *result;
+ PyObject *close, *result, *application;
nxt_bool_t prepare_environ;
nxt_python_ctx_t *pctx;
@@ -348,7 +348,8 @@ nxt_python_request_handler(nxt_unit_request_info_t *req)
Py_INCREF(pctx->start_resp);
PyTuple_SET_ITEM(args, 1, pctx->start_resp);
- response = PyObject_CallObject(nxt_py_application, args);
+ application = nxt_py_targets->target[req->request->app_target].application;
+ response = PyObject_CallObject(application, args);
Py_DECREF(args);
diff --git a/src/ruby/nxt_ruby_stream_io.c b/src/ruby/nxt_ruby_stream_io.c
index 69bf289e..82ad3908 100644
--- a/src/ruby/nxt_ruby_stream_io.c
+++ b/src/ruby/nxt_ruby_stream_io.c
@@ -25,7 +25,9 @@ nxt_ruby_stream_io_input_init(void)
{
VALUE stream_io;
- stream_io = rb_define_class("NGINX_Unit_Stream_IO_Read", rb_cData);
+ stream_io = rb_define_class("NGINX_Unit_Stream_IO_Read", rb_cObject);
+
+ rb_undef_alloc_func(stream_io);
rb_gc_register_address(&stream_io);
@@ -46,7 +48,9 @@ nxt_ruby_stream_io_error_init(void)
{
VALUE stream_io;
- stream_io = rb_define_class("NGINX_Unit_Stream_IO_Error", rb_cData);
+ stream_io = rb_define_class("NGINX_Unit_Stream_IO_Error", rb_cObject);
+
+ rb_undef_alloc_func(stream_io);
rb_gc_register_address(&stream_io);
diff --git a/test/conftest.py b/test/conftest.py
index 20ac6e81..5ea4e49d 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -6,7 +6,6 @@ import platform
import re
import shutil
import signal
-import socket
import stat
import subprocess
import sys
@@ -15,13 +14,16 @@ import time
from multiprocessing import Process
import pytest
+
+from unit.check.chroot import check_chroot
from unit.check.go import check_go
from unit.check.isolation import check_isolation
from unit.check.node import check_node
-from unit.check.tls import check_openssl
from unit.check.regex import check_regex
+from unit.check.tls import check_openssl
from unit.http import TestHTTP
from unit.option import option
+from unit.log import Log
from unit.utils import public_dir
from unit.utils import waitforfiles
@@ -57,6 +59,12 @@ def pytest_addoption(parser):
help="Default user for non-privileged processes of unitd",
)
parser.addoption(
+ "--fds-threshold",
+ type=int,
+ default=0,
+ help="File descriptors threshold",
+ )
+ parser.addoption(
"--restart",
default=False,
action="store_true",
@@ -65,14 +73,25 @@ def pytest_addoption(parser):
unit_instance = {}
-unit_log_copy = "unit.log.copy"
_processes = []
+_fds_check = {
+ 'main': {'fds': 0, 'skip': False},
+ 'router': {'name': 'unit: router', 'pid': -1, 'fds': 0, 'skip': False},
+ 'controller': {
+ 'name': 'unit: controller',
+ 'pid': -1,
+ 'fds': 0,
+ 'skip': False,
+ },
+}
http = TestHTTP()
+
def pytest_configure(config):
option.config = config.option
option.detailed = config.option.detailed
+ option.fds_threshold = config.option.fds_threshold
option.print_log = config.option.print_log
option.save_log = config.option.save_log
option.unsafe = config.option.unsafe
@@ -98,9 +117,11 @@ def pytest_configure(config):
def pytest_generate_tests(metafunc):
cls = metafunc.cls
- if (not hasattr(cls, 'application_type')
- or cls.application_type == None
- or cls.application_type == 'external'):
+ if (
+ not hasattr(cls, 'application_type')
+ or cls.application_type == None
+ or cls.application_type == 'external'
+ ):
return
type = cls.application_type
@@ -145,12 +166,11 @@ def pytest_sessionstart(session):
option.available = {'modules': {}, 'features': {}}
unit = unit_run()
- option.temp_dir = unit['temp_dir']
# read unit.log
for i in range(50):
- with open(unit['temp_dir'] + '/unit.log', 'r') as f:
+ with open(Log.get_path(), 'r') as f:
log = f.read()
m = re.search('controller started', log)
@@ -185,6 +205,7 @@ def pytest_sessionstart(session):
k: v for k, v in option.available['modules'].items() if v is not None
}
+ check_chroot()
check_isolation()
_clear_conf(unit['temp_dir'] + '/control.unit.sock')
@@ -196,8 +217,6 @@ def pytest_sessionstart(session):
if option.restart:
shutil.rmtree(unit_instance['temp_dir'])
- elif option.save_log:
- open(unit_instance['temp_dir'] + '/' + unit_log_copy, 'w').close()
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
@@ -248,7 +267,6 @@ def check_prerequisites(request):
@pytest.fixture(autouse=True)
def run(request):
unit = unit_run()
- option.temp_dir = unit['temp_dir']
option.skip_alerts = [
r'read signalfd\(4\) failed',
@@ -257,6 +275,10 @@ def run(request):
]
option.skip_sanitizer = False
+ _fds_check['main']['skip'] = False
+ _fds_check['router']['skip'] = False
+ _fds_check['controller']['skip'] = False
+
yield
# stop unit
@@ -266,44 +288,83 @@ def run(request):
# prepare log
- with open(
- unit_instance['log'], 'r', encoding='utf-8', errors='ignore'
- ) as f:
+ with Log.open(encoding='utf-8') as f:
log = f.read()
-
- if not option.restart and option.save_log:
- with open(unit_instance['temp_dir'] + '/' + unit_log_copy, 'a') as f:
- f.write(log)
-
- # remove unit.log
+ Log.set_pos(f.tell())
if not option.save_log and option.restart:
shutil.rmtree(unit['temp_dir'])
+ Log.set_pos(0)
# clean temp_dir before the next test
if not option.restart:
_clear_conf(unit['temp_dir'] + '/control.unit.sock', log)
- open(unit['log'], 'w').close()
-
for item in os.listdir(unit['temp_dir']):
if item not in [
'control.unit.sock',
'state',
'unit.pid',
'unit.log',
- unit_log_copy,
]:
path = os.path.join(unit['temp_dir'], item)
public_dir(path)
- if os.path.isfile(path) or stat.S_ISSOCK(os.stat(path).st_mode):
+ if os.path.isfile(path) or stat.S_ISSOCK(
+ os.stat(path).st_mode
+ ):
os.remove(path)
else:
shutil.rmtree(path)
+ # check descriptors (wait for some time before check)
+
+ def waitforfds(diff):
+ for i in range(600):
+ fds_diff = diff()
+
+ if fds_diff <= option.fds_threshold:
+ break
+
+ time.sleep(0.1)
+
+ return fds_diff
+
+ ps = _fds_check['main']
+ if not ps['skip']:
+ fds_diff = waitforfds(
+ lambda: _count_fds(unit_instance['pid']) - ps['fds']
+ )
+ ps['fds'] += fds_diff
+
+ assert (
+ fds_diff <= option.fds_threshold
+ ), 'descriptors leak main process'
+
+ else:
+ ps['fds'] = _count_fds(unit_instance['pid'])
+
+ for name in ['controller', 'router']:
+ ps = _fds_check[name]
+ ps_pid = ps['pid']
+ ps['pid'] = pid_by_name(ps['name'])
+
+ if not ps['skip']:
+ fds_diff = waitforfds(lambda: _count_fds(ps['pid']) - ps['fds'])
+ ps['fds'] += fds_diff
+
+ if not option.restart:
+ assert ps['pid'] == ps_pid, 'same pid %s' % name
+
+ assert fds_diff <= option.fds_threshold, (
+ 'descriptors leak %s' % name
+ )
+
+ else:
+ ps['fds'] = _count_fds(ps['pid'])
+
# print unit.log in case of error
if hasattr(request.node, 'rep_call') and request.node.rep_call.failed:
@@ -319,6 +380,7 @@ def run(request):
_check_alerts(log=log)
+
def unit_run():
global unit_instance
@@ -367,10 +429,27 @@ def unit_run():
exit('Could not start unit')
unit_instance['temp_dir'] = temp_dir
- unit_instance['log'] = temp_dir + '/unit.log'
unit_instance['control_sock'] = temp_dir + '/control.unit.sock'
unit_instance['unitd'] = unitd
+ option.temp_dir = temp_dir
+ Log.temp_dir = temp_dir
+
+ with open(temp_dir + '/unit.pid', 'r') as f:
+ unit_instance['pid'] = f.read().rstrip()
+
+ _clear_conf(unit_instance['temp_dir'] + '/control.unit.sock')
+
+ _fds_check['main']['fds'] = _count_fds(unit_instance['pid'])
+
+ router = _fds_check['router']
+ router['pid'] = pid_by_name(router['name'])
+ router['fds'] = _count_fds(router['pid'])
+
+ controller = _fds_check['controller']
+ controller['pid'] = pid_by_name(controller['name'])
+ controller['fds'] = _count_fds(controller['pid'])
+
return unit_instance
@@ -402,13 +481,9 @@ def unit_stop():
return 'Could not terminate unit'
-
-def _check_alerts(path=None, log=None):
- if path is None:
- path = unit_instance['log']
-
+def _check_alerts(log=None):
if log is None:
- with open(path, 'r', encoding='utf-8', errors='ignore') as f:
+ with Log.open(encoding='utf-8') as f:
log = f.read()
found = False
@@ -440,7 +515,7 @@ def _check_alerts(path=None, log=None):
def _print_log(data=None):
- path = unit_instance['log']
+ path = Log.get_path()
print('Path to unit.log:\n' + path + '\n')
@@ -474,24 +549,47 @@ def _clear_conf(sock, log=None):
return
try:
- certs = json.loads(http.get(
- url='/certificates',
- sock_type='unix',
- addr=sock,
- )['body']).keys()
+ certs = json.loads(
+ http.get(url='/certificates', sock_type='unix', addr=sock,)['body']
+ ).keys()
except json.JSONDecodeError:
pytest.fail('Can\'t parse certificates list.')
for cert in certs:
resp = http.delete(
- url='/certificates/' + cert,
- sock_type='unix',
- addr=sock,
+ url='/certificates/' + cert, sock_type='unix', addr=sock,
)['body']
check_success(resp)
+
+def _count_fds(pid):
+ procfile = '/proc/%s/fd' % pid
+ if os.path.isdir(procfile):
+ return len(os.listdir(procfile))
+
+ try:
+ out = subprocess.check_output(
+ ['procstat', '-f', pid], stderr=subprocess.STDOUT,
+ ).decode()
+ return len(out.splitlines())
+
+ except (FileNotFoundError, TypeError, subprocess.CalledProcessError):
+ pass
+
+ try:
+ out = subprocess.check_output(
+ ['lsof', '-n', '-p', pid], stderr=subprocess.STDOUT,
+ ).decode()
+ return len(out.splitlines())
+
+ except (FileNotFoundError, TypeError, subprocess.CalledProcessError):
+ pass
+
+ return 0
+
+
def run_process(target, *args):
global _processes
@@ -500,6 +598,7 @@ def run_process(target, *args):
_processes.append(process)
+
def stop_processes():
if not _processes:
return
@@ -517,6 +616,18 @@ def stop_processes():
return 'Fail to stop process(es)'
+def pid_by_name(name):
+ output = subprocess.check_output(['ps', 'ax', '-O', 'ppid']).decode()
+ m = re.search(
+ r'\s*(\d+)\s*' + str(unit_instance['pid']) + r'.*' + name, output
+ )
+ return None if m is None else m.group(1)
+
+
+def find_proc(name, ps_output):
+ return re.findall(str(unit_instance['pid']) + r'.*' + name, ps_output)
+
+
@pytest.fixture()
def skip_alert():
def _skip(*alerts):
@@ -525,27 +636,47 @@ def skip_alert():
return _skip
+@pytest.fixture()
+def skip_fds_check():
+ def _skip(main=False, router=False, controller=False):
+ _fds_check['main']['skip'] = main
+ _fds_check['router']['skip'] = router
+ _fds_check['controller']['skip'] = controller
+
+ return _skip
+
+
@pytest.fixture
def temp_dir(request):
return unit_instance['temp_dir']
+
@pytest.fixture
def is_unsafe(request):
return request.config.getoption("--unsafe")
+
@pytest.fixture
def is_su(request):
return os.geteuid() == 0
+
@pytest.fixture
def unit_pid(request):
return unit_instance['process'].pid
+
def pytest_sessionfinish(session):
if not option.restart and option.save_log:
- print('Path to unit.log:\n' + unit_instance['log'] + '\n')
+ print('Path to unit.log:\n' + Log.get_path() + '\n')
option.restart = True
unit_stop()
+
+ public_dir(option.cache_dir)
shutil.rmtree(option.cache_dir)
+
+ if not option.save_log and os.path.isdir(option.temp_dir):
+ public_dir(option.temp_dir)
+ shutil.rmtree(option.temp_dir)
diff --git a/test/node/404/app.js b/test/node/404/app.js
index 587c432d..ba15c104 100755..100644
--- a/test/node/404/app.js
+++ b/test/node/404/app.js
@@ -1,7 +1,6 @@
-#!/usr/bin/env node
var fs = require('fs');
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
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 7820c474..9092022c 100755..100644
--- a/test/node/basic/app.js
+++ b/test/node/basic/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
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 63912097..653e33b1 100755..100644
--- a/test/node/double_end/app.js
+++ b/test/node/double_end/app.js
@@ -1,5 +1,4 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.end().end();
}).listen(7080);
diff --git a/test/node/get_header_names/app.js b/test/node/get_header_names/app.js
index 4cbccc16..a938b762 100755..100644
--- a/test/node/get_header_names/app.js
+++ b/test/node/get_header_names/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.setHeader('DATE', ['date1', 'date2']);
res.setHeader('X-Header', 'blah');
res.setHeader('X-Names', res.getHeaderNames());
diff --git a/test/node/get_header_type/app.js b/test/node/get_header_type/app.js
index b606f142..6e45b71f 100755..100644
--- a/test/node/get_header_type/app.js
+++ b/test/node/get_header_type/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.setHeader('X-Number', 100);
res.setHeader('X-Type', typeof(res.getHeader('X-Number')));
res.end();
diff --git a/test/node/get_variables/app.js b/test/node/get_variables/app.js
index 5c1faf41..cded43d2 100755..100644
--- a/test/node/get_variables/app.js
+++ b/test/node/get_variables/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
let query = require('url').parse(req.url, true).query;
res.setHeader('X-Var-1', query.var1);
res.setHeader('X-Var-2', query.var2);
diff --git a/test/node/has_header/app.js b/test/node/has_header/app.js
index eff7f4ff..04b13916 100755..100644
--- a/test/node/has_header/app.js
+++ b/test/node/has_header/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.setHeader('X-Has-Header', res.hasHeader(req.headers['x-header']) + '');
res.end();
}).listen(7080);
diff --git a/test/node/header_name_case/app.js b/test/node/header_name_case/app.js
index 490bd4d5..af157547 100755..100644
--- a/test/node/header_name_case/app.js
+++ b/test/node/header_name_case/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.setHeader('X-Header', '1');
res.setHeader('X-header', '2');
res.setHeader('X-HEADER', '3');
diff --git a/test/node/header_name_valid/app.js b/test/node/header_name_valid/app.js
index 425f026f..c0c36098 100755..100644
--- a/test/node/header_name_valid/app.js
+++ b/test/node/header_name_valid/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.writeHead(200, {});
res.setHeader('@$', 'test');
res.end();
diff --git a/test/node/header_value_object/app.js b/test/node/header_value_object/app.js
index ff4e2bb0..bacdc7d5 100755..100644
--- a/test/node/header_value_object/app.js
+++ b/test/node/header_value_object/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.setHeader('X-Header', {});
res.end();
}).listen(7080);
diff --git a/test/node/loader/es_modules_http/app.mjs b/test/node/loader/es_modules_http/app.mjs
new file mode 100644
index 00000000..c7bcfe49
--- /dev/null
+++ b/test/node/loader/es_modules_http/app.mjs
@@ -0,0 +1,6 @@
+import http from "http"
+
+http.createServer(function (req, res) {
+ res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'})
+ .end('Hello World\n');
+}).listen(7080);
diff --git a/test/node/loader/es_modules_http_indirect/app.js b/test/node/loader/es_modules_http_indirect/app.js
new file mode 100644
index 00000000..535befba
--- /dev/null
+++ b/test/node/loader/es_modules_http_indirect/app.js
@@ -0,0 +1 @@
+import("./module.mjs")
diff --git a/test/node/loader/es_modules_http_indirect/module.mjs b/test/node/loader/es_modules_http_indirect/module.mjs
new file mode 100644
index 00000000..c7bcfe49
--- /dev/null
+++ b/test/node/loader/es_modules_http_indirect/module.mjs
@@ -0,0 +1,6 @@
+import http from "http"
+
+http.createServer(function (req, res) {
+ res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'})
+ .end('Hello World\n');
+}).listen(7080);
diff --git a/test/node/loader/es_modules_websocket/app.mjs b/test/node/loader/es_modules_websocket/app.mjs
new file mode 100644
index 00000000..a71ffa9d
--- /dev/null
+++ b/test/node/loader/es_modules_websocket/app.mjs
@@ -0,0 +1,30 @@
+import http from "http"
+import websocket from "websocket"
+
+let server = http.createServer(function() {});
+let webSocketServer = 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/loader/es_modules_websocket_indirect/app.js b/test/node/loader/es_modules_websocket_indirect/app.js
new file mode 100644
index 00000000..535befba
--- /dev/null
+++ b/test/node/loader/es_modules_websocket_indirect/app.js
@@ -0,0 +1 @@
+import("./module.mjs")
diff --git a/test/node/loader/es_modules_websocket_indirect/module.mjs b/test/node/loader/es_modules_websocket_indirect/module.mjs
new file mode 100644
index 00000000..a71ffa9d
--- /dev/null
+++ b/test/node/loader/es_modules_websocket_indirect/module.mjs
@@ -0,0 +1,30 @@
+import http from "http"
+import websocket from "websocket"
+
+let server = http.createServer(function() {});
+let webSocketServer = 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/loader/transitive_dependency/app.js b/test/node/loader/transitive_dependency/app.js
new file mode 100644
index 00000000..aaca5216
--- /dev/null
+++ b/test/node/loader/transitive_dependency/app.js
@@ -0,0 +1 @@
+require("./transitive_http")
diff --git a/test/node/loader/transitive_dependency/transitive_http.js b/test/node/loader/transitive_dependency/transitive_http.js
new file mode 100644
index 00000000..f1eb98e5
--- /dev/null
+++ b/test/node/loader/transitive_dependency/transitive_http.js
@@ -0,0 +1,8 @@
+const http = require("http");
+
+http.createServer(function (req, res) {
+ res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'})
+ .end('Hello World\n');
+}).listen(7080);
+
+module.exports = http;
diff --git a/test/node/loader/unit_http/app.js b/test/node/loader/unit_http/app.js
new file mode 100644
index 00000000..9172e44f
--- /dev/null
+++ b/test/node/loader/unit_http/app.js
@@ -0,0 +1,4 @@
+require("unit-http").createServer(function (req, res) {
+ res.writeHead(200, {'Content-Length': 12, 'Content-Type': 'text/plain'})
+ .end('Hello World\n');
+}).listen(7080);
diff --git a/test/node/mirror/app.js b/test/node/mirror/app.js
index 1488917e..bdefe1cd 100755..100644
--- a/test/node/mirror/app.js
+++ b/test/node/mirror/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
diff --git a/test/node/post_variables/app.js b/test/node/post_variables/app.js
index 928a38cf..12b867cb 100755..100644
--- a/test/node/post_variables/app.js
+++ b/test/node/post_variables/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
diff --git a/test/node/promise_end/app.js b/test/node/promise_end/app.js
index ed22464c..373c3bc6 100755..100644
--- a/test/node/promise_end/app.js
+++ b/test/node/promise_end/app.js
@@ -1,8 +1,7 @@
-#!/usr/bin/env node
var fs = require('fs');
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.write('blah');
Promise.resolve().then(() => {
diff --git a/test/node/promise_handler/app.js b/test/node/promise_handler/app.js
index 51c3666b..32d7d7b9 100755..100644
--- a/test/node/promise_handler/app.js
+++ b/test/node/promise_handler/app.js
@@ -1,8 +1,7 @@
-#!/usr/bin/env node
var fs = require('fs');
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.end();
if (req.headers['x-write-call']) {
diff --git a/test/node/remove_header/app.js b/test/node/remove_header/app.js
index cd7b80c3..2a591235 100755..100644
--- a/test/node/remove_header/app.js
+++ b/test/node/remove_header/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.setHeader('X-Header', 'test');
res.setHeader('Was-Header', res.hasHeader('X-Header').toString());
diff --git a/test/node/set_header_array/app.js b/test/node/set_header_array/app.js
index faac45c7..965330e2 100755..100644
--- a/test/node/set_header_array/app.js
+++ b/test/node/set_header_array/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.setHeader('Set-Cookie', ['tc=one,two,three', 'tc=four,five,six']);
res.end();
}).listen(7080);
diff --git a/test/node/status_message/app.js b/test/node/status_message/app.js
index e8a798dd..ba51d35b 100755..100644
--- a/test/node/status_message/app.js
+++ b/test/node/status_message/app.js
@@ -1,5 +1,4 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.writeHead(200, 'blah', {'Content-Type': 'text/plain'}).end();
}).listen(7080);
diff --git a/test/node/update_header/app.js b/test/node/update_header/app.js
index 0c5cd237..905ac294 100755..100644
--- a/test/node/update_header/app.js
+++ b/test/node/update_header/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.setHeader('X-Header', 'test');
res.setHeader('X-Header', 'new');
res.end();
diff --git a/test/node/variables/app.js b/test/node/variables/app.js
index d8cdc20c..a569dddd 100755..100644
--- a/test/node/variables/app.js
+++ b/test/node/variables/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
diff --git a/test/node/websockets/mirror/app.js b/test/node/websockets/mirror/app.js
index 23746465..0443adb2 100755..100644
--- a/test/node/websockets/mirror/app.js
+++ b/test/node/websockets/mirror/app.js
@@ -1,9 +1,6 @@
-#!/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 = require('http').createServer(function() {});
+webSocketServer = require('websocket').server;
server.listen(7080, function() {});
diff --git a/test/node/websockets/mirror_fragmentation/app.js b/test/node/websockets/mirror_fragmentation/app.js
index 7024252a..ea580ac2 100755..100644
--- a/test/node/websockets/mirror_fragmentation/app.js
+++ b/test/node/websockets/mirror_fragmentation/app.js
@@ -1,9 +1,6 @@
-#!/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 = require('http').createServer(function() {});
+webSocketServer = require('websocket').server;
server.listen(7080, function() {});
diff --git a/test/node/write_before_write_head/app.js b/test/node/write_before_write_head/app.js
index 724b0efb..2293111a 100755..100644
--- a/test/node/write_before_write_head/app.js
+++ b/test/node/write_before_write_head/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.write('blah');
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 a7623523..506e8613 100755..100644
--- a/test/node/write_buffer/app.js
+++ b/test/node/write_buffer/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'})
.end(new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]));
}).listen(7080);
diff --git a/test/node/write_callback/app.js b/test/node/write_callback/app.js
index 3a9e51e8..71eb4116 100755..100644
--- a/test/node/write_callback/app.js
+++ b/test/node/write_callback/app.js
@@ -1,8 +1,7 @@
-#!/usr/bin/env node
var fs = require('fs');
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
var a = 'world';
res.write('hello', 'utf8', function() {
diff --git a/test/node/write_multiple/app.js b/test/node/write_multiple/app.js
index 3cbb3b86..e9c51ae0 100755..100644
--- a/test/node/write_multiple/app.js
+++ b/test/node/write_multiple/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain', 'Content-Length': 14});
res.write('write');
res.write('write2');
diff --git a/test/node/write_return/app.js b/test/node/write_return/app.js
index 82dfbc6e..345b6c4b 100755..100644
--- a/test/node/write_return/app.js
+++ b/test/node/write_return/app.js
@@ -1,6 +1,5 @@
-#!/usr/bin/env node
-require('unit-http').createServer(function (req, res) {
+require('http').createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'})
.end(res.write('body').toString());
}).listen(7080);
diff --git a/test/php/fastcgi_finish_request/index.php b/test/php/fastcgi_finish_request/index.php
index a6211303..fbfae71b 100644
--- a/test/php/fastcgi_finish_request/index.php
+++ b/test/php/fastcgi_finish_request/index.php
@@ -8,4 +8,6 @@ if (!fastcgi_finish_request()) {
}
echo "4567";
+
+include 'server.php';
?>
diff --git a/test/php/fastcgi_finish_request/server.php b/test/php/fastcgi_finish_request/server.php
new file mode 100644
index 00000000..af71c5f2
--- /dev/null
+++ b/test/php/fastcgi_finish_request/server.php
@@ -0,0 +1,4 @@
+<?php
+echo $_SERVER['REQUEST_METHOD'];
+echo $_SERVER['HTTP_HOST'];
+?>
diff --git a/test/python/atexit/wsgi.py b/test/python/atexit/wsgi.py
index a5a9918d..b64b8477 100644
--- a/test/python/atexit/wsgi.py
+++ b/test/python/atexit/wsgi.py
@@ -1,5 +1,6 @@
import atexit
+
def application(environ, start_response):
def at_exit():
environ['wsgi.errors'].write('At exit called.\n')
diff --git a/test/python/body_generate/wsgi.py b/test/python/body_generate/wsgi.py
new file mode 100644
index 00000000..73462be6
--- /dev/null
+++ b/test/python/body_generate/wsgi.py
@@ -0,0 +1,6 @@
+def application(env, start_response):
+ length = env.get('HTTP_X_LENGTH', '10')
+ bytes = b'X' * int(length)
+
+ start_response('200', [('Content-Length', length)])
+ return [bytes]
diff --git a/test/python/body_io/wsgi.py b/test/python/body_io/wsgi.py
index 14303b5f..58ce76a4 100644
--- a/test/python/body_io/wsgi.py
+++ b/test/python/body_io/wsgi.py
@@ -1,5 +1,6 @@
import io
+
def application(env, start_response):
start_response('200', [('Content-Length', '10')])
f = io.BytesIO(b'0123456789')
diff --git a/test/python/callable/wsgi.py b/test/python/callable/wsgi.py
index 365f82fa..374ecb28 100644
--- a/test/python/callable/wsgi.py
+++ b/test/python/callable/wsgi.py
@@ -2,6 +2,7 @@ def application(env, start_response):
start_response('204', [('Content-Length', '0')])
return []
+
def app(env, start_response):
start_response('200', [('Content-Length', '0')])
return []
diff --git a/test/python/ctx_iter_atexit/wsgi.py b/test/python/ctx_iter_atexit/wsgi.py
index d0b33daa..b2f12c35 100644
--- a/test/python/ctx_iter_atexit/wsgi.py
+++ b/test/python/ctx_iter_atexit/wsgi.py
@@ -1,5 +1,6 @@
import atexit
+
class application:
def __init__(self, environ, start_response):
self.environ = environ
@@ -11,13 +12,14 @@ class application:
content_length = int(self.environ.get('CONTENT_LENGTH', 0))
body = bytes(self.environ['wsgi.input'].read(content_length))
- self.start('200', [
- ('Content-Type', self.environ.get('CONTENT_TYPE')),
- ('Content-Length', str(len(body)))
- ])
+ self.start(
+ '200',
+ [
+ ('Content-Type', self.environ.get('CONTENT_TYPE')),
+ ('Content-Length', str(len(body))),
+ ],
+ )
yield body
def _atexit(self):
- self.start('200', [
- ('Content-Length', '0')
- ])
+ self.start('200', [('Content-Length', '0')])
diff --git a/test/python/custom_header/wsgi.py b/test/python/custom_header/wsgi.py
index 44f145d1..44fc2af5 100644
--- a/test/python/custom_header/wsgi.py
+++ b/test/python/custom_header/wsgi.py
@@ -1,7 +1,10 @@
def application(environ, start_response):
- start_response('200', [
- ('Content-Length', '0'),
- ('Custom-Header', environ.get('HTTP_CUSTOM_HEADER'))
- ])
+ start_response(
+ '200',
+ [
+ ('Content-Length', '0'),
+ ('Custom-Header', environ.get('HTTP_CUSTOM_HEADER')),
+ ],
+ )
return []
diff --git a/test/python/delayed/asgi.py b/test/python/delayed/asgi.py
index d5cad929..1cb15a92 100644
--- a/test/python/delayed/asgi.py
+++ b/test/python/delayed/asgi.py
@@ -1,5 +1,6 @@
import asyncio
+
async def application(scope, receive, send):
assert scope['type'] == 'http'
@@ -28,13 +29,13 @@ async def application(scope, receive, send):
loop.call_later(n, future.set_result, None)
await future
- await send({
- 'type': 'http.response.start',
- 'status': 200,
- 'headers': [
- (b'content-length', str(len(body)).encode()),
- ]
- })
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [(b'content-length', str(len(body)).encode()),],
+ }
+ )
if not body:
await sleep(delay)
@@ -42,10 +43,12 @@ async def application(scope, receive, send):
step = int(len(body) / parts)
for i in range(0, len(body), step):
- await send({
- 'type': 'http.response.body',
- 'body': body[i : i + step],
- 'more_body': True,
- })
+ await send(
+ {
+ 'type': 'http.response.body',
+ 'body': body[i : i + step],
+ 'more_body': True,
+ }
+ )
await sleep(delay)
diff --git a/test/python/empty/asgi.py b/test/python/empty/asgi.py
index 58b7c1f2..14a40629 100644
--- a/test/python/empty/asgi.py
+++ b/test/python/empty/asgi.py
@@ -1,10 +1,10 @@
async def application(scope, receive, send):
assert scope['type'] == 'http'
- await send({
- 'type': 'http.response.start',
- 'status': 200,
- 'headers': [
- (b'content-length', b'0'),
- ]
- })
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [(b'content-length', b'0')],
+ }
+ )
diff --git a/test/python/environment/wsgi.py b/test/python/environment/wsgi.py
index fa3a1d2b..d1564f29 100644
--- a/test/python/environment/wsgi.py
+++ b/test/python/environment/wsgi.py
@@ -1,5 +1,6 @@
import os
+
def application(env, start_response):
body = ''
vars = env.get('HTTP_X_VARIABLES').split(',')
diff --git a/test/python/header_fields/wsgi.py b/test/python/header_fields/wsgi.py
index bd1ba0e2..41144528 100644
--- a/test/python/header_fields/wsgi.py
+++ b/test/python/header_fields/wsgi.py
@@ -2,8 +2,7 @@ def application(environ, start_response):
h = (k for k, v in environ.items() if k.startswith('HTTP_'))
- start_response('200', [
- ('Content-Length', '0'),
- ('All-Headers', ','.join(h))
- ])
+ start_response(
+ '200', [('Content-Length', '0'), ('All-Headers', ','.join(h))]
+ )
return []
diff --git a/test/python/host/wsgi.py b/test/python/host/wsgi.py
index db7de306..0a08bc36 100644
--- a/test/python/host/wsgi.py
+++ b/test/python/host/wsgi.py
@@ -1,7 +1,10 @@
def application(env, start_response):
- start_response('200', [
- ('Content-Length', '0'),
- ('X-Server-Name', env.get('SERVER_NAME')),
- ('X-Http-Host', str(env.get('HTTP_HOST')))
- ])
+ start_response(
+ '200',
+ [
+ ('Content-Length', '0'),
+ ('X-Server-Name', env.get('SERVER_NAME')),
+ ('X-Http-Host', str(env.get('HTTP_HOST'))),
+ ],
+ )
return []
diff --git a/test/python/iter_exception/wsgi.py b/test/python/iter_exception/wsgi.py
index 66a09af7..2779a845 100644
--- a/test/python/iter_exception/wsgi.py
+++ b/test/python/iter_exception/wsgi.py
@@ -8,7 +8,9 @@ class application:
def __iter__(self):
self.__i = 0
self._skip_level = int(self.environ.get('HTTP_X_SKIP', 0))
- self._not_skip_close = int(self.environ.get('HTTP_X_NOT_SKIP_CLOSE', 0))
+ self._not_skip_close = int(
+ self.environ.get('HTTP_X_NOT_SKIP_CLOSE', 0)
+ )
self._is_chunked = self.environ.get('HTTP_X_CHUNKED')
headers = [(('Content-Length', '10'))]
diff --git a/test/python/legacy/asgi.py b/test/python/legacy/asgi.py
index f065d026..1d45cc4f 100644
--- a/test/python/legacy/asgi.py
+++ b/test/python/legacy/asgi.py
@@ -3,11 +3,12 @@ def application(scope):
return app_http
+
async def app_http(receive, send):
- await send({
- 'type': 'http.response.start',
- 'status': 200,
- 'headers': [
- (b'content-length', b'0'),
- ]
- })
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [(b'content-length', b'0'),],
+ }
+ )
diff --git a/test/python/legacy_force/asgi.py b/test/python/legacy_force/asgi.py
index 2e5859f2..ad2785f2 100644
--- a/test/python/legacy_force/asgi.py
+++ b/test/python/legacy_force/asgi.py
@@ -7,11 +7,12 @@ def application(scope, receive=None, send=None):
else:
return app_http(receive, send)
+
async def app_http(receive, send):
- await send({
- 'type': 'http.response.start',
- 'status': 200,
- 'headers': [
- (b'content-length', b'0'),
- ]
- })
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [(b'content-length', b'0'),],
+ }
+ )
diff --git a/test/python/lifespan/empty/asgi.py b/test/python/lifespan/empty/asgi.py
index ea43af13..8ceecc2f 100644
--- a/test/python/lifespan/empty/asgi.py
+++ b/test/python/lifespan/empty/asgi.py
@@ -1,19 +1,19 @@
import os
-async def application(scope, receive, send):
+async def handler(prefix, scope, receive, send):
if scope['type'] == 'lifespan':
- with open('version', 'w+') as f:
+ with open(prefix + 'version', 'w+') as f:
f.write(
scope['asgi']['version'] + ' ' + scope['asgi']['spec_version']
)
while True:
message = await receive()
if message['type'] == 'lifespan.startup':
- os.remove('startup')
+ os.remove(prefix + 'startup')
await send({'type': 'lifespan.startup.complete'})
elif message['type'] == 'lifespan.shutdown':
- os.remove('shutdown')
+ os.remove(prefix + 'shutdown')
await send({'type': 'lifespan.shutdown.complete'})
return
@@ -25,3 +25,11 @@ async def application(scope, receive, send):
'headers': [(b'content-length', b'0'),],
}
)
+
+
+async def application(scope, receive, send):
+ return await handler('', scope, receive, send)
+
+
+async def application2(scope, receive, send):
+ return await handler('app2_', scope, receive, send)
diff --git a/test/python/mirror/asgi.py b/test/python/mirror/asgi.py
index 7088e893..18a66a4c 100644
--- a/test/python/mirror/asgi.py
+++ b/test/python/mirror/asgi.py
@@ -8,15 +8,12 @@ async def application(scope, receive, send):
if not m.get('more_body', False):
break
- await send({
- 'type': 'http.response.start',
- 'status': 200,
- 'headers': [
- (b'content-length', str(len(body)).encode()),
- ]
- })
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [(b'content-length', str(len(body)).encode())],
+ }
+ )
- await send({
- 'type': 'http.response.body',
- 'body': body,
- })
+ await send({'type': 'http.response.body', 'body': body})
diff --git a/test/python/mirror/wsgi.py b/test/python/mirror/wsgi.py
index eb1fb922..3cd95437 100644
--- a/test/python/mirror/wsgi.py
+++ b/test/python/mirror/wsgi.py
@@ -3,7 +3,5 @@ def application(environ, start_response):
content_length = int(environ.get('CONTENT_LENGTH', 0))
body = bytes(environ['wsgi.input'].read(content_length))
- start_response('200', [
- ('Content-Length', str(len(body)))
- ])
+ start_response('200', [('Content-Length', str(len(body)))])
return [body]
diff --git a/test/python/path/wsgi.py b/test/python/path/wsgi.py
index 2807f6ef..da7e1ff1 100644
--- a/test/python/path/wsgi.py
+++ b/test/python/path/wsgi.py
@@ -1,6 +1,7 @@
import os
import sys
+
def application(environ, start_response):
body = os.pathsep.join(sys.path).encode()
diff --git a/test/python/query_string/asgi.py b/test/python/query_string/asgi.py
index 28f4d107..5b659f9c 100644
--- a/test/python/query_string/asgi.py
+++ b/test/python/query_string/asgi.py
@@ -1,11 +1,13 @@
async def application(scope, receive, send):
assert scope['type'] == 'http'
- await send({
- 'type': 'http.response.start',
- 'status': 200,
- 'headers': [
- (b'content-length', b'0'),
- (b'query-string', scope['query_string']),
- ]
- })
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [
+ (b'content-length', b'0'),
+ (b'query-string', scope['query_string']),
+ ],
+ }
+ )
diff --git a/test/python/query_string/wsgi.py b/test/python/query_string/wsgi.py
index 90f1c7ec..54a67b03 100644
--- a/test/python/query_string/wsgi.py
+++ b/test/python/query_string/wsgi.py
@@ -1,7 +1,10 @@
def application(environ, start_response):
- start_response('200', [
- ('Content-Length', '0'),
- ('Query-String', environ.get('QUERY_STRING'))
- ])
+ start_response(
+ '200',
+ [
+ ('Content-Length', '0'),
+ ('Query-String', environ.get('QUERY_STRING')),
+ ],
+ )
return []
diff --git a/test/python/server_port/asgi.py b/test/python/server_port/asgi.py
index e79ced00..810a182c 100644
--- a/test/python/server_port/asgi.py
+++ b/test/python/server_port/asgi.py
@@ -1,11 +1,13 @@
async def application(scope, receive, send):
assert scope['type'] == 'http'
- await send({
- 'type': 'http.response.start',
- 'status': 200,
- 'headers': [
- (b'content-length', b'0'),
- (b'server-port', str(scope['server'][1]).encode()),
- ]
- })
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [
+ (b'content-length', b'0'),
+ (b'server-port', str(scope['server'][1]).encode()),
+ ],
+ }
+ )
diff --git a/test/python/server_port/wsgi.py b/test/python/server_port/wsgi.py
index 89cd82b3..c796da95 100644
--- a/test/python/server_port/wsgi.py
+++ b/test/python/server_port/wsgi.py
@@ -1,7 +1,7 @@
def application(environ, start_response):
- start_response('200', [
- ('Content-Length', '0'),
- ('Server-Port', environ.get('SERVER_PORT'))
- ])
+ start_response(
+ '200',
+ [('Content-Length', '0'), ('Server-Port', environ.get('SERVER_PORT'))],
+ )
return []
diff --git a/test/python/targets/asgi.py b/test/python/targets/asgi.py
new file mode 100644
index 00000000..b51f3964
--- /dev/null
+++ b/test/python/targets/asgi.py
@@ -0,0 +1,54 @@
+async def application_201(scope, receive, send):
+ assert scope['type'] == 'http'
+
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 201,
+ 'headers': [(b'content-length', b'0')],
+ }
+ )
+
+
+async def application_200(scope, receive, send):
+ assert scope['type'] == 'http'
+
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [(b'content-length', b'0')],
+ }
+ )
+
+
+def legacy_application_200(scope):
+ assert scope['type'] == 'http'
+
+ return legacy_app_http_200
+
+
+async def legacy_app_http_200(receive, send):
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [(b'content-length', b'0')],
+ }
+ )
+
+
+def legacy_application_201(scope, receive=None, send=None):
+ assert scope['type'] == 'http'
+
+ return legacy_app_http_201
+
+
+async def legacy_app_http_201(receive, send):
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 201,
+ 'headers': [(b'content-length', b'0')],
+ }
+ )
diff --git a/test/python/targets/wsgi.py b/test/python/targets/wsgi.py
new file mode 100644
index 00000000..fa17ab87
--- /dev/null
+++ b/test/python/targets/wsgi.py
@@ -0,0 +1,8 @@
+def wsgi_target_a(env, start_response):
+ start_response('200', [('Content-Length', '1')])
+ return [b'1']
+
+
+def wsgi_target_b(env, start_response):
+ start_response('200', [('Content-Length', '1')])
+ return [b'2']
diff --git a/test/python/threading/asgi.py b/test/python/threading/asgi.py
index 3c978e50..c4169a24 100644
--- a/test/python/threading/asgi.py
+++ b/test/python/threading/asgi.py
@@ -33,10 +33,10 @@ async def application(scope, receive, send):
Foo(Foo.num).start()
Foo.num += 10
- await send({
- 'type': 'http.response.start',
- 'status': 200,
- 'headers': [
- (b'content-length', b'0'),
- ]
- })
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [(b'content-length', b'0')],
+ }
+ )
diff --git a/test/python/threads/asgi.py b/test/python/threads/asgi.py
index d51ae431..ff4e52ad 100644
--- a/test/python/threads/asgi.py
+++ b/test/python/threads/asgi.py
@@ -2,6 +2,7 @@ import asyncio
import time
import threading
+
async def application(scope, receive, send):
assert scope['type'] == 'http'
@@ -17,11 +18,13 @@ async def application(scope, receive, send):
time.sleep(delay)
- await send({
- 'type': 'http.response.start',
- 'status': 200,
- 'headers': [
- (b'content-length', b'0'),
- (b'x-thread', str(threading.currentThread().ident).encode()),
- ]
- })
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [
+ (b'content-length', b'0'),
+ (b'x-thread', str(threading.currentThread().ident).encode()),
+ ],
+ }
+ )
diff --git a/test/python/threads/wsgi.py b/test/python/threads/wsgi.py
index 1cc8ffe2..cc283cfe 100644
--- a/test/python/threads/wsgi.py
+++ b/test/python/threads/wsgi.py
@@ -1,15 +1,19 @@
import time
import threading
+
def application(environ, start_response):
delay = float(environ.get('HTTP_X_DELAY', 0))
time.sleep(delay)
- start_response('200', [
- ('Content-Length', '0'),
- ('Wsgi-Multithread', str(environ['wsgi.multithread'])),
- ('X-Thread', str(threading.currentThread().ident))
- ])
+ start_response(
+ '200',
+ [
+ ('Content-Length', '0'),
+ ('Wsgi-Multithread', str(environ['wsgi.multithread'])),
+ ('X-Thread', str(threading.currentThread().ident)),
+ ],
+ )
return []
diff --git a/test/python/upload/wsgi.py b/test/python/upload/wsgi.py
index 37ee89eb..953c5ecc 100644
--- a/test/python/upload/wsgi.py
+++ b/test/python/upload/wsgi.py
@@ -1,6 +1,7 @@
from tempfile import TemporaryFile
import os, cgi
+
def read(environ):
length = int(environ.get('CONTENT_LENGTH', 0))
@@ -11,6 +12,7 @@ def read(environ):
environ['wsgi.input'] = body
return body
+
def application(environ, start_response):
file = read(environ)
@@ -19,9 +21,9 @@ def application(environ, start_response):
filename = form['file'].filename
data = filename.encode() + form['file'].file.read()
- start_response('200 OK', [
- ('Content-Type', 'text/plain'),
- ('Content-Length', str(len(data))),
- ])
+ start_response(
+ '200 OK',
+ [('Content-Type', 'text/plain'), ('Content-Length', str(len(data)))],
+ )
return data
diff --git a/test/python/user_group/wsgi.py b/test/python/user_group/wsgi.py
index f5deb87d..4003c064 100644
--- a/test/python/user_group/wsgi.py
+++ b/test/python/user_group/wsgi.py
@@ -1,18 +1,19 @@
import json
import os
+
def application(environ, start_response):
uid = os.geteuid()
gid = os.getegid()
- out = json.dumps({
- 'UID': uid,
- 'GID': gid,
- }).encode('utf-8')
+ out = json.dumps({'UID': uid, 'GID': gid,}).encode('utf-8')
- start_response('200 OK', [
- ('Content-Length', str(len(out))),
- ('Content-Type', 'application/json')
- ])
+ start_response(
+ '200 OK',
+ [
+ ('Content-Length', str(len(out))),
+ ('Content-Type', 'application/json'),
+ ],
+ )
return [out]
diff --git a/test/python/variables/asgi.py b/test/python/variables/asgi.py
index dd1cca72..5a4f55e8 100644
--- a/test/python/variables/asgi.py
+++ b/test/python/variables/asgi.py
@@ -17,24 +17,23 @@ async def application(scope, receive, send):
res.append(h[1])
return b', '.join(res)
- await send({
- 'type': 'http.response.start',
- 'status': 200,
- 'headers': [
- (b'content-type', get_header(b'content-type')),
- (b'content-length', str(len(body)).encode()),
- (b'request-method', scope['method'].encode()),
- (b'request-uri', scope['path'].encode()),
- (b'http-host', get_header(b'host')),
- (b'http-version', scope['http_version'].encode()),
- (b'asgi-version', scope['asgi']['version'].encode()),
- (b'asgi-spec-version', scope['asgi']['spec_version'].encode()),
- (b'scheme', scope['scheme'].encode()),
- (b'custom-header', get_header(b'custom-header')),
- ]
- })
+ await send(
+ {
+ 'type': 'http.response.start',
+ 'status': 200,
+ 'headers': [
+ (b'content-type', get_header(b'content-type')),
+ (b'content-length', str(len(body)).encode()),
+ (b'request-method', scope['method'].encode()),
+ (b'request-uri', scope['path'].encode()),
+ (b'http-host', get_header(b'host')),
+ (b'http-version', scope['http_version'].encode()),
+ (b'asgi-version', scope['asgi']['version'].encode()),
+ (b'asgi-spec-version', scope['asgi']['spec_version'].encode()),
+ (b'scheme', scope['scheme'].encode()),
+ (b'custom-header', get_header(b'custom-header')),
+ ],
+ }
+ )
- await send({
- 'type': 'http.response.body',
- 'body': body,
- })
+ await send({'type': 'http.response.body', 'body': body})
diff --git a/test/python/variables/wsgi.py b/test/python/variables/wsgi.py
index 53991e5e..5d77902d 100644
--- a/test/python/variables/wsgi.py
+++ b/test/python/variables/wsgi.py
@@ -3,19 +3,22 @@ def application(environ, start_response):
content_length = int(environ.get('CONTENT_LENGTH', 0))
body = bytes(environ['wsgi.input'].read(content_length))
- start_response('200', [
- ('Content-Type', environ.get('CONTENT_TYPE')),
- ('Content-Length', str(len(body))),
- ('Request-Method', environ.get('REQUEST_METHOD')),
- ('Request-Uri', environ.get('REQUEST_URI')),
- ('Http-Host', environ.get('HTTP_HOST')),
- ('Server-Protocol', environ.get('SERVER_PROTOCOL')),
- ('Server-Software', environ.get('SERVER_SOFTWARE')),
- ('Custom-Header', environ.get('HTTP_CUSTOM_HEADER')),
- ('Wsgi-Version', str(environ['wsgi.version'])),
- ('Wsgi-Url-Scheme', environ['wsgi.url_scheme']),
- ('Wsgi-Multithread', str(environ['wsgi.multithread'])),
- ('Wsgi-Multiprocess', str(environ['wsgi.multiprocess'])),
- ('Wsgi-Run-Once', str(environ['wsgi.run_once']))
- ])
+ start_response(
+ '200',
+ [
+ ('Content-Type', environ.get('CONTENT_TYPE')),
+ ('Content-Length', str(len(body))),
+ ('Request-Method', environ.get('REQUEST_METHOD')),
+ ('Request-Uri', environ.get('REQUEST_URI')),
+ ('Http-Host', environ.get('HTTP_HOST')),
+ ('Server-Protocol', environ.get('SERVER_PROTOCOL')),
+ ('Server-Software', environ.get('SERVER_SOFTWARE')),
+ ('Custom-Header', environ.get('HTTP_CUSTOM_HEADER')),
+ ('Wsgi-Version', str(environ['wsgi.version'])),
+ ('Wsgi-Url-Scheme', environ['wsgi.url_scheme']),
+ ('Wsgi-Multithread', str(environ['wsgi.multithread'])),
+ ('Wsgi-Multiprocess', str(environ['wsgi.multiprocess'])),
+ ('Wsgi-Run-Once', str(environ['wsgi.run_once'])),
+ ],
+ )
return [body]
diff --git a/test/python/websockets/mirror/asgi.py b/test/python/websockets/mirror/asgi.py
index 0f1d9953..72a32d67 100644
--- a/test/python/websockets/mirror/asgi.py
+++ b/test/python/websockets/mirror/asgi.py
@@ -3,16 +3,16 @@ async def application(scope, receive, send):
while True:
m = await receive()
if m['type'] == 'websocket.connect':
- await send({
- 'type': 'websocket.accept',
- })
+ await send({'type': 'websocket.accept'})
if m['type'] == 'websocket.receive':
- await send({
- 'type': 'websocket.send',
- 'bytes': m.get('bytes', None),
- 'text': m.get('text', None),
- })
+ await send(
+ {
+ 'type': 'websocket.send',
+ 'bytes': m.get('bytes', None),
+ 'text': m.get('text', None),
+ }
+ )
if m['type'] == 'websocket.disconnect':
- break;
+ break
diff --git a/test/python/websockets/subprotocol/asgi.py b/test/python/websockets/subprotocol/asgi.py
index 92263dd7..0385bb9d 100644
--- a/test/python/websockets/subprotocol/asgi.py
+++ b/test/python/websockets/subprotocol/asgi.py
@@ -6,20 +6,24 @@ async def application(scope, receive, send):
if m['type'] == 'websocket.connect':
subprotocols = scope['subprotocols']
- await send({
- 'type': 'websocket.accept',
- 'headers': [
- (b'x-subprotocols', str(subprotocols).encode()),
- ],
- 'subprotocol': subprotocols[0],
- })
+ await send(
+ {
+ 'type': 'websocket.accept',
+ 'headers': [
+ (b'x-subprotocols', str(subprotocols).encode()),
+ ],
+ 'subprotocol': subprotocols[0],
+ }
+ )
if m['type'] == 'websocket.receive':
- await send({
- 'type': 'websocket.send',
- 'bytes': m.get('bytes', None),
- 'text': m.get('text', None),
- })
+ await send(
+ {
+ 'type': 'websocket.send',
+ 'bytes': m.get('bytes', None),
+ 'text': m.get('text', None),
+ }
+ )
if m['type'] == 'websocket.disconnect':
- break;
+ break
diff --git a/test/ruby/encoding/config.ru b/test/ruby/encoding/config.ru
new file mode 100644
index 00000000..60bf0efa
--- /dev/null
+++ b/test/ruby/encoding/config.ru
@@ -0,0 +1,8 @@
+app = Proc.new do |env|
+ ['200', {
+ 'Content-Length' => '0',
+ 'X-Enc' => Encoding.default_external.to_s,
+ }, ['']]
+end
+
+run app
diff --git a/test/test_access_log.py b/test/test_access_log.py
index 65d5e50a..ba254c5e 100644
--- a/test/test_access_log.py
+++ b/test/test_access_log.py
@@ -1,6 +1,7 @@
import time
import pytest
+
from unit.applications.lang.python import TestApplicationPython
from unit.option import option
@@ -248,7 +249,7 @@ Connection: close
assert self.search_in_log(r'/delete', 'access.log') is None, 'delete'
- def test_access_log_change(self, temp_dir):
+ def test_access_log_change(self):
self.load('empty')
self.get()
diff --git a/test/test_asgi_application.py b/test/test_asgi_application.py
index 886a160b..f503fa82 100644
--- a/test/test_asgi_application.py
+++ b/test/test_asgi_application.py
@@ -3,19 +3,17 @@ import time
from distutils.version import LooseVersion
import pytest
+
from unit.applications.lang.python import TestApplicationPython
from unit.option import option
class TestASGIApplication(TestApplicationPython):
- prerequisites = {'modules': {'python':
- lambda v: LooseVersion(v) >= LooseVersion('3.5')}}
+ prerequisites = {
+ 'modules': {'python': lambda v: LooseVersion(v) >= LooseVersion('3.5')}
+ }
load_module = 'asgi'
- def findall(self, pattern):
- with open(option.temp_dir + '/unit.log', 'r', errors='ignore') as f:
- return re.findall(pattern, f.read())
-
def test_asgi_application_variables(self):
self.load('variables')
@@ -31,7 +29,8 @@ Content-Type: text/html
Connection: close
custom-header: BLAH
-%s""" % (len(body), body.encode()),
+%s"""
+ % (len(body), body.encode()),
raw=True,
)
@@ -145,7 +144,7 @@ custom-header: BLAH
assert 'success' in self.conf(
'{"http":{"max_body_size": ' + str(max_body_size) + ' }}',
- 'settings'
+ 'settings',
)
assert self.get()['status'] == 200, 'init'
@@ -398,7 +397,7 @@ Connection: close
socks.append(sock)
- time.sleep(1.0) # required to avoid greedy request reading
+ time.sleep(1.0) # required to avoid greedy request reading
threads = set()
diff --git a/test/test_asgi_lifespan.py b/test/test_asgi_lifespan.py
index 3ecece43..90866ec3 100644
--- a/test/test_asgi_lifespan.py
+++ b/test/test_asgi_lifespan.py
@@ -2,6 +2,7 @@ import os
from distutils.version import LooseVersion
import pytest
+
from conftest import unit_stop
from unit.applications.lang.python import TestApplicationPython
from unit.option import option
@@ -13,45 +14,88 @@ class TestASGILifespan(TestApplicationPython):
}
load_module = 'asgi'
- def test_asgi_lifespan(self):
- self.load('lifespan/empty')
+ def setup_cookies(self, prefix):
+ base_dir = option.test_dir + '/python/lifespan/empty'
- startup_path = option.test_dir + '/python/lifespan/empty/startup'
- shutdown_path = option.test_dir + '/python/lifespan/empty/shutdown'
- version_path = option.test_dir + '/python/lifespan/empty/version'
+ os.chmod(base_dir, 0o777)
- os.chmod(option.test_dir + '/python/lifespan/empty', 0o777)
+ for name in ['startup', 'shutdown', 'version']:
+ path = option.test_dir + '/python/lifespan/empty/' + prefix + name
+ open(path, 'a').close()
+ os.chmod(path, 0o777)
- open(startup_path, 'a').close()
- os.chmod(startup_path, 0o777)
+ def assert_cookies(self, prefix):
+ for name in ['startup', 'shutdown']:
+ path = option.test_dir + '/python/lifespan/empty/' + prefix + name
+ exists = os.path.isfile(path)
+ if exists:
+ os.remove(path)
- open(shutdown_path, 'a').close()
- os.chmod(shutdown_path, 0o777)
+ assert not exists, name
- open(version_path, 'a').close()
- os.chmod(version_path, 0o777)
+ path = option.test_dir + '/python/lifespan/empty/' + prefix + 'version'
- assert self.get()['status'] == 204
+ with open(path, 'r') as f:
+ version = f.read()
- unit_stop()
+ os.remove(path)
+
+ assert version == '3.0 2.0', 'version'
- is_startup = os.path.isfile(startup_path)
- is_shutdown = os.path.isfile(shutdown_path)
+ def test_asgi_lifespan(self):
+ self.load('lifespan/empty')
- if is_startup:
- os.remove(startup_path)
+ self.setup_cookies('')
- if is_shutdown:
- os.remove(shutdown_path)
+ assert self.get()['status'] == 204
- with open(version_path, 'r') as f:
- version = f.read()
+ unit_stop()
- os.remove(version_path)
+ self.assert_cookies('')
+
+ def test_asgi_lifespan_targets(self):
+ assert 'success' in self.conf(
+ {
+ "listeners": {"*:7080": {"pass": "routes"}},
+ "routes": [
+ {
+ "match": {"uri": "/1"},
+ "action": {"pass": "applications/targets/1"},
+ },
+ {
+ "match": {"uri": "/2"},
+ "action": {"pass": "applications/targets/2"},
+ },
+ ],
+ "applications": {
+ "targets": {
+ "type": "python",
+ "processes": {"spare": 0},
+ "working_directory": option.test_dir
+ + "/python/lifespan/empty",
+ "path": option.test_dir + '/python/lifespan/empty',
+ "targets": {
+ "1": {"module": "asgi", "callable": "application"},
+ "2": {
+ "module": "asgi",
+ "callable": "application2",
+ },
+ },
+ }
+ },
+ }
+ )
+
+ self.setup_cookies('')
+ self.setup_cookies('app2_')
+
+ assert self.get(url="/1")['status'] == 204
+ assert self.get(url="/2")['status'] == 204
- assert not is_startup, 'startup'
- assert not is_shutdown, 'shutdown'
- assert version == '3.0 2.0', 'version'
+ unit_stop()
+
+ self.assert_cookies('')
+ self.assert_cookies('app2_')
def test_asgi_lifespan_failed(self):
self.load('lifespan/failed')
diff --git a/test/test_asgi_targets.py b/test/test_asgi_targets.py
new file mode 100644
index 00000000..a0eb1f84
--- /dev/null
+++ b/test/test_asgi_targets.py
@@ -0,0 +1,92 @@
+from distutils.version import LooseVersion
+
+import pytest
+
+from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
+
+
+class TestASGITargets(TestApplicationPython):
+ prerequisites = {
+ 'modules': {'python': lambda v: LooseVersion(v) >= LooseVersion('3.5')}
+ }
+ load_module = 'asgi'
+
+ @pytest.fixture(autouse=True)
+ def setup_method_fixture(self):
+ assert 'success' in self.conf(
+ {
+ "listeners": {"*:7080": {"pass": "routes"}},
+ "routes": [
+ {
+ "match": {"uri": "/1"},
+ "action": {"pass": "applications/targets/1"},
+ },
+ {
+ "match": {"uri": "/2"},
+ "action": {"pass": "applications/targets/2"},
+ },
+ ],
+ "applications": {
+ "targets": {
+ "type": "python",
+ "processes": {"spare": 0},
+ "working_directory": option.test_dir
+ + "/python/targets/",
+ "path": option.test_dir + '/python/targets/',
+ "protocol": "asgi",
+ "targets": {
+ "1": {
+ "module": "asgi",
+ "callable": "application_200",
+ },
+ "2": {
+ "module": "asgi",
+ "callable": "application_201",
+ },
+ },
+ }
+ },
+ }
+ )
+
+ def conf_targets(self, targets):
+ assert 'success' in self.conf(targets, 'applications/targets/targets')
+
+ def test_asgi_targets(self):
+ assert self.get(url='/1')['status'] == 200
+ assert self.get(url='/2')['status'] == 201
+
+ def test_asgi_targets_legacy(self):
+ self.conf_targets(
+ {
+ "1": {"module": "asgi", "callable": "legacy_application_200"},
+ "2": {"module": "asgi", "callable": "legacy_application_201"},
+ }
+ )
+
+ assert self.get(url='/1')['status'] == 200
+ assert self.get(url='/2')['status'] == 201
+
+ def test_asgi_targets_mix(self):
+ self.conf_targets(
+ {
+ "1": {"module": "asgi", "callable": "application_200"},
+ "2": {"module": "asgi", "callable": "legacy_application_201"},
+ }
+ )
+
+ assert self.get(url='/1')['status'] == 200
+ assert self.get(url='/2')['status'] == 201
+
+ def test_asgi_targets_broken(self, skip_alert):
+ skip_alert(r'Python failed to get "blah" from module')
+
+ self.conf_targets(
+ {
+ "1": {"module": "asgi", "callable": "application_200"},
+ "2": {"module": "asgi", "callable": "blah"},
+ }
+ )
+
+ assert self.get(url='/1')['status'] != 200
diff --git a/test/test_asgi_websockets.py b/test/test_asgi_websockets.py
index 7218d526..140bcb9a 100644
--- a/test/test_asgi_websockets.py
+++ b/test/test_asgi_websockets.py
@@ -3,14 +3,16 @@ import time
from distutils.version import LooseVersion
import pytest
+
from unit.applications.lang.python import TestApplicationPython
from unit.applications.websockets import TestApplicationWebsocket
from unit.option import option
class TestASGIWebsockets(TestApplicationPython):
- prerequisites = {'modules': {'python':
- lambda v: LooseVersion(v) >= LooseVersion('3.5')}}
+ prerequisites = {
+ 'modules': {'python': lambda v: LooseVersion(v) >= LooseVersion('3.5')}
+ }
load_module = 'asgi'
ws = TestApplicationWebsocket()
@@ -74,7 +76,9 @@ class TestASGIWebsockets(TestApplicationPython):
sock.close()
assert resp['status'] == 101, 'status'
- assert resp['headers']['x-subprotocols'] == "('chat', 'phone', 'video')", 'subprotocols'
+ assert (
+ resp['headers']['x-subprotocols'] == "('chat', 'phone', 'video')"
+ ), 'subprotocols'
assert resp['headers']['sec-websocket-protocol'] == 'chat', 'key'
def test_asgi_websockets_mirror(self):
@@ -159,7 +163,7 @@ class TestASGIWebsockets(TestApplicationPython):
self.ws.frame_write(sock, self.ws.OP_TEXT, 'fragment1', fin=False)
self.ws.frame_write(
- sock, self.ws.OP_CONT, 'fragment2', length=2**64 - 1
+ sock, self.ws.OP_CONT, 'fragment2', length=2 ** 64 - 1
)
self.check_close(sock, 1009) # 1009 - CLOSE_TOO_LARGE
@@ -231,7 +235,7 @@ class TestASGIWebsockets(TestApplicationPython):
@pytest.mark.skip('not yet')
def test_asgi_websockets_handshake_upgrade_absent(
- self
+ self,
): # FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1
self.load('websockets/mirror')
@@ -324,7 +328,9 @@ class TestASGIWebsockets(TestApplicationPython):
},
)
- assert resp['status'] == 400, 'key double' # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1
+ assert (
+ resp['status'] == 400
+ ), 'key double' # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1
def test_asgi_websockets_handshake_method_invalid(self):
self.load('websockets/mirror')
@@ -419,14 +425,14 @@ class TestASGIWebsockets(TestApplicationPython):
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
+ 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)
@@ -445,14 +451,14 @@ class TestASGIWebsockets(TestApplicationPython):
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
+ 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)
@@ -470,11 +476,11 @@ class TestASGIWebsockets(TestApplicationPython):
self.check_frame(frame, True, op_pong, payload, decode=decode)
- check_ping('') # 2_1
- check_ping('Hello, world!') # 2_2
+ 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
+ check_ping(b'\xfe' * 125, decode=False) # 2_4
+ check_ping(b'\xfe' * 125, chopsize=1, decode=False) # 2_6
self.close_connection(sock)
@@ -935,7 +941,9 @@ class TestASGIWebsockets(TestApplicationPython):
frame = self.ws.frame_read(sock)
if frame['opcode'] == self.ws.OP_TEXT:
- self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2')
+ self.check_frame(
+ frame, True, self.ws.OP_TEXT, 'fragment1fragment2'
+ )
frame = None
self.check_close(sock, 1002, frame=frame)
@@ -1092,27 +1100,27 @@ class TestASGIWebsockets(TestApplicationPython):
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
+ # 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_asgi_websockets_7_1_1__7_5_1(self):
self.load('websockets/mirror')
@@ -1239,15 +1247,15 @@ class TestASGIWebsockets(TestApplicationPython):
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)
+ # # 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_asgi_websockets_7_7_X__7_9_X(self):
self.load('websockets/mirror')
@@ -1350,52 +1358,52 @@ class TestASGIWebsockets(TestApplicationPython):
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_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
+ 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 option.system != 'Darwin' and option.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_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
diff --git a/test/test_configuration.py b/test/test_configuration.py
index b7417264..880aef6c 100644
--- a/test/test_configuration.py
+++ b/test/test_configuration.py
@@ -1,4 +1,6 @@
import pytest
+
+import socket
from unit.control import TestControl
@@ -260,6 +262,33 @@ class TestConfiguration(TestControl):
}
), 'explicit ipv6'
+ def test_listeners_port_release(self):
+ for i in range(10):
+ fail = False
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+ self.conf(
+ {
+ "listeners": {"127.0.0.1:7080": {"pass": "routes"}},
+ "routes": [],
+ }
+ )
+
+ resp = self.conf({"listeners": {}, "applications": {}})
+
+ try:
+ s.bind(('127.0.0.1', 7080))
+ s.listen()
+
+ except OSError:
+ fail = True
+
+ if fail:
+ pytest.fail('cannot bind or listen to the address')
+
+ assert 'success' in resp, 'port release'
+
@pytest.mark.skip('not yet, unsafe')
def test_listeners_no_port(self):
assert 'error' in self.conf(
diff --git a/test/test_go_application.py b/test/test_go_application.py
index e833d190..438ce2e0 100644
--- a/test/test_go_application.py
+++ b/test/test_go_application.py
@@ -129,11 +129,9 @@ class TestGoApplication(TestApplicationGo):
def test_go_application_command_line_arguments_type(self):
self.load('command_line_arguments')
- assert 'error' in \
- self.conf(
- '' "a b c", 'applications/command_line_arguments/arguments'
- ), \
- 'arguments type'
+ assert 'error' in self.conf(
+ '' "a b c", 'applications/command_line_arguments/arguments'
+ ), 'arguments type'
def test_go_application_command_line_arguments_0(self):
self.load('command_line_arguments')
diff --git a/test/test_go_isolation.py b/test/test_go_isolation.py
index e8fa26c6..e02ef1cf 100644
--- a/test/test_go_isolation.py
+++ b/test/test_go_isolation.py
@@ -3,10 +3,12 @@ import os
import pwd
import pytest
+
from unit.applications.lang.go import TestApplicationGo
from unit.option import option
from unit.utils import getns
+
class TestGoIsolation(TestApplicationGo):
prerequisites = {'modules': {'go': 'any'}, 'features': ['isolation']}
@@ -279,7 +281,7 @@ class TestGoIsolation(TestApplicationGo):
isolation['namespaces'] = {
'mount': True,
'credential': True,
- 'pid': True
+ 'pid': True,
}
self.load('ns_inspect', isolation=isolation)
@@ -337,12 +339,10 @@ class TestGoIsolation(TestApplicationGo):
isolation['namespaces'] = {
'mount': True,
'credential': True,
- 'pid': True
+ 'pid': True,
}
- isolation['automount'] = {
- 'tmpfs': False
- }
+ isolation['automount'] = {'tmpfs': False}
self.load('ns_inspect', isolation=isolation)
@@ -352,9 +352,7 @@ class TestGoIsolation(TestApplicationGo):
"/ /tmp" not in obj['Mounts'] and "tmpfs" not in obj['Mounts']
), 'app has no /tmp mounted'
- isolation['automount'] = {
- 'tmpfs': True
- }
+ isolation['automount'] = {'tmpfs': True}
self.load('ns_inspect', isolation=isolation)
diff --git a/test/test_go_isolation_rootfs.py b/test/test_go_isolation_rootfs.py
index 2bded5ec..1cc59c67 100644
--- a/test/test_go_isolation_rootfs.py
+++ b/test/test_go_isolation_rootfs.py
@@ -1,6 +1,7 @@
import os
import pytest
+
from unit.applications.lang.go import TestApplicationGo
diff --git a/test/test_http_header.py b/test/test_http_header.py
index ca355eb7..fdb557cf 100644
--- a/test/test_http_header.py
+++ b/test/test_http_header.py
@@ -1,4 +1,5 @@
import pytest
+
from unit.applications.lang.python import TestApplicationPython
diff --git a/test/test_java_application.py b/test/test_java_application.py
index 4a67f291..3fd5c26e 100644
--- a/test/test_java_application.py
+++ b/test/test_java_application.py
@@ -7,6 +7,7 @@ from unit.applications.lang.java import TestApplicationJava
from unit.option import option
from unit.utils import public_dir
+
class TestJavaApplication(TestApplicationJava):
prerequisites = {'modules': {'java': 'all'}}
@@ -712,9 +713,9 @@ class TestJavaApplication(TestApplicationJava):
assert (
'javax.servlet.include.request_uri: /data/test' in body
) == True, 'include request uri'
- #assert (
+ # assert (
# 'javax.servlet.include.context_path: ' in body
- #) == True, 'include request context path'
+ # ) == True, 'include request context path'
assert (
'javax.servlet.include.servlet_path: /data' in body
) == True, 'include request servlet path'
@@ -754,9 +755,9 @@ class TestJavaApplication(TestApplicationJava):
assert (
'javax.servlet.include.request_uri: null' in body
) == True, 'include request uri'
- #assert (
+ # assert (
# 'javax.servlet.include.context_path: null' in body
- #) == True, 'include request context path'
+ # ) == True, 'include request context path'
assert (
'javax.servlet.include.servlet_path: null' in body
) == True, 'include request servlet path'
@@ -1034,7 +1035,7 @@ class TestJavaApplication(TestApplicationJava):
socks.append(sock)
- time.sleep(0.25) # required to avoid greedy request reading
+ time.sleep(0.25) # required to avoid greedy request reading
threads = set()
diff --git a/test/test_java_isolation_rootfs.py b/test/test_java_isolation_rootfs.py
index 91773981..a401e23b 100644
--- a/test/test_java_isolation_rootfs.py
+++ b/test/test_java_isolation_rootfs.py
@@ -2,6 +2,7 @@ import os
import subprocess
import pytest
+
from unit.applications.lang.java import TestApplicationJava
from unit.option import option
diff --git a/test/test_java_websockets.py b/test/test_java_websockets.py
index df9e0885..df0f76e8 100644
--- a/test/test_java_websockets.py
+++ b/test/test_java_websockets.py
@@ -2,6 +2,7 @@ import struct
import time
import pytest
+
from unit.applications.lang.java import TestApplicationJava
from unit.applications.websockets import TestApplicationWebsocket
from unit.option import option
@@ -163,7 +164,7 @@ class TestJavaWebsockets(TestApplicationJava):
@pytest.mark.skip('not yet')
def test_java_websockets_handshake_upgrade_absent(
- self
+ self,
): # FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1
self.load('websockets_mirror')
@@ -256,7 +257,9 @@ class TestJavaWebsockets(TestApplicationJava):
},
)
- assert resp['status'] == 400, 'key double' # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1
+ assert (
+ resp['status'] == 400
+ ), 'key double' # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1
def test_java_websockets_handshake_method_invalid(self):
self.load('websockets_mirror')
@@ -351,14 +354,14 @@ class TestJavaWebsockets(TestApplicationJava):
frame = self.ws.message_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
+ 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)
@@ -377,14 +380,14 @@ class TestJavaWebsockets(TestApplicationJava):
frame = self.ws.message_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
+ 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)
@@ -402,11 +405,11 @@ class TestJavaWebsockets(TestApplicationJava):
self.check_frame(frame, True, op_pong, payload, decode=decode)
- check_ping('') # 2_1
- check_ping('Hello, world!') # 2_2
+ 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
+ check_ping(b'\xfe' * 125, decode=False) # 2_4
+ check_ping(b'\xfe' * 125, chopsize=1, decode=False) # 2_6
self.close_connection(sock)
@@ -867,7 +870,9 @@ class TestJavaWebsockets(TestApplicationJava):
frame = self.ws.frame_read(sock)
if frame['opcode'] == self.ws.OP_TEXT:
- self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2')
+ self.check_frame(
+ frame, True, self.ws.OP_TEXT, 'fragment1fragment2'
+ )
frame = None
self.check_close(sock, 1002, frame=frame)
@@ -1024,27 +1029,27 @@ class TestJavaWebsockets(TestApplicationJava):
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
+ # 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_java_websockets_7_1_1__7_5_1(self):
self.load('websockets_mirror')
@@ -1171,15 +1176,15 @@ class TestJavaWebsockets(TestApplicationJava):
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)
+ # # 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_java_websockets_7_7_X__7_9_X(self):
self.load('websockets_mirror')
@@ -1282,52 +1287,52 @@ class TestJavaWebsockets(TestApplicationJava):
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_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
+ 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 option.system != 'Darwin' and option.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_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
diff --git a/test/test_node_application.py b/test/test_node_application.py
index b277dc3a..48ed8d3d 100644
--- a/test/test_node_application.py
+++ b/test/test_node_application.py
@@ -1,6 +1,7 @@
import re
import pytest
+
from unit.applications.lang.node import TestApplicationNode
from unit.utils import waitforfiles
@@ -8,13 +9,26 @@ from unit.utils import waitforfiles
class TestNodeApplication(TestApplicationNode):
prerequisites = {'modules': {'node': 'all'}}
- def test_node_application_basic(self):
- self.load('basic')
-
+ def assert_basic_application(self):
resp = self.get()
assert resp['headers']['Content-Type'] == 'text/plain', 'basic header'
assert resp['body'] == 'Hello World\n', 'basic body'
+ def test_node_application_basic(self):
+ self.load('basic')
+
+ self.assert_basic_application()
+
+ def test_node_application_loader_unit_http(self):
+ self.load('loader/unit_http')
+
+ self.assert_basic_application()
+
+ def test_node_application_loader_transitive_dependency(self):
+ self.load('loader/transitive_dependency')
+
+ self.assert_basic_application()
+
def test_node_application_seq(self):
self.load('basic')
@@ -205,7 +219,9 @@ class TestNodeApplication(TestApplicationNode):
def test_node_application_status_message(self):
self.load('status_message')
- assert re.search(r'200 blah', self.get(raw_resp=True)), 'status message'
+ assert re.search(
+ r'200 blah', self.get(raw_resp=True)
+ ), 'status message'
def test_node_application_get_header_type(self):
self.load('get_header_type')
diff --git a/test/test_node_es_modules.py b/test/test_node_es_modules.py
new file mode 100644
index 00000000..0945a967
--- /dev/null
+++ b/test/test_node_es_modules.py
@@ -0,0 +1,50 @@
+import pytest
+
+from distutils.version import LooseVersion
+from unit.applications.lang.node import TestApplicationNode
+from unit.applications.websockets import TestApplicationWebsocket
+
+
+class TestNodeESModules(TestApplicationNode):
+ prerequisites = {
+ 'modules': {
+ 'node': lambda v: LooseVersion(v) >= LooseVersion("14.16.0")
+ }
+ }
+
+ es_modules = True
+ ws = TestApplicationWebsocket()
+
+ def assert_basic_application(self):
+ resp = self.get()
+ assert resp['headers']['Content-Type'] == 'text/plain', 'basic header'
+ assert resp['body'] == 'Hello World\n', 'basic body'
+
+ def test_node_es_modules_loader_http(self):
+ self.load('loader/es_modules_http', name="app.mjs")
+
+ self.assert_basic_application()
+
+ def test_node_es_modules_loader_http_indirect(self):
+ self.load('loader/es_modules_http_indirect', name="app.js")
+
+ self.assert_basic_application()
+
+ def test_node_es_modules_loader_websockets(self):
+ self.load('loader/es_modules_websocket', name="app.mjs")
+
+ message = 'blah'
+
+ _, sock, _ = self.ws.upgrade()
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, message)
+ frame = self.ws.frame_read(sock)
+
+ assert message == frame['data'].decode('utf-8'), 'mirror'
+
+ self.ws.frame_write(sock, self.ws.OP_TEXT, message)
+ frame = self.ws.frame_read(sock)
+
+ assert message == frame['data'].decode('utf-8'), 'mirror 2'
+
+ sock.close()
diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py
index d5f79811..51515f4e 100644
--- a/test/test_node_websockets.py
+++ b/test/test_node_websockets.py
@@ -2,6 +2,7 @@ import struct
import time
import pytest
+
from unit.applications.lang.node import TestApplicationNode
from unit.applications.websockets import TestApplicationWebsocket
from unit.option import option
@@ -182,7 +183,7 @@ class TestNodeWebsockets(TestApplicationNode):
@pytest.mark.skip('not yet')
def test_node_websockets_handshake_upgrade_absent(
- self
+ self,
): # FAIL https://tools.ietf.org/html/rfc6455#section-4.2.1
self.load('websockets/mirror')
@@ -275,7 +276,9 @@ class TestNodeWebsockets(TestApplicationNode):
},
)
- assert resp['status'] == 400, 'key double' # FAIL https://tools.ietf.org/html/rfc6455#section-11.3.1
+ assert (
+ 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')
@@ -370,14 +373,14 @@ class TestNodeWebsockets(TestApplicationNode):
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
+ 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)
@@ -396,14 +399,14 @@ class TestNodeWebsockets(TestApplicationNode):
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
+ 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)
@@ -421,11 +424,11 @@ class TestNodeWebsockets(TestApplicationNode):
self.check_frame(frame, True, op_pong, payload, decode=decode)
- check_ping('') # 2_1
- check_ping('Hello, world!') # 2_2
+ 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
+ check_ping(b'\xfe' * 125, decode=False) # 2_4
+ check_ping(b'\xfe' * 125, chopsize=1, decode=False) # 2_6
self.close_connection(sock)
@@ -886,7 +889,9 @@ class TestNodeWebsockets(TestApplicationNode):
frame = self.ws.frame_read(sock)
if frame['opcode'] == self.ws.OP_TEXT:
- self.check_frame(frame, True, self.ws.OP_TEXT, 'fragment1fragment2')
+ self.check_frame(
+ frame, True, self.ws.OP_TEXT, 'fragment1fragment2'
+ )
frame = None
self.check_close(sock, 1002, frame=frame)
@@ -1043,27 +1048,27 @@ class TestNodeWebsockets(TestApplicationNode):
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
+ # 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')
@@ -1190,15 +1195,15 @@ class TestNodeWebsockets(TestApplicationNode):
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)
+ # # 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')
@@ -1301,52 +1306,52 @@ class TestNodeWebsockets(TestApplicationNode):
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_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
+ 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 option.system != 'Darwin' and option.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_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
diff --git a/test/test_perl_application.py b/test/test_perl_application.py
index dfd8be6c..e906aaca 100644
--- a/test/test_perl_application.py
+++ b/test/test_perl_application.py
@@ -1,6 +1,7 @@
import re
import pytest
+
from unit.applications.lang.perl import TestApplicationPerl
diff --git a/test/test_php_application.py b/test/test_php_application.py
index e73c67ba..66e2ef7d 100644
--- a/test/test_php_application.py
+++ b/test/test_php_application.py
@@ -1,13 +1,15 @@
import os
import re
import shutil
+import signal
import time
-from subprocess import call
import pytest
+
from unit.applications.lang.php import TestApplicationPHP
from unit.option import option
+
class TestPHPApplication(TestApplicationPHP):
prerequisites = {'modules': {'php': 'all'}}
@@ -93,37 +95,39 @@ class TestPHPApplication(TestApplicationPHP):
assert resp['status'] == 200, 'query string empty status'
assert resp['headers']['Query-String'] == '', 'query string empty'
- def test_php_application_fastcgi_finish_request(self, temp_dir):
+ def test_php_application_fastcgi_finish_request(self, unit_pid):
self.load('fastcgi_finish_request')
- assert self.get()['body'] == '0123'
+ assert 'success' in self.conf(
+ {"admin": {"auto_globals_jit": "1"}},
+ 'applications/fastcgi_finish_request/options',
+ )
- with open(temp_dir + '/unit.pid', 'r') as f:
- pid = f.read().rstrip()
+ assert self.get()['body'] == '0123'
- call(['kill', '-s', 'USR1', pid])
+ os.kill(unit_pid, signal.SIGUSR1)
- with open(temp_dir + '/unit.log', 'r', errors='ignore') as f:
- errs = re.findall(r'Error in fastcgi_finish_request', f.read())
+ errs = self.findall(r'Error in fastcgi_finish_request')
- assert len(errs) == 0, 'no error'
+ assert len(errs) == 0, 'no error'
- def test_php_application_fastcgi_finish_request_2(self, temp_dir):
+ def test_php_application_fastcgi_finish_request_2(self, unit_pid):
self.load('fastcgi_finish_request')
+ assert 'success' in self.conf(
+ {"admin": {"auto_globals_jit": "1"}},
+ 'applications/fastcgi_finish_request/options',
+ )
+
resp = self.get(url='/?skip')
assert resp['status'] == 200
assert resp['body'] == ''
- with open(temp_dir + '/unit.pid', 'r') as f:
- pid = f.read().rstrip()
-
- call(['kill', '-s', 'USR1', pid])
+ os.kill(unit_pid, signal.SIGUSR1)
- with open(temp_dir + '/unit.log', 'r', errors='ignore') as f:
- errs = re.findall(r'Error in fastcgi_finish_request', f.read())
+ errs = self.findall(r'Error in fastcgi_finish_request')
- assert len(errs) == 0, 'no error'
+ assert len(errs) == 0, 'no error'
def test_php_application_query_string_absent(self):
self.load('query_string')
@@ -428,11 +432,13 @@ class TestPHPApplication(TestApplicationPHP):
self.load('auth')
def check_auth(auth):
- resp = self.get(headers={
- 'Host': 'localhost',
- 'Authorization': auth,
- 'Connection': 'close',
- })
+ resp = self.get(
+ headers={
+ 'Host': 'localhost',
+ 'Authorization': auth,
+ 'Connection': 'close',
+ }
+ )
assert resp['status'] == 200, 'status'
assert resp['headers']['X-Digest'] == 'not set', 'Digest'
@@ -534,7 +540,7 @@ class TestPHPApplication(TestApplicationPHP):
r'012345', self.get()['body']
), 'disable_classes before'
- def test_php_application_error_log(self, temp_dir):
+ def test_php_application_error_log(self):
self.load('error_log')
assert self.get()['status'] == 200, 'status'
@@ -547,14 +553,13 @@ class TestPHPApplication(TestApplicationPHP):
assert self.wait_for_record(pattern) is not None, 'errors print'
- with open(temp_dir + '/unit.log', 'r', errors='ignore') as f:
- errs = re.findall(pattern, f.read())
+ errs = self.findall(pattern)
- assert len(errs) == 2, 'error_log count'
+ assert len(errs) == 2, 'error_log count'
- date = errs[0].split('[')[0]
- date2 = errs[1].split('[')[0]
- assert date != date2, 'date diff'
+ date = errs[0].split('[')[0]
+ date2 = errs[1].split('[')[0]
+ assert date != date2, 'date diff'
def test_php_application_script(self):
assert 'success' in self.conf(
diff --git a/test/test_php_isolation.py b/test/test_php_isolation.py
index 2e458023..8db6b590 100644
--- a/test/test_php_isolation.py
+++ b/test/test_php_isolation.py
@@ -1,4 +1,5 @@
import pytest
+
from unit.applications.lang.php import TestApplicationPHP
from unit.option import option
@@ -28,7 +29,7 @@ class TestPHPIsolation(TestApplicationPHP):
isolation['namespaces'] = {
'mount': True,
'credential': True,
- 'pid': True
+ 'pid': True,
}
self.load('phpinfo', isolation=isolation)
@@ -64,7 +65,7 @@ class TestPHPIsolation(TestApplicationPHP):
isolation['namespaces'] = {
'mount': True,
'credential': True,
- 'pid': True
+ 'pid': True,
}
self.load('list-extensions', isolation=isolation)
diff --git a/test/test_proxy.py b/test/test_proxy.py
index 7e7c7246..3d59cf24 100644
--- a/test/test_proxy.py
+++ b/test/test_proxy.py
@@ -3,6 +3,7 @@ import socket
import time
import pytest
+
from conftest import run_process
from unit.applications.lang.python import TestApplicationPython
from unit.option import option
@@ -464,9 +465,9 @@ Content-Length: 10
def test_proxy_invalid(self):
def check_proxy(proxy):
- assert 'error' in \
- self.conf([{"action": {"proxy": proxy}}], 'routes'), \
- 'proxy invalid'
+ assert 'error' in self.conf(
+ [{"action": {"proxy": proxy}}], 'routes'
+ ), 'proxy invalid'
check_proxy('blah')
check_proxy('/blah')
@@ -502,7 +503,8 @@ Content-Length: 10
"type": "python",
"processes": {"spare": 0},
"path": option.test_dir + "/python/mirror",
- "working_directory": option.test_dir + "/python/mirror",
+ "working_directory": option.test_dir
+ + "/python/mirror",
"module": "wsgi",
},
},
diff --git a/test/test_python_application.py b/test/test_python_application.py
index 41f6f538..48c3d603 100644
--- a/test/test_python_application.py
+++ b/test/test_python_application.py
@@ -5,17 +5,13 @@ import re
import time
import pytest
+
from unit.applications.lang.python import TestApplicationPython
-from unit.option import option
class TestPythonApplication(TestApplicationPython):
prerequisites = {'modules': {'python': 'all'}}
- def findall(self, pattern):
- with open(option.temp_dir + '/unit.log', 'r', errors='ignore') as f:
- return re.findall(pattern, f.read())
-
def test_python_application_variables(self):
self.load('variables')
@@ -31,7 +27,8 @@ Content-Type: text/html
Connection: close
custom-header: BLAH
-%s""" % (len(body), body.encode()),
+%s"""
+ % (len(body), body.encode()),
raw=True,
)
@@ -816,7 +813,11 @@ last line: 987654321
assert ['/new', *sys_path] == get_path(), 'check path update'
set_path('["/blah1", "/blah2"]')
- assert ['/blah1', '/blah2', *sys_path] == get_path(), 'check path array'
+ assert [
+ '/blah1',
+ '/blah2',
+ *sys_path,
+ ] == get_path(), 'check path array'
def test_python_application_path_invalid(self):
self.load('path')
diff --git a/test/test_python_isolation.py b/test/test_python_isolation.py
index 680f2c03..93f85264 100644
--- a/test/test_python_isolation.py
+++ b/test/test_python_isolation.py
@@ -1,5 +1,5 @@
-
import pytest
+
from unit.applications.lang.python import TestApplicationPython
from unit.option import option
from unit.utils import findmnt
@@ -32,7 +32,7 @@ class TestPythonIsolation(TestApplicationPython):
isolation['namespaces'] = {
'mount': True,
'credential': True,
- 'pid': True
+ 'pid': True,
}
self.load('ns_inspect', isolation=isolation)
@@ -43,8 +43,7 @@ class TestPythonIsolation(TestApplicationPython):
), 'temp_dir does not exists in rootfs'
assert (
- self.getjson(url='/?path=/proc/self')['body']['FileExists']
- == True
+ self.getjson(url='/?path=/proc/self')['body']['FileExists'] == True
), 'no /proc/self'
assert (
@@ -66,15 +65,12 @@ class TestPythonIsolation(TestApplicationPython):
if not is_su:
pytest.skip('requires root')
- isolation = {
- 'rootfs': temp_dir,
- 'automount': {'language_deps': False}
- }
+ isolation = {'rootfs': temp_dir, 'automount': {'language_deps': False}}
self.load('empty', isolation=isolation)
assert findmnt().find(temp_dir) == -1
- assert (self.get()['status'] != 200), 'disabled language_deps'
+ assert self.get()['status'] != 200, 'disabled language_deps'
assert findmnt().find(temp_dir) == -1
isolation['automount']['language_deps'] = True
@@ -82,7 +78,7 @@ class TestPythonIsolation(TestApplicationPython):
self.load('empty', isolation=isolation)
assert findmnt().find(temp_dir) == -1
- assert (self.get()['status'] == 200), 'enabled language_deps'
+ assert self.get()['status'] == 200, 'enabled language_deps'
assert waitformount(temp_dir), 'language_deps mount'
self.conf({"listeners": {}, "applications": {}})
@@ -90,8 +86,6 @@ class TestPythonIsolation(TestApplicationPython):
assert waitforunmount(temp_dir), 'language_deps unmount'
def test_python_isolation_procfs(self, is_su, temp_dir):
- isolation_features = option.available['features']['isolation'].keys()
-
if not is_su:
pytest.skip('requires root')
diff --git a/test/test_python_isolation_chroot.py b/test/test_python_isolation_chroot.py
index 7281f5f6..54fbad12 100644
--- a/test/test_python_isolation_chroot.py
+++ b/test/test_python_isolation_chroot.py
@@ -1,4 +1,5 @@
import pytest
+
from unit.applications.lang.python import TestApplicationPython
@@ -21,8 +22,7 @@ class TestPythonIsolation(TestApplicationPython):
), 'temp_dir does not exists in rootfs'
assert (
- self.getjson(url='/?path=/proc/self')['body']['FileExists']
- == True
+ self.getjson(url='/?path=/proc/self')['body']['FileExists'] == True
), 'no /proc/self'
assert (
diff --git a/test/test_python_procman.py b/test/test_python_procman.py
index ac403ce4..b0d0f5af 100644
--- a/test/test_python_procman.py
+++ b/test/test_python_procman.py
@@ -3,6 +3,7 @@ import subprocess
import time
import pytest
+
from unit.applications.lang.python import TestApplicationPython
from unit.option import option
diff --git a/test/test_python_targets.py b/test/test_python_targets.py
new file mode 100644
index 00000000..ca736c0d
--- /dev/null
+++ b/test/test_python_targets.py
@@ -0,0 +1,51 @@
+import pytest
+
+from unit.applications.lang.python import TestApplicationPython
+from unit.option import option
+
+
+class TestPythonTargets(TestApplicationPython):
+ prerequisites = {'modules': {'python': 'all'}}
+
+ def test_python_targets(self):
+ assert 'success' in self.conf(
+ {
+ "listeners": {"*:7080": {"pass": "routes"}},
+ "routes": [
+ {
+ "match": {"uri": "/1"},
+ "action": {"pass": "applications/targets/1"},
+ },
+ {
+ "match": {"uri": "/2"},
+ "action": {"pass": "applications/targets/2"},
+ },
+ ],
+ "applications": {
+ "targets": {
+ "type": "python",
+ "working_directory": option.test_dir
+ + "/python/targets/",
+ "path": option.test_dir + '/python/targets/',
+ "targets": {
+ "1": {
+ "module": "wsgi",
+ "callable": "wsgi_target_a",
+ },
+ "2": {
+ "module": "wsgi",
+ "callable": "wsgi_target_b",
+ },
+ },
+ }
+ },
+ }
+ )
+
+ resp = self.get(url='/1')
+ assert resp['status'] == 200
+ assert resp['body'] == '1'
+
+ resp = self.get(url='/2')
+ assert resp['status'] == 200
+ assert resp['body'] == '2'
diff --git a/test/test_respawn.py b/test/test_respawn.py
index ed85ee95..edbfa2a8 100644
--- a/test/test_respawn.py
+++ b/test/test_respawn.py
@@ -58,7 +58,8 @@ class TestRespawn(TestApplicationPython):
assert len(self.find_proc(self.PATTERN_CONTROLLER, unit_pid, out)) == 1
assert len(self.find_proc(self.app_name, unit_pid, out)) == 1
- def test_respawn_router(self, skip_alert, unit_pid):
+ def test_respawn_router(self, skip_alert, unit_pid, skip_fds_check):
+ skip_fds_check(router=True)
pid = self.pid_by_name(self.PATTERN_ROUTER, unit_pid)
self.kill_pids(pid)
@@ -68,15 +69,17 @@ class TestRespawn(TestApplicationPython):
self.smoke_test(unit_pid)
- def test_respawn_controller(self, skip_alert, unit_pid):
+ def test_respawn_controller(self, skip_alert, unit_pid, skip_fds_check):
+ skip_fds_check(controller=True)
pid = self.pid_by_name(self.PATTERN_CONTROLLER, unit_pid)
self.kill_pids(pid)
skip_alert(r'process %s exited on signal 9' % pid)
- assert self.wait_for_process(
- self.PATTERN_CONTROLLER, unit_pid
- ) is not None
+ assert (
+ self.wait_for_process(self.PATTERN_CONTROLLER, unit_pid)
+ is not None
+ )
assert self.get()['status'] == 200
diff --git a/test/test_routing.py b/test/test_routing.py
index be9a1faf..eaa0a134 100644
--- a/test/test_routing.py
+++ b/test/test_routing.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import pytest
+
from unit.applications.proto import TestApplicationProto
from unit.option import option
@@ -232,11 +233,11 @@ class TestRouting(TestApplicationProto):
if not option.available['modules']['regex']:
pytest.skip('requires regex')
- self.route_match({"uri":"~"})
+ self.route_match({"uri": "~"})
assert self.get(url='/')['status'] == 200, 'empty regexp'
assert self.get(url='/anything')['status'] == 200, '/anything'
- self.route_match({"uri":"!~"})
+ self.route_match({"uri": "!~"})
assert self.get(url='/')['status'] == 404, 'empty regexp 2'
assert self.get(url='/nothing')['status'] == 404, '/nothing'
@@ -336,8 +337,7 @@ class TestRouting(TestApplicationProto):
"type": "python",
"processes": {"spare": 0},
"path": option.test_dir + '/python/empty',
- "working_directory": option.test_dir
- + '/python/empty',
+ "working_directory": option.test_dir + '/python/empty',
"module": "wsgi",
}
},
@@ -495,8 +495,7 @@ class TestRouting(TestApplicationProto):
'routes/0/action',
), 'proxy pass'
assert 'error' in self.conf(
- {"share": temp_dir, "pass": "applications/app"},
- 'routes/0/action',
+ {"share": temp_dir, "pass": "applications/app"}, 'routes/0/action',
), 'share pass'
def test_routes_rules_two(self):
@@ -1042,83 +1041,31 @@ class TestRouting(TestApplicationProto):
}
)
+ def check_headers(hds):
+ hds = dict({"Host": "localhost", "Connection": "close"}, **hds)
+ assert (
+ self.get(headers=hds)['status'] == 200
+ ), 'headers array match'
+
+ def check_headers_404(hds):
+ hds = dict({"Host": "localhost", "Connection": "close"}, **hds)
+ assert (
+ self.get(headers=hds)['status'] == 404
+ ), 'headers array no match'
+
assert self.get()['status'] == 404, 'match headers array'
- assert (
- self.get(
- headers={
- "Host": "localhost",
- "x-header1": "foo123",
- "Connection": "close",
- }
- )['status']
- == 200
- ), 'match headers array 2'
- assert (
- self.get(
- headers={
- "Host": "localhost",
- "x-header2": "bar",
- "Connection": "close",
- }
- )['status']
- == 200
- ), 'match headers array 3'
- assert (
- self.get(
- headers={
- "Host": "localhost",
- "x-header3": "bar",
- "Connection": "close",
- }
- )['status']
- == 200
- ), 'match headers array 4'
- assert (
- self.get(
- headers={
- "Host": "localhost",
- "x-header1": "bar",
- "Connection": "close",
- }
- )['status']
- == 404
- ), 'match headers array 5'
- assert (
- self.get(
- headers={
- "Host": "localhost",
- "x-header1": "bar",
- "x-header4": "foo",
- "Connection": "close",
- }
- )['status']
- == 200
- ), 'match headers array 6'
+ check_headers({"x-header1": "foo123"})
+ check_headers({"x-header2": "bar"})
+ check_headers({"x-header3": "bar"})
+ check_headers_404({"x-header1": "bar"})
+ check_headers({"x-header1": "bar", "x-header4": "foo"})
assert 'success' in self.conf_delete(
'routes/0/match/headers/1'
), 'match headers array configure 2'
- assert (
- self.get(
- headers={
- "Host": "localhost",
- "x-header2": "bar",
- "Connection": "close",
- }
- )['status']
- == 404
- ), 'match headers array 7'
- assert (
- self.get(
- headers={
- "Host": "localhost",
- "x-header3": "foo",
- "Connection": "close",
- }
- )['status']
- == 200
- ), 'match headers array 8'
+ check_headers_404({"x-header2": "bar"})
+ check_headers({"x-header3": "foo"})
def test_routes_match_arguments(self):
self.route_match({"arguments": {"foo": "bar"}})
@@ -1362,10 +1309,7 @@ class TestRouting(TestApplicationProto):
assert self.get(url='/?var2=val2')['status'] == 404, 'arr 7'
assert self.get(url='/?var3=foo')['status'] == 200, 'arr 8'
- def test_routes_match_arguments_invalid(self, skip_alert):
- # TODO remove it after controller fixed
- skip_alert(r'failed to apply new conf')
-
+ def test_routes_match_arguments_invalid(self):
self.route_match_invalid({"arguments": ["var"]})
self.route_match_invalid({"arguments": [{"var1": {}}]})
self.route_match_invalid({"arguments": {"": "bar"}})
diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py
index b18a6cee..ddd31f59 100644
--- a/test/test_ruby_application.py
+++ b/test/test_ruby_application.py
@@ -1,6 +1,8 @@
import re
+import subprocess
import pytest
+
from unit.applications.lang.ruby import TestApplicationRuby
@@ -207,7 +209,6 @@ class TestRubyApplication(TestApplicationRuby):
self.get()
-
assert (
self.wait_for_record(r'\[error\].+1234567890') is not None
), 'errors write int'
@@ -223,6 +224,57 @@ class TestRubyApplication(TestApplicationRuby):
self.wait_for_record(r'\[error\].+At exit called\.') is not None
), 'at exit'
+ def test_ruby_application_encoding(self):
+ self.load('encoding')
+
+ try:
+ locales = (
+ subprocess.check_output(
+ ['locale', '-a'], stderr=subprocess.STDOUT,
+ )
+ .decode()
+ .split('\n')
+ )
+
+ except (FileNotFoundError, subprocess.CalledProcessError):
+ pytest.skip('require locale')
+
+ def get_locale(pattern):
+ return next(
+ (
+ l
+ for l in locales
+ if re.match(pattern, l.upper()) is not None
+ ),
+ None,
+ )
+
+ utf8 = get_locale(r'.*UTF[-_]?8')
+ iso88591 = get_locale(r'.*ISO[-_]?8859[-_]?1')
+
+ def check_locale(enc):
+ assert 'success' in self.conf(
+ {"LC_CTYPE": enc, "LC_ALL": ""},
+ '/config/applications/encoding/environment',
+ )
+
+ resp = self.get()
+ assert resp['status'] == 200, 'status'
+
+ enc_default = re.sub(r'[-_]', '', resp['headers']['X-Enc']).upper()
+ assert (
+ enc_default == re.sub(r'[-_]', '', enc.split('.')[-1]).upper()
+ )
+
+ if utf8:
+ check_locale(utf8)
+
+ if iso88591:
+ check_locale(iso88591)
+
+ if not utf8 and not iso88591:
+ pytest.skip('no available locales')
+
def test_ruby_application_header_custom(self):
self.load('header_custom')
diff --git a/test/test_ruby_isolation.py b/test/test_ruby_isolation.py
index c49b6732..8443d857 100644
--- a/test/test_ruby_isolation.py
+++ b/test/test_ruby_isolation.py
@@ -2,6 +2,7 @@ import os
import shutil
import pytest
+
from unit.applications.lang.ruby import TestApplicationRuby
from unit.option import option
diff --git a/test/test_settings.py b/test/test_settings.py
index c59ca8b9..49041b62 100644
--- a/test/test_settings.py
+++ b/test/test_settings.py
@@ -3,7 +3,9 @@ import socket
import time
import pytest
+
from unit.applications.lang.python import TestApplicationPython
+from unit.utils import sysctl
class TestSettings(TestApplicationPython):
@@ -12,33 +14,42 @@ class TestSettings(TestApplicationPython):
def test_settings_header_read_timeout(self):
self.load('empty')
- self.conf({'http': {'header_read_timeout': 2}}, 'settings')
-
- (resp, sock) = self.http(
- b"""GET / HTTP/1.1
+ def req():
+ (resp, sock) = self.http(
+ b"""GET / HTTP/1.1
""",
- start=True,
- read_timeout=1,
- raw=True,
- )
+ start=True,
+ read_timeout=1,
+ raw=True,
+ )
- time.sleep(3)
+ time.sleep(3)
- resp = self.http(
- b"""Host: localhost
+ return self.http(
+ b"""Host: localhost
Connection: close
-""",
- sock=sock,
- raw=True,
+ """,
+ sock=sock,
+ raw=True,
+ )
+
+ assert 'success' in self.conf(
+ {'http': {'header_read_timeout': 2}}, 'settings'
)
+ assert req()['status'] == 408, 'status header read timeout'
- assert resp['status'] == 408, 'status header read timeout'
+ assert 'success' in self.conf(
+ {'http': {'header_read_timeout': 7}}, 'settings'
+ )
+ assert req()['status'] == 200, 'status header read timeout 2'
def test_settings_header_read_timeout_update(self):
self.load('empty')
- self.conf({'http': {'header_read_timeout': 4}}, 'settings')
+ assert 'success' in self.conf(
+ {'http': {'header_read_timeout': 4}}, 'settings'
+ )
(resp, sock) = self.http(
b"""GET / HTTP/1.1
@@ -89,31 +100,40 @@ Connection: close
def test_settings_body_read_timeout(self):
self.load('empty')
- self.conf({'http': {'body_read_timeout': 2}}, 'settings')
-
- (resp, sock) = self.http(
- b"""POST / HTTP/1.1
+ def req():
+ (resp, sock) = self.http(
+ b"""POST / HTTP/1.1
Host: localhost
Content-Length: 10
Connection: close
""",
- start=True,
- raw_resp=True,
- read_timeout=1,
- raw=True,
- )
+ start=True,
+ raw_resp=True,
+ read_timeout=1,
+ raw=True,
+ )
- time.sleep(3)
+ time.sleep(3)
- resp = self.http(b"""0123456789""", sock=sock, raw=True)
+ return self.http(b"""0123456789""", sock=sock, raw=True)
- assert resp['status'] == 408, 'status body read timeout'
+ assert 'success' in self.conf(
+ {'http': {'body_read_timeout': 2}}, 'settings'
+ )
+ assert req()['status'] == 408, 'status body read timeout'
+
+ assert 'success' in self.conf(
+ {'http': {'body_read_timeout': 7}}, 'settings'
+ )
+ assert req()['status'] == 200, 'status body read timeout 2'
def test_settings_body_read_timeout_update(self):
self.load('empty')
- self.conf({'http': {'body_read_timeout': 4}}, 'settings')
+ assert 'success' in self.conf(
+ {'http': {'body_read_timeout': 4}}, 'settings'
+ )
(resp, sock) = self.http(
b"""POST / HTTP/1.1
@@ -146,85 +166,116 @@ Connection: close
assert resp['status'] == 200, 'status body read timeout update'
def test_settings_send_timeout(self, temp_dir):
- self.load('mirror')
+ self.load('body_generate')
- data_len = 1048576
+ def req(addr, data_len):
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ sock.connect(addr)
- self.conf({'http': {'send_timeout': 1}}, 'settings')
+ req = (
+ """GET / HTTP/1.1
+Host: localhost
+X-Length: %d
+Connection: close
- addr = temp_dir + '/sock'
+"""
+ % data_len
+ )
- self.conf({"unix:" + addr: {'application': 'mirror'}}, 'listeners')
+ sock.sendall(req.encode())
- sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- sock.connect(addr)
+ data = sock.recv(16).decode()
- req = """POST / HTTP/1.1
-Host: localhost
-Content-Type: text/html
-Content-Length: %d
-Connection: close
+ time.sleep(3)
-""" % data_len + (
- 'X' * data_len
+ data += self.recvall(sock).decode()
+
+ sock.close()
+
+ return data
+
+ sysctl_out = sysctl()
+ values = re.findall(
+ r'net.core.[rw]mem_(?:max|default).*?(\d+)', sysctl_out
)
+ values = [int(v) for v in values]
- sock.sendall(req.encode())
+ data_len = 1048576 if len(values) == 0 else 10 * max(values)
- data = sock.recv(16).decode()
+ addr = temp_dir + '/sock'
- time.sleep(3)
+ assert 'success' in self.conf(
+ {"unix:" + addr: {'application': 'body_generate'}}, 'listeners'
+ )
- data += self.recvall(sock).decode()
+ assert 'success' in self.conf(
+ {'http': {'send_timeout': 1}}, 'settings'
+ )
- sock.close()
+ data = req(addr, data_len)
+ assert re.search(r'200 OK', data), 'send timeout status'
+ assert len(data) < data_len, 'send timeout data '
- assert re.search(r'200 OK', data), 'status send timeout'
- assert len(data) < data_len, 'data send timeout'
+ self.conf({'http': {'send_timeout': 7}}, 'settings')
+
+ data = req(addr, data_len)
+ assert re.search(r'200 OK', data), 'send timeout status 2'
+ assert len(data) > data_len, 'send timeout data 2'
def test_settings_idle_timeout(self):
self.load('empty')
- assert self.get()['status'] == 200, 'init'
+ def req():
+ (resp, sock) = self.get(
+ headers={'Host': 'localhost', 'Connection': 'keep-alive'},
+ start=True,
+ read_timeout=1,
+ )
- self.conf({'http': {'idle_timeout': 2}}, 'settings')
+ time.sleep(3)
- (resp, sock) = self.get(
- headers={'Host': 'localhost', 'Connection': 'keep-alive'},
- start=True,
- read_timeout=1,
- )
+ return self.get(sock=sock)
- time.sleep(3)
+ assert self.get()['status'] == 200, 'init'
- resp = self.get(
- headers={'Host': 'localhost', 'Connection': 'close'}, sock=sock
+ assert 'success' in self.conf(
+ {'http': {'idle_timeout': 2}}, 'settings'
)
+ assert req()['status'] == 408, 'status idle timeout'
- assert resp['status'] == 408, 'status idle timeout'
+ assert 'success' in self.conf(
+ {'http': {'idle_timeout': 7}}, 'settings'
+ )
+ assert req()['status'] == 200, 'status idle timeout 2'
def test_settings_idle_timeout_2(self):
self.load('empty')
- assert self.get()['status'] == 200, 'init'
+ def req():
+ _, sock = self.http(b'', start=True, raw=True, no_recv=True)
- self.conf({'http': {'idle_timeout': 1}}, 'settings')
+ time.sleep(3)
- _, sock = self.http(b'', start=True, raw=True, no_recv=True)
+ return self.get(sock=sock)
- time.sleep(3)
+ assert self.get()['status'] == 200, 'init'
- assert (
- self.get(
- headers={'Host': 'localhost', 'Connection': 'close'}, sock=sock
- )['status']
- == 408
- ), 'status idle timeout'
+ assert 'success' in self.conf(
+ {'http': {'idle_timeout': 1}}, 'settings'
+ )
+ assert req()['status'] == 408, 'status idle timeout'
+
+ assert 'success' in self.conf(
+ {'http': {'idle_timeout': 7}}, 'settings'
+ )
+ assert req()['status'] == 200, 'status idle timeout 2'
def test_settings_max_body_size(self):
self.load('empty')
- self.conf({'http': {'max_body_size': 5}}, 'settings')
+ assert 'success' in self.conf(
+ {'http': {'max_body_size': 5}}, 'settings'
+ )
assert self.post(body='01234')['status'] == 200, 'status size'
assert self.post(body='012345')['status'] == 413, 'status size max'
@@ -232,7 +283,9 @@ Connection: close
def test_settings_max_body_size_large(self):
self.load('mirror')
- self.conf({'http': {'max_body_size': 32 * 1024 * 1024}}, 'settings')
+ assert 'success' in self.conf(
+ {'http': {'max_body_size': 32 * 1024 * 1024}}, 'settings'
+ )
body = '0123456789abcdef' * 4 * 64 * 1024
resp = self.post(body=body, read_buffer_size=1024 * 1024)
diff --git a/test/test_share_chroot.py b/test/test_share_chroot.py
new file mode 100644
index 00000000..7e53d3f7
--- /dev/null
+++ b/test/test_share_chroot.py
@@ -0,0 +1,108 @@
+import os
+from pathlib import Path
+
+import pytest
+
+from unit.applications.proto import TestApplicationProto
+
+
+class TestShareChroot(TestApplicationProto):
+ prerequisites = {'features': ['chroot']}
+
+ @pytest.fixture(autouse=True)
+ def setup_method_fixture(self, temp_dir):
+ os.makedirs(temp_dir + '/assets/dir')
+ with open(temp_dir + '/assets/index.html', 'w') as index, open(
+ temp_dir + '/assets/dir/file', 'w'
+ ) as file:
+ index.write('0123456789')
+ file.write('blah')
+
+ test = Path(__file__)
+ self.test_path = '/' + test.parent.name + '/' + test.name
+
+ self._load_conf(
+ {
+ "listeners": {"*:7080": {"pass": "routes"}},
+ "routes": [{"action": {"share": temp_dir + "/assets"}}],
+ }
+ )
+
+ def test_share_chroot(self, temp_dir):
+ assert self.get(url='/dir/file')['status'] == 200, 'default chroot'
+ assert self.get(url='/index.html')['status'] == 200, 'default chroot 2'
+
+ assert 'success' in self.conf(
+ {
+ "share": temp_dir + "/assets",
+ "chroot": temp_dir + "/assets/dir",
+ },
+ 'routes/0/action',
+ ), 'configure chroot'
+
+ assert self.get(url='/dir/file')['status'] == 200, 'chroot'
+ assert self.get(url='/index.html')['status'] == 403, 'chroot 403 2'
+ assert self.get(url='/file')['status'] == 403, 'chroot 403'
+
+ def test_share_chroot_permission(self, temp_dir):
+ os.chmod(temp_dir + '/assets/dir', 0o100)
+
+ assert 'success' in self.conf(
+ {
+ "share": temp_dir + "/assets",
+ "chroot": temp_dir + "/assets/dir",
+ },
+ 'routes/0/action',
+ ), 'configure chroot'
+
+ assert self.get(url='/dir/file')['status'] == 200, 'chroot'
+
+ def test_share_chroot_empty(self, temp_dir):
+ assert 'success' in self.conf(
+ {"share": temp_dir + "/assets", "chroot": ""}, 'routes/0/action',
+ ), 'configure chroot empty absolute'
+
+ assert (
+ self.get(url='/dir/file')['status'] == 200
+ ), 'chroot empty absolute'
+
+ assert 'success' in self.conf(
+ {"share": ".", "chroot": ""}, 'routes/0/action',
+ ), 'configure chroot empty relative'
+
+ assert (
+ self.get(url=self.test_path)['status'] == 200
+ ), 'chroot empty relative'
+
+ def test_share_chroot_relative(self, is_su, temp_dir):
+ if is_su:
+ pytest.skip('does\'t work under root')
+
+ assert 'success' in self.conf(
+ {"share": temp_dir + "/assets", "chroot": "."}, 'routes/0/action',
+ ), 'configure relative chroot'
+
+ assert self.get(url='/dir/file')['status'] == 403, 'relative chroot'
+
+ assert 'success' in self.conf(
+ {"share": "."}, 'routes/0/action',
+ ), 'configure relative share'
+
+ assert self.get(url=self.test_path)['status'] == 200, 'relative share'
+
+ assert 'success' in self.conf(
+ {"share": ".", "chroot": "."}, 'routes/0/action',
+ ), 'configure relative'
+
+ assert self.get(url=self.test_path)['status'] == 200, 'relative'
+
+ def test_share_chroot_invalid(self, temp_dir):
+ assert 'error' in self.conf(
+ {"share": temp_dir, "chroot": True}, 'routes/0/action',
+ ), 'configure chroot error'
+ assert 'error' in self.conf(
+ {"share": temp_dir, "symlinks": "True"}, 'routes/0/action',
+ ), 'configure symlink error'
+ assert 'error' in self.conf(
+ {"share": temp_dir, "mount": "True"}, 'routes/0/action',
+ ), 'configure mount error'
diff --git a/test/test_share_fallback.py b/test/test_share_fallback.py
index 46464670..0b1c270e 100644
--- a/test/test_share_fallback.py
+++ b/test/test_share_fallback.py
@@ -1,6 +1,7 @@
import os
import pytest
+
from unit.applications.proto import TestApplicationProto
from unit.option import option
@@ -81,10 +82,7 @@ class TestStatic(TestApplicationProto):
def test_fallback_share(self, temp_dir):
self.action_update(
- {
- "share": "/blah",
- "fallback": {"share": temp_dir + "/assets"},
- }
+ {"share": "/blah", "fallback": {"share": temp_dir + "/assets"},}
)
resp = self.get()
diff --git a/test/test_share_mount.py b/test/test_share_mount.py
new file mode 100644
index 00000000..f22fbe75
--- /dev/null
+++ b/test/test_share_mount.py
@@ -0,0 +1,142 @@
+import os
+import subprocess
+
+import pytest
+
+from unit.applications.proto import TestApplicationProto
+
+
+class TestShareMount(TestApplicationProto):
+ prerequisites = {'features': ['chroot']}
+
+ @pytest.fixture(autouse=True)
+ def setup_method_fixture(self, is_su, temp_dir):
+ if not is_su:
+ pytest.skip('requires root')
+
+ os.makedirs(temp_dir + '/assets/dir/mount')
+ os.makedirs(temp_dir + '/assets/dir/dir')
+ os.makedirs(temp_dir + '/assets/mount')
+ with open(temp_dir + '/assets/index.html', 'w') as index, open(
+ temp_dir + '/assets/dir/dir/file', 'w'
+ ) as file, open(temp_dir + '/assets/mount/index.html', 'w') as mount:
+ index.write('index')
+ file.write('file')
+ mount.write('mount')
+
+ try:
+ process = subprocess.Popen(
+ [
+ "mount",
+ "--bind",
+ temp_dir + "/assets/mount",
+ temp_dir + "/assets/dir/mount",
+ ],
+ stderr=subprocess.STDOUT,
+ )
+
+ process.communicate()
+
+ except KeyboardInterrupt:
+ raise
+
+ except:
+ pytest.fail('Can\'t run mount process.')
+
+ self._load_conf(
+ {
+ "listeners": {"*:7080": {"pass": "routes"}},
+ "routes": [{"action": {"share": temp_dir + "/assets/dir"}}],
+ }
+ )
+
+ yield
+
+ try:
+ process = subprocess.Popen(
+ ["umount", "--lazy", temp_dir + "/assets/dir/mount"],
+ stderr=subprocess.STDOUT,
+ )
+
+ process.communicate()
+
+ except KeyboardInterrupt:
+ raise
+
+ except:
+ pytest.fail('Can\'t run umount process.')
+
+ def test_share_mount(self, temp_dir, skip_alert):
+ skip_alert(r'opening.*failed')
+
+ resp = self.get(url='/mount/')
+ assert resp['status'] == 200
+ assert resp['body'] == 'mount'
+
+ assert 'success' in self.conf(
+ {"share": temp_dir + "/assets/dir", "traverse_mounts": False},
+ 'routes/0/action',
+ ), 'configure mount disable'
+
+ assert self.get(url='/mount/')['status'] == 403
+
+ assert 'success' in self.conf(
+ {"share": temp_dir + "/assets/dir", "traverse_mounts": True},
+ 'routes/0/action',
+ ), 'configure mount enable'
+
+ resp = self.get(url='/mount/')
+ assert resp['status'] == 200
+ assert resp['body'] == 'mount'
+
+ def test_share_mount_two_blocks(self, temp_dir, skip_alert):
+ skip_alert(r'opening.*failed')
+
+ os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link')
+
+ assert 'success' in self.conf(
+ [
+ {
+ "match": {"method": "HEAD"},
+ "action": {
+ "share": temp_dir + "/assets/dir",
+ "traverse_mounts": False,
+ },
+ },
+ {
+ "match": {"method": "GET"},
+ "action": {
+ "share": temp_dir + "/assets/dir",
+ "traverse_mounts": True,
+ },
+ },
+ ],
+ 'routes',
+ ), 'configure two options'
+
+ assert self.get(url='/mount/')['status'] == 200, 'block enabled'
+ assert self.head(url='/mount/')['status'] == 403, 'block disabled'
+
+ def test_share_mount_chroot(self, temp_dir, skip_alert):
+ skip_alert(r'opening.*failed')
+
+ assert 'success' in self.conf(
+ {
+ "share": temp_dir + "/assets/dir",
+ "chroot": temp_dir + "/assets",
+ },
+ 'routes/0/action',
+ ), 'configure chroot mount default'
+
+ assert self.get(url='/mount/')['status'] == 200, 'chroot'
+
+ assert 'success' in self.conf(
+ {
+ "share": temp_dir + "/assets/dir",
+ "chroot": temp_dir + "/assets",
+ "traverse_mounts": False,
+ },
+ 'routes/0/action',
+ ), 'configure chroot mount disable'
+
+ assert self.get(url='/mount/')['status'] == 403, 'chroot mount'
diff --git a/test/test_share_symlink.py b/test/test_share_symlink.py
new file mode 100644
index 00000000..3970b605
--- /dev/null
+++ b/test/test_share_symlink.py
@@ -0,0 +1,96 @@
+import os
+
+import pytest
+
+from unit.applications.proto import TestApplicationProto
+
+
+class TestShareSymlink(TestApplicationProto):
+ prerequisites = {'features': ['chroot']}
+
+ @pytest.fixture(autouse=True)
+ def setup_method_fixture(self, temp_dir):
+ os.makedirs(temp_dir + '/assets/dir/dir')
+ with open(temp_dir + '/assets/index.html', 'w') as index, open(
+ temp_dir + '/assets/dir/file', 'w'
+ ) as file:
+ index.write('0123456789')
+ file.write('blah')
+
+ self._load_conf(
+ {
+ "listeners": {"*:7080": {"pass": "routes"}},
+ "routes": [{"action": {"share": temp_dir + "/assets"}}],
+ }
+ )
+
+ def test_share_symlink(self, temp_dir, skip_alert):
+ skip_alert(r'opening.*failed')
+
+ os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link')
+
+ assert self.get(url='/dir')['status'] == 301, 'dir'
+ assert self.get(url='/dir/file')['status'] == 200, 'file'
+ assert self.get(url='/link')['status'] == 301, 'symlink dir'
+ assert self.get(url='/link/file')['status'] == 200, 'symlink file'
+
+ assert 'success' in self.conf(
+ {"share": temp_dir + "/assets", "follow_symlinks": False},
+ 'routes/0/action',
+ ), 'configure symlink disable'
+
+ assert self.get(url='/link/file')['status'] == 403, 'symlink disabled'
+
+ assert 'success' in self.conf(
+ {"share": temp_dir + "/assets", "follow_symlinks": True},
+ 'routes/0/action',
+ ), 'configure symlink enable'
+
+ assert self.get(url='/link/file')['status'] == 200, 'symlink enabled'
+
+ def test_share_symlink_two_blocks(self, temp_dir, skip_alert):
+ skip_alert(r'opening.*failed')
+
+ os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link')
+
+ assert 'success' in self.conf(
+ [
+ {
+ "match": {"method": "HEAD"},
+ "action": {
+ "share": temp_dir + "/assets",
+ "follow_symlinks": False,
+ },
+ },
+ {
+ "match": {"method": "GET"},
+ "action": {
+ "share": temp_dir + "/assets",
+ "follow_symlinks": True,
+ },
+ },
+ ],
+ 'routes',
+ ), 'configure two options'
+
+ assert self.get(url='/link/file')['status'] == 200, 'block enabled'
+ assert self.head(url='/link/file')['status'] == 403, 'block disabled'
+
+ def test_share_symlink_chroot(self, temp_dir, skip_alert):
+ skip_alert(r'opening.*failed')
+
+ os.symlink(
+ temp_dir + '/assets/dir/file', temp_dir + '/assets/dir/dir/link'
+ )
+
+ assert self.get(url='/dir/dir/link')['status'] == 200, 'default chroot'
+
+ assert 'success' in self.conf(
+ {
+ "share": temp_dir + "/assets",
+ "chroot": temp_dir + "/assets/dir/dir",
+ },
+ 'routes/0/action',
+ ), 'configure chroot'
+
+ assert self.get(url='/dir/dir/link')['status'] == 404, 'chroot'
diff --git a/test/test_share_types.py b/test/test_share_types.py
new file mode 100644
index 00000000..b5ed97a0
--- /dev/null
+++ b/test/test_share_types.py
@@ -0,0 +1,170 @@
+import os
+from pathlib import Path
+
+import pytest
+from unit.applications.proto import TestApplicationProto
+from unit.option import option
+
+
+class TestShareTypes(TestApplicationProto):
+ prerequisites = {}
+
+ @pytest.fixture(autouse=True)
+ def setup_method_fixture(self, temp_dir):
+ Path(temp_dir + '/assets').mkdir()
+ for ext in ['.xml', '.mp4', '.php', '', '.txt', '.html', '.png']:
+ Path(temp_dir + '/assets/file' + ext).write_text(ext)
+
+ Path(temp_dir + '/assets/index.html').write_text('index')
+
+ self._load_conf(
+ {
+ "listeners": {
+ "*:7080": {"pass": "routes"},
+ "*:7081": {"pass": "routes"},
+ },
+ "routes": [{"action": {"share": temp_dir + "/assets"}}],
+ "applications": {},
+ }
+ )
+
+ def action_update(self, conf):
+ assert 'success' in self.conf(conf, 'routes/0/action')
+
+ def check_body(self, http_url, body):
+ resp = self.get(url=http_url)
+ assert resp['status'] == 200, 'status'
+ assert resp['body'] == body, 'body'
+
+ def test_share_types_basic(self, temp_dir):
+ self.action_update({"share": temp_dir + "/assets"})
+ self.check_body('/index.html', 'index')
+ self.check_body('/file.xml', '.xml')
+
+ self.action_update(
+ {"share": temp_dir + "/assets", "types": "application/xml"}
+ )
+ self.check_body('/file.xml', '.xml')
+
+ self.action_update(
+ {"share": temp_dir + "/assets", "types": ["application/xml"]}
+ )
+ self.check_body('/file.xml', '.xml')
+
+ self.action_update({"share": temp_dir + "/assets", "types": [""]})
+ assert self.get(url='/file.xml')['status'] == 403, 'no mtype'
+
+ def test_share_types_wildcard(self, temp_dir):
+ self.action_update(
+ {"share": temp_dir + "/assets", "types": ["application/*"]}
+ )
+ self.check_body('/file.xml', '.xml')
+ assert self.get(url='/file.mp4')['status'] == 403, 'app * mtype mp4'
+
+ self.action_update(
+ {"share": temp_dir + "/assets", "types": ["video/*"]}
+ )
+ assert self.get(url='/file.xml')['status'] == 403, 'video * mtype xml'
+ self.check_body('/file.mp4', '.mp4')
+
+ def test_share_types_negation(self, temp_dir):
+ self.action_update(
+ {"share": temp_dir + "/assets", "types": ["!application/xml"]}
+ )
+ assert self.get(url='/file.xml')['status'] == 403, 'forbidden negation'
+ self.check_body('/file.mp4', '.mp4')
+
+ # sorting negation
+ self.action_update(
+ {
+ "share": temp_dir + "/assets",
+ "types": ["!video/*", "image/png", "!image/jpg"],
+ }
+ )
+ assert self.get(url='/file.mp4')['status'] == 403, 'negation sort mp4'
+ self.check_body('/file.png', '.png')
+ assert self.get(url='/file.jpg')['status'] == 403, 'negation sort jpg'
+
+ def test_share_types_regex(self, temp_dir):
+ self.action_update(
+ {"share": temp_dir + "/assets", "types": ["~text/(html|plain)"]}
+ )
+ assert self.get(url='/file.php')['status'] == 403, 'regex fail'
+ self.check_body('/file.html', '.html')
+ self.check_body('/file.txt', '.txt')
+
+ def test_share_types_case(self, temp_dir):
+ self.action_update(
+ {"share": temp_dir + "/assets", "types": ["!APpliCaTiOn/xMl"]}
+ )
+ self.check_body('/file.mp4', '.mp4')
+ assert (
+ self.get(url='/file.xml')['status'] == 403
+ ), 'mixed case xml negation'
+
+ self.action_update(
+ {"share": temp_dir + "/assets", "types": ["vIdEo/mp4"]}
+ )
+ assert self.get(url='/file.mp4')['status'] == 200, 'mixed case'
+ assert (
+ self.get(url='/file.xml')['status'] == 403
+ ), 'mixed case video negation'
+
+ self.action_update(
+ {"share": temp_dir + "/assets", "types": ["vIdEo/*"]}
+ )
+ self.check_body('/file.mp4', '.mp4')
+ assert (
+ self.get(url='/file.xml')['status'] == 403
+ ), 'mixed case video * negation'
+
+ def test_share_types_fallback(self, temp_dir):
+ assert 'success' in self.conf(
+ [
+ {
+ "match": {"destination": "*:7081"},
+ "action": {"return": 200},
+ },
+ {
+ "action": {
+ "share": temp_dir + "/assets",
+ "types": ["!application/x-httpd-php"],
+ "fallback": {"proxy": "http://127.0.0.1:7081"},
+ }
+ },
+ ],
+ 'routes',
+ ), 'configure fallback proxy route'
+
+ self.check_body('/file.php', '')
+ self.check_body('/file.mp4', '.mp4')
+
+ def test_share_types_index(self, temp_dir):
+ self.action_update(
+ {"share": temp_dir + "/assets", "types": "application/xml"}
+ )
+ self.check_body('/', 'index')
+ self.check_body('/file.xml', '.xml')
+ assert self.get(url='/file.mp4')['status'] == 403, 'forbidden mtype'
+
+ def test_share_types_custom_mime(self, temp_dir):
+ self._load_conf(
+ {
+ "listeners": {"*:7080": {"pass": "routes"}},
+ "routes": [{"action": {"share": temp_dir + "/assets"}}],
+ "applications": {},
+ "settings": {
+ "http": {
+ "static": {"mime_types": {"test/mime-type": ["file"]}}
+ }
+ },
+ }
+ )
+
+ self.action_update({"share": temp_dir + "/assets", "types": [""]})
+ assert self.get(url='/file')['status'] == 403, 'forbidden custom mime'
+
+ self.action_update(
+ {"share": temp_dir + "/assets", "types": ["test/mime-type"]}
+ )
+ self.check_body('/file', '')
diff --git a/test/test_static.py b/test/test_static.py
index 3e85b435..669e265d 100644
--- a/test/test_static.py
+++ b/test/test_static.py
@@ -2,6 +2,7 @@ import os
import socket
import pytest
+
from unit.applications.proto import TestApplicationProto
from unit.option import option
from unit.utils import waitforfiles
@@ -85,8 +86,7 @@ class TestStatic(TestApplicationProto):
def test_static_space_in_name(self, temp_dir):
os.rename(
- temp_dir + '/assets/dir/file',
- temp_dir + '/assets/dir/fi le',
+ temp_dir + '/assets/dir/file', temp_dir + '/assets/dir/fi le',
)
assert waitforfiles(temp_dir + '/assets/dir/fi le')
assert self.get(url='/dir/fi le')['body'] == 'blah', 'file name'
@@ -95,9 +95,7 @@ class TestStatic(TestApplicationProto):
assert waitforfiles(temp_dir + '/assets/di r/fi le')
assert self.get(url='/di r/fi le')['body'] == 'blah', 'dir name'
- os.rename(
- temp_dir + '/assets/di r', temp_dir + '/assets/ di r '
- )
+ os.rename(temp_dir + '/assets/di r', temp_dir + '/assets/ di r ')
assert waitforfiles(temp_dir + '/assets/ di r /fi le')
assert (
self.get(url='/ di r /fi le')['body'] == 'blah'
@@ -150,8 +148,7 @@ class TestStatic(TestApplicationProto):
), 'file name 2'
os.rename(
- temp_dir + '/assets/ di r ',
- temp_dir + '/assets/ди ректория',
+ temp_dir + '/assets/ di r ', temp_dir + '/assets/ди ректория',
)
assert waitforfiles(temp_dir + '/assets/ди ректория/фа йл')
assert (
@@ -171,14 +168,6 @@ class TestStatic(TestApplicationProto):
assert self.get(url='/fifo')['status'] == 404, 'fifo'
- def test_static_symlink(self, temp_dir):
- os.symlink(temp_dir + '/assets/dir', temp_dir + '/assets/link')
-
- assert self.get(url='/dir')['status'] == 301, 'dir'
- assert self.get(url='/dir/file')['status'] == 200, 'file'
- assert self.get(url='/link')['status'] == 301, 'symlink dir'
- assert self.get(url='/link/file')['status'] == 200, 'symlink file'
-
def test_static_method(self):
resp = self.head()
assert resp['status'] == 200, 'HEAD status'
diff --git a/test/test_tls.py b/test/test_tls.py
index 206ea828..0cfeaded 100644
--- a/test/test_tls.py
+++ b/test/test_tls.py
@@ -2,8 +2,10 @@ import io
import re
import ssl
import subprocess
+import time
import pytest
+
from unit.applications.tls import TestApplicationTLS
from unit.option import option
@@ -11,10 +13,6 @@ from unit.option import option
class TestTLS(TestApplicationTLS):
prerequisites = {'modules': {'python': 'any', 'openssl': 'any'}}
- def findall(self, pattern):
- with open(option.temp_dir + '/unit.log', 'r', errors='ignore') as f:
- return re.findall(pattern, f.read())
-
def openssl_date_to_sec_epoch(self, date):
return self.date_to_sec_epoch(date, '%b %d %H:%M:%S %Y %Z')
@@ -22,7 +20,7 @@ class TestTLS(TestApplicationTLS):
assert 'success' in self.conf(
{
"pass": "applications/" + application,
- "tls": {"certificate": cert}
+ "tls": {"certificate": cert},
},
'listeners/*:' + str(port),
)
@@ -32,6 +30,91 @@ class TestTLS(TestApplicationTLS):
{"pass": "applications/" + application}, 'listeners/*:' + str(port)
)
+ def req(self, name='localhost', subject=None, x509=False):
+ subj = subject if subject is not None else '/CN=' + name + '/'
+
+ subprocess.call(
+ [
+ 'openssl',
+ 'req',
+ '-new',
+ '-subj',
+ subj,
+ '-config',
+ option.temp_dir + '/openssl.conf',
+ '-out',
+ option.temp_dir + '/' + name + '.csr',
+ '-keyout',
+ option.temp_dir + '/' + name + '.key',
+ ],
+ stderr=subprocess.STDOUT,
+ )
+
+ def generate_ca_conf(self):
+ with open(option.temp_dir + '/ca.conf', 'w') as f:
+ f.write(
+ """[ ca ]
+default_ca = myca
+
+[ myca ]
+new_certs_dir = %(dir)s
+database = %(database)s
+default_md = sha256
+policy = myca_policy
+serial = %(certserial)s
+default_days = 1
+x509_extensions = myca_extensions
+copy_extensions = copy
+
+[ myca_policy ]
+commonName = optional
+
+[ myca_extensions ]
+basicConstraints = critical,CA:TRUE"""
+ % {
+ 'dir': option.temp_dir,
+ 'database': option.temp_dir + '/certindex',
+ 'certserial': option.temp_dir + '/certserial',
+ }
+ )
+
+ with open(option.temp_dir + '/certserial', 'w') as f:
+ f.write('1000')
+
+ with open(option.temp_dir + '/certindex', 'w') as f:
+ f.write('')
+
+ with open(option.temp_dir + '/certindex.attr', 'w') as f:
+ f.write('')
+
+ def ca(self, cert='root', out='localhost'):
+ subprocess.call(
+ [
+ 'openssl',
+ 'ca',
+ '-batch',
+ '-config',
+ option.temp_dir + '/ca.conf',
+ '-keyfile',
+ option.temp_dir + '/' + cert + '.key',
+ '-cert',
+ option.temp_dir + '/' + cert + '.crt',
+ '-in',
+ option.temp_dir + '/' + out + '.csr',
+ '-out',
+ option.temp_dir + '/' + out + '.crt',
+ ],
+ stderr=subprocess.STDOUT,
+ )
+
+ def set_certificate_req_context(self, cert='root'):
+ self.context = ssl.create_default_context()
+ self.context.check_hostname = False
+ self.context.verify_mode = ssl.CERT_REQUIRED
+ self.context.load_verify_locations(
+ option.temp_dir + '/' + cert + '.crt'
+ )
+
def test_tls_listener_option_add(self):
self.load('empty')
@@ -212,113 +295,13 @@ class TestTLS(TestApplicationTLS):
self.certificate('root', False)
- subprocess.call(
- [
- 'openssl',
- 'req',
- '-new',
- '-subj',
- '/CN=int/',
- '-config',
- temp_dir + '/openssl.conf',
- '-out',
- temp_dir + '/int.csr',
- '-keyout',
- temp_dir + '/int.key',
- ],
- stderr=subprocess.STDOUT,
- )
+ self.req('int')
+ self.req('end')
- subprocess.call(
- [
- 'openssl',
- 'req',
- '-new',
- '-subj',
- '/CN=end/',
- '-config',
- temp_dir + '/openssl.conf',
- '-out',
- temp_dir + '/end.csr',
- '-keyout',
- temp_dir + '/end.key',
- ],
- stderr=subprocess.STDOUT,
- )
+ self.generate_ca_conf()
- with open(temp_dir + '/ca.conf', 'w') as f:
- f.write(
- """[ ca ]
-default_ca = myca
-
-[ myca ]
-new_certs_dir = %(dir)s
-database = %(database)s
-default_md = sha256
-policy = myca_policy
-serial = %(certserial)s
-default_days = 1
-x509_extensions = myca_extensions
-
-[ myca_policy ]
-commonName = supplied
-
-[ myca_extensions ]
-basicConstraints = critical,CA:TRUE"""
- % {
- 'dir': temp_dir,
- 'database': temp_dir + '/certindex',
- 'certserial': temp_dir + '/certserial',
- }
- )
-
- with open(temp_dir + '/certserial', 'w') as f:
- f.write('1000')
-
- with open(temp_dir + '/certindex', 'w') as f:
- f.write('')
-
- subprocess.call(
- [
- 'openssl',
- 'ca',
- '-batch',
- '-subj',
- '/CN=int/',
- '-config',
- temp_dir + '/ca.conf',
- '-keyfile',
- temp_dir + '/root.key',
- '-cert',
- temp_dir + '/root.crt',
- '-in',
- temp_dir + '/int.csr',
- '-out',
- temp_dir + '/int.crt',
- ],
- stderr=subprocess.STDOUT,
- )
-
- subprocess.call(
- [
- 'openssl',
- 'ca',
- '-batch',
- '-subj',
- '/CN=end/',
- '-config',
- temp_dir + '/ca.conf',
- '-keyfile',
- temp_dir + '/int.key',
- '-cert',
- temp_dir + '/int.crt',
- '-in',
- temp_dir + '/end.csr',
- '-out',
- temp_dir + '/end.crt',
- ],
- stderr=subprocess.STDOUT,
- )
+ self.ca(cert='root', out='int')
+ self.ca(cert='int', out='end')
crt_path = temp_dir + '/end-int.crt'
end_path = temp_dir + '/end.crt'
@@ -329,10 +312,7 @@ basicConstraints = critical,CA:TRUE"""
) as int:
crt.write(end.read() + int.read())
- self.context = ssl.create_default_context()
- self.context.check_hostname = False
- self.context.verify_mode = ssl.CERT_REQUIRED
- self.context.load_verify_locations(temp_dir + '/root.crt')
+ self.set_certificate_req_context()
# incomplete chain
@@ -406,6 +386,67 @@ basicConstraints = critical,CA:TRUE"""
self.get_ssl()['status'] == 200
), 'certificate chain intermediate server'
+ def test_tls_certificate_empty_cn(self, temp_dir):
+ self.certificate('root', False)
+
+ self.req(subject='/')
+
+ self.generate_ca_conf()
+ self.ca()
+
+ self.set_certificate_req_context()
+
+ assert 'success' in self.certificate_load('localhost', 'localhost')
+
+ cert = self.conf_get('/certificates/localhost')
+ assert cert['chain'][0]['subject'] == {}, 'empty subject'
+ assert cert['chain'][0]['issuer']['common_name'] == 'root', 'issuer'
+
+ def test_tls_certificate_empty_cn_san(self, temp_dir):
+ self.certificate('root', False)
+
+ self.openssl_conf(
+ rewrite=True, alt_names=["example.com", "www.example.net"]
+ )
+
+ self.req(subject='/')
+
+ self.generate_ca_conf()
+ self.ca()
+
+ self.set_certificate_req_context()
+
+ assert 'success' in self.certificate_load('localhost', 'localhost')
+
+ cert = self.conf_get('/certificates/localhost')
+ assert cert['chain'][0]['subject'] == {
+ 'alt_names': ['example.com', 'www.example.net']
+ }, 'subject alt_names'
+ assert cert['chain'][0]['issuer']['common_name'] == 'root', 'issuer'
+
+ def test_tls_certificate_empty_cn_san_ip(self):
+ self.certificate('root', False)
+
+ self.openssl_conf(
+ rewrite=True,
+ alt_names=['example.com', 'www.example.net', 'IP|10.0.0.1'],
+ )
+
+ self.req(subject='/')
+
+ self.generate_ca_conf()
+ self.ca()
+
+ self.set_certificate_req_context()
+
+ assert 'success' in self.certificate_load('localhost', 'localhost')
+
+ cert = self.conf_get('/certificates/localhost')
+ assert cert['chain'][0]['subject'] == {
+ 'alt_names': ['example.com', 'www.example.net']
+ }, 'subject alt_names'
+ assert cert['chain'][0]['issuer']['common_name'] == 'root', 'issuer'
+
@pytest.mark.skip('not yet')
def test_tls_reconfigure(self):
self.load('empty')
@@ -461,6 +502,28 @@ basicConstraints = critical,CA:TRUE"""
assert resp['body'] == '0123456789', 'keepalive 2'
+ def test_tls_no_close_notify(self):
+ self.certificate()
+
+ assert 'success' in self.conf(
+ {
+ "listeners": {
+ "*:7080": {
+ "pass": "routes",
+ "tls": {"certificate": "default"},
+ }
+ },
+ "routes": [{"action": {"return": 200}}],
+ "applications": {},
+ }
+ ), 'load application configuration'
+
+ (resp, sock) = self.get_ssl(start=True)
+
+ time.sleep(5)
+
+ sock.close()
+
@pytest.mark.skip('not yet')
def test_tls_keepalive_certificate_remove(self):
self.load('empty')
diff --git a/test/test_tls_conf_command.py b/test/test_tls_conf_command.py
new file mode 100644
index 00000000..ccae09ad
--- /dev/null
+++ b/test/test_tls_conf_command.py
@@ -0,0 +1,112 @@
+import ssl
+
+import pytest
+
+from unit.applications.tls import TestApplicationTLS
+
+
+class TestTLSConfCommand(TestApplicationTLS):
+ prerequisites = {'modules': {'openssl': 'any'}}
+
+ @pytest.fixture(autouse=True)
+ def setup_method_fixture(self, request):
+ self.certificate()
+
+ assert 'success' in self.conf(
+ {
+ "listeners": {
+ "*:7080": {
+ "pass": "routes",
+ "tls": {"certificate": "default"},
+ }
+ },
+ "routes": [{"action": {"return": 200}}],
+ "applications": {},
+ }
+ ), 'load application configuration'
+
+ def test_tls_conf_command(self):
+ def check_no_connection():
+ try:
+ self.get_ssl()
+ pytest.fail('Unexpected connection.')
+
+ except (ssl.SSLError, ConnectionRefusedError):
+ pass
+
+ # Set one conf_commands (disable protocol).
+
+ (resp, sock) = self.get_ssl(start=True)
+
+ shared_ciphers = sock.shared_ciphers()
+ protocols = list(set(c[1] for c in shared_ciphers))
+ protocol = sock.cipher()[1]
+
+ if '/' in protocol:
+ pytest.skip('Complex protocol format.')
+
+ assert 'success' in self.conf(
+ {
+ "certificate": "default",
+ "conf_commands": {"protocol": '-' + protocol},
+ },
+ 'listeners/*:7080/tls',
+ ), 'protocol disabled'
+
+ sock.close()
+
+ if len(protocols) > 1:
+ (resp, sock) = self.get_ssl(start=True)
+
+ cipher = sock.cipher()
+ assert cipher[1] != protocol, 'new protocol used'
+
+ shared_ciphers = sock.shared_ciphers()
+ ciphers = list(set(c for c in shared_ciphers if c[1] == cipher[1]))
+
+ sock.close()
+ else:
+ check_no_connection()
+ pytest.skip('One TLS protocol available only.')
+
+ # Set two conf_commands (disable protocol and cipher).
+
+ assert 'success' in self.conf(
+ {
+ "certificate": "default",
+ "conf_commands": {
+ "protocol": '-' + protocol,
+ "cipherstring": cipher[1] + ":!" + cipher[0],
+ },
+ },
+ 'listeners/*:7080/tls',
+ ), 'cipher disabled'
+
+ if len(ciphers) > 1:
+ (resp, sock) = self.get_ssl(start=True)
+
+ cipher_new = sock.cipher()
+ assert cipher_new[1] == cipher[1], 'previous protocol used'
+ assert cipher_new[0] != cipher[0], 'new cipher used'
+
+ sock.close()
+
+ else:
+ check_no_connection()
+
+ def test_tls_conf_command_invalid(self, skip_alert):
+ skip_alert(r'SSL_CONF_cmd', r'failed to apply new conf')
+
+ def check_conf_commands(conf_commands):
+ assert 'error' in self.conf(
+ {"certificate": "default", "conf_commands": conf_commands},
+ 'listeners/*:7080/tls',
+ ), 'ivalid conf_commands'
+
+ check_conf_commands([])
+ check_conf_commands("blah")
+ check_conf_commands({"": ""})
+ check_conf_commands({"blah": ""})
+ check_conf_commands({"protocol": {}})
+ check_conf_commands({"protocol": "blah"})
+ check_conf_commands({"protocol": "TLSv1.2", "blah": ""})
diff --git a/test/test_tls_sni.py b/test/test_tls_sni.py
new file mode 100644
index 00000000..2e5424e2
--- /dev/null
+++ b/test/test_tls_sni.py
@@ -0,0 +1,276 @@
+import ssl
+import subprocess
+
+import pytest
+
+from unit.applications.tls import TestApplicationTLS
+from unit.option import option
+
+
+class TestTLSSNI(TestApplicationTLS):
+ prerequisites = {'modules': {'openssl': 'any'}}
+
+ def setup_method(self):
+ self._load_conf(
+ {
+ "listeners": {"*:7080": {"pass": "routes"}},
+ "routes": [{"action": {"return": 200}}],
+ "applications": {},
+ }
+ )
+
+ def openssl_date_to_sec_epoch(self, date):
+ return self.date_to_sec_epoch(date, '%b %d %H:%M:%S %Y %Z')
+
+ def add_tls(self, cert='default'):
+ assert 'success' in self.conf(
+ {"pass": "routes", "tls": {"certificate": cert}},
+ 'listeners/*:7080',
+ )
+
+ def remove_tls(self):
+ assert 'success' in self.conf({"pass": "routes"}, 'listeners/*:7080')
+
+ def generate_ca_conf(self):
+ with open(option.temp_dir + '/ca.conf', 'w') as f:
+ f.write(
+ """[ ca ]
+default_ca = myca
+
+[ myca ]
+new_certs_dir = %(dir)s
+database = %(database)s
+default_md = sha256
+policy = myca_policy
+serial = %(certserial)s
+default_days = 1
+x509_extensions = myca_extensions
+copy_extensions = copy
+
+[ myca_policy ]
+commonName = optional
+
+[ myca_extensions ]
+basicConstraints = critical,CA:TRUE"""
+ % {
+ 'dir': option.temp_dir,
+ 'database': option.temp_dir + '/certindex',
+ 'certserial': option.temp_dir + '/certserial',
+ }
+ )
+
+ with open(option.temp_dir + '/certserial', 'w') as f:
+ f.write('1000')
+
+ with open(option.temp_dir + '/certindex', 'w') as f:
+ f.write('')
+
+ def config_bundles(self, bundles):
+ self.certificate('root', False)
+
+ for b in bundles:
+ self.openssl_conf(rewrite=True, alt_names=bundles[b]['alt_names'])
+ subj = (
+ '/CN={}/'.format(bundles[b]['subj'])
+ if 'subj' in bundles[b]
+ else '/'
+ )
+
+ subprocess.call(
+ [
+ 'openssl',
+ 'req',
+ '-new',
+ '-subj',
+ subj,
+ '-config',
+ option.temp_dir + '/openssl.conf',
+ '-out',
+ option.temp_dir + '/{}.csr'.format(b),
+ '-keyout',
+ option.temp_dir + '/{}.key'.format(b),
+ ],
+ stderr=subprocess.STDOUT,
+ )
+
+ self.generate_ca_conf()
+
+ for b in bundles:
+ subj = (
+ '/CN={}/'.format(bundles[b]['subj'])
+ if 'subj' in bundles[b]
+ else '/'
+ )
+
+ subprocess.call(
+ [
+ 'openssl',
+ 'ca',
+ '-batch',
+ '-subj',
+ subj,
+ '-config',
+ option.temp_dir + '/ca.conf',
+ '-keyfile',
+ option.temp_dir + '/root.key',
+ '-cert',
+ option.temp_dir + '/root.crt',
+ '-in',
+ option.temp_dir + '/{}.csr'.format(b),
+ '-out',
+ option.temp_dir + '/{}.crt'.format(b),
+ ],
+ stderr=subprocess.STDOUT,
+ )
+
+ self.context = ssl.create_default_context()
+ self.context.check_hostname = False
+ self.context.verify_mode = ssl.CERT_REQUIRED
+ self.context.load_verify_locations(option.temp_dir + '/root.crt')
+
+ self.load_certs(bundles)
+
+ def load_certs(self, bundles):
+ for bname, bvalue in bundles.items():
+ assert 'success' in self.certificate_load(
+ bname, bname
+ ), 'certificate {} upload'.format(bvalue['subj'])
+
+ def check_cert(self, host, expect):
+ resp, sock = self.get_ssl(
+ headers={
+ 'Host': host,
+ 'Content-Length': '0',
+ 'Connection': 'close',
+ },
+ start=True,
+ )
+
+ assert resp['status'] == 200
+ assert sock.getpeercert()['subject'][0][0][1] == expect
+
+ def test_tls_sni(self):
+ bundles = {
+ "default": {"subj": "default", "alt_names": ["default"]},
+ "localhost.com": {
+ "subj": "localhost.com",
+ "alt_names": ["alt1.localhost.com"],
+ },
+ "example.com": {
+ "subj": "example.com",
+ "alt_names": ["alt1.example.com", "alt2.example.com"],
+ },
+ }
+ self.config_bundles(bundles)
+ self.add_tls(["default", "localhost.com", "example.com"])
+
+ self.check_cert('alt1.localhost.com', bundles['localhost.com']['subj'])
+ self.check_cert('alt2.example.com', bundles['example.com']['subj'])
+ self.check_cert('blah', bundles['default']['subj'])
+
+ def test_tls_sni_upper_case(self):
+ bundles = {
+ "localhost.com": {"subj": "LOCALHOST.COM", "alt_names": []},
+ "example.com": {
+ "subj": "example.com",
+ "alt_names": ["ALT1.EXAMPLE.COM", "*.ALT2.EXAMPLE.COM"],
+ },
+ }
+ self.config_bundles(bundles)
+ self.add_tls(["localhost.com", "example.com"])
+
+ self.check_cert('localhost.com', bundles['localhost.com']['subj'])
+ self.check_cert('LOCALHOST.COM', bundles['localhost.com']['subj'])
+ self.check_cert('EXAMPLE.COM', bundles['localhost.com']['subj'])
+ self.check_cert('ALT1.EXAMPLE.COM', bundles['example.com']['subj'])
+ self.check_cert('WWW.ALT2.EXAMPLE.COM', bundles['example.com']['subj'])
+
+ def test_tls_sni_only_bundle(self):
+ bundles = {
+ "localhost.com": {
+ "subj": "localhost.com",
+ "alt_names": ["alt1.localhost.com", "alt2.localhost.com"],
+ }
+ }
+ self.config_bundles(bundles)
+ self.add_tls(["localhost.com"])
+
+ self.check_cert('domain.com', bundles['localhost.com']['subj'])
+ self.check_cert('alt1.domain.com', bundles['localhost.com']['subj'])
+
+ def test_tls_sni_wildcard(self):
+ bundles = {
+ "localhost.com": {"subj": "localhost.com", "alt_names": []},
+ "example.com": {
+ "subj": "example.com",
+ "alt_names": ["*.example.com", "*.alt.example.com"],
+ },
+ }
+ self.config_bundles(bundles)
+ self.add_tls(["localhost.com", "example.com"])
+
+ self.check_cert('example.com', bundles['localhost.com']['subj'])
+ self.check_cert('www.example.com', bundles['example.com']['subj'])
+ self.check_cert('alt.example.com', bundles['example.com']['subj'])
+ self.check_cert('www.alt.example.com', bundles['example.com']['subj'])
+ self.check_cert('www.alt.example.ru', bundles['localhost.com']['subj'])
+
+ def test_tls_sni_duplicated_bundle(self):
+ bundles = {
+ "localhost.com": {
+ "subj": "localhost.com",
+ "alt_names": ["localhost.com", "alt2.localhost.com"],
+ }
+ }
+ self.config_bundles(bundles)
+ self.add_tls(["localhost.com", "localhost.com"])
+
+ self.check_cert('localhost.com', bundles['localhost.com']['subj'])
+ self.check_cert('alt2.localhost.com', bundles['localhost.com']['subj'])
+
+ def test_tls_sni_same_alt(self):
+ bundles = {
+ "localhost": {"subj": "subj1", "alt_names": "same.altname.com"},
+ "example": {"subj": "subj2", "alt_names": "same.altname.com"},
+ }
+ self.config_bundles(bundles)
+ self.add_tls(["localhost", "example"])
+
+ self.check_cert('localhost', bundles['localhost']['subj'])
+ self.check_cert('example', bundles['localhost']['subj'])
+
+ def test_tls_sni_empty_cn(self):
+ bundles = {"localhost": {"alt_names": ["alt.localhost.com"]}}
+ self.config_bundles(bundles)
+ self.add_tls(["localhost"])
+
+ resp, sock = self.get_ssl(
+ headers={
+ 'Host': 'domain.com',
+ 'Content-Length': '0',
+ 'Connection': 'close',
+ },
+ start=True,
+ )
+
+ assert resp['status'] == 200
+ assert (
+ sock.getpeercert()['subjectAltName'][0][1] == 'alt.localhost.com'
+ )
+
+ def test_tls_sni_invalid(self):
+ self.config_bundles({"localhost": {"subj": "subj1", "alt_names": ''}})
+ self.add_tls(["localhost"])
+
+ def check_certificate(cert):
+ assert 'error' in self.conf(
+ {"pass": "routes", "tls": {"certificate": cert}},
+ 'listeners/*:7080',
+ )
+
+ check_certificate('')
+ check_certificate('blah')
+ check_certificate([])
+ check_certificate(['blah'])
+ check_certificate(['localhost', 'blah'])
+ check_certificate(['localhost', []])
diff --git a/test/test_usr1.py b/test/test_usr1.py
index dbb5265c..9a12b747 100644
--- a/test/test_usr1.py
+++ b/test/test_usr1.py
@@ -1,14 +1,15 @@
import os
-from subprocess import call
+import signal
from unit.applications.lang.python import TestApplicationPython
+from unit.log import Log
from unit.utils import waitforfiles
class TestUSR1(TestApplicationPython):
prerequisites = {'modules': {'python': 'any'}}
- def test_usr1_access_log(self, temp_dir):
+ def test_usr1_access_log(self, temp_dir, unit_pid):
self.load('empty')
log = 'access.log'
@@ -31,10 +32,7 @@ class TestUSR1(TestApplicationPython):
), 'rename new'
assert not os.path.isfile(log_path), 'rename old'
- with open(temp_dir + '/unit.pid', 'r') as f:
- pid = f.read().rstrip()
-
- call(['kill', '-s', 'USR1', pid])
+ os.kill(unit_pid, signal.SIGUSR1)
assert waitforfiles(log_path), 'reopen'
@@ -46,7 +44,7 @@ class TestUSR1(TestApplicationPython):
), 'reopen 2'
assert self.search_in_log(r'/usr1', log_new) is None, 'rename new 2'
- def test_usr1_unit_log(self, temp_dir):
+ def test_usr1_unit_log(self, temp_dir, unit_pid):
self.load('log_body')
log_new = 'new.log'
@@ -55,28 +53,37 @@ class TestUSR1(TestApplicationPython):
os.rename(log_path, log_path_new)
- body = 'body_for_a_log_new'
- assert self.post(body=body)['status'] == 200
+ Log.swap(log_new)
- assert self.wait_for_record(body, log_new) is not None, 'rename new'
- assert not os.path.isfile(log_path), 'rename old'
+ try:
+ body = 'body_for_a_log_new\n'
+ assert self.post(body=body)['status'] == 200
- with open(temp_dir + '/unit.pid', 'r') as f:
- pid = f.read().rstrip()
+ assert (
+ self.wait_for_record(body, log_new) is not None
+ ), 'rename new'
+ assert not os.path.isfile(log_path), 'rename old'
- call(['kill', '-s', 'USR1', pid])
+ os.kill(unit_pid, signal.SIGUSR1)
- assert waitforfiles(log_path), 'reopen'
+ assert waitforfiles(log_path), 'reopen'
+
+ body = 'body_for_a_log_unit\n'
+ assert self.post(body=body)['status'] == 200
+
+ assert self.wait_for_record(body) is not None, 'rename new'
+ assert self.search_in_log(body, log_new) is None, 'rename new 2'
- body = 'body_for_a_log_unit'
- assert self.post(body=body)['status'] == 200
+ finally:
+ # merge two log files into unit.log to check alerts
- assert self.wait_for_record(body) is not None, 'rename new'
- assert self.search_in_log(body, log_new) is None, 'rename new 2'
+ with open(log_path, 'r', errors='ignore') as unit_log:
+ log = unit_log.read()
- # merge two log files into unit.log to check alerts
+ with open(log_path, 'w') as unit_log, open(
+ log_path_new, 'r', errors='ignore'
+ ) as unit_log_new:
+ unit_log.write(unit_log_new.read())
+ unit_log.write(log)
- with open(log_path, 'w') as unit_log, open(
- log_path_new, 'r'
- ) as unit_log_new:
- unit_log.write(unit_log_new.read())
+ Log.swap(log_new)
diff --git a/test/test_variables.py b/test/test_variables.py
index bbb8f769..139d867e 100644
--- a/test/test_variables.py
+++ b/test/test_variables.py
@@ -92,16 +92,8 @@ class TestVariables(TestApplicationProto):
"*:7080": {"pass": "upstreams$uri"},
"*:7081": {"pass": "routes/one"},
},
- "upstreams": {
- "1": {
- "servers": {
- "127.0.0.1:7081": {},
- },
- },
- },
- "routes": {
- "one": [{"action": {"return": 200}}],
- },
+ "upstreams": {"1": {"servers": {"127.0.0.1:7081": {}}}},
+ "routes": {"one": [{"action": {"return": 200}}]},
},
), 'upstreams initial configuration'
diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py
index a17b1af4..6be1667b 100644
--- a/test/unit/applications/lang/go.py
+++ b/test/unit/applications/lang/go.py
@@ -13,6 +13,7 @@ class TestApplicationGo(TestApplicationProto):
env = os.environ.copy()
env['GOPATH'] = option.current_dir + '/build/go'
env['GOCACHE'] = option.cache_dir + '/go'
+ env['GO111MODULE'] = 'auto'
if static:
args = [
diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py
index cc6d06ef..5d05c70c 100644
--- a/test/unit/applications/lang/node.py
+++ b/test/unit/applications/lang/node.py
@@ -7,15 +7,16 @@ from unit.utils import public_dir
class TestApplicationNode(TestApplicationProto):
+ application_type = "node"
+ es_modules = False
+
def prepare_env(self, script):
# copy application
-
shutil.copytree(
option.test_dir + '/node/' + script, option.temp_dir + '/node'
)
# copy modules
-
shutil.copytree(
option.current_dir + '/node/node_modules',
option.temp_dir + '/node/node_modules',
@@ -26,6 +27,19 @@ class TestApplicationNode(TestApplicationProto):
def load(self, script, name='app.js', **kwargs):
self.prepare_env(script)
+ if self.es_modules:
+ arguments = [
+ "node",
+ "--loader",
+ "unit-http/loader.mjs",
+ "--require",
+ "unit-http/loader",
+ name,
+ ]
+
+ else:
+ arguments = ["node", "--require", "unit-http/loader", name]
+
self._load_conf(
{
"listeners": {
@@ -36,7 +50,8 @@ class TestApplicationNode(TestApplicationProto):
"type": "external",
"processes": {"spare": 0},
"working_directory": option.temp_dir + '/node',
- "executable": name,
+ "executable": '/usr/bin/env',
+ "arguments": arguments,
}
},
},
diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py
index 287d23f0..b399dffd 100644
--- a/test/unit/applications/lang/python.py
+++ b/test/unit/applications/lang/python.py
@@ -42,8 +42,15 @@ class TestApplicationPython(TestApplicationProto):
"module": module,
}
- for attr in ('callable', 'home', 'limits', 'path', 'protocol',
- 'threads'):
+ for attr in (
+ 'callable',
+ 'home',
+ 'limits',
+ 'path',
+ 'protocol',
+ 'targets',
+ 'threads',
+ ):
if attr in kwargs:
app[attr] = kwargs.pop(attr)
diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py
index 5c400621..92754c03 100644
--- a/test/unit/applications/proto.py
+++ b/test/unit/applications/proto.py
@@ -4,6 +4,7 @@ import time
from unit.control import TestControl
from unit.option import option
+from unit.log import Log
class TestApplicationProto(TestControl):
@@ -15,18 +16,23 @@ class TestApplicationProto(TestControl):
def date_to_sec_epoch(self, date, template='%a, %d %b %Y %H:%M:%S %Z'):
return time.mktime(time.strptime(date, template))
+ def findall(self, pattern, name='unit.log'):
+ with Log.open(name) as f:
+ return re.findall(pattern, f.read())
+
def search_in_log(self, pattern, name='unit.log'):
- with open(option.temp_dir + '/' + name, 'r', errors='ignore') as f:
+ with Log.open(name) as f:
return re.search(pattern, f.read())
def wait_for_record(self, pattern, name='unit.log', wait=150):
- for i in range(wait):
- found = self.search_in_log(pattern, name)
+ with Log.open(name) as f:
+ for i in range(wait):
+ found = re.search(pattern, f.read())
- if found is not None:
- break
+ if found is not None:
+ break
- time.sleep(0.1)
+ time.sleep(0.1)
return found
diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py
index b0cd5abb..583b618f 100644
--- a/test/unit/applications/tls.py
+++ b/test/unit/applications/tls.py
@@ -21,10 +21,14 @@ class TestApplicationTLS(TestApplicationProto):
'req',
'-x509',
'-new',
- '-subj', '/CN=' + name + '/',
- '-config', option.temp_dir + '/openssl.conf',
- '-out', option.temp_dir + '/' + name + '.crt',
- '-keyout', option.temp_dir + '/' + name + '.key',
+ '-subj',
+ '/CN=' + name + '/',
+ '-config',
+ option.temp_dir + '/openssl.conf',
+ '-out',
+ option.temp_dir + '/' + name + '.crt',
+ '-keyout',
+ option.temp_dir + '/' + name + '.key',
],
stderr=subprocess.STDOUT,
)
@@ -63,19 +67,43 @@ class TestApplicationTLS(TestApplicationProto):
return ssl.get_server_certificate(addr, ssl_version=ssl_version)
- def openssl_conf(self):
+ def openssl_conf(self, rewrite=False, alt_names=[]):
conf_path = option.temp_dir + '/openssl.conf'
- if os.path.exists(conf_path):
+ if not rewrite and os.path.exists(conf_path):
return
+ # Generates alt_names section with dns names
+ a_names = "[alt_names]\n"
+ for i, k in enumerate(alt_names, 1):
+ k = k.split('|')
+
+ if k[0] == 'IP':
+ a_names += "IP.%d = %s\n" % (i, k[1])
+ else:
+ a_names += "DNS.%d = %s\n" % (i, k[0])
+
+ # Generates section for sign request extension
+ a_sec = """req_extensions = myca_req_extensions
+
+[ myca_req_extensions ]
+subjectAltName = @alt_names
+
+{a_names}""".format(
+ a_names=a_names
+ )
+
with open(conf_path, 'w') as f:
f.write(
"""[ req ]
default_bits = 2048
encrypt_key = no
distinguished_name = req_distinguished_name
-[ req_distinguished_name ]"""
+
+{a_sec}
+[ req_distinguished_name ]""".format(
+ a_sec=a_sec if alt_names else ""
+ )
)
def load(self, script, name=None):
diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py
index cc720a98..aa83339c 100644
--- a/test/unit/applications/websockets.py
+++ b/test/unit/applications/websockets.py
@@ -43,11 +43,7 @@ class TestApplicationWebsocket(TestApplicationProto):
'Sec-WebSocket-Version': 13,
}
- _, sock = self.get(
- headers=headers,
- no_recv=True,
- start=True,
- )
+ _, sock = self.get(headers=headers, no_recv=True, start=True,)
resp = ''
while True:
@@ -57,7 +53,7 @@ class TestApplicationWebsocket(TestApplicationProto):
resp += sock.recv(4096).decode()
- if (resp.startswith('HTTP/') and '\r\n\r\n' in resp):
+ if resp.startswith('HTTP/') and '\r\n\r\n' in resp:
resp = self._resp_to_dict(resp)
break
@@ -90,8 +86,8 @@ class TestApplicationWebsocket(TestApplicationProto):
frame = {}
- head1, = struct.unpack('!B', recv_bytes(sock, 1))
- head2, = struct.unpack('!B', recv_bytes(sock, 1))
+ (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)
@@ -103,10 +99,10 @@ class TestApplicationWebsocket(TestApplicationProto):
length = head2 & 0b01111111
if length == 126:
data = recv_bytes(sock, 2)
- length, = struct.unpack('!H', data)
+ (length,) = struct.unpack('!H', data)
elif length == 127:
data = recv_bytes(sock, 8)
- length, = struct.unpack('!Q', data)
+ (length,) = struct.unpack('!Q', data)
if frame['mask']:
mask_bits = recv_bytes(sock, 4)
@@ -121,7 +117,7 @@ class TestApplicationWebsocket(TestApplicationProto):
if frame['opcode'] == self.OP_CLOSE:
if length >= 2:
- code, = struct.unpack('!H', data[:2])
+ (code,) = struct.unpack('!H', data[:2])
reason = data[2:].decode('utf-8')
if not (code in self.CLOSE_CODES or 3000 <= code < 5000):
pytest.fail('Invalid status code')
diff --git a/test/unit/check/chroot.py b/test/unit/check/chroot.py
new file mode 100644
index 00000000..40b75058
--- /dev/null
+++ b/test/unit/check/chroot.py
@@ -0,0 +1,32 @@
+import json
+
+from unit.http import TestHTTP
+from unit.option import option
+
+http = TestHTTP()
+
+
+def check_chroot():
+ available = option.available
+
+ resp = http.put(
+ url='/config',
+ sock_type='unix',
+ addr=option.temp_dir + '/control.unit.sock',
+ body=json.dumps(
+ {
+ "listeners": {"*:7080": {"pass": "routes"}},
+ "routes": [
+ {
+ "action": {
+ "share": option.temp_dir,
+ "chroot": option.temp_dir,
+ }
+ }
+ ],
+ }
+ ),
+ )
+
+ if 'success' in resp['body']:
+ available['features']['chroot'] = True
diff --git a/test/unit/check/go.py b/test/unit/check/go.py
index 35b0c2d5..309091c0 100644
--- a/test/unit/check/go.py
+++ b/test/unit/check/go.py
@@ -8,6 +8,7 @@ def check_go(current_dir, temp_dir, test_dir):
env = os.environ.copy()
env['GOPATH'] = current_dir + '/build/go'
+ env['GO111MODULE'] = 'auto'
try:
process = subprocess.Popen(
diff --git a/test/unit/check/isolation.py b/test/unit/check/isolation.py
index fe5a41f8..7c83ae35 100644
--- a/test/unit/check/isolation.py
+++ b/test/unit/check/isolation.py
@@ -12,6 +12,7 @@ from unit.utils import getns
allns = ['pid', 'mnt', 'ipc', 'uts', 'cgroup', 'net']
http = TestHTTP()
+
def check_isolation():
test_conf = {"namespaces": {"credential": True}}
available = option.available
@@ -117,8 +118,7 @@ def check_isolation():
"body_empty": {
"type": "perl",
"processes": {"spare": 0},
- "working_directory": option.test_dir
- + "/perl/body_empty",
+ "working_directory": option.test_dir + "/perl/body_empty",
"script": option.test_dir + "/perl/body_empty/psgi.pl",
"isolation": {"namespaces": {"credential": True}},
}
diff --git a/test/unit/check/node.py b/test/unit/check/node.py
index 236ba7b5..e053a749 100644
--- a/test/unit/check/node.py
+++ b/test/unit/check/node.py
@@ -1,6 +1,15 @@
import os
+import subprocess
def check_node(current_dir):
- if os.path.exists(current_dir + '/node/node_modules'):
- return True
+ if not os.path.exists(current_dir + '/node/node_modules'):
+ return None
+
+ try:
+ v_bytes = subprocess.check_output(['/usr/bin/env', 'node', '-v'])
+
+ return [str(v_bytes, 'utf-8').lstrip('v').rstrip()]
+
+ except subprocess.CalledProcessError:
+ return None
diff --git a/test/unit/http.py b/test/unit/http.py
index 57e6ed3a..797b7681 100644
--- a/test/unit/http.py
+++ b/test/unit/http.py
@@ -10,15 +10,16 @@ import pytest
from unit.option import option
-class TestHTTP():
+class TestHTTP:
def http(self, start_str, **kwargs):
sock_type = kwargs.get('sock_type', 'ipv4')
port = kwargs.get('port', 7080)
url = kwargs.get('url', '/')
http = 'HTTP/1.0' if 'http_10' in kwargs else 'HTTP/1.1'
- headers = kwargs.get('headers',
- {'Host': 'localhost', 'Connection': 'close'})
+ headers = kwargs.get(
+ 'headers', {'Host': 'localhost', 'Connection': 'close'}
+ )
body = kwargs.get('body', b'')
crlf = '\r\n'
@@ -44,7 +45,8 @@ class TestHTTP():
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
if 'wrapper' in kwargs:
- sock = kwargs['wrapper'](sock)
+ server_hostname = headers.get('Host', 'localhost')
+ sock = kwargs['wrapper'](sock, server_hostname=server_hostname)
connect_args = addr if sock_type == 'unix' else (addr, port)
try:
@@ -304,8 +306,9 @@ class TestHTTP():
return body, content_type
def form_url_encode(self, fields):
- data = "&".join("%s=%s" % (name, value)
- for name, value in fields.items()).encode()
+ data = "&".join(
+ "%s=%s" % (name, value) for name, value in fields.items()
+ ).encode()
return data, 'application/x-www-form-urlencoded'
def multipart_encode(self, fields):
@@ -325,7 +328,9 @@ class TestHTTP():
datatype = value['type']
if not isinstance(value['data'], io.IOBase):
- pytest.fail('multipart encoding of file requires a stream.')
+ pytest.fail(
+ 'multipart encoding of file requires a stream.'
+ )
data = value['data'].read()
@@ -335,9 +340,10 @@ class TestHTTP():
else:
pytest.fail('multipart requires a string or stream data')
- body += (
- "--%s\r\nContent-Disposition: form-data; name=\"%s\""
- ) % (boundary, field)
+ body += ("--%s\r\nContent-Disposition: form-data; name=\"%s\"") % (
+ boundary,
+ field,
+ )
if filename != '':
body += "; filename=\"%s\"" % filename
diff --git a/test/unit/log.py b/test/unit/log.py
new file mode 100644
index 00000000..7263443d
--- /dev/null
+++ b/test/unit/log.py
@@ -0,0 +1,23 @@
+UNIT_LOG = 'unit.log'
+
+
+class Log:
+ temp_dir = None
+ pos = {}
+
+ def open(name=UNIT_LOG, encoding=None):
+ f = open(Log.get_path(name), 'r', encoding=encoding, errors='ignore')
+ f.seek(Log.pos.get(name, 0))
+
+ return f
+
+ def set_pos(pos, name=UNIT_LOG):
+ Log.pos[name] = pos
+
+ def swap(name):
+ pos = Log.pos.get(UNIT_LOG, 0)
+ Log.pos[UNIT_LOG] = Log.pos.get(name, 0)
+ Log.pos[name] = pos
+
+ def get_path(name=UNIT_LOG):
+ return Log.temp_dir + '/' + name
diff --git a/test/unit/option.py b/test/unit/option.py
index 677d806e..cb3803dc 100644
--- a/test/unit/option.py
+++ b/test/unit/option.py
@@ -1,4 +1,4 @@
-class Options():
+class Options:
_options = {
'skip_alerts': [],
'skip_sanitizer': False,
@@ -13,4 +13,5 @@ class Options():
raise AttributeError
+
option = Options()
diff --git a/test/unit/utils.py b/test/unit/utils.py
index e80fc469..a627e9f5 100644
--- a/test/unit/utils.py
+++ b/test/unit/utils.py
@@ -61,6 +61,17 @@ def findmnt():
return out
+def sysctl():
+ try:
+ out = subprocess.check_output(
+ ['sysctl', '-a'], stderr=subprocess.STDOUT
+ ).decode()
+ except FileNotFoundError:
+ pytest.skip('requires sysctl')
+
+ return out
+
+
def waitformount(template, wait=50):
for i in range(wait):
if findmnt().find(template) != -1:
diff --git a/version b/version
index 6d66d5f1..4483727a 100644
--- a/version
+++ b/version
@@ -1,5 +1,5 @@
# Copyright (C) NGINX, Inc.
-NXT_VERSION=1.23.0
-NXT_VERNUM=12300
+NXT_VERSION=1.24.0
+NXT_VERNUM=12400