diff options
author | Andrei Belov <defan@nginx.com> | 2019-05-30 17:44:29 +0300 |
---|---|---|
committer | Andrei Belov <defan@nginx.com> | 2019-05-30 17:44:29 +0300 |
commit | 4921df052be8437d912f3c60faa9a667890e4498 (patch) | |
tree | 3678c551f148a0d177721597de978c090237f205 | |
parent | 3b7a7ff2aa5840d4238584410ee1ebc6860fb9c5 (diff) | |
parent | 7da320a93af07765e79c929287704936c431f3cd (diff) | |
download | unit-1.9.0-1.tar.gz unit-1.9.0-1.tar.bz2 |
Merged with the default branch.1.9.0-1
145 files changed, 9896 insertions, 4406 deletions
@@ -21,3 +21,4 @@ abb8cfb421f608df1c23f5c333c5f049a79a681a 1.7-1 fe0d5eb09b66e77a2b66455faa51d3fa04146d3d 1.7.1-1 0a18a14d169f156f8e2daca35aa86d5a6dd9b1ae 1.8.0 f47fc64d3d9e3dedb95042e93c7f73b31f458338 1.8.0-1 +dda6319de785dc2d225d818349aba56fc48d47f6 1.9.0 @@ -1,4 +1,35 @@ +Changes with Unit 1.9.0 30 May 2019 + + *) Feature: request routing by arguments, headers, and cookies. + + *) Feature: route matching patterns allow a wildcard in the middle. + + *) Feature: POST operation for appending elements to arrays in + configuration. + + *) Feature: support for changing credentials using CAP_SETUID and + CAP_SETGID capabilities on Linux without running main process as + privileged user. + + *) Bugfix: memory leak in the router process might have happened when a + client prematurely closed the connection. + + *) Bugfix: applying a large configuration might have failed. + + *) Bugfix: PUT and DELETE operations on array elements in configuration + did not work. + + *) Bugfix: request schema in applications did not reflect TLS + connections. + + *) Bugfix: restored compatibility with Node.js applications that use + ServerResponse._implicitHeader() function; the bug had appeared in + 1.7. + + *) Bugfix: various compatibility issues with Node.js applications. + + Changes with Unit 1.8.0 01 Mar 2019 *) Change: now three numbers are always used for versioning: major, @@ -58,4 +58,7 @@ cat << END nodejs OPTIONS configure Node.js module run "./configure nodejs --help" to see available options + java OPTIONS configure Java module + run "./configure java --help" to see available options + END diff --git a/docs/changes.xml b/docs/changes.xml index e4b8158d..e1a8734c 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -5,6 +5,109 @@ <change_log title="unit"> +<changes apply="unit-go1.11" ver="1.9.0" rev="1" + date="2019-05-30" time="18:00:00 +0300" + packager="Andrei Belov <defan@nginx.com>"> + +<change> +<para> +Initial release of Go 1.11 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-go unit-go1.7 unit-go1.8 unit-go1.9 unit-go1.10 + unit-perl + unit-ruby + unit-jsc-common unit-jsc8 unit-jsc10 unit-jsc11" + ver="1.9.0" rev="1" + date="2019-05-30" time="18:00:00 +0300" + packager="Andrei Belov <defan@nginx.com>"> + +<change> +<para> +NGINX Unit updated to 1.9.0. +</para> +</change> + +</changes> + + +<changes apply="unit" ver="1.9.0" rev="1" + date="2019-05-30" time="18:00:00 +0300" + packager="Andrei Belov <defan@nginx.com>"> + +<change type="feature"> +<para> +request routing by arguments, headers, and cookies. +</para> +</change> + +<change type="feature"> +<para> +route matching patterns allow a wildcard in the middle. +</para> +</change> + +<change type="feature"> +<para> +POST operation for appending elements to arrays in configuration. +</para> +</change> + +<change type="feature"> +<para> +support for changing credentials using CAP_SETUID and CAP_SETGID capabilities +on Linux without running main process as privileged user. +</para> +</change> + +<change type="bugfix"> +<para> +memory leak in the router process might have happened when a client +prematurely closed the connection. +</para> +</change> + +<change type="bugfix"> +<para> +applying a large configuration might have failed. +</para> +</change> + +<change type="bugfix"> +<para> +PUT and DELETE operations on array elements in configuration did not work. +</para> +</change> + +<change type="bugfix"> +<para> +request schema in applications did not reflect TLS connections. +</para> +</change> + +<change type="bugfix"> +<para> +restored compatibility with Node.js applications that use +ServerResponse._implicitHeader() function; the bug had appeared in 1.7. +</para> +</change> + +<change type="bugfix"> +<para> +various compatibility issues with Node.js applications. +</para> +</change> + +</changes> + + <changes apply="unit-jsc-common" ver="1.8.0" rev="1" date="2019-03-01" time="18:00:00 +0300" packager="Andrei Belov <defan@nginx.com>"> diff --git a/pkg/deb/Makefile b/pkg/deb/Makefile index f481ff02..421c4169 100644 --- a/pkg/deb/Makefile +++ b/pkg/deb/Makefile @@ -16,6 +16,19 @@ BUILD_DEPENDS = $(BUILD_DEPENDS_unit) MODULES= +# Ubuntu 19.04 +ifeq ($(CODENAME),disco) +include Makefile.php +include Makefile.python27 +include Makefile.python37 +include Makefile.go110 +include Makefile.go111 +include Makefile.perl +include Makefile.ruby +include Makefile.jsc-common +include Makefile.jsc11 +endif + # Ubuntu 18.10 ifeq ($(CODENAME),cosmic) include Makefile.php diff --git a/pkg/deb/Makefile.go111 b/pkg/deb/Makefile.go111 new file mode 100644 index 00000000..f8ed5ae8 --- /dev/null +++ b/pkg/deb/Makefile.go111 @@ -0,0 +1,48 @@ +MODULES+= go111 +MODULE_SUFFIX_go111= go1.11 + +MODULE_SUMMARY_go111= Go 1.11 module for NGINX Unit + +MODULE_VERSION_go111= $(VERSION) +MODULE_RELEASE_go111= 1 + +MODULE_CONFARGS_go111= go --go=/usr/lib/go-1.11/bin/go --go-path=/usr/share/gocode +MODULE_MAKEARGS_go111= /usr/lib/go-1.11/bin/go +MODULE_INSTARGS_go111= /usr/lib/go-1.11/bin/go-install + +MODULE_SOURCES_go111= unit.example-go-app \ + unit.example-go1.11-config + +BUILD_DEPENDS_go111= golang-1.11 +BUILD_DEPENDS+= $(BUILD_DEPENDS_go111) + +MODULE_BUILD_DEPENDS_go111=,golang-1.11 +MODULE_DEPENDS_go111=,golang-1.11 + +define MODULE_PREINSTALL_go111 + mkdir -p debian/unit-go1.11/usr/share/doc/unit-go1.11/examples/go-app + install -m 644 -p debian/unit.example-go-app debian/unit-go1.11/usr/share/doc/unit-go1.11/examples/go-app/let-my-people.go + install -m 644 -p debian/unit.example-go1.11-config debian/unit-go1.11/usr/share/doc/unit-go1.11/examples/unit.config +endef +export MODULE_PREINSTALL_go111 + +define MODULE_POST_go111 +cat <<BANNER +---------------------------------------------------------------------- + +The $(MODULE_SUMMARY_go111) has been installed. + +To check out the sample app, run these commands: + + GOPATH=/usr/share/gocode /usr/lib/go-1.11/bin/go build -o /tmp/go1.11-app /usr/share/doc/unit-$(MODULE_SUFFIX_go111)/examples/go-app/let-my-people.go + sudo service unit restart + cd /usr/share/doc/unit-$(MODULE_SUFFIX_go111)/examples + sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config + curl http://localhost:8500/ + +Online documentation is available at https://unit.nginx.org + +---------------------------------------------------------------------- +BANNER +endef +export MODULE_POST_go111 diff --git a/pkg/deb/Makefile.jsc-common b/pkg/deb/Makefile.jsc-common index 42fdb12f..080e248f 100644 --- a/pkg/deb/Makefile.jsc-common +++ b/pkg/deb/Makefile.jsc-common @@ -6,11 +6,17 @@ MODULE_SUMMARY_jsc_common= Java shared packages for NGINX Unit MODULE_VERSION_jsc_common= $(VERSION) MODULE_RELEASE_jsc_common= 1 -MODULE_CONFARGS_jsc_common= java --home=/usr/lib/jvm/java-8-openjdk-$$\(DEB_HOST_ARCH\) --jars=/usr/share/unit-jsc-common/ +ifeq ($(CODENAME),disco) +JAVA_MINVERSION= 11 +else +JAVA_MINVERSION= 8 +endif + +MODULE_CONFARGS_jsc_common= java --home=/usr/lib/jvm/java-$(JAVA_MINVERSION)-openjdk-$$\(DEB_HOST_ARCH\) --jars=/usr/share/unit-jsc-common/ MODULE_MAKEARGS_jsc_common= java MODULE_INSTARGS_jsc_common= java-shared-install -BUILD_DEPENDS_jsc_common= openjdk-8-jdk-headless openjdk-8-jre-headless +BUILD_DEPENDS_jsc_common= openjdk-$(JAVA_MINVERSION)-jdk-headless openjdk-$(JAVA_MINVERSION)-jre-headless BUILD_DEPENDS+= $(BUILD_DEPENDS_jsc_common) MODULE_NOARCH_jsc_common= true diff --git a/pkg/deb/debian.module/unit.example-go-config b/pkg/deb/debian.module/unit.example-go-config index 079ce0b8..a2c91e80 100644 --- a/pkg/deb/debian.module/unit.example-go-config +++ b/pkg/deb/debian.module/unit.example-go-config @@ -9,7 +9,7 @@ "listeners": { "*:8500": { - "application": "example_go" + "pass": "applications/example_go" } } } diff --git a/pkg/deb/debian.module/unit.example-go1.10-config b/pkg/deb/debian.module/unit.example-go1.10-config index 29bb9059..61790b73 100644 --- a/pkg/deb/debian.module/unit.example-go1.10-config +++ b/pkg/deb/debian.module/unit.example-go1.10-config @@ -9,7 +9,7 @@ "listeners": { "*:8500": { - "application": "example_go" + "pass": "applications/example_go" } } } diff --git a/pkg/deb/debian.module/unit.example-go1.11-config b/pkg/deb/debian.module/unit.example-go1.11-config new file mode 100644 index 00000000..5e063a4c --- /dev/null +++ b/pkg/deb/debian.module/unit.example-go1.11-config @@ -0,0 +1,15 @@ +{ + "applications": { + "example_go": { + "type": "external", + "user": "nobody", + "executable": "/tmp/go1.11-app" + } + }, + + "listeners": { + "*:8500": { + "pass": "applications/example_go" + } + } +} diff --git a/pkg/deb/debian.module/unit.example-go1.7-config b/pkg/deb/debian.module/unit.example-go1.7-config index bd98fe3a..e1a8e1a4 100644 --- a/pkg/deb/debian.module/unit.example-go1.7-config +++ b/pkg/deb/debian.module/unit.example-go1.7-config @@ -9,7 +9,7 @@ "listeners": { "*:8500": { - "application": "example_go" + "pass": "applications/example_go" } } } diff --git a/pkg/deb/debian.module/unit.example-go1.8-config b/pkg/deb/debian.module/unit.example-go1.8-config index 1c14ca25..e570f38c 100644 --- a/pkg/deb/debian.module/unit.example-go1.8-config +++ b/pkg/deb/debian.module/unit.example-go1.8-config @@ -9,7 +9,7 @@ "listeners": { "*:8500": { - "application": "example_go" + "pass": "applications/example_go" } } } diff --git a/pkg/deb/debian.module/unit.example-go1.9-config b/pkg/deb/debian.module/unit.example-go1.9-config index 341894b7..90ef7d5f 100644 --- a/pkg/deb/debian.module/unit.example-go1.9-config +++ b/pkg/deb/debian.module/unit.example-go1.9-config @@ -9,7 +9,7 @@ "listeners": { "*:8500": { - "application": "example_go" + "pass": "applications/example_go" } } } diff --git a/pkg/deb/debian.module/unit.example-jsc10-config b/pkg/deb/debian.module/unit.example-jsc10-config index 6929356d..969491e5 100644 --- a/pkg/deb/debian.module/unit.example-jsc10-config +++ b/pkg/deb/debian.module/unit.example-jsc10-config @@ -9,7 +9,7 @@ "listeners": { "*:8800": { - "application": "example_java10" + "pass": "applications/example_java10" } } } diff --git a/pkg/deb/debian.module/unit.example-jsc11-config b/pkg/deb/debian.module/unit.example-jsc11-config index 6c1d9549..3f7dd518 100644 --- a/pkg/deb/debian.module/unit.example-jsc11-config +++ b/pkg/deb/debian.module/unit.example-jsc11-config @@ -9,7 +9,7 @@ "listeners": { "*:8800": { - "application": "example_java11" + "pass": "applications/example_java11" } } } diff --git a/pkg/deb/debian.module/unit.example-jsc8-config b/pkg/deb/debian.module/unit.example-jsc8-config index 0254677b..4d79112f 100644 --- a/pkg/deb/debian.module/unit.example-jsc8-config +++ b/pkg/deb/debian.module/unit.example-jsc8-config @@ -9,7 +9,7 @@ "listeners": { "*:8800": { - "application": "example_java8" + "pass": "applications/example_java8" } } } diff --git a/pkg/deb/debian.module/unit.example-jsc9-config b/pkg/deb/debian.module/unit.example-jsc9-config index c64a1aff..a8faa268 100644 --- a/pkg/deb/debian.module/unit.example-jsc9-config +++ b/pkg/deb/debian.module/unit.example-jsc9-config @@ -9,7 +9,7 @@ "listeners": { "*:8800": { - "application": "example_java9" + "pass": "applications/example_java9" } } } diff --git a/pkg/deb/debian.module/unit.example-perl-config b/pkg/deb/debian.module/unit.example-perl-config index 0329b78a..031928ce 100644 --- a/pkg/deb/debian.module/unit.example-perl-config +++ b/pkg/deb/debian.module/unit.example-perl-config @@ -11,7 +11,7 @@ "listeners": { "*:8600": { - "application": "example_perl" + "pass": "applications/example_perl" } } } diff --git a/pkg/deb/debian.module/unit.example-php-config b/pkg/deb/debian.module/unit.example-php-config index 71564df3..8f23c984 100644 --- a/pkg/deb/debian.module/unit.example-php-config +++ b/pkg/deb/debian.module/unit.example-php-config @@ -11,7 +11,7 @@ "listeners": { "*:8300": { - "application": "example_php" + "pass": "applications/example_php" } } } diff --git a/pkg/deb/debian.module/unit.example-python-config b/pkg/deb/debian.module/unit.example-python-config index 2866cc16..d612c89d 100644 --- a/pkg/deb/debian.module/unit.example-python-config +++ b/pkg/deb/debian.module/unit.example-python-config @@ -11,7 +11,7 @@ "listeners": { "*:8400": { - "application": "example_python" + "pass": "applications/example_python" } } } diff --git a/pkg/deb/debian.module/unit.example-python2.7-config b/pkg/deb/debian.module/unit.example-python2.7-config index 70df9d6d..bede8899 100644 --- a/pkg/deb/debian.module/unit.example-python2.7-config +++ b/pkg/deb/debian.module/unit.example-python2.7-config @@ -11,7 +11,7 @@ "listeners": { "*:8400": { - "application": "example_python" + "pass": "applications/example_python" } } } diff --git a/pkg/deb/debian.module/unit.example-python3.4-config b/pkg/deb/debian.module/unit.example-python3.4-config index fbda2886..dd496bd8 100644 --- a/pkg/deb/debian.module/unit.example-python3.4-config +++ b/pkg/deb/debian.module/unit.example-python3.4-config @@ -11,7 +11,7 @@ "listeners": { "*:8400": { - "application": "example_python" + "pass": "applications/example_python" } } } diff --git a/pkg/deb/debian.module/unit.example-python3.5-config b/pkg/deb/debian.module/unit.example-python3.5-config index 495995ef..2be6de4a 100644 --- a/pkg/deb/debian.module/unit.example-python3.5-config +++ b/pkg/deb/debian.module/unit.example-python3.5-config @@ -11,7 +11,7 @@ "listeners": { "*:8400": { - "application": "example_python" + "pass": "applications/example_python" } } } diff --git a/pkg/deb/debian.module/unit.example-python3.6-config b/pkg/deb/debian.module/unit.example-python3.6-config index 1a75fdbf..a77e8e07 100644 --- a/pkg/deb/debian.module/unit.example-python3.6-config +++ b/pkg/deb/debian.module/unit.example-python3.6-config @@ -11,7 +11,7 @@ "listeners": { "*:8400": { - "application": "example_python" + "pass": "applications/example_python" } } } diff --git a/pkg/deb/debian.module/unit.example-python3.7-config b/pkg/deb/debian.module/unit.example-python3.7-config index 00c24f23..9b13c058 100644 --- a/pkg/deb/debian.module/unit.example-python3.7-config +++ b/pkg/deb/debian.module/unit.example-python3.7-config @@ -11,7 +11,7 @@ "listeners": { "*:8400": { - "application": "example_python" + "pass": "applications/example_python" } } } diff --git a/pkg/deb/debian.module/unit.example-ruby-config b/pkg/deb/debian.module/unit.example-ruby-config index 53eee534..15a92735 100644 --- a/pkg/deb/debian.module/unit.example-ruby-config +++ b/pkg/deb/debian.module/unit.example-ruby-config @@ -10,7 +10,7 @@ "listeners": { "*:8700": { - "application": "example_ruby" + "pass": "applications/example_ruby" } } } diff --git a/pkg/deb/debian/unit.example.config b/pkg/deb/debian/unit.example.config index 8d86acbe..5610cb3a 100644 --- a/pkg/deb/debian/unit.example.config +++ b/pkg/deb/debian/unit.example.config @@ -33,19 +33,19 @@ "listeners": { "*:8300": { - "application": "example_php" + "pass": "applications/example_php" }, "*:8400": { - "application": "example_python" + "pass": "applications/example_python" }, "*:8500": { - "application": "example_go" + "pass": "applications/example_go" }, "*:8600": { - "application": "example_perl" + "pass": "applications/example_perl" } } } diff --git a/pkg/docker/Dockerfile.full b/pkg/docker/Dockerfile.full index 7e688710..14afc75b 100644 --- a/pkg/docker/Dockerfile.full +++ b/pkg/docker/Dockerfile.full @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.8.0-1~stretch +ENV UNIT_VERSION 1.9.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.go1.7-dev b/pkg/docker/Dockerfile.go1.7-dev index c0245ea7..ad3d888d 100644 --- a/pkg/docker/Dockerfile.go1.7-dev +++ b/pkg/docker/Dockerfile.go1.7-dev @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.8.0-1~stretch +ENV UNIT_VERSION 1.9.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.go1.8-dev b/pkg/docker/Dockerfile.go1.8-dev index 3c13c018..915d859a 100644 --- a/pkg/docker/Dockerfile.go1.8-dev +++ b/pkg/docker/Dockerfile.go1.8-dev @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.8.0-1~stretch +ENV UNIT_VERSION 1.9.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.minimal b/pkg/docker/Dockerfile.minimal index b48f8410..5214c24a 100644 --- a/pkg/docker/Dockerfile.minimal +++ b/pkg/docker/Dockerfile.minimal @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.8.0-1~stretch +ENV UNIT_VERSION 1.9.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.perl5.24 b/pkg/docker/Dockerfile.perl5.24 index 4d5f502e..c2c91866 100644 --- a/pkg/docker/Dockerfile.perl5.24 +++ b/pkg/docker/Dockerfile.perl5.24 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.8.0-1~stretch +ENV UNIT_VERSION 1.9.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.php7.0 b/pkg/docker/Dockerfile.php7.0 index 0afcf28a..b14a38b4 100644 --- a/pkg/docker/Dockerfile.php7.0 +++ b/pkg/docker/Dockerfile.php7.0 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.8.0-1~stretch +ENV UNIT_VERSION 1.9.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.python2.7 b/pkg/docker/Dockerfile.python2.7 index 57619e26..82de3318 100644 --- a/pkg/docker/Dockerfile.python2.7 +++ b/pkg/docker/Dockerfile.python2.7 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.8.0-1~stretch +ENV UNIT_VERSION 1.9.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.python3.5 b/pkg/docker/Dockerfile.python3.5 index 410f395c..b103d8f9 100644 --- a/pkg/docker/Dockerfile.python3.5 +++ b/pkg/docker/Dockerfile.python3.5 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.8.0-1~stretch +ENV UNIT_VERSION 1.9.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.ruby2.3 b/pkg/docker/Dockerfile.ruby2.3 index 7b674178..50ec7ed6 100644 --- a/pkg/docker/Dockerfile.ruby2.3 +++ b/pkg/docker/Dockerfile.ruby2.3 @@ -2,7 +2,7 @@ FROM debian:stretch-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.8.0-1~stretch +ENV UNIT_VERSION 1.9.0-1~stretch RUN set -x \ && apt-get update \ diff --git a/pkg/rpm/Makefile b/pkg/rpm/Makefile index d94890f2..9e343aa2 100644 --- a/pkg/rpm/Makefile +++ b/pkg/rpm/Makefile @@ -11,6 +11,8 @@ ifeq ($(shell test `rpm --eval '0%{?rhel} -eq 6 -a 0%{?amzn} -eq 0'`; echo $$?), OSVER = centos6 else ifeq ($(shell test `rpm --eval '0%{?rhel} -eq 7 -a 0%{?amzn} -eq 0'`; echo $$?), 0) OSVER = centos7 +else ifeq ($(shell rpm --eval "%{?rhel}"), 8) +OSVER = centos8 else ifeq ($(shell rpm --eval "%{?amzn}"), 1) OSVER = amazonlinux1 else ifeq ($(shell rpm --eval "%{?amzn}"), 2) @@ -63,6 +65,17 @@ include Makefile.jsc8 include Makefile.jsc11 endif +ifeq ($(OSVER), centos8) +include Makefile.php +include Makefile.python27 +include Makefile.python36 +include Makefile.go +include Makefile.perl +include Makefile.jsc-common +include Makefile.jsc8 +include Makefile.jsc11 +endif + ifeq ($(OSVER), amazonlinux1) include Makefile.php include Makefile.python27 diff --git a/pkg/rpm/Makefile.python27 b/pkg/rpm/Makefile.python27 index 005eff17..95b392a9 100644 --- a/pkg/rpm/Makefile.python27 +++ b/pkg/rpm/Makefile.python27 @@ -15,7 +15,7 @@ MODULE_SOURCES_python27= unit.example-python-app \ ifneq (,$(findstring $(OSVER),opensuse-leap opensuse-tumbleweed sles)) BUILD_DEPENDS_python27= python-devel -else ifeq ($(OSVER), fedora) +else ifneq (,$(findstring $(OSVER),fedora centos8)) BUILD_DEPENDS_python27= python2-devel else BUILD_DEPENDS_python27= python27-devel diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-go-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-go-config index 079ce0b8..a2c91e80 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.example-go-config +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-go-config @@ -9,7 +9,7 @@ "listeners": { "*:8500": { - "application": "example_go" + "pass": "applications/example_go" } } } diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc11-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc11-config index 6c1d9549..3f7dd518 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc11-config +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc11-config @@ -9,7 +9,7 @@ "listeners": { "*:8800": { - "application": "example_java11" + "pass": "applications/example_java11" } } } diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc8-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc8-config index 0254677b..4d79112f 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc8-config +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc8-config @@ -9,7 +9,7 @@ "listeners": { "*:8800": { - "application": "example_java8" + "pass": "applications/example_java8" } } } diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-perl-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-perl-config index 0329b78a..031928ce 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.example-perl-config +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-perl-config @@ -11,7 +11,7 @@ "listeners": { "*:8600": { - "application": "example_perl" + "pass": "applications/example_perl" } } } diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-php-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-php-config index 71564df3..8f23c984 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.example-php-config +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-php-config @@ -11,7 +11,7 @@ "listeners": { "*:8300": { - "application": "example_php" + "pass": "applications/example_php" } } } diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-python-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-python-config index 2866cc16..d612c89d 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.example-python-config +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-python-config @@ -11,7 +11,7 @@ "listeners": { "*:8400": { - "application": "example_python" + "pass": "applications/example_python" } } } diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-python27-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-python27-config index c8a876f5..7541fcb3 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.example-python27-config +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-python27-config @@ -11,7 +11,7 @@ "listeners": { "*:8400": { - "application": "example_python" + "pass": "applications/example_python" } } } diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-python34-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-python34-config index cab381e2..b64e570c 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.example-python34-config +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-python34-config @@ -11,7 +11,7 @@ "listeners": { "*:8400": { - "application": "example_python" + "pass": "applications/example_python" } } } diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-python35-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-python35-config index 23e0ea53..025f3428 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.example-python35-config +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-python35-config @@ -11,7 +11,7 @@ "listeners": { "*:8400": { - "application": "example_python" + "pass": "applications/example_python" } } } diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-python36-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-python36-config index 758dab25..825cabc4 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.example-python36-config +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-python36-config @@ -11,7 +11,7 @@ "listeners": { "*:8400": { - "application": "example_python" + "pass": "applications/example_python" } } } diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-python37-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-python37-config index ada7ae5b..7f5e52f1 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.example-python37-config +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-python37-config @@ -11,7 +11,7 @@ "listeners": { "*:8400": { - "application": "example_python" + "pass": "applications/example_python" } } } diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-ruby-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-ruby-config index 53eee534..15a92735 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.example-ruby-config +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-ruby-config @@ -10,7 +10,7 @@ "listeners": { "*:8700": { - "application": "example_ruby" + "pass": "applications/example_ruby" } } } diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example.config b/pkg/rpm/rpmbuild/SOURCES/unit.example.config index 1715c971..6fe35e2f 100644 --- a/pkg/rpm/rpmbuild/SOURCES/unit.example.config +++ b/pkg/rpm/rpmbuild/SOURCES/unit.example.config @@ -33,19 +33,19 @@ "listeners": { "*:8300": { - "application": "example_php" + "pass": "applications/example_php" }, "*:8400": { - "application": "example_python" + "pass": "applications/example_python" }, "*:8500": { - "application": "example_go" + "pass": "applications/example_go" }, "*:8600": { - "application": "example_perl" + "pass": "applications/example_perl" } } } diff --git a/pkg/rpm/unit.module.spec.in b/pkg/rpm/unit.module.spec.in index ab55d2a4..023ebfab 100644 --- a/pkg/rpm/unit.module.spec.in +++ b/pkg/rpm/unit.module.spec.in @@ -48,7 +48,7 @@ This package contains %%SUMMARY%%. %debug_package %endif -%if 0%{?fedora} +%if (0%{?fedora}) || (0%{?rhel} >= 8) %define _debugsource_template %{nil} %endif diff --git a/pkg/rpm/unit.spec.in b/pkg/rpm/unit.spec.in index 2d5c1bd1..afd2f1ff 100644 --- a/pkg/rpm/unit.spec.in +++ b/pkg/rpm/unit.spec.in @@ -72,7 +72,7 @@ dynamically via an API. %debug_package %endif -%if 0%{?fedora} +%if (0%{?fedora}) || (0%{?rhel} >= 8) %define _debugsource_template %{nil} %endif diff --git a/src/go/unit/nxt_cgo_lib.c b/src/go/unit/nxt_cgo_lib.c index 98a23482..cc1228f5 100644 --- a/src/go/unit/nxt_cgo_lib.c +++ b/src/go/unit/nxt_cgo_lib.c @@ -83,6 +83,10 @@ nxt_cgo_request_handler(nxt_unit_request_info_t *req) nxt_go_request_set_remote_addr(go_req, nxt_cgo_str_init(&remote_addr, &r->remote, r->remote_length)); + if (r->tls) { + nxt_go_request_set_tls(go_req); + } + nxt_go_request_handler(go_req, (uintptr_t) req->unit->data); } diff --git a/src/go/unit/request.go b/src/go/unit/request.go index 829a2c64..ad56cabb 100644 --- a/src/go/unit/request.go +++ b/src/go/unit/request.go @@ -14,6 +14,7 @@ import ( "io" "net/http" "net/url" + "crypto/tls" "unsafe" ) @@ -125,6 +126,12 @@ func nxt_go_request_set_remote_addr(go_req uintptr, addr *C.nxt_cgo_str_t) { get_request(go_req).req.RemoteAddr = C.GoStringN(addr.start, addr.length) } +//export nxt_go_request_set_tls +func nxt_go_request_set_tls(go_req uintptr) { + + get_request(go_req).req.TLS = &tls.ConnectionState{ } +} + //export nxt_go_request_handler func nxt_go_request_handler(go_req uintptr, h uintptr) { r := get_request(go_req) diff --git a/src/java/nginx/unit/Context.java b/src/java/nginx/unit/Context.java index 643a336b..f6d5e339 100644 --- a/src/java/nginx/unit/Context.java +++ b/src/java/nginx/unit/Context.java @@ -306,7 +306,7 @@ public class Context implements ServletContext, InitParams PrintWriter writer = response.getWriter(); for (String n : ls) { - writer.println("<a href=\"" + n + "\">" + n + "</a><br>"); + writer.println("<a href=\"" + n + "\">" + n + "</a><br>"); } writer.close(); @@ -547,7 +547,7 @@ public class Context implements ServletContext, InitParams j = j.getParent(); } } - system_loader = j; + system_loader = j; } private boolean isSystemPath(String path) @@ -1733,7 +1733,7 @@ public class Context implements ServletContext, InitParams @Override public FileVisitResult visitFile( - Path file, BasicFileAttributes attrs) + Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; diff --git a/src/java/nginx/unit/Request.java b/src/java/nginx/unit/Request.java index dc73c656..3ba46f6c 100644 --- a/src/java/nginx/unit/Request.java +++ b/src/java/nginx/unit/Request.java @@ -920,7 +920,7 @@ public class Request implements HttpServletRequest, DynamicPathRequest @Override public String getScheme() { - log("getScheme"); + trace("getScheme"); return getScheme(req_ptr); } @@ -980,11 +980,13 @@ public class Request implements HttpServletRequest, DynamicPathRequest @Override public boolean isSecure() { - log("isSecure"); + trace("isSecure"); - return false; + return isSecure(req_ptr); } + private static native boolean isSecure(long req_ptr); + @Override public void removeAttribute(String name) { diff --git a/src/java/nxt_jni_Context.c b/src/java/nxt_jni_Context.c index 8f7adddf..589e1c5b 100644 --- a/src/java/nxt_jni_Context.c +++ b/src/java/nxt_jni_Context.c @@ -55,7 +55,7 @@ nxt_java_initContext(JNIEnv *env, jobject cl) } nxt_java_Context_stop = (*env)->GetMethodID(env, cls, "stop", "()V"); - if (nxt_java_Context_service == NULL) { + if (nxt_java_Context_stop == NULL) { nxt_unit_warn(NULL, "nginx.unit.Context.stop() not found"); goto failed; } diff --git a/src/java/nxt_jni_Request.c b/src/java/nxt_jni_Request.c index 6fb9cb44..733290dd 100644 --- a/src/java/nxt_jni_Request.c +++ b/src/java/nxt_jni_Request.c @@ -56,6 +56,8 @@ static jstring JNICALL nxt_java_Request_getServerName(JNIEnv *env, jclass cls, jlong req_ptr); static jint JNICALL nxt_java_Request_getServerPort(JNIEnv *env, jclass cls, jlong req_ptr); +static jboolean JNICALL nxt_java_Request_isSecure(JNIEnv *env, jclass cls, + jlong req_ptr); static void JNICALL nxt_java_Request_log(JNIEnv *env, jclass cls, jlong req_info_ptr, jstring msg, jint msg_len); static void JNICALL nxt_java_Request_trace(JNIEnv *env, jclass cls, @@ -166,6 +168,10 @@ nxt_java_initRequest(JNIEnv *env, jobject cl) (char *) "(J)I", nxt_java_Request_getServerPort }, + { (char *) "isSecure", + (char *) "(J)Z", + nxt_java_Request_isSecure }, + { (char *) "log", (char *) "(JLjava/lang/String;I)V", nxt_java_Request_log }, @@ -536,7 +542,11 @@ nxt_java_Request_getRemotePort(JNIEnv *env, jclass cls, jlong req_ptr) static jstring JNICALL nxt_java_Request_getScheme(JNIEnv *env, jclass cls, jlong req_ptr) { - return (*env)->NewStringUTF(env, "http"); + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return (*env)->NewStringUTF(env, r->tls ? "https" : "http"); } @@ -603,6 +613,17 @@ nxt_java_Request_getServerPort(JNIEnv *env, jclass cls, jlong req_ptr) } +static jboolean JNICALL +nxt_java_Request_isSecure(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return r->tls != 0; +} + + static void JNICALL nxt_java_Request_log(JNIEnv *env, jclass cls, jlong req_info_ptr, jstring msg, jint msg_len) diff --git a/src/nodejs/unit-http/binding.gyp b/src/nodejs/unit-http/binding.gyp index ee09bfed..55d965bd 100644 --- a/src/nodejs/unit-http/binding.gyp +++ b/src/nodejs/unit-http/binding.gyp @@ -1,6 +1,15 @@ { 'targets': [{ 'target_name': "unit-http", + 'cflags!': [ '-fno-exceptions' ], + 'cflags_cc!': [ '-fno-exceptions' ], + 'conditions': [ + ['OS=="mac"', { + 'xcode_settings': { + 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES' + } + }] + ], 'sources': ["unit.cpp", "addon.cpp"], 'include_dirs': [ "<!(echo $UNIT_SRC_PATH)", "<!(echo $UNIT_BUILD_PATH)" diff --git a/src/nodejs/unit-http/binding_pub.gyp b/src/nodejs/unit-http/binding_pub.gyp index 6fe3d9bc..3c39933a 100644 --- a/src/nodejs/unit-http/binding_pub.gyp +++ b/src/nodejs/unit-http/binding_pub.gyp @@ -1,6 +1,15 @@ { 'targets': [{ 'target_name': "unit-http", + 'cflags!': [ '-fno-exceptions' ], + 'cflags_cc!': [ '-fno-exceptions' ], + 'conditions': [ + ['OS=="mac"', { + 'xcode_settings': { + 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES' + } + }] + ], 'sources': ["unit.cpp", "addon.cpp"], 'libraries': ["-lunit"] }] diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js index 057a1f26..ae8e204a 100755 --- a/src/nodejs/unit-http/http_server.js +++ b/src/nodejs/unit-http/http_server.js @@ -197,6 +197,14 @@ function writeHead(statusCode, reason, obj) { } }; +/* + * Some Node.js packages are known to be using this undocumented function, + * notably "compression" middleware. + */ +ServerResponse.prototype._implicitHeader = function _implicitHeader() { + this.writeHead(this.statusCode); +}; + ServerResponse.prototype._writeBody = function(chunk, encoding, callback) { var contentLength = 0; @@ -387,6 +395,10 @@ Server.prototype.emit_events = function (server, req, res) { }); }; +Server.prototype.emit_close = function () { + this.emit('close'); +}; + function connectionListener(socket) { } diff --git a/src/nodejs/unit-http/nxt_napi.h b/src/nodejs/unit-http/nxt_napi.h new file mode 100644 index 00000000..9bcf3a21 --- /dev/null +++ b/src/nodejs/unit-http/nxt_napi.h @@ -0,0 +1,656 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_NODEJS_NAPI_H_INCLUDED_ +#define _NXT_NODEJS_NAPI_H_INCLUDED_ + +#include <node_api.h> + + +#ifdef __cplusplus +extern "C" { +#endif + +#include "version.h" +#include <nxt_unit.h> + +#if NXT_VERNUM != NXT_NODE_VERNUM +#error "libunit version mismatch." +#endif + +#include <nxt_unit_response.h> +#include <nxt_unit_request.h> + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + + +struct nxt_napi { + + struct exception { + exception(const char *s) : str(s) { } + + const char *str; + }; + + + nxt_napi(napi_env env) : env_(env) { } + + + inline napi_value + coerce_to_string(napi_value val) + { + napi_value res; + napi_status status; + + status = napi_coerce_to_string(env_, val, &res); + if (status != napi_ok) { + throw exception("Failed to coerce to string"); + } + + return res; + } + + + inline napi_value + create_buffer(size_t size, void **data) + { + napi_value res; + napi_status status; + + status = napi_create_buffer(env_, size, data, &res); + if (status != napi_ok) { + throw exception("Failed to create buffer"); + } + + return res; + } + + + inline napi_value + create_function(const char *name, size_t len, napi_callback cb, void *data) + { + napi_value res; + napi_status status; + + status = napi_create_function(env_, name, len, cb, data, &res); + if (status != napi_ok) { + throw exception("Failed to create function"); + } + + return res; + } + + + inline napi_value + create_function(napi_callback cb) + { + return create_function(NULL, 0, cb, NULL); + } + + + inline napi_value + create_object() + { + napi_value res; + napi_status status; + + status = napi_create_object(env_, &res); + if (status != napi_ok) { + throw exception("Failed to create object"); + } + + return res; + } + + + inline napi_ref + create_reference(napi_value val, int ref_count = 1) + { + napi_ref res; + napi_status status; + + status = napi_create_reference(env_, val, ref_count, &res); + if (status != napi_ok) { + throw exception("Failed to create reference"); + } + + return res; + } + + + inline napi_value + create_string_latin1(const char *str, size_t len) + { + napi_value res; + napi_status status; + + status = napi_create_string_latin1(env_, str, len, &res); + if (status != napi_ok) { + throw exception("Failed to create latin1 string"); + } + + return res; + } + + + inline napi_value + create_string_latin1(nxt_unit_sptr_t &str, size_t len) + { + const char *p; + + p = (const char *) nxt_unit_sptr_get(&str); + + return create_string_latin1(p, len); + } + + + inline napi_value + define_class(const char *name, napi_callback ctor, size_t prop_count, + const napi_property_descriptor* props) + { + napi_value res; + napi_status status; + + status = napi_define_class(env_, name, NAPI_AUTO_LENGTH, ctor, nullptr, + prop_count, props, &res); + if (status != napi_ok) { + throw exception("Failed to define class"); + } + + return res; + } + + + inline void + delete_reference(napi_ref ref) + { + napi_delete_reference(env_, ref); + } + + + inline uint32_t + get_array_length(napi_value val) + { + uint32_t res; + napi_status status; + + status = napi_get_array_length(env_, val, &res); + if (status != napi_ok) { + throw exception("Failed to get array length"); + } + + return res; + } + + + inline napi_value + get_cb_info(napi_callback_info info, size_t &argc, napi_value *argv) + { + napi_value res; + napi_status status; + + status = napi_get_cb_info(env_, info, &argc, argv, &res, nullptr); + if (status != napi_ok) { + throw exception("Failed to get arguments from js"); + } + + return res; + } + + + inline napi_value + get_cb_info(napi_callback_info info) + { + napi_value res; + napi_status status; + + status = napi_get_cb_info(env_, info, nullptr, nullptr, &res, nullptr); + if (status != napi_ok) { + throw exception("Failed to get arguments from js"); + } + + return res; + } + + + inline napi_value + get_element(napi_value obj, uint32_t i) + { + napi_value res; + napi_status status; + + status = napi_get_element(env_, obj, i, &res); + if (status != napi_ok) { + throw exception("Failed to get element"); + } + + return res; + } + + + inline napi_value + get_named_property(napi_value obj, const char *name) + { + napi_value res; + napi_status status; + + status = napi_get_named_property(env_, obj, name, &res); + if (status != napi_ok) { + throw exception("Failed to get named property"); + } + + return res; + } + + + inline napi_value + get_new_target(napi_callback_info info) + { + napi_value res; + napi_status status; + + status = napi_get_new_target(env_, info, &res); + if (status != napi_ok) { + throw exception("Failed to get new target"); + } + + return res; + } + + + inline napi_value + get_property(napi_value val, napi_value key) + { + napi_value res; + napi_status status; + + status = napi_get_property(env_, val, key, &res); + if (status != napi_ok) { + throw exception("Failed to get property"); + } + + return res; + } + + + inline napi_value + get_property_names(napi_value val) + { + napi_value res; + napi_status status; + + status = napi_get_property_names(env_, val, &res); + if (status != napi_ok) { + throw exception("Failed to get property names"); + } + + return res; + } + + + inline napi_value + get_reference_value(napi_ref ref) + { + napi_value res; + napi_status status; + + status = napi_get_reference_value(env_, ref, &res); + if (status != napi_ok) { + throw exception("Failed to get reference value"); + } + + return res; + } + + + inline nxt_unit_request_info_t * + get_request_info(napi_value obj) + { + int64_t n; + napi_status status; + + status = napi_get_value_int64(env_, obj, &n); + if (status != napi_ok) { + throw exception("Failed to get request pointer"); + } + + return (nxt_unit_request_info_t *) (intptr_t) n; + } + + + inline size_t + get_value_string_latin1(napi_value val, char *buf, size_t bufsize) + { + size_t res; + napi_status status; + + status = napi_get_value_string_latin1(env_, val, buf, bufsize, &res); + if (status != napi_ok) { + throw exception("Failed to get string latin1"); + } + + return res; + } + + + inline uint32_t + get_value_uint32(napi_value obj) + { + uint32_t res; + napi_status status; + + status = napi_get_value_uint32(env_, obj, &res); + if (status != napi_ok) { + throw exception("Failed to get uint32_t"); + } + + return res; + } + + + inline bool + is_array(napi_value val) + { + bool res; + napi_status status; + + status = napi_is_array(env_, val, &res); + if (status != napi_ok) { + throw exception("Failed to confirm value is array"); + } + + return res; + } + + + inline napi_value + make_callback(napi_async_context ctx, napi_value val, napi_value func, + int argc, const napi_value *argv) + { + napi_value res, ex; + napi_status status; + + status = napi_make_callback(env_, ctx, val, func, argc, argv, &res); + if (status != napi_ok) { + if (status != napi_pending_exception) { + throw exception("Failed to make callback"); + } + + status = napi_get_and_clear_last_exception(env_, &ex); + if (status != napi_ok) { + throw exception("Failed to get and clear last exception"); + } + + /* Logging a description of the error and call stack. */ + status = napi_fatal_exception(env_, ex); + if (status != napi_ok) { + throw exception("Failed napi_fatal_exception()"); + } + } + + return res; + } + + + inline napi_value + new_instance(napi_value ctor) + { + napi_value res; + napi_status status; + + status = napi_new_instance(env_, ctor, 0, NULL, &res); + if (status != napi_ok) { + throw exception("Failed to create instance"); + } + + return res; + } + + + inline napi_value + new_instance(napi_value ctor, napi_value param) + { + napi_value res; + napi_status status; + + status = napi_new_instance(env_, ctor, 1, ¶m, &res); + if (status != napi_ok) { + throw exception("Failed to create instance"); + } + + return res; + } + + + inline void + set_element(napi_value obj, uint32_t i, napi_value val) + { + napi_status status; + + status = napi_set_element(env_, obj, i, val); + if (status != napi_ok) { + throw exception("Failed to set element"); + } + } + + + inline void + set_named_property(napi_value obj, const char *name, napi_value val) + { + napi_status status; + + status = napi_set_named_property(env_, obj, name, val); + if (status != napi_ok) { + throw exception("Failed to set named property"); + } + } + + + inline void + set_named_property(napi_value obj, const char *name, napi_callback cb) + { + set_named_property(obj, name, create_function(cb)); + } + + + inline napi_value + set_named_property(napi_value obj, const char *name, nxt_unit_sptr_t &val, + size_t len) + { + napi_value str; + + str = create_string_latin1(val, len); + + set_named_property(obj, name, str); + + return str; + } + + + inline void + set_named_property(napi_value obj, const char *name, intptr_t val) + { + napi_value ptr; + napi_status status; + + status = napi_create_int64(env_, val, &ptr); + if (status != napi_ok) { + throw exception("Failed to create int64"); + } + + set_named_property(obj, name, ptr); + } + + + inline void + throw_error(const char *str) + { + napi_throw_error(env_, NULL, str); + } + + + inline void + throw_error(const exception &e) + { + napi_throw_error(env_, NULL, e.str); + } + + + inline napi_valuetype + type_of(napi_value val) + { + napi_status status; + napi_valuetype res; + + status = napi_typeof(env_, val, &res); + if (status != napi_ok) { + throw exception("Failed to get typeof"); + } + + return res; + } + + + inline void * + unwrap(napi_value val) + { + void *res; + napi_status status; + + status = napi_unwrap(env_, val, &res); + if (status != napi_ok) { + throw exception("Failed to unwrap"); + } + + return res; + } + + + inline napi_ref + wrap(napi_value val, void *obj, napi_finalize fin_cb, void *hint = nullptr) + { + napi_ref res; + napi_status status; + + status = napi_wrap(env_, val, obj, fin_cb, hint, &res); + if (status != napi_ok) { + throw exception("Failed to wrap"); + } + + return res; + } + + + inline + operator napi_env() + { + return env_; + } + + + napi_env env() + { + return env_; + } + +private: + napi_env env_; +}; + + +struct nxt_handle_scope : public nxt_napi { + nxt_handle_scope(napi_env env) : nxt_napi(env) + { + napi_status status; + + status = napi_open_handle_scope(env, &scope_); + if (status != napi_ok) { + throw exception("Failed to open handle scope"); + } + } + + ~nxt_handle_scope() + { + napi_status status; + + status = napi_close_handle_scope(env(), scope_); + if (status != napi_ok) { + throw_error("Failed to close handle scope"); + } + } + +private: + napi_handle_scope scope_; +}; + + +struct nxt_async_context : public nxt_napi { + nxt_async_context(napi_env env, const char *name) : + nxt_napi(env) + { + napi_value name_val; + napi_status status; + + name_val = create_string_latin1(name, NAPI_AUTO_LENGTH); + + status = napi_async_init(env, NULL, name_val, &context_); + if (status != napi_ok) { + throw exception("Failed to init async object"); + } + } + + operator napi_async_context() { + return context_; + } + + ~nxt_async_context() + { + napi_status status; + + status = napi_async_destroy(env(), context_); + if (status != napi_ok) { + throw_error("Failed to destroy async object"); + } + } + +private: + napi_async_context context_; +}; + + +struct nxt_callback_scope : public nxt_napi { + nxt_callback_scope(nxt_async_context& ctx) : + nxt_napi(ctx.env()) + { + napi_value resource; + napi_status status; + + resource = create_object(); + + status = napi_open_callback_scope(env(), resource, ctx, &scope_); + if (status != napi_ok) { + throw exception("Failed to open callback scope"); + } + } + + ~nxt_callback_scope() + { + napi_status status; + + status = napi_close_callback_scope(env(), scope_); + if (status != napi_ok) { + throw_error("Failed to close callback scope"); + } + } + +private: + napi_callback_scope scope_; +}; + + +#endif /* _NXT_NODEJS_NAPI_H_INCLUDED_ */ diff --git a/src/nodejs/unit-http/unit.cpp b/src/nodejs/unit-http/unit.cpp index 60b0412a..3f66189a 100644 --- a/src/nodejs/unit-http/unit.cpp +++ b/src/nodejs/unit-http/unit.cpp @@ -20,9 +20,9 @@ struct nxt_nodejs_ctx_t { }; -Unit::Unit(napi_env env): - env_(env), - wrapper_(nullptr), +Unit::Unit(napi_env env, napi_value jsthis): + nxt_napi(env), + wrapper_(wrap(jsthis, this, destroy)), unit_ctx_(nullptr) { } @@ -30,15 +30,15 @@ Unit::Unit(napi_env env): Unit::~Unit() { - napi_delete_reference(env_, wrapper_); + delete_reference(wrapper_); } napi_value Unit::init(napi_env env, napi_value exports) { - napi_value cons, fn; - napi_status status; + nxt_napi napi(env); + napi_value cons; napi_property_descriptor properties[] = { { "createServer", 0, create_server, 0, 0, 0, napi_default, 0 }, @@ -46,61 +46,22 @@ Unit::init(napi_env env, napi_value exports) { "_read", 0, _read, 0, 0, 0, napi_default, 0 } }; - status = napi_define_class(env, "Unit", NAPI_AUTO_LENGTH, create, nullptr, - 3, properties, &cons); - if (status != napi_ok) { - goto failed; - } - - status = napi_create_reference(env, cons, 1, &constructor_); - if (status != napi_ok) { - goto failed; - } - - status = napi_set_named_property(env, exports, "Unit", cons); - if (status != napi_ok) { - goto failed; - } - - status = napi_create_function(env, NULL, 0, response_send_headers, NULL, - &fn); - if (status != napi_ok) { - goto failed; - } - - status = napi_set_named_property(env, exports, - "unit_response_headers", fn); - if (status != napi_ok) { - goto failed; - } - - status = napi_create_function(env, NULL, 0, response_write, NULL, &fn); - if (status != napi_ok) { - goto failed; - } + try { + cons = napi.define_class("Unit", create, 3, properties); + constructor_ = napi.create_reference(cons); - status = napi_set_named_property(env, exports, "unit_response_write", fn); - if (status != napi_ok) { - goto failed; - } + napi.set_named_property(exports, "Unit", cons); + napi.set_named_property(exports, "unit_response_headers", + response_send_headers); + napi.set_named_property(exports, "unit_response_write", response_write); + napi.set_named_property(exports, "unit_response_end", response_end); - status = napi_create_function(env, NULL, 0, response_end, NULL, &fn); - if (status != napi_ok) { - goto failed; - } - - status = napi_set_named_property(env, exports, "unit_response_end", fn); - if (status != napi_ok) { - goto failed; + } catch (exception &e) { + napi.throw_error(e); + return nullptr; } return exports; - -failed: - - napi_throw_error(env, NULL, "Failed to define Unit class"); - - return nullptr; } @@ -116,63 +77,33 @@ Unit::destroy(napi_env env, void *nativeObject, void *finalize_hint) napi_value Unit::create(napi_env env, napi_callback_info info) { - Unit *obj; - napi_ref ref; - napi_value target, cons, instance, jsthis; - napi_status status; - - status = napi_get_new_target(env, info, &target); - if (status != napi_ok) { - goto failed; - } + nxt_napi napi(env); + napi_value target, cons, instance, jsthis; - if (target != nullptr) { - /* Invoked as constructor: `new Unit(...)` */ - status = napi_get_cb_info(env, info, nullptr, nullptr, &jsthis, - nullptr); - if (status != napi_ok) { - goto failed; - } + try { + target = napi.get_new_target(info); - obj = new Unit(env); + if (target != nullptr) { + /* Invoked as constructor: `new Unit(...)`. */ + jsthis = napi.get_cb_info(info); - status = napi_wrap(env, jsthis, reinterpret_cast<void *>(obj), - destroy, nullptr, &obj->wrapper_); - if (status != napi_ok) { - goto failed; - } + new Unit(env, jsthis); + napi.create_reference(jsthis); - status = napi_create_reference(env, jsthis, 1, &ref); - if (status != napi_ok) { - goto failed; + return jsthis; } - return jsthis; - } - - /* Invoked as plain function `Unit(...)`, turn into construct call. */ - status = napi_get_reference_value(env, constructor_, &cons); - if (status != napi_ok) { - goto failed; - } + /* Invoked as plain function `Unit(...)`, turn into construct call. */ + cons = napi.get_reference_value(constructor_); + instance = napi.new_instance(cons); + napi.create_reference(instance); - status = napi_new_instance(env, cons, 0, nullptr, &instance); - if (status != napi_ok) { - goto failed; - } - - status = napi_create_reference(env, instance, 1, &ref); - if (status != napi_ok) { - goto failed; + } catch (exception &e) { + napi.throw_error(e); + return nullptr; } return instance; - -failed: - - napi_throw_error(env, NULL, "Failed to create Unit object"); - - return nullptr; } @@ -181,20 +112,19 @@ Unit::create_server(napi_env env, napi_callback_info info) { Unit *obj; size_t argc; + nxt_napi napi(env); napi_value jsthis, argv; - napi_status status; nxt_unit_init_t unit_init; argc = 1; - status = napi_get_cb_info(env, info, &argc, &argv, &jsthis, nullptr); - if (status != napi_ok) { - goto failed; - } + try { + jsthis = napi.get_cb_info(info, argc, &argv); + obj = (Unit *) napi.unwrap(jsthis); - status = napi_unwrap(env, jsthis, reinterpret_cast<void **>(&obj)); - if (status != napi_ok) { - goto failed; + } catch (exception &e) { + napi.throw_error(e); + return nullptr; } memset(&unit_init, 0, sizeof(nxt_unit_init_t)); @@ -230,40 +160,22 @@ Unit::listen(napi_env env, napi_callback_info info) napi_value Unit::_read(napi_env env, napi_callback_info info) { - Unit *obj; void *data; size_t argc; - int64_t req_pointer; - napi_value jsthis, buffer, argv; - napi_status status; + nxt_napi napi(env); + napi_value buffer, argv; nxt_unit_request_info_t *req; argc = 1; - status = napi_get_cb_info(env, info, &argc, &argv, &jsthis, nullptr); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Failed to get arguments from js"); - return nullptr; - } + try { + napi.get_cb_info(info, argc, &argv); - status = napi_unwrap(env, jsthis, reinterpret_cast<void **>(&obj)); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Failed to get Unit object form js"); - return nullptr; - } + req = napi.get_request_info(argv); + buffer = napi.create_buffer((size_t) req->content_length, &data); - status = napi_get_value_int64(env, argv, &req_pointer); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Failed to get request pointer"); - return nullptr; - } - - req = (nxt_unit_request_info_t *) (uintptr_t) req_pointer; - - status = napi_create_buffer(env, (size_t) req->content_length, - &data, &buffer); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Failed to create request buffer"); + } catch (exception &e) { + napi.throw_error(e); return nullptr; } @@ -276,138 +188,38 @@ Unit::_read(napi_env env, napi_callback_info info) void Unit::request_handler(nxt_unit_request_info_t *req) { - Unit *obj; - napi_value socket, request, response, global, server_obj, except; - napi_value emit_events, events_res, async_name, resource_object; - napi_status status; - napi_async_context async_context; - napi_callback_scope async_scope; - napi_value events_args[3]; + Unit *obj; + napi_value socket, request, response, server_obj; + napi_value emit_events; + napi_value events_args[3]; obj = reinterpret_cast<Unit *>(req->unit->data); - napi_handle_scope scope; - status = napi_open_handle_scope(obj->env_, &scope); - if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to create handle scope"); - return; - } - - server_obj = obj->get_server_object(); - if (server_obj == nullptr) { - napi_throw_error(obj->env_, NULL, "Failed to get server object"); - return; - } - - status = napi_get_global(obj->env_, &global); - if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to get global variable"); - return; - } - - socket = obj->create_socket(server_obj, req); - if (socket == nullptr) { - napi_throw_error(obj->env_, NULL, "Failed to create socket object"); - return; - } - - request = obj->create_request(server_obj, socket); - if (request == nullptr) { - napi_throw_error(obj->env_, NULL, "Failed to create request object"); - return; - } - - response = obj->create_response(server_obj, socket, request, req, obj); - if (response == nullptr) { - napi_throw_error(obj->env_, NULL, "Failed to create response object"); - return; - } - - status = obj->create_headers(req, request); - if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to create headers"); - return; - } - - status = napi_get_named_property(obj->env_, server_obj, "emit_events", - &emit_events); - if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to get " - "'emit_events' function"); - return; - } - - events_args[0] = server_obj; - events_args[1] = request; - events_args[2] = response; + try { + nxt_handle_scope scope(obj->env()); - status = napi_create_string_utf8(obj->env_, "unit_request_handler", - sizeof("unit_request_handler") - 1, - &async_name); - if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to create utf-8 string"); - return; - } + server_obj = obj->get_server_object(); - status = napi_async_init(obj->env_, NULL, async_name, &async_context); - if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to init async object"); - return; - } + socket = obj->create_socket(server_obj, req); + request = obj->create_request(server_obj, socket); + response = obj->create_response(server_obj, socket, request, req); - status = napi_create_object(obj->env_, &resource_object); - if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to create object for " - "callback scope"); - return; - } + obj->create_headers(req, request); - status = napi_open_callback_scope(obj->env_, resource_object, async_context, - &async_scope); - if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to open callback scope"); - return; - } + emit_events = obj->get_named_property(server_obj, "emit_events"); - status = napi_make_callback(obj->env_, async_context, server_obj, - emit_events, 3, events_args, &events_res); - if (status != napi_ok) { - if (status != napi_pending_exception) { - napi_throw_error(obj->env_, NULL, "Failed to make callback"); - return; - } + events_args[0] = server_obj; + events_args[1] = request; + events_args[2] = response; - status = napi_get_and_clear_last_exception(obj->env_, &except); - if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, - "Failed to get and clear last exception"); - return; - } - - /* Logging a description of the error and call stack. */ - status = napi_fatal_exception(obj->env_, except); - if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to call " - "napi_fatal_exception() function"); - return; - } - } + nxt_async_context async_context(obj->env(), "unit_request_handler"); + nxt_callback_scope async_scope(async_context); - status = napi_close_callback_scope(obj->env_, async_scope); - if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to close callback scope"); - return; - } + obj->make_callback(async_context, server_obj, emit_events, + 3, events_args); - status = napi_async_destroy(obj->env_, async_context); - if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to destroy async object"); - return; - } - - status = napi_close_handle_scope(obj->env_, scope); - if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to close handle scope"); + } catch (exception &e) { + obj->throw_error(e); } } @@ -432,14 +244,14 @@ Unit::add_port(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port) obj = reinterpret_cast<Unit *>(ctx->unit->data); if (fcntl(port->in_fd, F_SETFL, O_NONBLOCK) == -1) { - napi_throw_error(obj->env_, NULL, "Failed to upgrade read" + obj->throw_error("Failed to upgrade read" " file descriptor to O_NONBLOCK"); return -1; } - status = napi_get_uv_event_loop(obj->env_, &loop); + status = napi_get_uv_event_loop(obj->env(), &loop); if (status != napi_ok) { - napi_throw_error(obj->env_, NULL, "Failed to get uv.loop"); + obj->throw_error("Failed to get uv.loop"); return NXT_UNIT_ERROR; } @@ -447,13 +259,13 @@ Unit::add_port(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port) err = uv_poll_init(loop, &node_ctx->poll, port->in_fd); if (err < 0) { - napi_throw_error(obj->env_, NULL, "Failed to init uv.poll"); + obj->throw_error("Failed to init uv.poll"); return NXT_UNIT_ERROR; } err = uv_poll_start(&node_ctx->poll, UV_READABLE, nxt_uv_read_callback); if (err < 0) { - napi_throw_error(obj->env_, NULL, "Failed to start uv.poll"); + obj->throw_error("Failed to start uv.poll"); return NXT_UNIT_ERROR; } @@ -467,7 +279,7 @@ Unit::add_port(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port) } -inline bool +inline bool operator == (const nxt_unit_port_id_t &p1, const nxt_unit_port_id_t &p2) { return p1.pid == p2.pid && p1.id == p2.id; @@ -498,6 +310,27 @@ Unit::remove_port(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id) void Unit::quit(nxt_unit_ctx_t *ctx) { + Unit *obj; + napi_value server_obj, emit_close; + + obj = reinterpret_cast<Unit *>(ctx->unit->data); + + try { + nxt_handle_scope scope(obj->env()); + + server_obj = obj->get_server_object(); + + emit_close = obj->get_named_property(server_obj, "emit_close"); + + nxt_async_context async_context(obj->env(), "unit_quit"); + nxt_callback_scope async_scope(async_context); + + obj->make_callback(async_context, server_obj, emit_close, 0, NULL); + + } catch (exception &e) { + obj->throw_error(e); + } + nxt_unit_done(ctx); } @@ -505,200 +338,105 @@ Unit::quit(nxt_unit_ctx_t *ctx) napi_value Unit::get_server_object() { - napi_value unit_obj, server_obj; - napi_status status; + napi_value unit_obj; - status = napi_get_reference_value(env_, wrapper_, &unit_obj); - if (status != napi_ok) { - return nullptr; - } - - status = napi_get_named_property(env_, unit_obj, "server", &server_obj); - if (status != napi_ok) { - return nullptr; - } + unit_obj = get_reference_value(wrapper_); - return server_obj; + return get_named_property(unit_obj, "server"); } -napi_status +void Unit::create_headers(nxt_unit_request_info_t *req, napi_value request) { uint32_t i; - const char *p; - napi_value headers, raw_headers, str; + napi_value headers, raw_headers; napi_status status; - nxt_unit_field_t *f; nxt_unit_request_t *r; r = req->request; - status = napi_create_object(env_, &headers); - if (status != napi_ok) { - return status; - } + headers = create_object(); - status = napi_create_array_with_length(env_, r->fields_count * 2, + status = napi_create_array_with_length(env(), r->fields_count * 2, &raw_headers); if (status != napi_ok) { - return status; + throw exception("Failed to create array"); } for (i = 0; i < r->fields_count; i++) { - f = r->fields + i; - - status = this->append_header(f, headers, raw_headers, i); - if (status != napi_ok) { - return status; - } - } - - status = napi_set_named_property(env_, request, "headers", headers); - if (status != napi_ok) { - return status; - } - - status = napi_set_named_property(env_, request, "rawHeaders", raw_headers); - if (status != napi_ok) { - return status; - } - - p = (const char *) nxt_unit_sptr_get(&r->version); - - status = napi_create_string_latin1(env_, p, r->version_length, &str); - if (status != napi_ok) { - return status; - } - - status = napi_set_named_property(env_, request, "httpVersion", str); - if (status != napi_ok) { - return status; - } - - p = (const char *) nxt_unit_sptr_get(&r->method); - - status = napi_create_string_latin1(env_, p, r->method_length, &str); - if (status != napi_ok) { - return status; + append_header(r->fields + i, headers, raw_headers, i); } - status = napi_set_named_property(env_, request, "method", str); - if (status != napi_ok) { - return status; - } - - p = (const char *) nxt_unit_sptr_get(&r->target); - - status = napi_create_string_latin1(env_, p, r->target_length, &str); - if (status != napi_ok) { - return status; - } + set_named_property(request, "headers", headers); + set_named_property(request, "rawHeaders", raw_headers); + set_named_property(request, "httpVersion", r->version, r->version_length); + set_named_property(request, "method", r->method, r->method_length); + set_named_property(request, "url", r->target, r->target_length); +} - status = napi_set_named_property(env_, request, "url", str); - if (status != napi_ok) { - return status; - } - return napi_ok; +inline char +lowcase(char c) +{ + return (c >= 'A' && c <= 'Z') ? (c | 0x20) : c; } -inline napi_status +inline void Unit::append_header(nxt_unit_field_t *f, napi_value headers, - napi_value raw_headers, uint32_t idx) + napi_value raw_headers, uint32_t idx) { - const char *name, *value; - napi_value str, vstr; - napi_status status; + char *name; + uint8_t i; + napi_value str, vstr; - value = (const char *) nxt_unit_sptr_get(&f->value); + name = (char *) nxt_unit_sptr_get(&f->name); - status = napi_create_string_latin1(env_, value, f->value_length, &vstr); - if (status != napi_ok) { - return status; - } - - name = (const char *) nxt_unit_sptr_get(&f->name); - - status = napi_set_named_property(env_, headers, name, vstr); - if (status != napi_ok) { - return status; - } + str = create_string_latin1(name, f->name_length); - status = napi_create_string_latin1(env_, name, f->name_length, &str); - if (status != napi_ok) { - return status; + for (i = 0; i < f->name_length; i++) { + name[i] = lowcase(name[i]); } - status = napi_set_element(env_, raw_headers, idx * 2, str); - if (status != napi_ok) { - return status; - } - - status = napi_set_element(env_, raw_headers, idx * 2 + 1, vstr); - if (status != napi_ok) { - return status; - } + vstr = set_named_property(headers, name, f->value, f->value_length); - return napi_ok; + set_element(raw_headers, idx * 2, str); + set_element(raw_headers, idx * 2 + 1, vstr); } napi_value Unit::create_socket(napi_value server_obj, nxt_unit_request_info_t *req) { - napi_value constructor, return_val, req_pointer; - napi_status status; + napi_value constructor, res; + nxt_unit_request_t *r; - status = napi_get_named_property(env_, server_obj, "socket", - &constructor); - if (status != napi_ok) { - return nullptr; - } + r = req->request; - status = napi_new_instance(env_, constructor, 0, NULL, &return_val); - if (status != napi_ok) { - return nullptr; - } + constructor = get_named_property(server_obj, "socket"); - status = napi_create_int64(env_, (uintptr_t) req, &req_pointer); - if (status != napi_ok) { - return nullptr; - } + res = new_instance(constructor); - status = napi_set_named_property(env_, return_val, "req_pointer", - req_pointer); - if (status != napi_ok) { - return nullptr; - } + set_named_property(res, "req_pointer", (intptr_t) req); + set_named_property(res, "remoteAddress", r->remote, r->remote_length); + set_named_property(res, "localAddress", r->local, r->local_length); - return return_val; + return res; } napi_value Unit::create_request(napi_value server_obj, napi_value socket) { - napi_value constructor, return_val; - napi_status status; + napi_value constructor, return_val; - status = napi_get_named_property(env_, server_obj, "request", - &constructor); - if (status != napi_ok) { - return nullptr; - } + constructor = get_named_property(server_obj, "request"); - status = napi_new_instance(env_, constructor, 1, &server_obj, - &return_val); - if (status != napi_ok) { - return nullptr; - } + return_val = new_instance(constructor, server_obj); - status = napi_set_named_property(env_, return_val, "socket", socket); - if (status != napi_ok) { - return nullptr; - } + set_named_property(return_val, "socket", socket); + set_named_property(return_val, "connection", socket); return return_val; } @@ -706,37 +444,17 @@ Unit::create_request(napi_value server_obj, napi_value socket) napi_value Unit::create_response(napi_value server_obj, napi_value socket, - napi_value request, nxt_unit_request_info_t *req, - Unit *obj) + napi_value request, nxt_unit_request_info_t *req) { - napi_value constructor, return_val, req_num; - napi_status status; + napi_value constructor, return_val; - status = napi_get_named_property(env_, server_obj, "response", - &constructor); - if (status != napi_ok) { - return nullptr; - } + constructor = get_named_property(server_obj, "response"); - status = napi_new_instance(env_, constructor, 1, &request, &return_val); - if (status != napi_ok) { - return nullptr; - } + return_val = new_instance(constructor, request); - status = napi_set_named_property(env_, return_val, "socket", socket); - if (status != napi_ok) { - return nullptr; - } - - status = napi_create_int64(env_, (int64_t) (uintptr_t) req, &req_num); - if (status != napi_ok) { - return nullptr; - } - - status = napi_set_named_property(env_, return_val, "_req_point", req_num); - if (status != napi_ok) { - return nullptr; - } + set_named_property(return_val, "socket", socket); + set_named_property(return_val, "connection", socket); + set_named_property(return_val, "_req_point", (intptr_t) req); return return_val; } @@ -749,13 +467,12 @@ Unit::response_send_headers(napi_env env, napi_callback_info info) char *ptr, *name_ptr; bool is_array; size_t argc, name_len, value_len; - int64_t req_p; uint32_t status_code, header_len, keys_len, array_len; uint32_t keys_count, i, j; uint16_t hash; + nxt_napi napi(env); napi_value this_arg, headers, keys, name, value, array_val; napi_value req_num, array_entry; - napi_status status; napi_valuetype val_type; nxt_unit_field_t *f; nxt_unit_request_info_t *req; @@ -763,137 +480,97 @@ Unit::response_send_headers(napi_env env, napi_callback_info info) argc = 5; - status = napi_get_cb_info(env, info, &argc, argv, &this_arg, NULL); - if (status != napi_ok) { - return nullptr; - } + try { + this_arg = napi.get_cb_info(info, argc, argv); + if (argc != 5) { + napi.throw_error("Wrong args count. Expected: " + "statusCode, headers, headers count, " + "headers length"); + return nullptr; + } - if (argc != 5) { - napi_throw_error(env, NULL, "Wrong args count. Need three: " - "statusCode, headers, headers count, headers length"); - return nullptr; - } + req_num = napi.get_named_property(argv[0], "_req_point"); - status = napi_get_named_property(env, argv[0], "_req_point", &req_num); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Failed to get request pointer"); - return nullptr; - } + req = napi.get_request_info(req_num); - status = napi_get_value_int64(env, req_num, &req_p); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Failed to get request pointer"); - return nullptr; - } + status_code = napi.get_value_uint32(argv[1]); + keys_count = napi.get_value_uint32(argv[3]); + header_len = napi.get_value_uint32(argv[4]); - req = (nxt_unit_request_info_t *) (uintptr_t) req_p; + /* Need to reserve extra byte for C-string 0-termination. */ + header_len++; - status = napi_get_value_uint32(env, argv[1], &status_code); - if (status != napi_ok) { - goto failed; - } + headers = argv[2]; - status = napi_get_value_uint32(env, argv[3], &keys_count); - if (status != napi_ok) { - goto failed; - } + ret = nxt_unit_response_init(req, status_code, keys_count, header_len); + if (ret != NXT_UNIT_OK) { + napi.throw_error("Failed to create response"); + return nullptr; + } - status = napi_get_value_uint32(env, argv[4], &header_len); - if (status != napi_ok) { - goto failed; - } + keys = napi.get_property_names(headers); + keys_len = napi.get_array_length(keys); - /* Need to reserve extra byte for C-string 0-termination. */ - header_len++; + ptr = req->response_buf->free; - headers = argv[2]; + for (i = 0; i < keys_len; i++) { + name = napi.get_element(keys, i); - ret = nxt_unit_response_init(req, status_code, keys_count, header_len); - if (ret != NXT_UNIT_OK) { - goto failed; - } + array_entry = napi.get_property(headers, name); - status = napi_get_property_names(env, headers, &keys); - if (status != napi_ok) { - goto failed; - } + name = napi.get_element(array_entry, 0); + value = napi.get_element(array_entry, 1); - status = napi_get_array_length(env, keys, &keys_len); - if (status != napi_ok) { - goto failed; - } + name_len = napi.get_value_string_latin1(name, ptr, header_len); + name_ptr = ptr; - ptr = req->response_buf->free; + ptr += name_len; + header_len -= name_len; - for (i = 0; i < keys_len; i++) { - status = napi_get_element(env, keys, i, &name); - if (status != napi_ok) { - goto failed; - } + hash = nxt_unit_field_hash(name_ptr, name_len); - status = napi_get_property(env, headers, name, &array_entry); - if (status != napi_ok) { - goto failed; - } + is_array = napi.is_array(value); - status = napi_get_element(env, array_entry, 0, &name); - if (status != napi_ok) { - goto failed; - } + if (is_array) { + array_len = napi.get_array_length(value); - status = napi_get_element(env, array_entry, 1, &value); - if (status != napi_ok) { - goto failed; - } + for (j = 0; j < array_len; j++) { + array_val = napi.get_element(value, j); - status = napi_get_value_string_latin1(env, name, ptr, header_len, - &name_len); - if (status != napi_ok) { - goto failed; - } + val_type = napi.type_of(array_val); - name_ptr = ptr; + if (val_type != napi_string) { + array_val = napi.coerce_to_string(array_val); + } - ptr += name_len; - header_len -= name_len; + value_len = napi.get_value_string_latin1(array_val, ptr, + header_len); - hash = nxt_unit_field_hash(name_ptr, name_len); + f = req->response->fields + req->response->fields_count; + f->skip = 0; - status = napi_is_array(env, value, &is_array); - if (status != napi_ok) { - goto failed; - } + nxt_unit_sptr_set(&f->name, name_ptr); - if (is_array) { - status = napi_get_array_length(env, value, &array_len); - if (status != napi_ok) { - goto failed; - } + f->name_length = name_len; + f->hash = hash; - for (j = 0; j < array_len; j++) { - status = napi_get_element(env, value, j, &array_val); - if (status != napi_ok) { - goto failed; - } + nxt_unit_sptr_set(&f->value, ptr); + f->value_length = (uint32_t) value_len; - napi_typeof(env, array_val, &val_type); - if (status != napi_ok) { - goto failed; + ptr += value_len; + header_len -= value_len; + + req->response->fields_count++; } + } else { + val_type = napi.type_of(value); + if (val_type != napi_string) { - status = napi_coerce_to_string(env, array_val, &array_val); - if (status != napi_ok) { - goto failed; - } + value = napi.coerce_to_string(value); } - status = napi_get_value_string_latin1(env, array_val, ptr, - header_len, - &value_len); - if (status != napi_ok) { - goto failed; - } + value_len = napi.get_value_string_latin1(value, ptr, header_len); f = req->response->fields + req->response->fields_count; f->skip = 0; @@ -911,60 +588,22 @@ Unit::response_send_headers(napi_env env, napi_callback_info info) req->response->fields_count++; } - - } else { - napi_typeof(env, value, &val_type); - if (status != napi_ok) { - goto failed; - } - - if (val_type != napi_string) { - status = napi_coerce_to_string(env, value, &value); - if (status != napi_ok) { - goto failed; - } - } - - status = napi_get_value_string_latin1(env, value, ptr, header_len, - &value_len); - if (status != napi_ok) { - goto failed; - } - - f = req->response->fields + req->response->fields_count; - f->skip = 0; - - nxt_unit_sptr_set(&f->name, name_ptr); - - f->name_length = name_len; - f->hash = hash; - - nxt_unit_sptr_set(&f->value, ptr); - f->value_length = (uint32_t) value_len; - - ptr += value_len; - header_len -= value_len; - - req->response->fields_count++; } + + } catch (exception &e) { + napi.throw_error(e); + return nullptr; } req->response_buf->free = ptr; ret = nxt_unit_response_send(req); if (ret != NXT_UNIT_OK) { - goto failed; + napi.throw_error("Failed to send response"); + return nullptr; } return this_arg; - -failed: - - req->response->fields_count = 0; - - napi_throw_error(env, NULL, "Failed to write headers"); - - return nullptr; } @@ -974,8 +613,8 @@ Unit::response_write(napi_env env, napi_callback_info info) int ret; char *ptr; size_t argc, have_buf_len; - int64_t req_p; uint32_t buf_len; + nxt_napi napi(env); napi_value this_arg, req_num; napi_status status; nxt_unit_buf_t *buf; @@ -985,39 +624,23 @@ Unit::response_write(napi_env env, napi_callback_info info) argc = 3; - status = napi_get_cb_info(env, info, &argc, argv, &this_arg, NULL); - if (status != napi_ok) { - goto failed; - } - - if (argc != 3) { - napi_throw_error(env, NULL, "Wrong args count. Need two: " - "chunk, chunk length"); - return nullptr; - } - - status = napi_get_named_property(env, argv[0], "_req_point", &req_num); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Failed to get request pointer"); - return nullptr; - } + try { + this_arg = napi.get_cb_info(info, argc, argv); + if (argc != 3) { + throw exception("Wrong args count. Expected: " + "chunk, chunk length"); + } - status = napi_get_value_int64(env, req_num, &req_p); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Failed to get request pointer"); - return nullptr; - } + req_num = napi.get_named_property(argv[0], "_req_point"); + req = napi.get_request_info(req_num); - req = (nxt_unit_request_info_t *) (uintptr_t) req_p; + buf_len = napi.get_value_uint32(argv[2]); - status = napi_get_value_uint32(env, argv[2], &buf_len); - if (status != napi_ok) { - goto failed; - } + buf_type = napi.type_of(argv[1]); - status = napi_typeof(env, argv[1], &buf_type); - if (status != napi_ok) { - goto failed; + } catch (exception &e) { + napi.throw_error(e); + return nullptr; } buf_len++; @@ -1055,7 +678,7 @@ Unit::response_write(napi_env env, napi_callback_info info) failed: - napi_throw_error(env, NULL, "Failed to write body"); + napi.throw_error("Failed to write body"); return nullptr; } @@ -1065,33 +688,23 @@ napi_value Unit::response_end(napi_env env, napi_callback_info info) { size_t argc; - int64_t req_p; + nxt_napi napi(env); napi_value resp, this_arg, req_num; - napi_status status; nxt_unit_request_info_t *req; argc = 1; - status = napi_get_cb_info(env, info, &argc, &resp, &this_arg, NULL); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Failed to finalize sending body"); - return nullptr; - } + try { + this_arg = napi.get_cb_info(info, argc, &resp); - status = napi_get_named_property(env, resp, "_req_point", &req_num); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Failed to get request pointer"); - return nullptr; - } + req_num = napi.get_named_property(resp, "_req_point"); + req = napi.get_request_info(req_num); - status = napi_get_value_int64(env, req_num, &req_p); - if (status != napi_ok) { - napi_throw_error(env, NULL, "Failed to get request pointer"); + } catch (exception &e) { + napi.throw_error(e); return nullptr; } - req = (nxt_unit_request_info_t *) (uintptr_t) req_p; - nxt_unit_request_done(req, NXT_UNIT_OK); return this_arg; diff --git a/src/nodejs/unit-http/unit.h b/src/nodejs/unit-http/unit.h index db85e85c..e76d805a 100644 --- a/src/nodejs/unit-http/unit.h +++ b/src/nodejs/unit-http/unit.h @@ -6,34 +6,15 @@ #ifndef _NXT_NODEJS_UNIT_H_INCLUDED_ #define _NXT_NODEJS_UNIT_H_INCLUDED_ -#include <node_api.h> +#include "nxt_napi.h" -#ifdef __cplusplus -extern "C" { -#endif -#include "version.h" -#include <nxt_unit.h> - -#if NXT_VERNUM != NXT_NODE_VERNUM -#error "libunit version mismatch." -#endif - -#include <nxt_unit_response.h> -#include <nxt_unit_request.h> - - -#ifdef __cplusplus -} /* extern "C" */ -#endif - - -class Unit { +class Unit : public nxt_napi { public: static napi_value init(napi_env env, napi_value exports); private: - Unit(napi_env env); + Unit(napi_env env, napi_value jsthis); ~Unit(); static napi_value create(napi_env env, napi_callback_info info); @@ -56,7 +37,7 @@ private: napi_value create_response(napi_value server_obj, napi_value socket, napi_value request, - nxt_unit_request_info_t *req, Unit *obj); + nxt_unit_request_info_t *req); static napi_value response_send_headers(napi_env env, napi_callback_info info); @@ -64,18 +45,16 @@ private: static napi_value response_write(napi_env env, napi_callback_info info); static napi_value response_end(napi_env env, napi_callback_info info); - napi_status create_headers(nxt_unit_request_info_t *req, - napi_value request); + void create_headers(nxt_unit_request_info_t *req, napi_value request); - inline napi_status append_header(nxt_unit_field_t *f, napi_value headers, + void append_header(nxt_unit_field_t *f, napi_value headers, napi_value raw_headers, uint32_t idx); static napi_ref constructor_; - napi_env env_; napi_ref wrapper_; nxt_unit_ctx_t *unit_ctx_; }; -#endif /* _NXT_NODEJS_H_INCLUDED_ */ +#endif /* _NXT_NODEJS_UNIT_H_INCLUDED_ */ diff --git a/src/nxt_application.c b/src/nxt_application.c index a2827b75..f63b90fb 100644 --- a/src/nxt_application.c +++ b/src/nxt_application.c @@ -36,8 +36,6 @@ static nxt_app_module_t *nxt_app_module_load(nxt_task_t *task, const char *name); static nxt_int_t nxt_app_set_environment(nxt_conf_value_t *environment); -static void nxt_app_http_release(nxt_task_t *task, void *obj, void *data); - static uint32_t compat[] = { NXT_VERNUM, NXT_DEBUG, @@ -431,32 +429,6 @@ nxt_app_set_environment(nxt_conf_value_t *environment) } -nxt_int_t -nxt_app_http_req_done(nxt_task_t *task, nxt_app_parse_ctx_t *ar) -{ - ar->timer.handler = nxt_app_http_release; - nxt_timer_add(task->thread->engine, &ar->timer, 0); - - return NXT_OK; -} - - -static void -nxt_app_http_release(nxt_task_t *task, void *obj, void *data) -{ - nxt_timer_t *timer; - nxt_app_parse_ctx_t *ar; - - timer = obj; - - nxt_debug(task, "http app release"); - - ar = nxt_timer_data(timer, nxt_app_parse_ctx_t, timer); - - nxt_mp_release(ar->request->mem_pool); -} - - nxt_app_lang_module_t * nxt_app_lang_module(nxt_runtime_t *rt, nxt_str_t *name) { diff --git a/src/nxt_application.h b/src/nxt_application.h index 781f05e0..7ff4bb11 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -99,62 +99,6 @@ struct nxt_common_app_conf_s { }; -typedef struct { - nxt_str_t method; - nxt_str_t target; - nxt_str_t version; - nxt_str_t path; - nxt_str_t query; - nxt_str_t server_name; - - nxt_list_t *fields; - - nxt_str_t cookie; - nxt_str_t content_length; - nxt_str_t content_type; - - off_t parsed_content_length; - nxt_bool_t done; - - size_t bufs; - nxt_buf_t *buf; -} nxt_app_request_header_t; - - -typedef struct { - size_t preread_size; - nxt_bool_t done; - - nxt_buf_t *buf; -} nxt_app_request_body_t; - - -typedef struct { - nxt_app_request_header_t header; - nxt_app_request_body_t body; - - nxt_str_t remote; - nxt_str_t local; -} nxt_app_request_t; - - -typedef struct nxt_app_parse_ctx_s nxt_app_parse_ctx_t; - - -struct nxt_app_parse_ctx_s { - nxt_app_request_t r; - nxt_http_request_t *request; - nxt_timer_t timer; - void *timer_data; - nxt_http_request_parse_t parser; - nxt_http_request_parse_t resp_parser; - nxt_mp_t *mem_pool; -}; - - -nxt_int_t nxt_app_http_req_done(nxt_task_t *task, nxt_app_parse_ctx_t *ctx); - - struct nxt_app_module_s { size_t compat_length; uint32_t *compat; diff --git a/src/nxt_buf.h b/src/nxt_buf.h index d9d4ee1b..9c22d650 100644 --- a/src/nxt_buf.h +++ b/src/nxt_buf.h @@ -206,7 +206,7 @@ nxt_buf_set_last(b) \ #define \ nxt_buf_clear_last(b) \ - (b)->is_last = 0 + (b)->is_last = 0 #define \ diff --git a/src/nxt_conf.c b/src/nxt_conf.c index 4c6d8839..57870838 100644 --- a/src/nxt_conf.c +++ b/src/nxt_conf.c @@ -87,7 +87,6 @@ struct nxt_conf_op_s { uint32_t index; uint32_t action; /* nxt_conf_op_action_t */ void *ctx; - nxt_conf_op_t *next; }; @@ -113,6 +112,8 @@ static void nxt_conf_json_parse_error(nxt_conf_json_error_t *error, u_char *pos, static nxt_int_t nxt_conf_copy_value(nxt_mp_t *mp, nxt_conf_op_t *op, nxt_conf_value_t *dst, nxt_conf_value_t *src); +static nxt_int_t nxt_conf_copy_array(nxt_mp_t *mp, nxt_conf_op_t *op, + nxt_conf_value_t *dst, nxt_conf_value_t *src); static nxt_int_t nxt_conf_copy_object(nxt_mp_t *mp, nxt_conf_op_t *op, nxt_conf_value_t *dst, nxt_conf_value_t *src); @@ -736,12 +737,14 @@ nxt_conf_array_qsort(nxt_conf_value_t *value, } -nxt_int_t +nxt_conf_op_ret_t nxt_conf_op_compile(nxt_mp_t *mp, nxt_conf_op_t **ops, nxt_conf_value_t *root, - nxt_str_t *path, nxt_conf_value_t *value) + nxt_str_t *path, nxt_conf_value_t *value, nxt_bool_t add) { nxt_str_t token; + nxt_int_t index; nxt_conf_op_t *op, **parent; + nxt_conf_value_t *node; nxt_conf_path_parse_t parse; nxt_conf_object_member_t *member; @@ -754,7 +757,7 @@ nxt_conf_op_compile(nxt_mp_t *mp, nxt_conf_op_t **ops, nxt_conf_value_t *root, for ( ;; ) { op = nxt_mp_zget(mp, sizeof(nxt_conf_op_t)); if (nxt_slow_path(op == NULL)) { - return NXT_ERROR; + return NXT_CONF_OP_ERROR; } *parent = op; @@ -762,50 +765,107 @@ nxt_conf_op_compile(nxt_mp_t *mp, nxt_conf_op_t **ops, nxt_conf_value_t *root, nxt_conf_path_next_token(&parse, &token); - root = nxt_conf_get_object_member(root, &token, &op->index); + switch (root->type) { + + case NXT_CONF_VALUE_OBJECT: + node = nxt_conf_get_object_member(root, &token, &op->index); + break; + + case NXT_CONF_VALUE_ARRAY: + index = nxt_int_parse(token.start, token.length); + + if (index < 0 || index > NXT_INT32_T_MAX) { + return NXT_CONF_OP_NOT_FOUND; + } + + op->index = index; + + node = nxt_conf_get_array_element(root, index); + break; + + default: + node = NULL; + } if (parse.last) { break; } - if (root == NULL) { - return NXT_DECLINED; + if (node == NULL) { + return NXT_CONF_OP_NOT_FOUND; } op->action = NXT_CONF_OP_PASS; + root = node; } if (value == NULL) { - if (root == NULL) { - return NXT_DECLINED; + if (node == NULL) { + return NXT_CONF_OP_NOT_FOUND; } op->action = NXT_CONF_OP_DELETE; - return NXT_OK; + return NXT_CONF_OP_OK; + } + + if (add) { + if (node == NULL) { + return NXT_CONF_OP_NOT_FOUND; + } + + if (node->type != NXT_CONF_VALUE_ARRAY) { + return NXT_CONF_OP_NOT_ALLOWED; + } + + op->action = NXT_CONF_OP_PASS; + + op = nxt_mp_zget(mp, sizeof(nxt_conf_op_t)); + if (nxt_slow_path(op == NULL)) { + return NXT_CONF_OP_ERROR; + } + + *parent = op; + + op->index = node->u.array->count; + op->action = NXT_CONF_OP_CREATE; + op->ctx = value; + + return NXT_CONF_OP_OK; + } + + if (node != NULL) { + op->action = NXT_CONF_OP_REPLACE; + op->ctx = value; + + return NXT_CONF_OP_OK; } - if (root == NULL) { + op->action = NXT_CONF_OP_CREATE; + if (root->type == NXT_CONF_VALUE_ARRAY) { + if (op->index > root->u.array->count) { + return NXT_CONF_OP_NOT_FOUND; + } + + op->ctx = value; + + } else { member = nxt_mp_zget(mp, sizeof(nxt_conf_object_member_t)); if (nxt_slow_path(member == NULL)) { - return NXT_ERROR; + return NXT_CONF_OP_ERROR; } nxt_conf_set_string(&member->name, &token); member->value = *value; - op->action = NXT_CONF_OP_CREATE; + op->index = root->u.object->count; op->ctx = member; - - } else { - op->action = NXT_CONF_OP_REPLACE; - op->ctx = value; } - return NXT_OK; + return NXT_CONF_OP_OK; } @@ -834,16 +894,13 @@ static nxt_int_t nxt_conf_copy_value(nxt_mp_t *mp, nxt_conf_op_t *op, nxt_conf_value_t *dst, nxt_conf_value_t *src) { - size_t size; - nxt_int_t rc; - nxt_uint_t n; - - if (op != NULL && src->type != NXT_CONF_VALUE_OBJECT) { + if (op != NULL + && src->type != NXT_CONF_VALUE_ARRAY + && src->type != NXT_CONF_VALUE_OBJECT) + { return NXT_ERROR; } - dst->type = src->type; - switch (src->type) { case NXT_CONF_VALUE_STRING: @@ -861,34 +918,116 @@ nxt_conf_copy_value(nxt_mp_t *mp, nxt_conf_op_t *op, nxt_conf_value_t *dst, break; case NXT_CONF_VALUE_ARRAY: + return nxt_conf_copy_array(mp, op, dst, src); - size = sizeof(nxt_conf_array_t) - + src->u.array->count * sizeof(nxt_conf_value_t); + case NXT_CONF_VALUE_OBJECT: + return nxt_conf_copy_object(mp, op, dst, src); - dst->u.array = nxt_mp_get(mp, size); - if (nxt_slow_path(dst->u.array == NULL)) { - return NXT_ERROR; + default: + dst->u = src->u; + } + + dst->type = src->type; + + return NXT_OK; +} + + +static nxt_int_t +nxt_conf_copy_array(nxt_mp_t *mp, nxt_conf_op_t *op, nxt_conf_value_t *dst, + nxt_conf_value_t *src) +{ + size_t size; + nxt_int_t rc; + nxt_uint_t s, d, count, index; + nxt_conf_op_t *pass_op; + nxt_conf_value_t *value; + + count = src->u.array->count; + + if (op != NULL) { + if (op->action == NXT_CONF_OP_CREATE) { + count++; + + } else if (op->action == NXT_CONF_OP_DELETE) { + count--; } + } + + size = sizeof(nxt_conf_array_t) + count * sizeof(nxt_conf_value_t); + + dst->u.array = nxt_mp_get(mp, size); + if (nxt_slow_path(dst->u.array == NULL)) { + return NXT_ERROR; + } + + dst->u.array->count = count; + + s = 0; + d = 0; - dst->u.array->count = src->u.array->count; + pass_op = NULL; - for (n = 0; n < src->u.array->count; n++) { - rc = nxt_conf_copy_value(mp, NULL, &dst->u.array->elements[n], - &src->u.array->elements[n]); + /* + * This initialization is needed only to + * suppress a warning on GCC 4.8 and older. + */ + index = 0; + + do { + if (pass_op == NULL) { + index = (op == NULL) ? src->u.array->count : op->index; + } + while (s != index) { + rc = nxt_conf_copy_value(mp, pass_op, &dst->u.array->elements[d], + &src->u.array->elements[s]); if (nxt_slow_path(rc != NXT_OK)) { return NXT_ERROR; } + + s++; + d++; } - break; + if (pass_op != NULL) { + pass_op = NULL; + continue; + } - case NXT_CONF_VALUE_OBJECT: - return nxt_conf_copy_object(mp, op, dst, src); + if (op != NULL) { + switch (op->action) { + case NXT_CONF_OP_PASS: + pass_op = op->ctx; + index++; + break; - default: - dst->u = src->u; - } + case NXT_CONF_OP_CREATE: + value = op->ctx; + dst->u.array->elements[d] = *value; + + d++; + break; + + case NXT_CONF_OP_REPLACE: + value = op->ctx; + dst->u.array->elements[d] = *value; + + s++; + d++; + break; + + case NXT_CONF_OP_DELETE: + s++; + break; + } + + op = NULL; + } + + } while (d != count); + + dst->type = src->type; return NXT_OK; } @@ -939,9 +1078,7 @@ nxt_conf_copy_object(nxt_mp_t *mp, nxt_conf_op_t *op, nxt_conf_value_t *dst, do { if (pass_op == NULL) { - index = (op == NULL || op->action == NXT_CONF_OP_CREATE) - ? src->u.object->count - : op->index; + index = (op == NULL) ? src->u.object->count : op->index; } while (s != index) { @@ -1015,7 +1152,7 @@ nxt_conf_copy_object(nxt_mp_t *mp, nxt_conf_op_t *op, nxt_conf_value_t *dst, break; } - op = op->next; + op = NULL; } } while (d != count); diff --git a/src/nxt_conf.h b/src/nxt_conf.h index 20ff3b1e..2435b0e2 100644 --- a/src/nxt_conf.h +++ b/src/nxt_conf.h @@ -20,6 +20,14 @@ typedef enum { } nxt_conf_type_t; +typedef enum { + NXT_CONF_OP_OK = 0, + NXT_CONF_OP_NOT_FOUND, + NXT_CONF_OP_NOT_ALLOWED, + NXT_CONF_OP_ERROR, +} nxt_conf_op_ret_t; + + typedef struct nxt_conf_value_s nxt_conf_value_t; typedef struct nxt_conf_op_s nxt_conf_op_t; @@ -80,8 +88,9 @@ NXT_EXPORT nxt_conf_value_t *nxt_conf_get_array_element(nxt_conf_value_t *value, NXT_EXPORT nxt_int_t nxt_conf_map_object(nxt_mp_t *mp, nxt_conf_value_t *value, nxt_conf_map_t *map, nxt_uint_t n, void *data); -nxt_int_t nxt_conf_op_compile(nxt_mp_t *mp, nxt_conf_op_t **ops, - nxt_conf_value_t *root, nxt_str_t *path, nxt_conf_value_t *value); +nxt_conf_op_ret_t nxt_conf_op_compile(nxt_mp_t *mp, nxt_conf_op_t **ops, + nxt_conf_value_t *root, nxt_str_t *path, nxt_conf_value_t *value, + nxt_bool_t add); nxt_conf_value_t *nxt_conf_clone(nxt_mp_t *mp, nxt_conf_op_t *op, nxt_conf_value_t *value); diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 5653b9eb..bee82dd4 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -66,6 +66,12 @@ static nxt_int_t nxt_conf_vldt_match_patterns(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_match_pattern(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_match_patterns_sets(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_match_patterns_set(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_match_patterns_set_member( + nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_app_name(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_app(nxt_conf_validation_t *vldt, @@ -218,6 +224,21 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_match_members[] = { &nxt_conf_vldt_match_patterns, NULL }, + { nxt_string("arguments"), + NXT_CONF_VLDT_OBJECT | NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_match_patterns_sets, + NULL }, + + { nxt_string("headers"), + NXT_CONF_VLDT_OBJECT | NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_match_patterns_sets, + NULL }, + + { nxt_string("cookies"), + NXT_CONF_VLDT_OBJECT | NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_match_patterns_sets, + NULL }, + NXT_CONF_VLDT_END }; @@ -741,9 +762,15 @@ nxt_conf_vldt_match_pattern(nxt_conf_validation_t *vldt, nxt_str_t pattern; nxt_uint_t i, first, last; + enum { + sw_none, + sw_side, + sw_middle + } state; + if (nxt_conf_type(value) != NXT_CONF_STRING) { - return nxt_conf_vldt_error(vldt, - "The \"match\" patterns must be strings."); + return nxt_conf_vldt_error(vldt, "The \"match\" patterns for \"host\", " + "\"uri\", and \"method\" must be strings."); } nxt_conf_get_string(value, &pattern); @@ -754,17 +781,37 @@ nxt_conf_vldt_match_pattern(nxt_conf_validation_t *vldt, first = (pattern.start[0] == '!'); last = pattern.length - 1; + state = sw_none; for (i = first; i != pattern.length; i++) { + ch = pattern.start[i]; if (ch != '*') { continue; } - if (i != first && i != last) { - return nxt_conf_vldt_error(vldt, "The \"match\" patterns can only " - "contain \"*\" markers at the sides."); + switch (state) { + case sw_none: + state = (i == first) ? sw_side : sw_middle; + break; + + case sw_side: + if (i == last) { + if (last - first != 1) { + break; + } + + return nxt_conf_vldt_error(vldt, "The \"match\" pattern must " + "not contain double \"*\" markers."); + } + + /* Fall through. */ + + case sw_middle: + return nxt_conf_vldt_error(vldt, "The \"match\" patterns can " + "either contain \"*\" markers at " + "the sides or only one in the middle."); } } @@ -772,6 +819,49 @@ nxt_conf_vldt_match_pattern(nxt_conf_validation_t *vldt, } +static nxt_int_t +nxt_conf_vldt_match_patterns_sets(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data) +{ + if (nxt_conf_type(value) == NXT_CONF_ARRAY) { + return nxt_conf_vldt_array_iterator(vldt, value, + &nxt_conf_vldt_match_patterns_set); + } + + /* NXT_CONF_OBJECT */ + + return nxt_conf_vldt_match_patterns_set(vldt, value); +} + + +static nxt_int_t +nxt_conf_vldt_match_patterns_set(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value) +{ + if (nxt_conf_type(value) != NXT_CONF_OBJECT) { + return nxt_conf_vldt_error(vldt, "The \"match\" patterns for " + "\"arguments\", \"cookies\", and " + "\"headers\" must be objects."); + } + + return nxt_conf_vldt_object_iterator(vldt, value, + &nxt_conf_vldt_match_patterns_set_member); +} + + +static nxt_int_t +nxt_conf_vldt_match_patterns_set_member(nxt_conf_validation_t *vldt, + nxt_str_t *name, nxt_conf_value_t *value) +{ + if (name->length == 0) { + return nxt_conf_vldt_error(vldt, "The \"match\" pattern objects must " + "not contain empty member names."); + } + + return nxt_conf_vldt_match_patterns(vldt, value, NULL); +} + + #if (NXT_TLS) static nxt_int_t @@ -1268,6 +1358,7 @@ nxt_conf_vldt_java_classpath(nxt_conf_validation_t *vldt, nxt_conf_value_t *valu return NXT_OK; } + static nxt_int_t nxt_conf_vldt_java_option(nxt_conf_validation_t *vldt, nxt_conf_value_t *value) { diff --git a/src/nxt_controller.c b/src/nxt_controller.c index 29838bd9..49afbe46 100644 --- a/src/nxt_controller.c +++ b/src/nxt_controller.c @@ -184,6 +184,7 @@ nxt_controller_start(nxt_task_t *task, void *data) vldt.pool = nxt_mp_create(1024, 128, 256, 32); if (nxt_slow_path(vldt.pool == NULL)) { + nxt_mp_destroy(mp); return NXT_ERROR; } @@ -929,6 +930,7 @@ nxt_controller_process_config(nxt_task_t *task, nxt_controller_request_t *req, nxt_mp_t *mp; nxt_int_t rc; nxt_conn_t *c; + nxt_bool_t post; nxt_buf_mem_t *mbuf; nxt_conf_op_t *ops; nxt_conf_value_t *value; @@ -957,7 +959,18 @@ nxt_controller_process_config(nxt_task_t *task, nxt_controller_request_t *req, return; } - if (nxt_str_eq(&req->parser.method, "PUT", 3)) { + if (nxt_str_eq(&req->parser.method, "POST", 4)) { + if (path->length == 1) { + goto not_allowed; + } + + post = 1; + + } else { + post = 0; + } + + if (post || nxt_str_eq(&req->parser.method, "PUT", 3)) { if (!nxt_queue_is_empty(&nxt_controller_waiting_requests)) { nxt_queue_insert_tail(&nxt_controller_waiting_requests, &req->link); @@ -999,15 +1012,20 @@ nxt_controller_process_config(nxt_task_t *task, nxt_controller_request_t *req, if (path->length != 1) { rc = nxt_conf_op_compile(c->mem_pool, &ops, nxt_controller_conf.root, - path, value); + path, value, post); - if (rc != NXT_OK) { + if (rc != NXT_CONF_OP_OK) { nxt_mp_destroy(mp); - if (rc == NXT_DECLINED) { + switch (rc) { + case NXT_CONF_OP_NOT_FOUND: goto not_found; + + case NXT_CONF_OP_NOT_ALLOWED: + goto not_allowed; } + /* rc == NXT_CONF_OP_ERROR */ goto alloc_fail; } @@ -1079,13 +1097,14 @@ nxt_controller_process_config(nxt_task_t *task, nxt_controller_request_t *req, } else { rc = nxt_conf_op_compile(c->mem_pool, &ops, nxt_controller_conf.root, - path, NULL); + path, NULL, 0); if (rc != NXT_OK) { - if (rc == NXT_DECLINED) { + if (rc == NXT_CONF_OP_NOT_FOUND) { goto not_found; } + /* rc == NXT_CONF_OP_ERROR */ goto alloc_fail; } @@ -1144,8 +1163,10 @@ nxt_controller_process_config(nxt_task_t *task, nxt_controller_request_t *req, return; } +not_allowed: + resp.status = 405; - resp.title = (u_char *) "Invalid method."; + resp.title = (u_char *) "Method isn't allowed."; resp.offset = -1; nxt_controller_response(task, req, &resp); diff --git a/src/nxt_epoll_engine.c b/src/nxt_epoll_engine.c index 9f9c8f62..9cdaab9b 100644 --- a/src/nxt_epoll_engine.c +++ b/src/nxt_epoll_engine.c @@ -1059,7 +1059,7 @@ nxt_epoll_edge_conn_io_connect(nxt_task_t *task, void *obj, void *data) state = c->write_state; - switch (nxt_socket_connect(task, c->socket.fd, c->remote) ){ + switch (nxt_socket_connect(task, c->socket.fd, c->remote)) { case NXT_OK: c->socket.write_ready = 1; diff --git a/src/nxt_errno.h b/src/nxt_errno.h index b3d7105a..e3ce8349 100644 --- a/src/nxt_errno.h +++ b/src/nxt_errno.h @@ -45,6 +45,7 @@ typedef int nxt_err_t; #define NXT_EILSEQ EILSEQ #define NXT_ETIME ETIME #define NXT_ENOMOREFILES 0 +#define NXT_ENOBUFS ENOBUFS #if (NXT_HPUX) /* HP-UX uses EWOULDBLOCK instead of EAGAIN. */ diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index 07e3c7bc..3a822042 100644 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c @@ -35,6 +35,7 @@ static void nxt_h1p_request_body_read(nxt_task_t *task, nxt_http_request_t *r); static void nxt_h1p_conn_request_body_read(nxt_task_t *task, void *obj, void *data); static void nxt_h1p_request_local_addr(nxt_task_t *task, nxt_http_request_t *r); +static void nxt_h1p_request_tls(nxt_task_t *task, nxt_http_request_t *r); static void nxt_h1p_request_header_send(nxt_task_t *task, nxt_http_request_t *r); static void nxt_h1p_request_send(nxt_task_t *task, nxt_http_request_t *r, @@ -103,6 +104,13 @@ const nxt_http_proto_local_addr_t nxt_http_proto_local_addr[3] = { }; +const nxt_http_proto_tls_t nxt_http_proto_tls[3] = { + nxt_h1p_request_tls, + NULL, + NULL, +}; + + const nxt_http_proto_header_send_t nxt_http_proto_header_send[3] = { nxt_h1p_request_header_send, NULL, @@ -813,6 +821,15 @@ nxt_h1p_request_local_addr(nxt_task_t *task, nxt_http_request_t *r) } +static void +nxt_h1p_request_tls(nxt_task_t *task, nxt_http_request_t *r) +{ +#if (NXT_TLS) + r->tls = r->proto.h1->conn->u.tls; +#endif +} + + #define NXT_HTTP_LAST_SUCCESS \ (NXT_HTTP_OK + nxt_nitems(nxt_http_success) - 1) diff --git a/src/nxt_hpux_sendfile.c b/src/nxt_hpux_sendfile.c index 3c42c559..df200b64 100644 --- a/src/nxt_hpux_sendfile.c +++ b/src/nxt_hpux_sendfile.c @@ -13,7 +13,7 @@ ssize_t nxt_hpux_event_conn_io_sendfile(nxt_event_conn_t *c, nxt_buf_t *b, size_t limit); static ssize_t nxt_sys_sendfile(int s, int fd, off_t offset, size_t nbytes, - const struct iovec *hdtrl, int flags) + const struct iovec *hdtrl, int flags) { return -1; } @@ -23,7 +23,7 @@ static ssize_t nxt_sys_sendfile(int s, int fd, off_t offset, size_t nbytes, /* sendfile() is not declared if _XOPEN_SOURCE_EXTENDED is defined. */ sbsize_t sendfile(int s, int fd, off_t offset, bsize_t nbytes, - const struct iovec *hdtrl, int flags); + const struct iovec *hdtrl, int flags); #define nxt_sys_sendfile sendfile diff --git a/src/nxt_http.h b/src/nxt_http.h index 23c406d3..835cf66d 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -114,12 +114,15 @@ struct nxt_http_request_s { const nxt_http_request_state_t *state; nxt_str_t host; + nxt_str_t server_name; nxt_str_t target; nxt_str_t version; nxt_str_t *method; nxt_str_t *path; nxt_str_t *args; + nxt_array_t *arguments; /* of nxt_http_name_value_t */ + nxt_array_t *cookies; /* of nxt_http_name_value_t */ nxt_list_t *fields; nxt_http_field_t *content_type; nxt_http_field_t *content_length; @@ -130,6 +133,10 @@ struct nxt_http_request_s { nxt_sockaddr_t *remote; nxt_sockaddr_t *local; + void *tls; + + nxt_timer_t timer; + void *timer_data; nxt_buf_t *last; @@ -165,6 +172,7 @@ typedef void (*nxt_http_proto_body_read_t)(nxt_task_t *task, nxt_http_request_t *r); typedef void (*nxt_http_proto_local_addr_t)(nxt_task_t *task, nxt_http_request_t *r); +typedef void (*nxt_http_proto_tls_t)(nxt_task_t *task, nxt_http_request_t *r); typedef void (*nxt_http_proto_header_send_t)(nxt_task_t *task, nxt_http_request_t *r); typedef void (*nxt_http_proto_send_t)(nxt_task_t *task, nxt_http_request_t *r, @@ -186,7 +194,6 @@ nxt_http_request_t *nxt_http_request_create(nxt_task_t *task); void nxt_http_request_error(nxt_task_t *task, nxt_http_request_t *r, nxt_http_status_t status); void nxt_http_request_read_body(nxt_task_t *task, nxt_http_request_t *r); -void nxt_http_request_local_addr(nxt_task_t *task, nxt_http_request_t *r); void nxt_http_request_header_send(nxt_task_t *task, nxt_http_request_t *r); void nxt_http_request_send(nxt_task_t *task, nxt_http_request_t *r, nxt_buf_t *out); @@ -221,6 +228,7 @@ extern nxt_lvlhsh_t nxt_response_fields_hash; extern const nxt_http_proto_body_read_t nxt_http_proto_body_read[]; extern const nxt_http_proto_local_addr_t nxt_http_proto_local_addr[]; +extern const nxt_http_proto_tls_t nxt_http_proto_tls[]; extern const nxt_http_proto_header_send_t nxt_http_proto_header_send[]; extern const nxt_http_proto_send_t nxt_http_proto_send[]; extern const nxt_http_proto_body_bytes_sent_t nxt_http_proto_body_bytes_sent[]; diff --git a/src/nxt_http_parse.c b/src/nxt_http_parse.c index 34eaaaf9..05df245e 100644 --- a/src/nxt_http_parse.c +++ b/src/nxt_http_parse.c @@ -34,10 +34,6 @@ static nxt_int_t nxt_http_field_hash_collision(nxt_lvlhsh_query_t *lhq, #define NXT_HTTP_FIELD_LVLHSH_SHIFT 5 -#define NXT_HTTP_FIELD_HASH_INIT 159406 -#define nxt_http_field_hash_char(h, c) (((h) << 4) + (h) + (c)) -#define nxt_http_field_hash_end(h) (((h) >> 16) ^ (h)) - typedef enum { NXT_HTTP_TARGET_SPACE = 1, /* \s */ @@ -119,7 +115,7 @@ nxt_http_parse_request_init(nxt_http_request_parse_t *rp, nxt_mp_t *mp) rp->mem_pool = mp; rp->fields = nxt_list_create(mp, 8, sizeof(nxt_http_field_t)); - if (nxt_slow_path(rp->fields == NULL)){ + if (nxt_slow_path(rp->fields == NULL)) { return NXT_ERROR; } diff --git a/src/nxt_http_parse.h b/src/nxt_http_parse.h index 0326d45c..6c629936 100644 --- a/src/nxt_http_parse.h +++ b/src/nxt_http_parse.h @@ -93,6 +93,11 @@ struct nxt_http_field_s { }; +#define NXT_HTTP_FIELD_HASH_INIT 159406U +#define nxt_http_field_hash_char(h, c) (((h) << 4) + (h) + (c)) +#define nxt_http_field_hash_end(h) (((h) >> 16) ^ (h)) + + nxt_int_t nxt_http_parse_request_init(nxt_http_request_parse_t *rp, nxt_mp_t *mp); nxt_int_t nxt_http_parse_request(nxt_http_request_parse_t *rp, diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index 724b0808..1265c186 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -11,6 +11,8 @@ static nxt_int_t nxt_http_validate_host(nxt_str_t *host, nxt_mp_t *mp); static void nxt_http_request_start(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_pass(nxt_task_t *task, void *obj, void *data); +static void nxt_http_request_proto_info(nxt_task_t *task, + nxt_http_request_t *r); static void nxt_http_request_mem_buf_completion(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_done(nxt_task_t *task, void *obj, void *data); @@ -293,26 +295,23 @@ nxt_http_request_pass(nxt_task_t *task, void *obj, void *data) pass = r->conf->socket_conf->pass; - if (nxt_slow_path(pass == NULL)) { - goto fail; - } + if (nxt_fast_path(pass != NULL)) { - for ( ;; ) { - nxt_debug(task, "http request route: %V", &pass->name); + do { + nxt_debug(task, "http request route: %V", &pass->name); - pass = pass->handler(task, r, pass); - if (pass == NULL) { - break; - } + pass = pass->handler(task, r, pass); - if (nxt_slow_path(r->pass_count++ == 255)) { - goto fail; - } - } + if (pass == NULL) { + return; + } - return; + if (pass == NXT_HTTP_PASS_ERROR) { + break; + } -fail: + } while (r->pass_count++ < 255); + } nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); } @@ -322,117 +321,52 @@ nxt_http_pass_t * nxt_http_request_application(nxt_task_t *task, nxt_http_request_t *r, nxt_http_pass_t *pass) { - nxt_int_t ret; - nxt_event_engine_t *engine; - nxt_app_parse_ctx_t *ar; + nxt_event_engine_t *engine; nxt_debug(task, "http request application"); - ar = nxt_mp_zget(r->mem_pool, sizeof(nxt_app_parse_ctx_t)); - if (nxt_slow_path(ar == NULL)) { - nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); - return NULL; - } - - ar->request = r; - ar->mem_pool = r->mem_pool; nxt_mp_retain(r->mem_pool); - // STUB engine = task->thread->engine; - ar->timer.task = &engine->task; - ar->timer.work_queue = &engine->fast_work_queue; - ar->timer.log = engine->task.log; - ar->timer.bias = NXT_TIMER_DEFAULT_BIAS; - - ar->r.remote.start = nxt_sockaddr_address(r->remote); - ar->r.remote.length = r->remote->address_length; + r->timer.task = &engine->task; + r->timer.work_queue = &engine->fast_work_queue; + r->timer.log = engine->task.log; + r->timer.bias = NXT_TIMER_DEFAULT_BIAS; /* * TODO: need an application flag to get local address * required by "SERVER_ADDR" in Pyhton and PHP. Not used in Go. */ - nxt_http_request_local_addr(task, r); - - if (nxt_fast_path(r->local != NULL)) { - ar->r.local.start = nxt_sockaddr_address(r->local); - ar->r.local.length = r->local->address_length; - } - - ar->r.header.fields = r->fields; - ar->r.header.done = 1; - ar->r.header.version = r->version; - - if (r->method != NULL) { - ar->r.header.method = *r->method; - } + nxt_http_request_proto_info(task, r); if (r->host.length != 0) { - ar->r.header.server_name = r->host; + r->server_name = r->host; } else { - nxt_str_set(&ar->r.header.server_name, "localhost"); + nxt_str_set(&r->server_name, "localhost"); } - ar->r.header.target = r->target; - - if (r->path != NULL) { - ar->r.header.path = *r->path; - } - - if (r->args != NULL) { - ar->r.header.query = *r->args; - } - - if (r->content_type != NULL) { - ar->r.header.content_type.length = r->content_type->value_length; - ar->r.header.content_type.start = r->content_type->value; - } - - if (r->content_length != NULL) { - ar->r.header.content_length.length = r->content_length->value_length; - ar->r.header.content_length.start = r->content_length->value; - } - - if (r->cookie != NULL) { - ar->r.header.cookie.length = r->cookie->value_length; - ar->r.header.cookie.start = r->cookie->value; - } - - if (r->body != NULL) { - ar->r.body.buf = r->body; - ar->r.body.preread_size = r->content_length_n; - ar->r.header.parsed_content_length = r->content_length_n; - } - - ar->r.body.done = 1; - - ret = nxt_http_parse_request_init(&ar->resp_parser, r->mem_pool); - if (nxt_slow_path(ret != NXT_OK)) { - nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); - return NULL; - } - - nxt_router_process_http_request(task, ar, pass->u.application); + nxt_router_process_http_request(task, r, pass->u.application); return NULL; } -void -nxt_http_request_read_body(nxt_task_t *task, nxt_http_request_t *r) +static void +nxt_http_request_proto_info(nxt_task_t *task, nxt_http_request_t *r) { if (r->proto.any != NULL) { - nxt_http_proto_body_read[r->protocol](task, r); + nxt_http_proto_local_addr[r->protocol](task, r); + nxt_http_proto_tls[r->protocol](task, r); } } void -nxt_http_request_local_addr(nxt_task_t *task, nxt_http_request_t *r) +nxt_http_request_read_body(nxt_task_t *task, nxt_http_request_t *r) { if (r->proto.any != NULL) { - nxt_http_proto_local_addr[r->protocol](task, r); + nxt_http_proto_body_read[r->protocol](task, r); } } @@ -589,6 +523,8 @@ nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data) nxt_debug(task, "http request error handler"); + r->error = 1; + if (proto.any != NULL) { nxt_http_proto_discard[r->protocol](task, r, nxt_http_buf_last(r)); } diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index 133c39ab..d6749acb 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -9,9 +9,9 @@ typedef enum { - NXT_HTTP_ROUTE_STRING = 0, + NXT_HTTP_ROUTE_TABLE = 0, + NXT_HTTP_ROUTE_STRING, NXT_HTTP_ROUTE_STRING_PTR, - NXT_HTTP_ROUTE_FIELD, NXT_HTTP_ROUTE_HEADER, NXT_HTTP_ROUTE_ARGUMENT, NXT_HTTP_ROUTE_COOKIE, @@ -21,6 +21,7 @@ typedef enum { typedef enum { NXT_HTTP_ROUTE_PATTERN_EXACT = 0, NXT_HTTP_ROUTE_PATTERN_BEGIN, + NXT_HTTP_ROUTE_PATTERN_MIDDLE, NXT_HTTP_ROUTE_PATTERN_END, NXT_HTTP_ROUTE_PATTERN_SUBSTRING, } nxt_http_route_pattern_type_t; @@ -37,11 +38,17 @@ typedef struct { nxt_conf_value_t *host; nxt_conf_value_t *uri; nxt_conf_value_t *method; + nxt_conf_value_t *headers; + nxt_conf_value_t *arguments; + nxt_conf_value_t *cookies; } nxt_http_route_match_conf_t; typedef struct { - nxt_str_t test; + u_char *start1; + u_char *start2; + uint32_t length1; + uint32_t length2; uint32_t min_length; nxt_http_route_pattern_type_t type:8; @@ -52,17 +59,66 @@ typedef struct { typedef struct { - uintptr_t offset; - uint32_t items; + uint16_t hash; + uint16_t name_length; + uint32_t value_length; + u_char *name; + u_char *value; +} nxt_http_name_value_t; + + +typedef struct { + uint16_t hash; + uint16_t name_length; + uint32_t value_length; + u_char *name; + u_char *value; +} nxt_http_cookie_t; + + +typedef struct { + /* The object must be the first field. */ nxt_http_route_object_t object:8; + uint32_t items; + + union { + uintptr_t offset; + + struct { + u_char *start; + uint16_t hash; + uint16_t length; + } name; + } u; + nxt_http_route_pattern_t pattern[0]; } nxt_http_route_rule_t; typedef struct { uint32_t items; - nxt_http_pass_t pass; nxt_http_route_rule_t *rule[0]; +} nxt_http_route_ruleset_t; + + +typedef struct { + /* The object must be the first field. */ + nxt_http_route_object_t object:8; + uint32_t items; + nxt_http_route_ruleset_t *ruleset[0]; +} nxt_http_route_table_t; + + +typedef union { + nxt_http_route_rule_t *rule; + nxt_http_route_table_t *table; +} nxt_http_route_test_t; + + +typedef struct { + uint32_t items; + nxt_http_pass_t pass; + nxt_http_route_test_t test[0]; } nxt_http_route_match_t; @@ -79,17 +135,39 @@ struct nxt_http_routes_s { }; +#define NJS_COOKIE_HASH \ + (nxt_http_field_hash_end( \ + nxt_http_field_hash_char( \ + nxt_http_field_hash_char( \ + nxt_http_field_hash_char( \ + nxt_http_field_hash_char( \ + nxt_http_field_hash_char( \ + nxt_http_field_hash_char(NXT_HTTP_FIELD_HASH_INIT, \ + 'c'), 'o'), 'o'), 'k'), 'i'), 'e')) & 0xFFFF) + + 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_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); +static nxt_http_route_ruleset_t *nxt_http_route_ruleset_create(nxt_task_t *task, + nxt_mp_t *mp, nxt_conf_value_t *ruleset_cv, nxt_http_route_object_t object, + nxt_bool_t case_sensitive); +static nxt_http_route_rule_t *nxt_http_route_rule_name_create(nxt_task_t *task, + nxt_mp_t *mp, nxt_conf_value_t *rule_cv, nxt_str_t *name, + nxt_bool_t case_sensitive); static nxt_http_route_rule_t *nxt_http_route_rule_create(nxt_task_t *task, - nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, - nxt_bool_t case_sensitive, nxt_http_route_pattern_case_t pattern_case); + nxt_mp_t *mp, nxt_conf_value_t *cv, nxt_bool_t case_sensitive, + nxt_http_route_pattern_case_t pattern_case); static int nxt_http_pattern_compare(const void *one, const void *two); static nxt_int_t nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv, nxt_http_route_pattern_t *pattern, nxt_http_route_pattern_case_t pattern_case); +static u_char *nxt_http_route_pattern_copy(nxt_mp_t *mp, nxt_str_t *test, + nxt_http_route_pattern_case_t pattern_case); static void nxt_http_route_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_route_t *route); @@ -103,10 +181,37 @@ static nxt_http_pass_t *nxt_http_route_pass(nxt_task_t *task, nxt_http_request_t *r, nxt_http_pass_t *start); static nxt_http_pass_t *nxt_http_route_match(nxt_http_request_t *r, nxt_http_route_match_t *match); -static nxt_bool_t nxt_http_route_rule(nxt_http_request_t *r, +static nxt_int_t nxt_http_route_table(nxt_http_request_t *r, + nxt_http_route_table_t *table); +static nxt_int_t nxt_http_route_ruleset(nxt_http_request_t *r, + nxt_http_route_ruleset_t *ruleset); +static nxt_int_t nxt_http_route_rule(nxt_http_request_t *r, + nxt_http_route_rule_t *rule); +static nxt_int_t nxt_http_route_header(nxt_http_request_t *r, nxt_http_route_rule_t *rule); -static nxt_bool_t nxt_http_route_pattern(nxt_http_request_t *r, +static nxt_int_t nxt_http_route_arguments(nxt_http_request_t *r, + nxt_http_route_rule_t *rule); +static nxt_array_t *nxt_http_route_arguments_parse(nxt_http_request_t *r); +static nxt_http_name_value_t *nxt_http_route_argument(nxt_array_t *array, + u_char *name, size_t name_length, uint32_t hash, u_char *start, + u_char *end); +static nxt_int_t nxt_http_route_test_argument(nxt_http_request_t *r, + nxt_http_route_rule_t *rule, nxt_array_t *array); +static nxt_int_t nxt_http_route_cookies(nxt_http_request_t *r, + nxt_http_route_rule_t *rule); +static nxt_array_t *nxt_http_route_cookies_parse(nxt_http_request_t *r); +static nxt_int_t nxt_http_route_cookie_parse(nxt_array_t *cookies, + u_char *start, u_char *end); +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, + size_t length, nxt_bool_t case_sensitive); nxt_http_routes_t * @@ -188,6 +293,24 @@ static nxt_conf_map_t nxt_http_route_match_conf[] = { NXT_CONF_MAP_PTR, offsetof(nxt_http_route_match_conf_t, method), }, + + { + nxt_string("headers"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_match_conf_t, headers), + }, + + { + nxt_string("arguments"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_match_conf_t, arguments), + }, + + { + nxt_string("cookies"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_match_conf_t, cookies), + }, }; @@ -233,10 +356,13 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, { size_t size; uint32_t n; + nxt_mp_t *mp; nxt_int_t ret; nxt_str_t pass, *string; nxt_conf_value_t *match_conf, *pass_conf; - nxt_http_route_rule_t *rule, **p; + nxt_http_route_test_t *test; + nxt_http_route_rule_t *rule; + nxt_http_route_table_t *table; nxt_http_route_match_t *match; nxt_http_route_match_conf_t mtcf; @@ -255,7 +381,9 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, 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 *); - match = nxt_mp_alloc(tmcf->router_conf->mem_pool, size); + mp = tmcf->router_conf->mem_pool; + + match = nxt_mp_alloc(mp, size); if (nxt_slow_path(match == NULL)) { return NULL; } @@ -264,7 +392,7 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, match->pass.handler = NULL; match->items = n; - string = nxt_str_dup(tmcf->router_conf->mem_pool, &match->pass.name, &pass); + string = nxt_str_dup(mp, &match->pass.name, &pass); if (nxt_slow_path(string == NULL)) { return NULL; } @@ -282,64 +410,232 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, return NULL; } - p = &match->rule[0]; + test = &match->test[0]; if (mtcf.host != NULL) { - rule = nxt_http_route_rule_create(task, tmcf, mtcf.host, 1, + rule = nxt_http_route_rule_create(task, mp, mtcf.host, 1, NXT_HTTP_ROUTE_PATTERN_LOWCASE); if (rule == NULL) { return NULL; } - rule->offset = offsetof(nxt_http_request_t, host); + rule->u.offset = offsetof(nxt_http_request_t, host); rule->object = NXT_HTTP_ROUTE_STRING; - *p++ = rule; + test->rule = rule; + test++; } if (mtcf.uri != NULL) { - rule = nxt_http_route_rule_create(task, tmcf, mtcf.uri, 1, + rule = nxt_http_route_rule_create(task, mp, mtcf.uri, 1, NXT_HTTP_ROUTE_PATTERN_NOCASE); if (rule == NULL) { return NULL; } - rule->offset = offsetof(nxt_http_request_t, path); + rule->u.offset = offsetof(nxt_http_request_t, path); rule->object = NXT_HTTP_ROUTE_STRING_PTR; - *p++ = rule; + test->rule = rule; + test++; } if (mtcf.method != NULL) { - rule = nxt_http_route_rule_create(task, tmcf, mtcf.method, 1, + rule = nxt_http_route_rule_create(task, mp, mtcf.method, 1, NXT_HTTP_ROUTE_PATTERN_UPCASE); if (rule == NULL) { return NULL; } - rule->offset = offsetof(nxt_http_request_t, method); + rule->u.offset = offsetof(nxt_http_request_t, method); rule->object = NXT_HTTP_ROUTE_STRING_PTR; - *p++ = rule; + test->rule = rule; + test++; + } + + if (mtcf.headers != NULL) { + table = nxt_http_route_table_create(task, mp, mtcf.headers, + NXT_HTTP_ROUTE_HEADER, 0); + if (table == NULL) { + return NULL; + } + + test->table = table; + test++; + } + + if (mtcf.arguments != NULL) { + table = nxt_http_route_table_create(task, mp, mtcf.arguments, + NXT_HTTP_ROUTE_ARGUMENT, 1); + if (table == NULL) { + return NULL; + } + + test->table = table; + test++; + } + + if (mtcf.cookies != NULL) { + table = nxt_http_route_table_create(task, mp, mtcf.cookies, + NXT_HTTP_ROUTE_COOKIE, 0); + if (table == NULL) { + return NULL; + } + + test->table = table; + test++; } return match; } +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) +{ + size_t size; + uint32_t i, n; + nxt_bool_t array; + nxt_conf_value_t *ruleset_cv; + nxt_http_route_table_t *table; + nxt_http_route_ruleset_t *ruleset; + + array = (nxt_conf_type(table_cv) == NXT_CONF_ARRAY); + n = array ? nxt_conf_array_elements_count(table_cv) : 1; + size = sizeof(nxt_http_route_table_t) + + n * sizeof(nxt_http_route_ruleset_t *); + + table = nxt_mp_alloc(mp, size); + if (nxt_slow_path(table == NULL)) { + return NULL; + } + + table->items = n; + table->object = NXT_HTTP_ROUTE_TABLE; + + if (!array) { + ruleset = nxt_http_route_ruleset_create(task, mp, table_cv, + object, case_sensitive); + if (nxt_slow_path(ruleset == NULL)) { + return NULL; + } + + table->ruleset[0] = ruleset; + + return table; + } + + for (i = 0; i < n; i++) { + ruleset_cv = nxt_conf_get_array_element(table_cv, i); + + ruleset = nxt_http_route_ruleset_create(task, mp, ruleset_cv, + object, case_sensitive); + if (nxt_slow_path(ruleset == NULL)) { + return NULL; + } + + table->ruleset[i] = ruleset; + } + + return table; +} + + +static nxt_http_route_ruleset_t * +nxt_http_route_ruleset_create(nxt_task_t *task, nxt_mp_t *mp, + nxt_conf_value_t *ruleset_cv, nxt_http_route_object_t object, + nxt_bool_t case_sensitive) +{ + size_t size; + uint32_t i, n, next; + nxt_str_t name; + nxt_conf_value_t *rule_cv; + nxt_http_route_rule_t *rule; + nxt_http_route_ruleset_t *ruleset; + + n = nxt_conf_object_members_count(ruleset_cv); + size = sizeof(nxt_http_route_ruleset_t) + + n * sizeof(nxt_http_route_rule_t *); + + ruleset = nxt_mp_alloc(mp, size); + if (nxt_slow_path(ruleset == NULL)) { + return NULL; + } + + ruleset->items = n; + + next = 0; + + for (i = 0; i < n; i++) { + rule_cv = nxt_conf_next_object_member(ruleset_cv, &name, &next); + + rule = nxt_http_route_rule_name_create(task, mp, rule_cv, &name, + case_sensitive); + if (nxt_slow_path(rule == NULL)) { + return NULL; + } + + rule->object = object; + ruleset->rule[i] = rule; + } + + return ruleset; +} + + static nxt_http_route_rule_t * -nxt_http_route_rule_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, +nxt_http_route_rule_name_create(nxt_task_t *task, nxt_mp_t *mp, + nxt_conf_value_t *rule_cv, nxt_str_t *name, nxt_bool_t case_sensitive) +{ + u_char c, *p; + uint32_t hash; + nxt_uint_t i; + nxt_http_route_rule_t *rule; + + rule = nxt_http_route_rule_create(task, mp, rule_cv, case_sensitive, + NXT_HTTP_ROUTE_PATTERN_NOCASE); + if (nxt_slow_path(rule == NULL)) { + return NULL; + } + + rule->u.name.length = name->length; + + p = nxt_mp_nget(mp, name->length); + if (nxt_slow_path(p == NULL)) { + return NULL; + } + + rule->u.name.start = p; + + hash = NXT_HTTP_FIELD_HASH_INIT; + + for (i = 0; i < name->length; i++) { + c = name->start[i]; + *p++ = c; + + c = nxt_lowcase(c); + hash = nxt_http_field_hash_char(hash, c); + } + + rule->u.name.hash = nxt_http_field_hash_end(hash) & 0xFFFF; + + return rule; +} + + +static nxt_http_route_rule_t * +nxt_http_route_rule_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv, nxt_bool_t case_sensitive, nxt_http_route_pattern_case_t pattern_case) { size_t size; uint32_t i, n; - nxt_mp_t *mp; nxt_int_t ret; nxt_bool_t string; nxt_conf_value_t *value; nxt_http_route_rule_t *rule; nxt_http_route_pattern_t *pattern; - mp = tmcf->router_conf->mem_pool; - string = (nxt_conf_type(cv) != NXT_CONF_ARRAY); n = string ? 1 : nxt_conf_array_elements_count(cv); size = sizeof(nxt_http_route_rule_t) + n * sizeof(nxt_http_route_pattern_t); @@ -407,8 +703,12 @@ nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, { u_char *start; nxt_str_t test; + nxt_uint_t n, length; nxt_http_route_pattern_type_t type; + /* Suppress warning about uninitialized variable. */ + length = 0; + type = NXT_HTTP_ROUTE_PATTERN_EXACT; nxt_conf_get_string(cv, &test); @@ -448,57 +748,98 @@ nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, } else if (test.start[test.length - 1] == '*') { test.length--; type = NXT_HTTP_ROUTE_PATTERN_BEGIN; + + } else { + length = test.length - 1; + + for (n = 1; n < length; n++) { + if (test.start[n] == '*') { + test.length = n; + type = NXT_HTTP_ROUTE_PATTERN_MIDDLE; + break; + } + } } } } pattern->type = type; pattern->min_length = test.length; - pattern->test.length = test.length; + pattern->length1 = test.length; - start = nxt_mp_nget(mp, test.length); + start = nxt_http_route_pattern_copy(mp, &test, pattern_case); if (nxt_slow_path(start == NULL)) { return NXT_ERROR; } - pattern->test.start = start; + pattern->start1 = start; + + if (type == NXT_HTTP_ROUTE_PATTERN_MIDDLE) { + length -= test.length; + pattern->length2 = length; + pattern->min_length += length; + + test.start = &test.start[test.length + 1]; + test.length = length; + + start = nxt_http_route_pattern_copy(mp, &test, pattern_case); + if (nxt_slow_path(start == NULL)) { + return NXT_ERROR; + } + + pattern->start2 = start; + } + + return NXT_OK; +} + + +static u_char * +nxt_http_route_pattern_copy(nxt_mp_t *mp, nxt_str_t *test, + nxt_http_route_pattern_case_t pattern_case) +{ + u_char *start; + + start = nxt_mp_nget(mp, test->length); + if (nxt_slow_path(start == NULL)) { + return start; + } switch (pattern_case) { case NXT_HTTP_ROUTE_PATTERN_UPCASE: - nxt_memcpy_upcase(start, test.start, test.length); + nxt_memcpy_upcase(start, test->start, test->length); break; case NXT_HTTP_ROUTE_PATTERN_LOWCASE: - nxt_memcpy_lowcase(start, test.start, test.length); + nxt_memcpy_lowcase(start, test->start, test->length); break; case NXT_HTTP_ROUTE_PATTERN_NOCASE: - nxt_memcpy(start, test.start, test.length); + nxt_memcpy(start, test->start, test->length); break; } - return NXT_OK; + return start; } void nxt_http_routes_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) { - nxt_uint_t items; - nxt_http_route_t **route; + nxt_http_route_t **route, **end; nxt_http_routes_t *routes; routes = tmcf->router_conf->routes; + if (routes != NULL) { - items = routes->items; route = &routes->route[0]; + end = route + routes->items; - while (items != 0) { + while (route < end) { nxt_http_route_resolve(task, tmcf, *route); route++; - items--; } } } @@ -508,17 +849,15 @@ static void nxt_http_route_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_route_t *route) { - nxt_uint_t items; - nxt_http_route_match_t **match; + nxt_http_route_match_t **match, **end; - items = route->items; match = &route->match[0]; + end = match + route->items; - while (items != 0) { + while (match < end) { nxt_http_pass_resolve(task, tmcf, &(*match)->pass); match++; - items--; } } @@ -561,21 +900,18 @@ nxt_http_pass_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, static nxt_http_route_t * nxt_http_route_find(nxt_http_routes_t *routes, nxt_str_t *name) { - nxt_uint_t items; - nxt_http_route_t **route; + nxt_http_route_t **route, **end; - items = routes->items; route = &routes->route[0]; + end = route + routes->items; - do { + while (route < end) { if (nxt_strstr_eq(&(*route)->name, name)) { return *route; } route++; - items--; - - } while (items != 0); + } return NULL; } @@ -627,20 +963,17 @@ nxt_http_pass_application(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, void nxt_http_routes_cleanup(nxt_task_t *task, nxt_http_routes_t *routes) { - nxt_uint_t items; - nxt_http_route_t **route; + nxt_http_route_t **route, **end; if (routes != NULL) { - items = routes->items; route = &routes->route[0]; + end = route + routes->items; - do { + while (route < end) { nxt_http_route_cleanup(task, *route); route++; - items--; - - } while (items != 0); + } } } @@ -648,19 +981,16 @@ nxt_http_routes_cleanup(nxt_task_t *task, nxt_http_routes_t *routes) static void nxt_http_route_cleanup(nxt_task_t *task, nxt_http_route_t *route) { - nxt_uint_t items; - nxt_http_route_match_t **match; + nxt_http_route_match_t **match, **end; - items = route->items; match = &route->match[0]; + end = match + route->items; - do { + while (match < end) { nxt_http_pass_cleanup(task, &(*match)->pass); match++; - items--; - - } while (items != 0); + } } @@ -677,23 +1007,21 @@ static nxt_http_pass_t * nxt_http_route_pass(nxt_task_t *task, nxt_http_request_t *r, nxt_http_pass_t *start) { - nxt_uint_t items; nxt_http_pass_t *pass; nxt_http_route_t *route; - nxt_http_route_match_t **match; + nxt_http_route_match_t **match, **end; route = start->u.route; - items = route->items; match = &route->match[0]; + end = match + route->items; - while (items != 0) { + while (match < end) { pass = nxt_http_route_match(r, *match); if (pass != NULL) { return pass; } match++; - items--; } nxt_http_request_error(task, r, NXT_HTTP_NOT_FOUND); @@ -705,87 +1033,488 @@ nxt_http_route_pass(nxt_task_t *task, nxt_http_request_t *r, static nxt_http_pass_t * nxt_http_route_match(nxt_http_request_t *r, nxt_http_route_match_t *match) { - nxt_uint_t items; - nxt_http_route_rule_t **rule; + nxt_int_t ret; + nxt_http_route_test_t *test, *end; - rule = &match->rule[0]; - items = match->items; + test = &match->test[0]; + end = test + match->items; - while (items != 0) { - if (!nxt_http_route_rule(r, *rule)) { - return NULL; + while (test < end) { + if (test->rule->object != NXT_HTTP_ROUTE_TABLE) { + ret = nxt_http_route_rule(r, test->rule); + + } else { + ret = nxt_http_route_table(r, test->table); } - rule++; - items--; + if (ret <= 0) { + /* 0 => NULL, -1 => NXT_HTTP_PASS_ERROR. */ + return (nxt_http_pass_t *) (intptr_t) ret; + } + + test++; } return &match->pass; } -static nxt_bool_t +static nxt_int_t +nxt_http_route_table(nxt_http_request_t *r, nxt_http_route_table_t *table) +{ + nxt_int_t ret; + nxt_http_route_ruleset_t **ruleset, **end; + + ret = 1; + ruleset = &table->ruleset[0]; + end = ruleset + table->items; + + while (ruleset < end) { + ret = nxt_http_route_ruleset(r, *ruleset); + + if (ret != 0) { + return ret; + } + + ruleset++; + } + + return ret; +} + + +static nxt_int_t +nxt_http_route_ruleset(nxt_http_request_t *r, nxt_http_route_ruleset_t *ruleset) +{ + nxt_int_t ret; + nxt_http_route_rule_t **rule, **end; + + rule = &ruleset->rule[0]; + end = rule + ruleset->items; + + while (rule < end) { + ret = nxt_http_route_rule(r, *rule); + + if (ret <= 0) { + return ret; + } + + rule++; + } + + return 1; +} + + +static nxt_int_t nxt_http_route_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule) { - void *p, **pp; - u_char *start; - size_t length; - nxt_str_t *s; - nxt_uint_t items; - nxt_bool_t ret; - nxt_http_field_t *f; - nxt_http_route_pattern_t *pattern; + void *p, **pp; + u_char *start; + size_t length; + nxt_str_t *s; + + switch (rule->object) { + + case NXT_HTTP_ROUTE_HEADER: + return nxt_http_route_header(r, rule); - p = nxt_pointer_to(r, rule->offset); + case NXT_HTTP_ROUTE_ARGUMENT: + return nxt_http_route_arguments(r, rule); + + case NXT_HTTP_ROUTE_COOKIE: + return nxt_http_route_cookies(r, rule); + + default: + break; + } + + p = nxt_pointer_to(r, rule->u.offset); if (rule->object == NXT_HTTP_ROUTE_STRING) { s = p; - length = s->length; - start = s->start; } else { + /* NXT_HTTP_ROUTE_STRING_PTR */ pp = p; - p = *pp; + s = *pp; - if (p == NULL) { + if (s == NULL) { return 0; } + } - switch (rule->object) { + length = s->length; + start = s->start; - case NXT_HTTP_ROUTE_STRING_PTR: - s = p; - length = s->length; - start = s->start; - break; + return nxt_http_route_test_rule(r, rule, start, length); +} - case NXT_HTTP_ROUTE_FIELD: - f = p; - length = f->value_length; - start = f->value; - break; - case NXT_HTTP_ROUTE_HEADER: - return 0; +static nxt_int_t +nxt_http_route_header(nxt_http_request_t *r, nxt_http_route_rule_t *rule) +{ + nxt_int_t ret; + nxt_http_field_t *f; - case NXT_HTTP_ROUTE_ARGUMENT: - return 0; + ret = 0; - case NXT_HTTP_ROUTE_COOKIE: - return 0; + nxt_list_each(f, r->fields) { - default: - nxt_unreachable(); - return 0; + if (rule->u.name.hash != f->hash + || rule->u.name.length != f->name_length + || nxt_strncasecmp(rule->u.name.start, f->name, f->name_length) + != 0) + { + continue; + } + + ret = nxt_http_route_test_rule(r, rule, f->value, f->value_length); + + if (ret == 0) { + return ret; + } + + } nxt_list_loop; + + return ret; +} + + +static nxt_int_t +nxt_http_route_arguments(nxt_http_request_t *r, nxt_http_route_rule_t *rule) +{ + nxt_array_t *arguments; + + if (r->args == NULL) { + return 0; + } + + arguments = nxt_http_route_arguments_parse(r); + if (nxt_slow_path(arguments == NULL)) { + return -1; + } + + return nxt_http_route_test_argument(r, rule, arguments); +} + + +static nxt_array_t * +nxt_http_route_arguments_parse(nxt_http_request_t *r) +{ + size_t name_length; + u_char c, *p, *start, *end, *name; + uint32_t hash; + nxt_bool_t valid; + nxt_array_t *args; + nxt_http_name_value_t *nv; + + if (r->arguments != NULL) { + return r->arguments; + } + + args = nxt_array_create(r->mem_pool, 2, sizeof(nxt_http_name_value_t)); + if (nxt_slow_path(args == NULL)) { + return NULL; + } + + hash = NXT_HTTP_FIELD_HASH_INIT; + valid = 1; + name = NULL; + name_length = 0; + + start = r->args->start; + end = start + r->args->length; + + for (p = start; p < end; p++) { + c = *p; + + if (c == '=') { + name_length = p - start; + name = start; + start = p + 1; + valid = (name_length != 0); + + } else if (c == '&') { + if (valid) { + nv = nxt_http_route_argument(args, name, name_length, hash, + start, p); + if (nxt_slow_path(nv == NULL)) { + return NULL; + } + } + + hash = NXT_HTTP_FIELD_HASH_INIT; + valid = 1; + name = NULL; + start = p + 1; + + } else if (name == NULL) { + hash = nxt_http_field_hash_char(hash, c); + } + } + + if (valid) { + nv = nxt_http_route_argument(args, name, name_length, hash, start, p); + if (nxt_slow_path(nv == NULL)) { + return NULL; + } + } + + r->arguments = args; + + return args; +} + + +static nxt_http_name_value_t * +nxt_http_route_argument(nxt_array_t *array, u_char *name, size_t name_length, + uint32_t hash, u_char *start, u_char *end) +{ + size_t length; + nxt_http_name_value_t *nv; + + nv = nxt_array_add(array); + if (nxt_slow_path(nv == NULL)) { + return NULL; + } + + nv->hash = nxt_http_field_hash_end(hash) & 0xFFFF; + + length = end - start; + + if (name == NULL) { + name_length = length; + name = start; + length = 0; + } + + nv->name_length = name_length; + nv->value_length = length; + nv->name = name; + nv->value = start; + + return nv; +} + + +static nxt_int_t +nxt_http_route_test_argument(nxt_http_request_t *r, + nxt_http_route_rule_t *rule, nxt_array_t *array) +{ + nxt_bool_t ret; + nxt_http_name_value_t *nv, *end; + + ret = 0; + + nv = array->elts; + end = nv + array->nelts; + + while (nv < end) { + + if (rule->u.name.hash == nv->hash + && rule->u.name.length == nv->name_length + && nxt_memcmp(rule->u.name.start, nv->name, nv->name_length) == 0) + { + ret = nxt_http_route_test_rule(r, rule, nv->value, + nv->value_length); + if (ret == 0) { + break; + } + } + + nv++; + } + + return ret; +} + + +static nxt_int_t +nxt_http_route_cookies(nxt_http_request_t *r, nxt_http_route_rule_t *rule) +{ + nxt_array_t *cookies; + + cookies = nxt_http_route_cookies_parse(r); + if (nxt_slow_path(cookies == NULL)) { + return -1; + } + + return nxt_http_route_test_cookie(r, rule, cookies); +} + + +static nxt_array_t * +nxt_http_route_cookies_parse(nxt_http_request_t *r) +{ + nxt_int_t ret; + nxt_array_t *cookies; + nxt_http_field_t *f; + + if (r->cookies != NULL) { + return r->cookies; + } + + cookies = nxt_array_create(r->mem_pool, 2, sizeof(nxt_http_name_value_t)); + if (nxt_slow_path(cookies == NULL)) { + return NULL; + } + + nxt_list_each(f, r->fields) { + + if (f->hash != NJS_COOKIE_HASH + || f->name_length != 6 + || nxt_strncasecmp(f->name, (u_char *) "Cookie", 6) != 0) + { + continue; + } + + ret = nxt_http_route_cookie_parse(cookies, f->value, + f->value + f->value_length); + if (ret != NXT_OK) { + return NULL; + } + + } nxt_list_loop; + + r->cookies = cookies; + + return cookies; +} + + +static nxt_int_t +nxt_http_route_cookie_parse(nxt_array_t *cookies, u_char *start, u_char *end) +{ + size_t name_length; + u_char c, *p, *name; + nxt_http_name_value_t *nv; + + name = NULL; + name_length = 0; + + for (p = start; p < end; p++) { + c = *p; + + if (c == '=') { + while (start[0] == ' ') { start++; } + + name_length = p - start; + + if (name_length != 0) { + name = start; + } + + start = p + 1; + + } else if (c == ';') { + if (name != NULL) { + nv = nxt_http_route_cookie(cookies, name, name_length, + start, p); + if (nxt_slow_path(nv == NULL)) { + return NXT_ERROR; + } + } + + name = NULL; + start = p + 1; + } + } + + if (name != NULL) { + nv = nxt_http_route_cookie(cookies, name, name_length, start, p); + if (nxt_slow_path(nv == NULL)) { + return NXT_ERROR; + } + } + + return NXT_OK; +} + + +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) +{ + u_char c, *p; + uint32_t hash; + nxt_http_name_value_t *nv; + + nv = nxt_array_add(array); + if (nxt_slow_path(nv == NULL)) { + return NULL; + } + + nv->name_length = name_length; + nv->name = name; + + hash = NXT_HTTP_FIELD_HASH_INIT; + + for (p = name; p < name + name_length; p++) { + c = *p; + c = nxt_lowcase(c); + hash = nxt_http_field_hash_char(hash, c); + } + + nv->hash = nxt_http_field_hash_end(hash) & 0xFFFF; + + while (start < end && end[-1] == ' ') { end--; } + + nv->value_length = end - start; + nv->value = start; + + return nv; +} + + +static nxt_int_t +nxt_http_route_test_cookie(nxt_http_request_t *r, + nxt_http_route_rule_t *rule, nxt_array_t *array) +{ + nxt_bool_t ret; + nxt_http_name_value_t *nv, *end; + + ret = 0; + + nv = array->elts; + end = nv + array->nelts; + + while (nv < end) { + + if (rule->u.name.hash == nv->hash + && rule->u.name.length == nv->name_length + && nxt_strncasecmp(rule->u.name.start, nv->name, nv->name_length) + == 0) + { + ret = nxt_http_route_test_rule(r, rule, nv->value, + nv->value_length); + if (ret == 0) { + break; + } } + + nv++; } - items = rule->items; + return ret; +} + + +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) +{ + nxt_int_t ret; + nxt_http_route_pattern_t *pattern, *end; + + ret = 1; pattern = &rule->pattern[0]; + end = pattern + rule->items; - do { + while (pattern < end) { ret = nxt_http_route_pattern(r, pattern, start, length); + /* nxt_http_route_pattern() returns either 1 or 0. */ ret ^= pattern->negative; if (pattern->any == ret) { @@ -793,30 +1522,31 @@ nxt_http_route_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule) } pattern++; - items--; - - } while (items != 0); + } return ret; } -static nxt_bool_t +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) { - nxt_str_t *test; + u_char *p, *end, *test; + size_t test_length; + nxt_int_t ret; if (length < pattern->min_length) { return 0; } - test = &pattern->test; + test = pattern->start1; + test_length = pattern->length1; switch (pattern->type) { case NXT_HTTP_ROUTE_PATTERN_EXACT: - if (length != test->length) { + if (length != test_length) { return 0; } @@ -825,25 +1555,52 @@ nxt_http_route_pattern(nxt_http_request_t *r, nxt_http_route_pattern_t *pattern, case NXT_HTTP_ROUTE_PATTERN_BEGIN: break; + case NXT_HTTP_ROUTE_PATTERN_MIDDLE: + ret = nxt_http_route_memcmp(start, test, test_length, + pattern->case_sensitive); + if (!ret) { + return ret; + } + + test = pattern->start2; + test_length = pattern->length2; + + /* Fall through. */ + case NXT_HTTP_ROUTE_PATTERN_END: - start += length - test->length; + start += length - test_length; break; case NXT_HTTP_ROUTE_PATTERN_SUBSTRING: + end = start + length; + if (pattern->case_sensitive) { - return (nxt_memstrn(start, start + length, - (char *) test->start, test->length) - != NULL); + p = nxt_memstrn(start, end, (char *) test, test_length); + + } else { + p = nxt_memcasestrn(start, end, (char *) test, test_length); } - return (nxt_memcasestrn(start, start + length, - (char *) test->start, test->length) - != NULL); + return (p != NULL); } - if (pattern->case_sensitive) { - return (nxt_memcmp(start, test->start, test->length) == 0); + return nxt_http_route_memcmp(start, test, test_length, + pattern->case_sensitive); +} + + +static nxt_int_t +nxt_http_route_memcmp(u_char *start, u_char *test, size_t test_length, + nxt_bool_t case_sensitive) +{ + nxt_int_t n; + + if (case_sensitive) { + n = nxt_memcmp(start, test, test_length); + + } else { + n = nxt_memcasecmp(start, test, test_length); } - return (nxt_memcasecmp(start, test->start, test->length) == 0); + return (n == 0); } diff --git a/src/nxt_job_resolve.c b/src/nxt_job_resolve.c index a1317756..0f1fb9aa 100644 --- a/src/nxt_job_resolve.c +++ b/src/nxt_job_resolve.c @@ -59,11 +59,11 @@ nxt_job_resolve(nxt_job_resolve_t *jbr) case AF_INET6: #endif case AF_INET: - n++; - break; + n++; + break; default: - break; + break; } } @@ -81,15 +81,15 @@ nxt_job_resolve(nxt_job_resolve_t *jbr) switch (r->ai_addr->sa_family) { #if (NXT_INET6) case AF_INET6: - length = NXT_INET6_ADDR_STR_LEN; - break; + length = NXT_INET6_ADDR_STR_LEN; + break; #endif case AF_INET: - length = NXT_INET_ADDR_STR_LEN; - break; + length = NXT_INET_ADDR_STR_LEN; + break; default: - continue; + continue; } sa = nxt_sockaddr_create(mp, r->ai_addr, r->ai_addrlen, length); diff --git a/src/nxt_kqueue_engine.c b/src/nxt_kqueue_engine.c index 0e68fbdc..0212b331 100644 --- a/src/nxt_kqueue_engine.c +++ b/src/nxt_kqueue_engine.c @@ -856,7 +856,7 @@ nxt_kqueue_conn_io_connect(nxt_task_t *task, void *obj, void *data) state = c->write_state; - switch (nxt_socket_connect(task, c->socket.fd, c->remote) ){ + switch (nxt_socket_connect(task, c->socket.fd, c->remote)) { case NXT_OK: c->socket.write_ready = 1; diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index f756bff7..40682eb9 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -842,7 +842,7 @@ nxt_main_process_sigusr1_handler(nxt_task_t *task, void *obj, void *data) nxt_mp_destroy(mp); return; - } + } fail: diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index 80321a85..a6ec6c60 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -699,7 +699,7 @@ fail: static int nxt_php_startup(sapi_module_struct *sapi_module) { - return php_module_startup(sapi_module, NULL, 0); + return php_module_startup(sapi_module, NULL, 0); } @@ -929,6 +929,10 @@ nxt_php_register_variables(zval *track_vars_array TSRMLS_DC) track_vars_array TSRMLS_CC); nxt_php_set_cstr(req, "SERVER_PORT", "80", 2, track_vars_array TSRMLS_CC); + if (r->tls) { + nxt_php_set_cstr(req, "HTTPS", "on", 2, track_vars_array TSRMLS_CC); + } + f_end = r->fields + r->fields_count; for (f = r->fields; f < f_end; f++) { name = nxt_unit_sptr_get(&f->name); diff --git a/src/nxt_poll_engine.c b/src/nxt_poll_engine.c index acb44a22..f514e0a9 100644 --- a/src/nxt_poll_engine.c +++ b/src/nxt_poll_engine.c @@ -370,7 +370,7 @@ nxt_poll_commit_changes(nxt_event_engine_t *engine) retval = NXT_ERROR; - next: + next: change++; diff --git a/src/nxt_port.c b/src/nxt_port.c index 30719ad3..aff46666 100644 --- a/src/nxt_port.c +++ b/src/nxt_port.c @@ -510,7 +510,7 @@ nxt_port_post(nxt_task_t *task, nxt_port_t *port, pw = nxt_zalloc(sizeof(nxt_port_work_t)); if (nxt_slow_path(pw == NULL)) { - return NXT_ERROR; + return NXT_ERROR; } nxt_atomic_fetch_add(&port->use_count, 1); diff --git a/src/nxt_port_memory.c b/src/nxt_port_memory.c index 774f1f33..b908041c 100644 --- a/src/nxt_port_memory.c +++ b/src/nxt_port_memory.c @@ -49,10 +49,10 @@ nxt_port_mmap_at(nxt_port_mmaps_t *port_mmaps, uint32_t i) while (i + 1 > cap) { if (cap < 16) { - cap = cap * 2; + cap = cap * 2; } else { - cap = cap + cap / 2; + cap = cap + cap / 2; } } diff --git a/src/nxt_port_socket.c b/src/nxt_port_socket.c index 01fe2dab..c9b5105b 100644 --- a/src/nxt_port_socket.c +++ b/src/nxt_port_socket.c @@ -195,7 +195,24 @@ nxt_port_msg_create(nxt_task_t *task, nxt_port_send_msg_t *m) static nxt_port_send_msg_t * -nxt_port_msg_push(nxt_task_t *task, nxt_port_t *port, nxt_port_send_msg_t *msg) +nxt_port_msg_insert_head(nxt_task_t *task, nxt_port_t *port, + nxt_port_send_msg_t *msg) +{ + if (msg->work.data == NULL) { + msg = nxt_port_msg_create(task, msg); + } + + if (msg != NULL) { + nxt_queue_insert_head(&port->messages, &msg->link); + } + + return msg; +} + + +static nxt_port_send_msg_t * +nxt_port_msg_insert_tail(nxt_task_t *task, nxt_port_t *port, + nxt_port_send_msg_t *msg) { if (msg->work.data == NULL) { msg = nxt_port_msg_create(task, msg); @@ -260,7 +277,7 @@ nxt_port_socket_twrite(nxt_task_t *task, nxt_port_t *port, nxt_uint_t type, } else { nxt_thread_mutex_lock(&port->write_mutex); - res = nxt_port_msg_push(task, port, &msg); + res = nxt_port_msg_insert_tail(task, port, &msg); nxt_thread_mutex_unlock(&port->write_mutex); @@ -349,6 +366,8 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data) iov[0].iov_len += sizeof(msg->tracking_msg); } + sb.limit -= iov[0].iov_len; + nxt_sendbuf_mem_coalesce(task, &sb); plain_size = sb.size; @@ -407,7 +426,7 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data) } data = NULL; - if (nxt_port_msg_push(task, port, msg) != NULL) { + if (nxt_port_msg_insert_tail(task, port, msg) != NULL) { use_delta++; } } @@ -422,16 +441,17 @@ nxt_port_write_handler(nxt_task_t *task, void *obj, void *data) data = NULL; } - } else if (nxt_slow_path(n == NXT_ERROR)) { + } else { if (msg->link.next == NULL) { - if (nxt_port_msg_push(task, port, msg) != NULL) { + if (nxt_port_msg_insert_head(task, port, msg) != NULL) { use_delta++; } } - goto fail; - } - /* n == NXT_AGAIN */ + if (nxt_slow_path(n == NXT_ERROR)) { + goto fail; + } + } } while (port->socket.write_ready); @@ -546,6 +566,7 @@ void nxt_port_read_close(nxt_port_t *port) { port->socket.read_ready = 0; + port->socket.read = NXT_EVENT_INACTIVE; nxt_socket_close(port->socket.task, port->pair[0]); port->pair[0] = -1; } @@ -618,15 +639,24 @@ nxt_port_read_handler(nxt_task_t *task, void *obj, void *data) } +typedef struct { + uint32_t stream; + uint32_t pid; +} nxt_port_frag_key_t; + + static nxt_int_t nxt_port_lvlhsh_frag_test(nxt_lvlhsh_query_t *lhq, void *data) { nxt_port_recv_msg_t *fmsg; + nxt_port_frag_key_t *frag_key; fmsg = data; + frag_key = (nxt_port_frag_key_t *) lhq->key.start; - if (lhq->key.length == sizeof(uint32_t) - && *(uint32_t *) lhq->key.start == fmsg->port_msg.stream) + if (lhq->key.length == sizeof(nxt_port_frag_key_t) + && frag_key->stream == fmsg->port_msg.stream + && frag_key->pid == (uint32_t) fmsg->port_msg.pid) { return NXT_OK; } @@ -664,6 +694,7 @@ nxt_port_frag_start(nxt_task_t *task, nxt_port_t *port, nxt_int_t res; nxt_lvlhsh_query_t lhq; nxt_port_recv_msg_t *fmsg; + nxt_port_frag_key_t frag_key; nxt_debug(task, "start frag stream #%uD", msg->port_msg.stream); @@ -675,9 +706,12 @@ nxt_port_frag_start(nxt_task_t *task, nxt_port_t *port, *fmsg = *msg; - lhq.key_hash = nxt_murmur_hash2(&fmsg->port_msg.stream, sizeof(uint32_t)); - lhq.key.length = sizeof(uint32_t); - lhq.key.start = (u_char *) &fmsg->port_msg.stream; + frag_key.stream = fmsg->port_msg.stream; + frag_key.pid = fmsg->port_msg.pid; + + lhq.key_hash = nxt_murmur_hash2(&frag_key, sizeof(nxt_port_frag_key_t)); + lhq.key.length = sizeof(nxt_port_frag_key_t); + lhq.key.start = (u_char *) &frag_key; lhq.proto = &lvlhsh_frag_proto; lhq.replace = 0; lhq.value = fmsg; @@ -710,17 +744,24 @@ nxt_port_frag_start(nxt_task_t *task, nxt_port_t *port, static nxt_port_recv_msg_t * -nxt_port_frag_find(nxt_task_t *task, nxt_port_t *port, uint32_t stream, - nxt_bool_t last) +nxt_port_frag_find(nxt_task_t *task, nxt_port_t *port, nxt_port_recv_msg_t *msg) { - nxt_int_t res; - nxt_lvlhsh_query_t lhq; + nxt_int_t res; + nxt_bool_t last; + nxt_lvlhsh_query_t lhq; + nxt_port_frag_key_t frag_key; + + last = msg->port_msg.mf == 0; + + nxt_debug(task, "%s frag stream #%uD", last ? "last" : "next", + msg->port_msg.stream); - nxt_debug(task, "%s frag stream #%uD", last ? "last" : "next", stream); + frag_key.stream = msg->port_msg.stream; + frag_key.pid = msg->port_msg.pid; - lhq.key_hash = nxt_murmur_hash2(&stream, sizeof(uint32_t)); - lhq.key.length = sizeof(uint32_t); - lhq.key.start = (u_char *) &stream; + lhq.key_hash = nxt_murmur_hash2(&frag_key, sizeof(nxt_port_frag_key_t)); + lhq.key.length = sizeof(nxt_port_frag_key_t); + lhq.key.start = (u_char *) &frag_key; lhq.proto = &lvlhsh_frag_proto; lhq.pool = port->mem_pool; @@ -733,7 +774,8 @@ nxt_port_frag_find(nxt_task_t *task, nxt_port_t *port, uint32_t stream, return lhq.value; default: - nxt_log(task, NXT_LOG_INFO, "frag stream #%uD not found", stream); + nxt_log(task, NXT_LOG_INFO, "frag stream #%uD not found", + frag_key.stream); return NULL; } @@ -773,8 +815,7 @@ nxt_port_read_msg_process(nxt_task_t *task, nxt_port_t *port, if (nxt_slow_path(msg->port_msg.nf != 0)) { - fmsg = nxt_port_frag_find(task, port, msg->port_msg.stream, - msg->port_msg.mf == 0); + fmsg = nxt_port_frag_find(task, port, msg); if (nxt_slow_path(fmsg == NULL)) { goto fmsg_failed; diff --git a/src/nxt_process.c b/src/nxt_process.c index 59520297..c4aef21c 100644 --- a/src/nxt_process.c +++ b/src/nxt_process.c @@ -136,9 +136,11 @@ nxt_process_start(nxt_task_t *task, nxt_process_t *process) nxt_random_init(&thread->random); - if (init->user_cred != NULL && getuid() == 0) { - /* Super-user. */ - + if (init->user_cred != NULL) { + /* + * Changing user credentials requires either root privileges + * or CAP_SETUID and CAP_SETGID capabilities on Linux. + */ ret = nxt_user_cred_set(task, init->user_cred); if (ret != NXT_OK) { goto fail; @@ -434,11 +436,7 @@ nxt_user_cred_get(nxt_task_t *task, nxt_user_cred_t *uc, const char *group) uc->base_gid = grp->gr_gid; } - if (getuid() == 0) { - return nxt_user_groups_get(task, uc); - } - - return NXT_OK; + return nxt_user_groups_get(task, uc); } @@ -505,14 +503,26 @@ nxt_user_groups_get(nxt_task_t *task, nxt_user_cred_t *uc) if (nsaved == -1) { nxt_alert(task, "getgroups(%d) failed %E", nsaved, nxt_errno); - goto fail; + goto free; } nxt_debug(task, "getgroups(): %d", nsaved); if (initgroups(uc->user, uc->base_gid) != 0) { - nxt_alert(task, "initgroups(%s, %d) failed", uc->user, uc->base_gid); - goto restore; + if (nxt_errno == NXT_EPERM) { + nxt_log(task, NXT_LOG_NOTICE, + "initgroups(%s, %d) failed %E, ignored", + uc->user, uc->base_gid, nxt_errno); + + ret = NXT_OK; + + goto free; + + } else { + nxt_alert(task, "initgroups(%s, %d) failed %E", + uc->user, uc->base_gid, nxt_errno); + goto restore; + } } ngroups = getgroups(0, NULL); @@ -567,7 +577,7 @@ restore: ret = NXT_ERROR; } -fail: +free: nxt_free(saved); @@ -582,8 +592,15 @@ nxt_user_cred_set(nxt_task_t *task, nxt_user_cred_t *uc) uc->user, (uint64_t) uc->uid, (uint64_t) uc->base_gid); if (setgid(uc->base_gid) != 0) { - nxt_alert(task, "setgid(%d) failed %E", uc->base_gid, nxt_errno); - return NXT_ERROR; + if (nxt_errno == NXT_EPERM) { + nxt_log(task, NXT_LOG_NOTICE, "setgid(%d) failed %E, ignored", + uc->base_gid, nxt_errno); + return NXT_OK; + + } else { + nxt_alert(task, "setgid(%d) failed %E", uc->base_gid, nxt_errno); + return NXT_ERROR; + } } if (uc->gids != NULL) { @@ -595,8 +612,8 @@ nxt_user_cred_set(nxt_task_t *task, nxt_user_cred_t *uc) } else { /* MacOSX fallback. */ if (initgroups(uc->user, uc->base_gid) != 0) { - nxt_alert(task, "initgroups(%s, %d) failed", - uc->user, uc->base_gid); + nxt_alert(task, "initgroups(%s, %d) failed %E", + uc->user, uc->base_gid, nxt_errno); return NXT_ERROR; } } diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index 6478f38c..a6d5f217 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -619,26 +619,6 @@ nxt_python_create_environ(nxt_task_t *task) } - obj = PyString_FromStringAndSize("http", nxt_length("http")); - - if (nxt_slow_path(obj == NULL)) { - nxt_alert(task, - "Python failed to create the \"wsgi.url_scheme\" environ value"); - goto fail; - } - - if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.url_scheme", obj) - != 0)) - { - nxt_alert(task, - "Python failed to set the \"wsgi.url_scheme\" environ value"); - goto fail; - } - - Py_DECREF(obj); - obj = NULL; - - if (nxt_slow_path(PyType_Ready(&nxt_py_input_type) != 0)) { nxt_alert(task, "Python failed to initialize the \"wsgi.input\" type object"); @@ -726,6 +706,13 @@ nxt_python_get_environ(nxt_python_run_ctx_t *ctx) RC(nxt_python_add_sptr(ctx, "REMOTE_ADDR", &r->remote, r->remote_length)); RC(nxt_python_add_sptr(ctx, "SERVER_ADDR", &r->local, r->local_length)); + if (r->tls) { + RC(nxt_python_add_str(ctx, "wsgi.url_scheme", "https", 5)); + + } else { + RC(nxt_python_add_str(ctx, "wsgi.url_scheme", "http", 4)); + } + RC(nxt_python_add_sptr(ctx, "SERVER_PROTOCOL", &r->version, r->version_length)); diff --git a/src/nxt_router.c b/src/nxt_router.c index e46e8f82..149a0ff3 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -62,7 +62,7 @@ typedef struct { uint32_t stream; nxt_app_t *app; nxt_port_t *app_port; - nxt_app_parse_ctx_t *ap; + nxt_http_request_t *request; nxt_msg_info_t msg_info; nxt_req_app_link_t *ra; @@ -75,7 +75,7 @@ struct nxt_req_app_link_s { nxt_atomic_t use_count; nxt_port_t *app_port; nxt_port_t *reply_port; - nxt_app_parse_ctx_t *ap; + nxt_http_request_t *request; nxt_msg_info_t msg_info; nxt_req_conn_link_t *rc; @@ -264,8 +264,8 @@ static nxt_int_t nxt_router_app_port(nxt_task_t *task, nxt_app_t *app, static void nxt_router_app_prepare_request(nxt_task_t *task, nxt_req_app_link_t *ra); -static nxt_buf_t *nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, - nxt_port_t *port, const nxt_str_t *prefix); +static nxt_buf_t *nxt_router_prepare_msg(nxt_task_t *task, + nxt_http_request_t *r, nxt_port_t *port, const nxt_str_t *prefix); static void nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data); static void nxt_router_adjust_idle_timer(nxt_task_t *task, void *obj, @@ -282,6 +282,11 @@ static void nxt_http_request_send_body(nxt_task_t *task, void *obj, void *data); static void nxt_router_app_joint_use(nxt_task_t *task, nxt_app_joint_t *app_joint, int i); +static nxt_int_t nxt_router_http_request_done(nxt_task_t *task, + nxt_http_request_t *r); +static void nxt_router_http_request_release(nxt_task_t *task, void *obj, + void *data); + static nxt_router_t *nxt_router; static const nxt_str_t http_prefix = nxt_string("HTTP_"); @@ -502,7 +507,7 @@ nxt_router_ra_init(nxt_task_t *task, nxt_req_app_link_t *ra, ra->rc = rc; rc->ra = ra; ra->reply_port = engine->port; - ra->ap = rc->ap; + ra->request = rc->request; ra->work.handler = NULL; ra->work.task = &engine->task; @@ -521,7 +526,7 @@ nxt_router_ra_create(nxt_task_t *task, nxt_req_app_link_t *ra_src) return ra_src; } - mp = ra_src->ap->mem_pool; + mp = ra_src->request->mem_pool; ra = nxt_mp_alloc(mp, sizeof(nxt_req_app_link_t)); @@ -645,16 +650,16 @@ nxt_router_ra_release(nxt_task_t *task, nxt_req_app_link_t *ra) if (rc != NULL) { if (nxt_slow_path(ra->err_code != 0)) { - nxt_http_request_error(task, rc->ap->request, ra->err_code); + nxt_http_request_error(task, rc->request, ra->err_code); } else { rc->app_port = ra->app_port; rc->msg_info = ra->msg_info; if (rc->app->timeout != 0) { - rc->ap->timer.handler = nxt_router_app_timeout; - rc->ap->timer_data = rc; - nxt_timer_add(task->thread->engine, &rc->ap->timer, + rc->request->timer.handler = nxt_router_app_timeout; + rc->request->timer_data = rc; + nxt_timer_add(task->thread->engine, &rc->request->timer, rc->app->timeout); } @@ -817,12 +822,12 @@ nxt_router_rc_unlink(nxt_task_t *task, nxt_req_conn_link_t *rc) rc->app = NULL; } - if (rc->ap != NULL) { - rc->ap->timer_data = NULL; + if (rc->request != NULL) { + rc->request->timer_data = NULL; - nxt_app_http_req_done(task, rc->ap); + nxt_router_http_request_done(task, rc->request); - rc->ap = NULL; + rc->request = NULL; } } @@ -3377,33 +3382,27 @@ static void nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) { - size_t dump_size; nxt_int_t ret; nxt_buf_t *b; + nxt_unit_field_t *f; + nxt_http_field_t *field; nxt_http_request_t *r; nxt_req_conn_link_t *rc; - nxt_app_parse_ctx_t *ar; nxt_unit_response_t *resp; b = msg->buf; rc = data; - dump_size = nxt_buf_used_size(b); - - if (dump_size > 300) { - dump_size = 300; - } - if (msg->size == 0) { b = NULL; } - ar = rc->ap; - if (nxt_slow_path(ar == NULL)) { + r = rc->request; + if (nxt_slow_path(r == NULL)) { return; } - if (ar->request->error) { + if (r->error) { nxt_router_rc_unlink(task, rc); return; } @@ -3411,15 +3410,15 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, if (msg->port_msg.last != 0) { nxt_debug(task, "router data create last buf"); - nxt_buf_chain_add(&b, nxt_http_buf_last(ar->request)); + nxt_buf_chain_add(&b, nxt_http_buf_last(r)); nxt_router_rc_unlink(task, rc); } else { if (rc->app != NULL && rc->app->timeout != 0) { - ar->timer.handler = nxt_router_app_timeout; - ar->timer_data = rc; - nxt_timer_add(task->thread->engine, &ar->timer, rc->app->timeout); + r->timer.handler = nxt_router_app_timeout; + r->timer_data = rc; + nxt_timer_add(task->thread->engine, &r->timer, rc->app->timeout); } } @@ -3432,8 +3431,6 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, msg->buf = NULL; } - r = ar->request; - if (r->header_sent) { nxt_buf_chain_add(&r->out, b); nxt_http_request_send_body(task, r, NULL); @@ -3451,11 +3448,8 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, goto fail; } - nxt_unit_field_t *f; - nxt_http_field_t *field; - for (f = resp->fields; f < resp->fields + resp->fields_count; f++) { - field = nxt_list_add(ar->resp_parser.fields); + field = nxt_list_add(r->resp.fields); if (nxt_slow_path(field == NULL)) { goto fail; @@ -3473,15 +3467,8 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, (size_t) field->name_length, field->name, (size_t) field->value_length, field->value); } - r->status = resp->status; -/* - ret = nxt_http_parse_fields(&ar->resp_parser, &b->mem); - if (nxt_slow_path(ret != NXT_DONE)) { - goto fail; - } -*/ - r->resp.fields = ar->resp_parser.fields; + r->status = resp->status; ret = nxt_http_fields_process(r->resp.fields, &nxt_response_fields_hash, r); @@ -3590,8 +3577,8 @@ nxt_router_response_error_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, } } - if (rc->ap != NULL) { - nxt_http_request_error(task, rc->ap->request, + if (rc->request != NULL) { + nxt_http_request_error(task, rc->request, NXT_HTTP_SERVICE_UNAVAILABLE); } @@ -4452,17 +4439,15 @@ nxt_router_app_port(nxt_task_t *task, nxt_app_t *app, nxt_req_app_link_t *ra) void -nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar, +nxt_router_process_http_request(nxt_task_t *task, nxt_http_request_t *r, nxt_app_t *app) { nxt_int_t res; nxt_port_t *port; nxt_event_engine_t *engine; - nxt_http_request_t *r; nxt_req_app_link_t ra_local, *ra; nxt_req_conn_link_t *rc; - r = ar->request; engine = task->thread->engine; rc = nxt_port_rpc_register_handler_ex(task, engine->port, @@ -4480,7 +4465,7 @@ nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar, nxt_router_app_use(task, app, 1); - rc->ap = ar; + rc->request = r; ra = &ra_local; nxt_router_ra_init(task, ra, rc); @@ -4511,17 +4496,15 @@ nxt_router_dummy_buf_completion(nxt_task_t *task, void *obj, void *data) static void nxt_router_app_prepare_request(nxt_task_t *task, nxt_req_app_link_t *ra) { - uint32_t request_failed; - nxt_buf_t *buf; - nxt_int_t res; - nxt_port_t *port, *c_port, *reply_port; - nxt_app_parse_ctx_t *ap; + uint32_t request_failed; + nxt_buf_t *buf; + nxt_int_t res; + nxt_port_t *port, *c_port, *reply_port; nxt_assert(ra->app_port != NULL); port = ra->app_port; reply_port = ra->reply_port; - ap = ra->ap; request_failed = 1; @@ -4539,7 +4522,7 @@ nxt_router_app_prepare_request(nxt_task_t *task, nxt_req_app_link_t *ra) nxt_process_connected_port_add(port->process, reply_port); } - buf = nxt_router_prepare_msg(task, &ap->r, port, + buf = nxt_router_prepare_msg(task, ra->request, port, nxt_app_msg_prefix[port->app->type]); if (nxt_slow_path(buf == NULL)) { @@ -4642,34 +4625,33 @@ nxt_fields_next(nxt_fields_iter_t *i) static nxt_buf_t * -nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, +nxt_router_prepare_msg(nxt_task_t *task, nxt_http_request_t *r, nxt_port_t *port, const nxt_str_t *prefix) { - void *target_pos, *query_pos; - u_char *pos, *end, *p, c; - size_t fields_count, req_size, size, free_size; - size_t copy_size; - nxt_buf_t *b, *buf, *out, **tail; - nxt_http_field_t *field, *dup; - nxt_unit_field_t *dst_field; - nxt_fields_iter_t iter, dup_iter; - nxt_unit_request_t *req; - nxt_app_request_header_t *h; - - h = &r->header; + void *target_pos, *query_pos; + u_char *pos, *end, *p, c; + size_t fields_count, req_size, size, free_size; + size_t copy_size; + nxt_off_t content_length; + nxt_buf_t *b, *buf, *out, **tail; + nxt_http_field_t *field, *dup; + nxt_unit_field_t *dst_field; + nxt_fields_iter_t iter, dup_iter; + nxt_unit_request_t *req; req_size = sizeof(nxt_unit_request_t) - + h->method.length + 1 - + h->version.length + 1 - + r->remote.length + 1 - + r->local.length + 1 - + h->server_name.length + 1 - + h->target.length + 1 - + (h->path.start != h->target.start ? h->path.length + 1 : 0); - + + r->method->length + 1 + + r->version.length + 1 + + r->remote->length + 1 + + r->local->length + 1 + + r->server_name.length + 1 + + r->target.length + 1 + + (r->path->start != r->target.start ? r->path->length + 1 : 0); + + content_length = r->content_length_n < 0 ? 0 : r->content_length_n; fields_count = 0; - nxt_list_each(field, h->fields) { + nxt_list_each(field, r->fields) { fields_count++; req_size += field->name_length + prefix->length + 1 @@ -4686,7 +4668,7 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, } out = nxt_port_mmap_get_buf(task, port, - nxt_min(req_size + r->body.preread_size, PORT_MMAP_DATA_SIZE)); + nxt_min(req_size + content_length, PORT_MMAP_DATA_SIZE)); if (nxt_slow_path(out == NULL)) { return NULL; } @@ -4694,57 +4676,60 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, req = (nxt_unit_request_t *) out->mem.free; out->mem.free += req_size; - req->content_length = h->parsed_content_length; + req->content_length = content_length; p = (u_char *) (req->fields + fields_count); nxt_debug(task, "fields_count=%d", (int) fields_count); - req->method_length = h->method.length; + req->method_length = r->method->length; nxt_unit_sptr_set(&req->method, p); - p = nxt_cpymem(p, h->method.start, h->method.length); + p = nxt_cpymem(p, r->method->start, r->method->length); *p++ = '\0'; - req->version_length = h->version.length; + req->version_length = r->version.length; nxt_unit_sptr_set(&req->version, p); - p = nxt_cpymem(p, h->version.start, h->version.length); + p = nxt_cpymem(p, r->version.start, r->version.length); *p++ = '\0'; - req->remote_length = r->remote.length; + req->remote_length = r->remote->address_length; nxt_unit_sptr_set(&req->remote, p); - p = nxt_cpymem(p, r->remote.start, r->remote.length); + p = nxt_cpymem(p, nxt_sockaddr_address(r->remote), + r->remote->address_length); *p++ = '\0'; - req->local_length = r->local.length; + req->local_length = r->local->address_length; nxt_unit_sptr_set(&req->local, p); - p = nxt_cpymem(p, r->local.start, r->local.length); + p = nxt_cpymem(p, nxt_sockaddr_address(r->local), r->local->address_length); *p++ = '\0'; - req->server_name_length = h->server_name.length; + req->tls = (r->tls != NULL); + + req->server_name_length = r->server_name.length; nxt_unit_sptr_set(&req->server_name, p); - p = nxt_cpymem(p, h->server_name.start, h->server_name.length); + p = nxt_cpymem(p, r->server_name.start, r->server_name.length); *p++ = '\0'; target_pos = p; - req->target_length = h->target.length; + req->target_length = (uint32_t) r->target.length; nxt_unit_sptr_set(&req->target, p); - p = nxt_cpymem(p, h->target.start, h->target.length); + p = nxt_cpymem(p, r->target.start, r->target.length); *p++ = '\0'; - req->path_length = h->path.length; - if (h->path.start == h->target.start) { + req->path_length = (uint32_t) r->path->length; + if (r->path->start == r->target.start) { nxt_unit_sptr_set(&req->path, target_pos); } else { nxt_unit_sptr_set(&req->path, p); - p = nxt_cpymem(p, h->path.start, h->path.length); + p = nxt_cpymem(p, r->path->start, r->path->length); *p++ = '\0'; } - req->query_length = h->query.length; - if (h->query.start != NULL) { + req->query_length = r->args != NULL ? (uint32_t) r->args->length : 0; + if (r->args != NULL && r->args->start != NULL) { query_pos = nxt_pointer_to(target_pos, - h->query.start - h->target.start); + r->args->start - r->target.start); nxt_unit_sptr_set(&req->query, query_pos); @@ -4758,7 +4743,7 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, dst_field = req->fields; - for (field = nxt_fields_first(h->fields, &iter); + for (field = nxt_fields_first(r->fields, &iter); field != NULL; field = nxt_fields_next(&iter)) { @@ -4771,13 +4756,13 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, dst_field->name_length = field->name_length + prefix->length; dst_field->value_length = field->value_length; - if (field->value == h->content_length.start) { + if (field == r->content_length) { req->content_length_field = dst_field - req->fields; - } else if (field->value == h->content_type.start) { + } else if (field == r->content_type) { req->content_type_field = dst_field - req->fields; - } else if (field->value == h->cookie.start) { + } else if (field == r->cookie) { req->cookie_field = dst_field - req->fields; } @@ -4846,14 +4831,14 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r, dst_field++; } - req->fields_count = dst_field - req->fields; + req->fields_count = (uint32_t) (dst_field - req->fields); nxt_unit_sptr_set(&req->preread_content, out->mem.free); buf = out; tail = &buf->next; - for (b = r->body.buf; b != NULL; b = b->next) { + for (b = r->body; b != NULL; b = b->next) { size = nxt_buf_mem_used_size(&b->mem); pos = b->mem.pos; @@ -4913,8 +4898,8 @@ nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data) nxt_port_t *port; nxt_timer_t *timer; nxt_queue_link_t *lnk; + nxt_http_request_t *r; nxt_req_app_link_t *pending_ra; - nxt_app_parse_ctx_t *ar; nxt_req_conn_link_t *rc; nxt_port_select_state_t state; @@ -4922,8 +4907,8 @@ nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data) nxt_debug(task, "router app timeout"); - ar = nxt_timer_data(timer, nxt_app_parse_ctx_t, timer); - rc = ar->timer_data; + r = nxt_timer_data(timer, nxt_http_request_t, timer); + rc = r->timer_data; app = rc->app; if (app == NULL) { @@ -4994,7 +4979,30 @@ nxt_router_app_timeout(nxt_task_t *task, void *obj, void *data) generate_error: - nxt_http_request_error(task, ar->request, NXT_HTTP_SERVICE_UNAVAILABLE); + nxt_http_request_error(task, r, NXT_HTTP_SERVICE_UNAVAILABLE); nxt_router_rc_unlink(task, rc); } + + +static nxt_int_t +nxt_router_http_request_done(nxt_task_t *task, nxt_http_request_t *r) +{ + r->timer.handler = nxt_router_http_request_release; + nxt_timer_add(task->thread->engine, &r->timer, 0); + + return NXT_OK; +} + + +static void +nxt_router_http_request_release(nxt_task_t *task, void *obj, void *data) +{ + nxt_http_request_t *r; + + nxt_debug(task, "http app release"); + + r = nxt_timer_data(obj, nxt_http_request_t, timer); + + nxt_mp_release(r->mem_pool); +} diff --git a/src/nxt_router.h b/src/nxt_router.h index dec56bd5..d9fbfe05 100644 --- a/src/nxt_router.h +++ b/src/nxt_router.h @@ -21,6 +21,9 @@ typedef struct nxt_http_routes_s nxt_http_routes_t; typedef struct nxt_router_access_log_s nxt_router_access_log_t; +#define NXT_HTTP_PASS_ERROR ((nxt_http_pass_t *) -1) + + typedef struct { nxt_thread_spinlock_t lock; nxt_queue_t engines; @@ -192,7 +195,7 @@ void nxt_router_remove_pid_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); void nxt_router_access_log_reopen_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); -void nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar, +void nxt_router_process_http_request(nxt_task_t *task, nxt_http_request_t *r, nxt_app_t *app); void nxt_router_app_port_close(nxt_task_t *task, nxt_port_t *port); nxt_app_t *nxt_router_listener_application(nxt_router_temp_conf_t *tmcf, diff --git a/src/nxt_runtime.c b/src/nxt_runtime.c index 547c7494..06478f72 100644 --- a/src/nxt_runtime.c +++ b/src/nxt_runtime.c @@ -53,14 +53,13 @@ nxt_runtime_create(nxt_task_t *task) nxt_app_lang_module_t *lang; mp = nxt_mp_create(1024, 128, 256, 32); - if (nxt_slow_path(mp == NULL)) { return NXT_ERROR; } rt = nxt_mp_zget(mp, sizeof(nxt_runtime_t)); if (nxt_slow_path(rt == NULL)) { - return NXT_ERROR; + goto fail; } task->thread->runtime = rt; diff --git a/src/nxt_sockaddr.c b/src/nxt_sockaddr.c index a001c730..99cf54b4 100644 --- a/src/nxt_sockaddr.c +++ b/src/nxt_sockaddr.c @@ -197,23 +197,23 @@ nxt_getsockname(nxt_task_t *task, nxt_mp_t *mp, nxt_socket_t s) switch (sockaddr.buf.sa_family) { #if (NXT_INET6) case AF_INET6: - length = NXT_INET6_ADDR_STR_LEN; - break; + length = NXT_INET6_ADDR_STR_LEN; + break; #endif #if (NXT_HAVE_UNIX_DOMAIN) case AF_UNIX: - length = nxt_length("unix:") + socklen; + length = nxt_length("unix:") + socklen; #endif - break; + break; case AF_INET: - length = NXT_INET_ADDR_STR_LEN; - break; + length = NXT_INET_ADDR_STR_LEN; + break; default: - length = 0; - break; + length = 0; + break; } return nxt_sockaddr_create(mp, &sockaddr.buf, socklen, length); diff --git a/src/nxt_socket.h b/src/nxt_socket.h index 42ef6c53..3f00648d 100644 --- a/src/nxt_socket.h +++ b/src/nxt_socket.h @@ -112,9 +112,9 @@ NXT_EXPORT nxt_int_t nxt_socketpair_create(nxt_task_t *task, nxt_socket_t *pair); NXT_EXPORT void nxt_socketpair_close(nxt_task_t *task, nxt_socket_t *pair); NXT_EXPORT ssize_t nxt_socketpair_send(nxt_fd_event_t *ev, nxt_fd_t fd, - nxt_iobuf_t *iob, nxt_uint_t niob); + nxt_iobuf_t *iob, nxt_uint_t niob); NXT_EXPORT ssize_t nxt_socketpair_recv(nxt_fd_event_t *ev, nxt_fd_t *fd, - nxt_iobuf_t *iob, nxt_uint_t niob); + nxt_iobuf_t *iob, nxt_uint_t niob); #define \ diff --git a/src/nxt_socketpair.c b/src/nxt_socketpair.c index a7396b31..10ea562e 100644 --- a/src/nxt_socketpair.c +++ b/src/nxt_socketpair.c @@ -21,9 +21,9 @@ static ssize_t nxt_sendmsg(nxt_socket_t s, nxt_fd_t fd, nxt_iobuf_t *iob, - nxt_uint_t niob); + nxt_uint_t niob); static ssize_t nxt_recvmsg(nxt_socket_t s, nxt_fd_t *fd, nxt_iobuf_t *iob, - nxt_uint_t niob); + nxt_uint_t niob); nxt_int_t @@ -94,9 +94,14 @@ nxt_socketpair_send(nxt_fd_event_t *ev, nxt_fd_t fd, nxt_iobuf_t *iob, case NXT_EAGAIN: nxt_debug(ev->task, "sendmsg(%d) not ready", ev->fd); - ev->write_ready = 0; + break; - return NXT_AGAIN; + /* + * Returned (at least on OSX) when trying to send many small messages. + */ + case NXT_ENOBUFS: + nxt_debug(ev->task, "sendmsg(%d) no buffers", ev->fd); + break; case NXT_EINTR: nxt_debug(ev->task, "sendmsg(%d) interrupted", ev->fd); @@ -108,6 +113,10 @@ nxt_socketpair_send(nxt_fd_event_t *ev, nxt_fd_t fd, nxt_iobuf_t *iob, return NXT_ERROR; } + + ev->write_ready = 0; + + return NXT_AGAIN; } } diff --git a/src/nxt_sprintf.c b/src/nxt_sprintf.c index 0b387883..240f47ef 100644 --- a/src/nxt_sprintf.c +++ b/src/nxt_sprintf.c @@ -76,12 +76,12 @@ nxt_sprintf(u_char *buf, u_char *end, const char *fmt, ...) */ typedef struct { - u_char *end; - const u_char *hex; - uint32_t width; - int32_t frac_width; - uint8_t max_width; - u_char padding; + u_char *end; + const u_char *hex; + uint32_t width; + int32_t frac_width; + uint8_t max_width; + u_char padding; } nxt_sprintf_t; diff --git a/src/nxt_timer.h b/src/nxt_timer.h index 4199f0dd..3ccff848 100644 --- a/src/nxt_timer.h +++ b/src/nxt_timer.h @@ -69,7 +69,7 @@ typedef struct { nxt_uint_t mchanges; nxt_uint_t nchanges; - nxt_timer_change_t *changes; + nxt_timer_change_t *changes; } nxt_timers_t; diff --git a/src/nxt_unicode_lowcase.pl b/src/nxt_unicode_lowcase.pl index 974ae23a..abf64965 100644 --- a/src/nxt_unicode_lowcase.pl +++ b/src/nxt_unicode_lowcase.pl @@ -29,9 +29,9 @@ my $last_block_size = $max_lowcase % BLOCK_SIZE + 1; for my $block (sort { $a <=> $b } keys %blocks) { - if ($max_block < $block) { - $max_block = $block; - } + if ($max_block < $block) { + $max_block = $block; + } } diff --git a/src/nxt_unit.c b/src/nxt_unit.c index 6339aec5..88c7fa6a 100644 --- a/src/nxt_unit.c +++ b/src/nxt_unit.c @@ -1946,10 +1946,10 @@ nxt_unit_mmap_at(nxt_unit_mmaps_t *mmaps, uint32_t i) while (i + 1 > cap) { if (cap < 16) { - cap = cap * 2; + cap = cap * 2; } else { - cap = cap + cap / 2; + cap = cap + cap / 2; } } diff --git a/src/nxt_unit_request.h b/src/nxt_unit_request.h index 88d569a6..2207cefa 100644 --- a/src/nxt_unit_request.h +++ b/src/nxt_unit_request.h @@ -19,6 +19,7 @@ struct nxt_unit_request_s { uint8_t version_length; uint8_t remote_length; uint8_t local_length; + uint8_t tls; uint32_t server_name_length; uint32_t target_length; uint32_t path_length; diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index 0b4b31d7..b99d3269 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -656,8 +656,11 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl, RC(nxt_perl_psgi_add_value(my_perl, hash_env, NL("psgi.version"), newRV_noinc((SV *) array_version))); + RC(nxt_perl_psgi_add_value(my_perl, hash_env, NL("psgi.url_scheme"), - newSVpv("http", 4))); + r->tls ? newSVpv("https", 5) + : newSVpv("http", 4))); + RC(nxt_perl_psgi_add_value(my_perl, hash_env, NL("psgi.input"), SvREFCNT_inc(nxt_perl_psgi_arg_input.io))); RC(nxt_perl_psgi_add_value(my_perl, hash_env, NL("psgi.errors"), diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c index b2398abe..ab9f7020 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -327,7 +327,6 @@ nxt_ruby_rack_env_create(VALUE arg) rb_ary_push(version, UINT2NUM(NXT_RUBY_RACK_API_VERSION_MINOR)); rb_hash_aset(hash_env, rb_str_new2("rack.version"), version); - rb_hash_aset(hash_env, rb_str_new2("rack.url_scheme"), rb_str_new2("http")); rb_hash_aset(hash_env, rb_str_new2("rack.input"), nxt_ruby_io_input); rb_hash_aset(hash_env, rb_str_new2("rack.errors"), nxt_ruby_io_error); rb_hash_aset(hash_env, rb_str_new2("rack.multithread"), Qfalse); @@ -454,6 +453,9 @@ nxt_ruby_read_request(VALUE hash_env) r->server_name_length); nxt_ruby_add_str(hash_env, NL("SERVER_PORT"), "80", 2); + rb_hash_aset(hash_env, rb_str_new2("rack.url_scheme"), + r->tls ? rb_str_new2("https") : rb_str_new2("http")); + for (i = 0; i < r->fields_count; i++) { f = r->fields + i; diff --git a/test/node/has_header/app.js b/test/node/has_header/app.js index 040f551e..eff7f4ff 100755 --- a/test/node/has_header/app.js +++ b/test/node/has_header/app.js @@ -1,6 +1,6 @@ #!/usr/bin/env node require('unit-http').createServer(function (req, res) { - res.setHeader('X-Has-Header', res.hasHeader(req['headers']['X-Header']) + ''); + res.setHeader('X-Has-Header', res.hasHeader(req.headers['x-header']) + ''); res.end(); }).listen(7080); diff --git a/test/node/promise_handler/app.js b/test/node/promise_handler/app.js index 54df09d1..60b0c3bb 100755 --- a/test/node/promise_handler/app.js +++ b/test/node/promise_handler/app.js @@ -5,7 +5,7 @@ var fs = require('fs'); require('unit-http').createServer(function (req, res) { res.end(); - if (req.headers['X-Write-Call']) { + if (req.headers['x-write-call']) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.write('blah'); } diff --git a/test/node/remove_header/app.js b/test/node/remove_header/app.js index 578b72a7..cd7b80c3 100755 --- a/test/node/remove_header/app.js +++ b/test/node/remove_header/app.js @@ -4,7 +4,7 @@ require('unit-http').createServer(function (req, res) { res.setHeader('X-Header', 'test'); res.setHeader('Was-Header', res.hasHeader('X-Header').toString()); - res.removeHeader(req['headers']['X-Remove']); + res.removeHeader(req.headers['x-remove']); res.setHeader('Has-Header', res.hasHeader('X-Header').toString()); res.end(); diff --git a/test/node/variables/app.js b/test/node/variables/app.js index 968afba5..4ed94d09 100755 --- a/test/node/variables/app.js +++ b/test/node/variables/app.js @@ -11,9 +11,9 @@ require('unit-http').createServer(function (req, res) { res.setHeader('Server-Protocol', req.httpVersion); res.setHeader('Request-Raw-Headers', req.rawHeaders.join()); res.setHeader('Content-Length', Buffer.byteLength(body)); - res.setHeader('Content-Type', req.headers['Content-Type']); - res.setHeader('Custom-Header', req.headers['Custom-Header']); - res.setHeader('Http-Host', req.headers['Host']); + res.setHeader('Content-Type', req.headers['content-type']); + res.setHeader('Custom-Header', req.headers['custom-header']); + res.setHeader('Http-Host', req.headers['host']); res.writeHead(200, {}); res.end(body); }); diff --git a/test/php/404/index.php b/test/php/404/index.php index 92afdf19..561dbec2 100644 --- a/test/php/404/index.php +++ b/test/php/404/index.php @@ -1,4 +1,10 @@ <?php -http_response_code(404); +if (!function_exists('http_response_code')) { + header('Temporary-Header: True', true, 404); + header_remove('Temporary-Header'); +} else { + http_response_code(404); +} + include('404.html'); ?> diff --git a/test/php/ini_precision/php.ini b/test/php/ini_precision/ini/php.ini index 51dbdede..51dbdede 100644 --- a/test/php/ini_precision/php.ini +++ b/test/php/ini_precision/ini/php.ini diff --git a/test/python/mirror/wsgi.py b/test/python/mirror/wsgi.py index e4df67ec..eb1fb922 100644 --- a/test/python/mirror/wsgi.py +++ b/test/python/mirror/wsgi.py @@ -4,7 +4,6 @@ def application(environ, start_response): body = bytes(environ['wsgi.input'].read(content_length)) start_response('200', [ - ('Content-Type', environ.get('CONTENT_TYPE')), ('Content-Length', str(len(body))) ]) return [body] diff --git a/test/test_access_log.py b/test/test_access_log.py index d6741c28..49497ad2 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -1,55 +1,63 @@ import os import re import time -from subprocess import call import unittest -import unit +from subprocess import call +from unit.applications.lang.python import TestApplicationPython -class TestUnitAccessLog(unit.TestUnitApplicationPython): - def setUpClass(): - unit.TestUnit().check_modules('python') +class TestAccessLog(TestApplicationPython): + prerequisites = ['python'] def load(self, script): super().load(script) self.conf('"' + self.testdir + '/access.log"', 'access_log') - def search_in_log(self, pattern, name='access.log'): - with open(self.testdir + '/' + name, 'r') as f: - return re.search(pattern, f.read()) + def wait_for_record(self, pattern, name='access.log'): + return super().wait_for_record(pattern, name) def test_access_log_keepalive(self): self.load('mirror') - (resp, sock) = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive', - 'Content-Type': 'text/html' - }, start=True, body='01234') + self.assertEqual(self.get()['status'], 200, 'init') - time.sleep(0.2) + (resp, sock) = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + }, + start=True, + body='01234', + read_timeout=1, + ) self.assertIsNotNone( - self.search_in_log(r'"POST / HTTP/1.1" 200 5'), 'keepalive 1') - - resp = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'close', - 'Content-Type': 'text/html' - }, sock=sock, body='0123456789') - - time.sleep(0.2) + self.wait_for_record(r'"POST / HTTP/1.1" 200 5'), 'keepalive 1' + ) + + resp = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'Content-Type': 'text/html', + }, + sock=sock, + body='0123456789', + ) self.stop() self.assertIsNotNone( - self.search_in_log(r'"POST / HTTP/1.1" 200 10'), 'keepalive 2') + self.wait_for_record(r'"POST / HTTP/1.1" 200 10'), 'keepalive 2' + ) def test_access_log_pipeline(self): self.load('empty') - self.http(b"""GET / HTTP/1.1 + self.http( + b"""GET / HTTP/1.1 Host: localhost Referer: Referer-1 @@ -62,180 +70,192 @@ Host: localhost Referer: Referer-3 Connection: close -""", raw_resp=True, raw=True) - - time.sleep(0.2) +""", + raw_resp=True, + raw=True, + ) self.stop() self.assertIsNotNone( - self.search_in_log(r'"GET / HTTP/1.1" 200 0 "Referer-1" "-"'), - 'pipeline 1') + self.wait_for_record(r'"GET / HTTP/1.1" 200 0 "Referer-1" "-"'), + 'pipeline 1', + ) self.assertIsNotNone( - self.search_in_log(r'"GET / HTTP/1.1" 200 0 "Referer-2" "-"'), - 'pipeline 2') + self.wait_for_record(r'"GET / HTTP/1.1" 200 0 "Referer-2" "-"'), + 'pipeline 2', + ) self.assertIsNotNone( - self.search_in_log(r'"GET / HTTP/1.1" 200 0 "Referer-3" "-"'), - 'pipeline 3') + self.wait_for_record(r'"GET / HTTP/1.1" 200 0 "Referer-3" "-"'), + 'pipeline 3', + ) def test_access_log_ipv6(self): self.load('empty') - self.conf({ - "[::1]:7080": { - "application": "empty" - } - }, 'listeners') + self.conf({"[::1]:7080": {"pass": "applications/empty"}}, 'listeners') self.get(sock_type='ipv6') - time.sleep(0.2) - self.stop() self.assertIsNotNone( - self.search_in_log( - r'::1 - - \[.+\] "GET / HTTP/1.1" 200 0 "-" "-"'), 'ipv6') + self.wait_for_record( + r'::1 - - \[.+\] "GET / HTTP/1.1" 200 0 "-" "-"' + ), + 'ipv6', + ) def test_access_log_unix(self): self.load('empty') addr = self.testdir + '/sock' - self.conf({ - "unix:" + addr: { - "application": "empty" - } - }, 'listeners') + self.conf({"unix:" + addr: {"pass": "applications/empty"}}, 'listeners') self.get(sock_type='unix', addr=addr) - time.sleep(0.2) - self.stop() - self.assertIsNotNone(self.search_in_log( - r'unix: - - \[.+\] "GET / HTTP/1.1" 200 0 "-" "-"'), 'unix') + self.assertIsNotNone( + self.wait_for_record( + r'unix: - - \[.+\] "GET / HTTP/1.1" 200 0 "-" "-"' + ), + 'unix', + ) def test_access_log_referer(self): self.load('empty') - self.get(headers={ - 'Host': 'localhost', - 'Referer': 'referer-value', - 'Connection': 'close' - }) - - time.sleep(0.2) + self.get( + headers={ + 'Host': 'localhost', + 'Referer': 'referer-value', + 'Connection': 'close', + } + ) self.stop() self.assertIsNotNone( - self.search_in_log(r'"GET / HTTP/1.1" 200 0 "referer-value" "-"'), - 'referer') + self.wait_for_record( + r'"GET / HTTP/1.1" 200 0 "referer-value" "-"' + ), + 'referer', + ) def test_access_log_user_agent(self): self.load('empty') - self.get(headers={ - 'Host': 'localhost', - 'User-Agent': 'user-agent-value', - 'Connection': 'close' - }) - - time.sleep(0.2) + self.get( + headers={ + 'Host': 'localhost', + 'User-Agent': 'user-agent-value', + 'Connection': 'close', + } + ) self.stop() self.assertIsNotNone( - self.search_in_log( - r'"GET / HTTP/1.1" 200 0 "-" "user-agent-value"'), 'user agent') + self.wait_for_record( + r'"GET / HTTP/1.1" 200 0 "-" "user-agent-value"' + ), + 'user agent', + ) def test_access_log_http10(self): self.load('empty') self.get(http_10=True) - time.sleep(0.2) - self.stop() self.assertIsNotNone( - self.search_in_log( - r'"GET / HTTP/1.0" 200 0 "-" "-"'), 'http 1.0') + self.wait_for_record(r'"GET / HTTP/1.0" 200 0 "-" "-"'), 'http 1.0' + ) def test_access_log_partial(self): self.load('empty') - self.http(b"""GE""", raw=True) + self.assertEqual(self.post()['status'], 200, 'init') - time.sleep(0.2) + resp = self.http(b"""GE""", raw=True, read_timeout=5) self.stop() self.assertIsNotNone( - self.search_in_log(r'"GE" 400 0 "-" "-"'), 'partial') + self.wait_for_record(r'"GE" 400 0 "-" "-"'), 'partial' + ) def test_access_log_partial_2(self): self.load('empty') - self.http(b"""GET /\n""", raw=True) + self.assertEqual(self.post()['status'], 200, 'init') - time.sleep(0.2) + self.http(b"""GET /\n""", raw=True) self.stop() self.assertIsNotNone( - self.search_in_log(r'"GET /" 400 \d+ "-" "-"'), 'partial 2') + self.wait_for_record(r'"GET /" 400 \d+ "-" "-"'), 'partial 2' + ) def test_access_log_partial_3(self): self.load('empty') - self.http(b"""GET / HTTP/1.1""", raw=True) + self.assertEqual(self.post()['status'], 200, 'init') - time.sleep(0.2) + resp = self.http(b"""GET / HTTP/1.1""", raw=True, read_timeout=5) self.stop() self.assertIsNotNone( - self.search_in_log(r'"GET /" 400 0 "-" "-"'), 'partial 3') + self.wait_for_record(r'"GET /" 400 0 "-" "-"'), 'partial 3' + ) def test_access_log_partial_4(self): self.load('empty') - resp = self.http(b"""GET / HTTP/1.1\n""", raw=True) + self.assertEqual(self.post()['status'], 200, 'init') - time.sleep(0.2) + resp = self.http(b"""GET / HTTP/1.1\n""", raw=True, read_timeout=5) self.stop() self.assertIsNotNone( - self.search_in_log(r'"GET / HTTP/1.1" 400 0 "-" "-"'), - 'partial 4') + self.wait_for_record(r'"GET / HTTP/1.1" 400 0 "-" "-"'), + 'partial 4', + ) + @unittest.skip('not yet') def test_access_log_partial_5(self): self.load('empty') - self.http(b"""GET / HTTP/1.1\n\n""", raw=True) + self.assertEqual(self.post()['status'], 200, 'init') + + self.get(headers={'Connection': 'close'}) self.stop() self.assertIsNotNone( - self.search_in_log(r'"GET / HTTP/1.1" 200 0 "-" "-"'), 'partial 5') + self.wait_for_record(r'"GET / HTTP/1.1" 400 \d+ "-" "-"'), + 'partial 5', + ) def test_access_log_get_parameters(self): self.load('empty') self.get(url='/?blah&var=val') - time.sleep(0.2) - self.stop() self.assertIsNotNone( - self.search_in_log( - r'"GET /\?blah&var=val HTTP/1.1" 200 0 "-" "-"'), - 'get parameters') + self.wait_for_record( + r'"GET /\?blah&var=val HTTP/1.1" 200 0 "-" "-"' + ), + 'get parameters', + ) def test_access_log_delete(self): self.load('empty') @@ -244,11 +264,11 @@ Connection: close self.get(url='/delete') - time.sleep(0.2) - self.stop() - self.assertIsNone(self.search_in_log(r'/delete'), 'delete') + self.assertIsNone( + self.search_in_log(r'/delete', 'access.log'), 'delete' + ) def test_access_log_change(self): self.load('empty') @@ -259,13 +279,12 @@ Connection: close self.get() - time.sleep(0.2) - self.stop() self.assertIsNotNone( - self.search_in_log(r'"GET / HTTP/1.1" 200 0 "-" "-"', 'new.log'), - 'change') + self.wait_for_record(r'"GET / HTTP/1.1" 200 0 "-" "-"', 'new.log'), + 'change', + ) def test_access_log_reopen(self): self.load('empty') @@ -280,11 +299,10 @@ Connection: close self.get() - time.sleep(0.2) - self.assertIsNotNone( - self.search_in_log(r'"GET / HTTP/1.1" 200 0 "-" "-"', 'new.log'), - 'rename new') + self.wait_for_record(r'"GET / HTTP/1.1" 200 0 "-" "-"', 'new.log'), + 'rename new', + ) self.assertFalse(os.path.isfile(log_path), 'rename old') with open(self.testdir + '/unit.pid', 'r') as f: @@ -296,13 +314,14 @@ Connection: close self.get(url='/usr1') - time.sleep(0.2) - - self.assertIsNone( - self.search_in_log(r'/usr1', 'new.log'), 'rename new 2') self.assertIsNotNone( - self.search_in_log(r'"GET /usr1 HTTP/1.1" 200 0 "-" "-"'), - 'reopen 2') + self.wait_for_record(r'"GET /usr1 HTTP/1.1" 200 0 "-" "-"'), + 'reopen 2', + ) + self.assertIsNone( + self.search_in_log(r'/usr1', 'new.log'), 'rename new 2' + ) + if __name__ == '__main__': - TestUnitAccessLog.main() + TestAccessLog.main() diff --git a/test/test_configuration.py b/test/test_configuration.py index 52a67d38..6e59c0a7 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -1,10 +1,9 @@ import unittest -import unit +from unit.control import TestControl -class TestUnitConfiguration(unit.TestUnitControl): - def setUpClass(): - unit.TestUnit().check_modules('python') +class TestConfiguration(TestControl): + prerequisites = ['python'] def test_json_empty(self): self.assertIn('error', self.conf(''), 'empty') @@ -13,7 +12,10 @@ class TestUnitConfiguration(unit.TestUnitControl): self.assertIn('error', self.conf('00'), 'leading zero') def test_json_unicode(self): - self.assertIn('success', self.conf(b""" + self.assertIn( + 'success', + self.conf( + b""" { "ap\u0070": { "type": "\u0070ython", @@ -22,32 +24,51 @@ class TestUnitConfiguration(unit.TestUnitControl): "module": "wsgi" } } - """, 'applications'), 'unicode') - - self.assertDictEqual(self.conf_get('applications'), { - "app": { - "type": "python", - "processes": { "spare": 0 }, - "path": "/app", - "module": "wsgi" - } - }, 'unicode get') + """, + 'applications', + ), + 'unicode', + ) + + self.assertDictEqual( + self.conf_get('applications'), + { + "app": { + "type": "python", + "processes": {"spare": 0}, + "path": "/app", + "module": "wsgi", + } + }, + 'unicode get', + ) def test_json_unicode_2(self): - self.assertIn('success', self.conf({ - "приложение": { - "type": "python", - "processes": { "spare": 0 }, - "path": "/app", - "module": "wsgi" - } - }, 'applications'), 'unicode 2') - - self.assertIn('приложение', self.conf_get('applications'), - 'unicode 2 get') + self.assertIn( + 'success', + self.conf( + { + "приложение": { + "type": "python", + "processes": {"spare": 0}, + "path": "/app", + "module": "wsgi", + } + }, + 'applications', + ), + 'unicode 2', + ) + + self.assertIn( + 'приложение', self.conf_get('applications'), 'unicode 2 get' + ) def test_json_unicode_number(self): - self.assertIn('error', self.conf(b""" + self.assertIn( + 'error', + self.conf( + b""" { "app": { "type": "python", @@ -56,7 +77,11 @@ class TestUnitConfiguration(unit.TestUnitControl): "module": "wsgi" } } - """, 'applications'), 'unicode number') + """, + 'applications', + ), + 'unicode number', + ) def test_applications_open_brace(self): self.assertIn('error', self.conf('{', 'applications'), 'open brace') @@ -64,21 +89,19 @@ class TestUnitConfiguration(unit.TestUnitControl): def test_applications_string(self): self.assertIn('error', self.conf('"{}"', 'applications'), 'string') + @unittest.skip('not yet, unsafe') def test_applications_type_only(self): - self.skip_alerts.extend([ - r'python module is empty', - r'failed to apply new conf', - r'process \d+ exited on signal' - ]) - - self.assertIn('error', self.conf({ - "app": { - "type": "python" - } - }, 'applications'), 'type only') + self.assertIn( + 'error', + self.conf({"app": {"type": "python"}}, 'applications'), + 'type only', + ) def test_applications_miss_quote(self): - self.assertIn('error', self.conf(""" + self.assertIn( + 'error', + self.conf( + """ { app": { "type": "python", @@ -87,10 +110,17 @@ class TestUnitConfiguration(unit.TestUnitControl): "module": "wsgi" } } - """, 'applications'), 'miss quote') + """, + 'applications', + ), + 'miss quote', + ) def test_applications_miss_colon(self): - self.assertIn('error', self.conf(""" + self.assertIn( + 'error', + self.conf( + """ { "app" { "type": "python", @@ -99,10 +129,17 @@ class TestUnitConfiguration(unit.TestUnitControl): "module": "wsgi" } } - """, 'applications'), 'miss colon') + """, + 'applications', + ), + 'miss colon', + ) def test_applications_miss_comma(self): - self.assertIn('error', self.conf(""" + self.assertIn( + 'error', + self.conf( + """ { "app": { "type": "python" @@ -111,158 +148,184 @@ class TestUnitConfiguration(unit.TestUnitControl): "module": "wsgi" } } - """, 'applications'), 'miss comma') + """, + 'applications', + ), + 'miss comma', + ) def test_applications_skip_spaces(self): - self.assertIn('success', self.conf(b'{ \n\r\t}', 'applications'), - 'skip spaces') + self.assertIn( + 'success', self.conf(b'{ \n\r\t}', 'applications'), 'skip spaces' + ) def test_applications_relative_path(self): - self.assertIn('success', self.conf({ - "app": { - "type": "python", - "processes": { "spare": 0 }, - "path": "../app", - "module": "wsgi" - } - }, 'applications'), 'relative path') - - @unittest.expectedFailure + self.assertIn( + 'success', + self.conf( + { + "app": { + "type": "python", + "processes": {"spare": 0}, + "path": "../app", + "module": "wsgi", + } + }, + 'applications', + ), + 'relative path', + ) + + @unittest.skip('not yet, unsafe') def test_listeners_empty(self): - self.skip_sanitizer = True - self.skip_alerts.extend([ - r'failed to apply previous configuration', - r'process \d+ exited on signal' - ]) - - self.assertIn('error', self.conf({"*:7080":{}}, 'listeners'), - 'listener empty') + self.assertIn( + 'error', self.conf({"*:7080": {}}, 'listeners'), 'listener empty' + ) def test_listeners_no_app(self): - self.assertIn('error', self.conf({"*:7080":{"application":"app"}}, - 'listeners'), 'listeners no app') + self.assertIn( + 'error', + self.conf({"*:7080": {"pass": "applications/app"}}, 'listeners'), + 'listeners no app', + ) def test_listeners_wildcard(self): - self.assertIn('success', self.conf({ - "listeners": { - "*:7080": { - "application":"app" + self.assertIn( + 'success', + self.conf( + { + "listeners": {"*:7080": {"pass": "applications/app"}}, + "applications": { + "app": { + "type": "python", + "processes": {"spare": 0}, + "path": "/app", + "module": "wsgi", + } + }, } - }, - "applications": { - "app": { - "type": "python", - "processes": { "spare": 0 }, - "path": "/app", - "module": "wsgi" - } - } - }), 'listeners wildcard') + ), + 'listeners wildcard', + ) def test_listeners_explicit(self): - self.assertIn('success', self.conf({ - "listeners": { - "127.0.0.1:7080": { - "application":"app" + self.assertIn( + 'success', + self.conf( + { + "listeners": {"127.0.0.1:7080": {"pass": "applications/app"}}, + "applications": { + "app": { + "type": "python", + "processes": {"spare": 0}, + "path": "/app", + "module": "wsgi", + } + }, } - }, - "applications": { - "app": { - "type": "python", - "processes": { "spare": 0 }, - "path": "/app", - "module": "wsgi" - } - } - }), 'explicit') + ), + 'explicit', + ) def test_listeners_explicit_ipv6(self): - self.assertIn('success', self.conf({ - "listeners": { - "[::1]:7080": { - "application":"app" - } - }, - "applications": { - "app": { - "type": "python", - "processes": { "spare": 0 }, - "path": "/app", - "module": "wsgi" + self.assertIn( + 'success', + self.conf( + { + "listeners": {"[::1]:7080": {"pass": "applications/app"}}, + "applications": { + "app": { + "type": "python", + "processes": {"spare": 0}, + "path": "/app", + "module": "wsgi", + } + }, } - } - }), 'explicit ipv6') + ), + 'explicit ipv6', + ) + @unittest.skip('not yet, unsafe') def test_listeners_no_port(self): - self.skip_alerts.extend([ - r'invalid listener "127\.0\.0\.1"', - r'failed to apply new conf', - r'process \d+ exited on signal' - ]) - - self.assertIn('error', self.conf({ - "listeners": { - "127.0.0.1": { - "application":"app" - } - }, - "applications": { - "app": { - "type": "python", - "processes": { "spare": 0 }, - "path": "/app", - "module": "wsgi" + self.assertIn( + 'error', + self.conf( + { + "listeners": {"127.0.0.1": {"pass": "applications/app"}}, + "applications": { + "app": { + "type": "python", + "processes": {"spare": 0}, + "path": "/app", + "module": "wsgi", + } + }, } - } - }), 'no port') + ), + 'no port', + ) - @unittest.expectedFailure def test_json_application_name_large(self): - self.skip_alerts.append(r'epoll_ctl.+failed') name = "X" * 1024 * 1024 - self.assertIn('success', self.conf({ - "listeners": { - "*:7080": { - "application": name + self.assertIn( + 'success', + self.conf( + { + "listeners": {"*:7080": {"pass": "applications/" + name}}, + "applications": { + name: { + "type": "python", + "processes": {"spare": 0}, + "path": "/app", + "module": "wsgi", + } + }, } - }, + ), + ) + + @unittest.skip('not yet') + def test_json_application_many(self): + apps = 999 + + conf = { "applications": { - name: { + "app-" + + str(a): { "type": "python", - "processes": { "spare": 0 }, + "processes": {"spare": 0}, "path": "/app", - "module": "wsgi" + "module": "wsgi", } - } - })) + for a in range(apps) + }, + "listeners": { + "*:" + str(7000 + a): {"pass": "applications/app-" + str(a)} + for a in range(apps) + }, + } - @unittest.expectedFailure - def test_json_application_many(self): - self.skip_alerts.extend([ - r'eventfd.+failed', - r'epoll_create.+failed', - r'failed to apply new conf' - ]) - apps = 999 + self.assertIn('success', self.conf(conf)) + def test_json_application_many2(self): conf = { - "applications": - {"app-" + str(a): { + "applications": { + "app-" + + str(a): { "type": "python", - "processes": { "spare": 0 }, + "processes": {"spare": 0}, "path": "/app", - "module": "wsgi" - } for a in range(apps) + "module": "wsgi", + } + for a in range(999) }, - "listeners": { - "*:" + str(7000 + a): { - "application": "app-" + str(a) - } for a in range(apps) - } + "listeners": {"*:7001": {"pass": "applications/app-1"}}, } self.assertIn('success', self.conf(conf)) + if __name__ == '__main__': - TestUnitConfiguration.main() + TestConfiguration.main() diff --git a/test/test_go_application.py b/test/test_go_application.py index 1ecc2536..488bfdd5 100644 --- a/test/test_go_application.py +++ b/test/test_go_application.py @@ -1,22 +1,23 @@ -import unittest -import unit +from unit.applications.lang.go import TestApplicationGo -class TestUnitGoApplication(unit.TestUnitApplicationGo): - def setUpClass(): - unit.TestUnit().check_modules('go') +class TestGoApplication(TestApplicationGo): + prerequisites = ['go'] def test_go_application_variables(self): self.load('variables') body = 'Test body string.' - resp = self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'Custom-Header': 'blah', - 'Connection': 'close' - }, body=body) + resp = self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Custom-Header': 'blah', + 'Connection': 'close', + }, + body=body, + ) self.assertEqual(resp['status'], 200, 'status') headers = resp['headers'] @@ -25,21 +26,28 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo): date = headers.pop('Date') self.assertEqual(date[-4:], ' GMT', 'date header timezone') - self.assertLess(abs(self.date_to_sec_epoch(date) - self.sec_epoch()), 5, - 'date header') - - self.assertDictEqual(headers, { - 'Content-Length': str(len(body)), - 'Content-Type': 'text/html', - 'Request-Method': 'POST', - 'Request-Uri': '/', - 'Http-Host': 'localhost', - 'Server-Protocol': 'HTTP/1.1', - 'Server-Protocol-Major': '1', - 'Server-Protocol-Minor': '1', - 'Custom-Header': 'blah', - 'Connection': 'close' - }, 'headers') + self.assertLess( + abs(self.date_to_sec_epoch(date) - self.sec_epoch()), + 5, + 'date header', + ) + + self.assertDictEqual( + headers, + { + 'Content-Length': str(len(body)), + 'Content-Type': 'text/html', + 'Request-Method': 'POST', + 'Request-Uri': '/', + 'Http-Host': 'localhost', + 'Server-Protocol': 'HTTP/1.1', + 'Server-Protocol-Major': '1', + 'Server-Protocol-Minor': '1', + 'Custom-Header': 'blah', + 'Connection': 'close', + }, + 'headers', + ) self.assertEqual(resp['body'], body, 'body') def test_go_application_get_variables(self): @@ -53,11 +61,14 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo): def test_go_application_post_variables(self): self.load('post_variables') - resp = self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'application/x-www-form-urlencoded', - 'Connection': 'close' - }, body='var1=val1&var2=&var3') + resp = self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'application/x-www-form-urlencoded', + 'Connection': 'close', + }, + body='var1=val1&var2=&var3', + ) self.assertEqual(resp['headers']['X-Var-1'], 'val1', 'POST variables') self.assertEqual(resp['headers']['X-Var-2'], '', 'POST variables 2') @@ -69,36 +80,50 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo): resp = self.get() self.assertEqual(resp['status'], 404, '404 status') - self.assertRegex(resp['body'], r'<title>404 Not Found</title>', - '404 body') + self.assertRegex( + resp['body'], r'<title>404 Not Found</title>', '404 body' + ) def test_go_keepalive_body(self): self.load('mirror') - (resp, sock) = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive', - 'Content-Type': 'text/html' - }, start=True, body='0123456789' * 500) + self.assertEqual(self.get()['status'], 200, 'init') + + (resp, sock) = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + }, + start=True, + body='0123456789' * 500, + read_timeout=1, + ) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') - resp = self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'Connection': 'close' - }, sock=sock, body='0123456789') + resp = self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Connection': 'close', + }, + sock=sock, + body='0123456789', + ) self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') def test_go_application_cookies(self): self.load('cookies') - resp = self.get(headers={ - 'Host': 'localhost', - 'Cookie': 'var1=val1; var2=val2', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'var1=val1; var2=val2', + 'Connection': 'close', + } + ) self.assertEqual(resp['headers']['X-Cookie-1'], 'val1', 'cookie 1') self.assertEqual(resp['headers']['X-Cookie-2'], 'val2', 'cookie 2') @@ -106,15 +131,22 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo): def test_go_application_command_line_arguments_type(self): self.load('command_line_arguments') - self.assertIn('error', self.conf(''"a b c", - 'applications/command_line_arguments/arguments'), 'arguments type') + self.assertIn( + 'error', + 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') - self.assertEqual(self.get()['headers']['X-Arg-0'], + self.assertEqual( + self.get()['headers']['X-Arg-0'], self.conf_get('applications/command_line_arguments/executable'), - 'argument 0') + 'argument 0', + ) def test_go_application_command_line_arguments(self): self.load('command_line_arguments') @@ -123,11 +155,14 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo): arg2 = '--cc-opt=\'-O0 -DNXT_DEBUG_MEMORY=1 -fsanitize=address\'' arg3 = '--debug' - self.conf('["' + arg1 + '", "' + arg2 + '", "' + arg3 + '"]', - 'applications/command_line_arguments/arguments') + self.conf( + '["' + arg1 + '", "' + arg2 + '", "' + arg3 + '"]', + 'applications/command_line_arguments/arguments', + ) - self.assertEqual(self.get()['body'], arg1 + ',' + arg2 + ',' + arg3, - 'arguments') + self.assertEqual( + self.get()['body'], arg1 + ',' + arg2 + ',' + arg3, 'arguments' + ) def test_go_application_command_line_arguments_change(self): self.load('command_line_arguments') @@ -144,8 +179,10 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo): self.conf('[]', args_path) - self.assertEqual(self.get()['headers']['Content-Length'], '0', - 'arguments empty') + self.assertEqual( + self.get()['headers']['Content-Length'], '0', 'arguments empty' + ) + if __name__ == '__main__': - TestUnitGoApplication.main() + TestGoApplication.main() diff --git a/test/test_http_header.py b/test/test_http_header.py index f2294371..603f6f0f 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -1,363 +1,482 @@ import unittest -import unit +from unit.applications.lang.python import TestApplicationPython -class TestUnitHTTPHeader(unit.TestUnitApplicationPython): - def setUpClass(): - unit.TestUnit().check_modules('python') +class TestHTTPHeader(TestApplicationPython): + prerequisites = ['python'] def test_http_header_value_leading_sp(self): self.load('custom_header') - resp = self.get(headers={ - 'Host': 'localhost', - 'Custom-Header': ' ,', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Custom-Header': ' ,', + 'Connection': 'close', + } + ) self.assertEqual(resp['status'], 200, 'value leading sp status') - self.assertEqual(resp['headers']['Custom-Header'], ',', - 'value leading sp custom header') + self.assertEqual( + resp['headers']['Custom-Header'], + ',', + 'value leading sp custom header', + ) def test_http_header_value_leading_htab(self): self.load('custom_header') - resp = self.get(headers={ - 'Host': 'localhost', - 'Custom-Header': '\t,', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Custom-Header': '\t,', + 'Connection': 'close', + } + ) self.assertEqual(resp['status'], 200, 'value leading htab status') - self.assertEqual(resp['headers']['Custom-Header'], ',', - 'value leading htab custom header') + self.assertEqual( + resp['headers']['Custom-Header'], + ',', + 'value leading htab custom header', + ) def test_http_header_value_trailing_sp(self): self.load('custom_header') - resp = self.get(headers={ - 'Host': 'localhost', - 'Custom-Header': ', ', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Custom-Header': ', ', + 'Connection': 'close', + } + ) self.assertEqual(resp['status'], 200, 'value trailing sp status') - self.assertEqual(resp['headers']['Custom-Header'], ',', - 'value trailing sp custom header') + self.assertEqual( + resp['headers']['Custom-Header'], + ',', + 'value trailing sp custom header', + ) def test_http_header_value_trailing_htab(self): self.load('custom_header') - resp = self.get(headers={ - 'Host': 'localhost', - 'Custom-Header': ',\t', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Custom-Header': ',\t', + 'Connection': 'close', + } + ) self.assertEqual(resp['status'], 200, 'value trailing htab status') - self.assertEqual(resp['headers']['Custom-Header'], ',', - 'value trailing htab custom header') + self.assertEqual( + resp['headers']['Custom-Header'], + ',', + 'value trailing htab custom header', + ) def test_http_header_value_both_sp(self): self.load('custom_header') - resp = self.get(headers={ - 'Host': 'localhost', - 'Custom-Header': ' , ', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Custom-Header': ' , ', + 'Connection': 'close', + } + ) self.assertEqual(resp['status'], 200, 'value both sp status') - self.assertEqual(resp['headers']['Custom-Header'], ',', - 'value both sp custom header') + self.assertEqual( + resp['headers']['Custom-Header'], + ',', + 'value both sp custom header', + ) def test_http_header_value_both_htab(self): self.load('custom_header') - resp = self.get(headers={ - 'Host': 'localhost', - 'Custom-Header': '\t,\t', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Custom-Header': '\t,\t', + 'Connection': 'close', + } + ) self.assertEqual(resp['status'], 200, 'value both htab status') - self.assertEqual(resp['headers']['Custom-Header'], ',', - 'value both htab custom header') + self.assertEqual( + resp['headers']['Custom-Header'], + ',', + 'value both htab custom header', + ) def test_http_header_value_chars(self): self.load('custom_header') - resp = self.get(headers={ - 'Host': 'localhost', - 'Custom-Header': '(),/:;<=>?@[\]{}\t !#$%&\'*+-.^_`|~', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Custom-Header': '(),/:;<=>?@[\]{}\t !#$%&\'*+-.^_`|~', + 'Connection': 'close', + } + ) self.assertEqual(resp['status'], 200, 'value chars status') - self.assertEqual(resp['headers']['Custom-Header'], - '(),/:;<=>?@[\]{}\t !#$%&\'*+-.^_`|~', 'value chars custom header') + self.assertEqual( + resp['headers']['Custom-Header'], + '(),/:;<=>?@[\]{}\t !#$%&\'*+-.^_`|~', + 'value chars custom header', + ) def test_http_header_value_chars_edge(self): self.load('custom_header') - resp = self.http(b"""GET / HTTP/1.1 + resp = self.http( + b"""GET / HTTP/1.1 Host: localhost Custom-Header: \x20\xFF Connection: close -""", raw=True, encoding='latin1') +""", + raw=True, + encoding='latin1', + ) self.assertEqual(resp['status'], 200, 'value chars edge status') - self.assertEqual(resp['headers']['Custom-Header'], '\xFF', - 'value chars edge') + self.assertEqual( + resp['headers']['Custom-Header'], '\xFF', 'value chars edge' + ) def test_http_header_value_chars_below(self): self.load('custom_header') - resp = self.http(b"""GET / HTTP/1.1 + resp = self.http( + b"""GET / HTTP/1.1 Host: localhost Custom-Header: \x1F Connection: close -""", raw=True) +""", + raw=True, + ) self.assertEqual(resp['status'], 400, 'value chars below') def test_http_header_field_leading_sp(self): self.load('empty') - resp = self.get(headers={ - 'Host': 'localhost', - ' Custom-Header': 'blah', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + ' Custom-Header': 'blah', + 'Connection': 'close', + } + ) self.assertEqual(resp['status'], 400, 'field leading sp') def test_http_header_field_leading_htab(self): self.load('empty') - resp = self.get(headers={ - 'Host': 'localhost', - '\tCustom-Header': 'blah', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + '\tCustom-Header': 'blah', + 'Connection': 'close', + } + ) self.assertEqual(resp['status'], 400, 'field leading htab') def test_http_header_field_trailing_sp(self): self.load('empty') - resp = self.get(headers={ - 'Host': 'localhost', - 'Custom-Header ': 'blah', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Custom-Header ': 'blah', + 'Connection': 'close', + } + ) self.assertEqual(resp['status'], 400, 'field trailing sp') def test_http_header_field_trailing_htab(self): self.load('empty') - resp = self.get(headers={ - 'Host': 'localhost', - 'Custom-Header\t': 'blah', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Custom-Header\t': 'blah', + 'Connection': 'close', + } + ) self.assertEqual(resp['status'], 400, 'field trailing htab') def test_http_header_content_length_big(self): self.load('empty') - self.assertEqual(self.post(headers={ - 'Host': 'localhost', - 'Content-Length': str(2 ** 64), - 'Connection': 'close' - }, body='X' * 1000)['status'], 400, 'Content-Length big') + self.assertEqual( + self.post( + headers={ + 'Host': 'localhost', + 'Content-Length': str(2 ** 64), + 'Connection': 'close', + }, + body='X' * 1000, + )['status'], + 400, + 'Content-Length big', + ) def test_http_header_content_length_negative(self): self.load('empty') - self.assertEqual(self.post(headers={ - 'Host': 'localhost', - 'Content-Length': '-100', - 'Connection': 'close' - }, body='X' * 1000)['status'], 400, 'Content-Length negative') + self.assertEqual( + self.post( + headers={ + 'Host': 'localhost', + 'Content-Length': '-100', + 'Connection': 'close', + }, + body='X' * 1000, + )['status'], + 400, + 'Content-Length negative', + ) def test_http_header_content_length_text(self): self.load('empty') - self.assertEqual(self.post(headers={ - 'Host': 'localhost', - 'Content-Length': 'blah', - 'Connection': 'close' - }, body='X' * 1000)['status'], 400, 'Content-Length text') + self.assertEqual( + self.post( + headers={ + 'Host': 'localhost', + 'Content-Length': 'blah', + 'Connection': 'close', + }, + body='X' * 1000, + )['status'], + 400, + 'Content-Length text', + ) def test_http_header_content_length_multiple_values(self): self.load('empty') - self.assertEqual(self.post(headers={ - 'Host': 'localhost', - 'Content-Length': '41, 42', - 'Connection': 'close' - }, body='X' * 1000)['status'], 400, 'Content-Length multiple value') + self.assertEqual( + self.post( + headers={ + 'Host': 'localhost', + 'Content-Length': '41, 42', + 'Connection': 'close', + }, + body='X' * 1000, + )['status'], + 400, + 'Content-Length multiple value', + ) def test_http_header_content_length_multiple_fields(self): self.load('empty') - self.assertEqual(self.post(headers={ - 'Host': 'localhost', - 'Content-Length': ['41', '42'], - 'Connection': 'close' - }, body='X' * 1000)['status'], 400, 'Content-Length multiple fields') - + self.assertEqual( + self.post( + headers={ + 'Host': 'localhost', + 'Content-Length': ['41', '42'], + 'Connection': 'close', + }, + body='X' * 1000, + )['status'], + 400, + 'Content-Length multiple fields', + ) + + @unittest.skip('not yet') def test_http_header_host_absent(self): self.load('host') resp = self.get(headers={'Connection': 'close'}) - self.assertEqual(resp['status'], 200, 'Host absent status') - self.assertNotEqual(resp['headers']['X-Server-Name'], '', - 'Host absent SERVER_NAME') + self.assertEqual(resp['status'], 400, 'Host absent status') def test_http_header_host_empty(self): self.load('host') - resp = self.get(headers={ - 'Host': '', - 'Connection': 'close' - }) + resp = self.get(headers={'Host': '', 'Connection': 'close'}) self.assertEqual(resp['status'], 200, 'Host empty status') - self.assertNotEqual(resp['headers']['X-Server-Name'], '', - 'Host empty SERVER_NAME') + self.assertNotEqual( + resp['headers']['X-Server-Name'], '', 'Host empty SERVER_NAME' + ) def test_http_header_host_big(self): self.load('empty') - self.assertEqual(self.get(headers={ - 'Host': 'X' * 10000, - 'Connection': 'close' - })['status'], 431, 'Host big') + self.assertEqual( + self.get(headers={'Host': 'X' * 10000, 'Connection': 'close'})[ + 'status' + ], + 431, + 'Host big', + ) def test_http_header_host_port(self): self.load('host') - resp = self.get(headers={ - 'Host': 'exmaple.com:7080', - 'Connection': 'close' - }) + resp = self.get( + headers={'Host': 'exmaple.com:7080', 'Connection': 'close'} + ) self.assertEqual(resp['status'], 200, 'Host port status') - self.assertEqual(resp['headers']['X-Server-Name'], 'exmaple.com', - 'Host port SERVER_NAME') - self.assertEqual(resp['headers']['X-Http-Host'], 'exmaple.com:7080', - 'Host port HTTP_HOST') + self.assertEqual( + resp['headers']['X-Server-Name'], + 'exmaple.com', + 'Host port SERVER_NAME', + ) + self.assertEqual( + resp['headers']['X-Http-Host'], + 'exmaple.com:7080', + 'Host port HTTP_HOST', + ) def test_http_header_host_port_empty(self): self.load('host') - resp = self.get(headers={ - 'Host': 'exmaple.com:', - 'Connection': 'close' - }) + resp = self.get( + headers={'Host': 'exmaple.com:', 'Connection': 'close'} + ) self.assertEqual(resp['status'], 200, 'Host port empty status') - self.assertEqual(resp['headers']['X-Server-Name'], 'exmaple.com', - 'Host port empty SERVER_NAME') - self.assertEqual(resp['headers']['X-Http-Host'], 'exmaple.com:', - 'Host port empty HTTP_HOST') + self.assertEqual( + resp['headers']['X-Server-Name'], + 'exmaple.com', + 'Host port empty SERVER_NAME', + ) + self.assertEqual( + resp['headers']['X-Http-Host'], + 'exmaple.com:', + 'Host port empty HTTP_HOST', + ) def test_http_header_host_literal(self): self.load('host') - resp = self.get(headers={ - 'Host': '127.0.0.1', - 'Connection': 'close' - }) + resp = self.get(headers={'Host': '127.0.0.1', 'Connection': 'close'}) self.assertEqual(resp['status'], 200, 'Host literal status') - self.assertEqual(resp['headers']['X-Server-Name'], '127.0.0.1', - 'Host literal SERVER_NAME') + self.assertEqual( + resp['headers']['X-Server-Name'], + '127.0.0.1', + 'Host literal SERVER_NAME', + ) def test_http_header_host_literal_ipv6(self): self.load('host') - resp = self.get(headers={ - 'Host': '[::1]:7080', - 'Connection': 'close' - }) + resp = self.get(headers={'Host': '[::1]:7080', 'Connection': 'close'}) self.assertEqual(resp['status'], 200, 'Host literal ipv6 status') - self.assertEqual(resp['headers']['X-Server-Name'], '[::1]', - 'Host literal ipv6 SERVER_NAME') - self.assertEqual(resp['headers']['X-Http-Host'], '[::1]:7080', - 'Host literal ipv6 HTTP_HOST') + self.assertEqual( + resp['headers']['X-Server-Name'], + '[::1]', + 'Host literal ipv6 SERVER_NAME', + ) + self.assertEqual( + resp['headers']['X-Http-Host'], + '[::1]:7080', + 'Host literal ipv6 HTTP_HOST', + ) def test_http_header_host_trailing_period(self): self.load('host') - resp = self.get(headers={ - 'Host': '127.0.0.1.', - 'Connection': 'close' - }) + resp = self.get(headers={'Host': '127.0.0.1.', 'Connection': 'close'}) self.assertEqual(resp['status'], 200, 'Host trailing period status') - self.assertEqual(resp['headers']['X-Server-Name'], '127.0.0.1', - 'Host trailing period SERVER_NAME') - self.assertEqual(resp['headers']['X-Http-Host'], '127.0.0.1.', - 'Host trailing period HTTP_HOST') + self.assertEqual( + resp['headers']['X-Server-Name'], + '127.0.0.1', + 'Host trailing period SERVER_NAME', + ) + self.assertEqual( + resp['headers']['X-Http-Host'], + '127.0.0.1.', + 'Host trailing period HTTP_HOST', + ) def test_http_header_host_trailing_period_2(self): self.load('host') - resp = self.get(headers={ - 'Host': 'EXAMPLE.COM.', - 'Connection': 'close' - }) + resp = self.get( + headers={'Host': 'EXAMPLE.COM.', 'Connection': 'close'} + ) self.assertEqual(resp['status'], 200, 'Host trailing period 2 status') - self.assertEqual(resp['headers']['X-Server-Name'], 'example.com', - 'Host trailing period 2 SERVER_NAME') - self.assertEqual(resp['headers']['X-Http-Host'], 'EXAMPLE.COM.', - 'Host trailing period 2 HTTP_HOST') + self.assertEqual( + resp['headers']['X-Server-Name'], + 'example.com', + 'Host trailing period 2 SERVER_NAME', + ) + self.assertEqual( + resp['headers']['X-Http-Host'], + 'EXAMPLE.COM.', + 'Host trailing period 2 HTTP_HOST', + ) def test_http_header_host_case_insensitive(self): self.load('host') - resp = self.get(headers={ - 'Host': 'EXAMPLE.COM', - 'Connection': 'close' - }) + resp = self.get(headers={'Host': 'EXAMPLE.COM', 'Connection': 'close'}) self.assertEqual(resp['status'], 200, 'Host case insensitive') - self.assertEqual(resp['headers']['X-Server-Name'], 'example.com', - 'Host case insensitive SERVER_NAME') + self.assertEqual( + resp['headers']['X-Server-Name'], + 'example.com', + 'Host case insensitive SERVER_NAME', + ) def test_http_header_host_double_dot(self): self.load('empty') - self.assertEqual(self.get(headers={ - 'Host': '127.0.0..1', - 'Connection': 'close' - })['status'], 400, 'Host double dot') + self.assertEqual( + self.get(headers={'Host': '127.0.0..1', 'Connection': 'close'})[ + 'status' + ], + 400, + 'Host double dot', + ) def test_http_header_host_slash(self): self.load('empty') - self.assertEqual(self.get(headers={ - 'Host': '/localhost', - 'Connection': 'close' - })['status'], 400, 'Host slash') + self.assertEqual( + self.get(headers={'Host': '/localhost', 'Connection': 'close'})[ + 'status' + ], + 400, + 'Host slash', + ) def test_http_header_host_multiple_fields(self): self.load('empty') - self.assertEqual(self.get(headers={ - 'Host': ['localhost', 'example.com'], - 'Connection': 'close' - })['status'], 400, 'Host multiple fields') + self.assertEqual( + self.get( + headers={ + 'Host': ['localhost', 'example.com'], + 'Connection': 'close', + } + )['status'], + 400, + 'Host multiple fields', + ) + if __name__ == '__main__': - TestUnitHTTPHeader.main() + TestHTTPHeader.main() diff --git a/test/test_java_application.py b/test/test_java_application.py index d603ed0f..5d0350fa 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -1,20 +1,20 @@ import time -import unittest -import unit +from unit.applications.lang.java import TestApplicationJava -class TestUnitJavaApplication(unit.TestUnitApplicationJava): - def setUpClass(): - unit.TestUnit().check_modules('java') +class TestJavaApplication(TestApplicationJava): + prerequisites = ['java'] def test_java_application_cookies(self): self.load('cookies') - headers = self.get(headers={ - 'Cookie': 'var1=val1; var2=val2', - 'Host': 'localhost', - 'Connection': 'close' - })['headers'] + headers = self.get( + headers={ + 'Cookie': 'var1=val1; var2=val2', + 'Host': 'localhost', + 'Connection': 'close', + } + )['headers'] self.assertEqual(headers['X-Cookie-1'], 'val1', 'cookie 1') self.assertEqual(headers['X-Cookie-2'], 'val2', 'cookie 2') @@ -27,33 +27,46 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): self.assertEqual(headers['X-Filter-Before'], '1', 'filter before') self.assertEqual(headers['X-Filter-After'], '1', 'filter after') - self.assertEqual(self.get(url='/test')['headers']['X-Filter-After'], - '0', 'filter after 2') + self.assertEqual( + self.get(url='/test')['headers']['X-Filter-After'], + '0', + 'filter after 2', + ) def test_java_application_get_variables(self): self.load('get_params') - headers = self.get(url='/?var1=val1&var2=&var4=val4&var4=foo')['headers'] + headers = self.get(url='/?var1=val1&var2=&var4=val4&var4=foo')[ + 'headers' + ] self.assertEqual(headers['X-Var-1'], 'val1', 'GET variables') self.assertEqual(headers['X-Var-2'], 'true', 'GET variables 2') self.assertEqual(headers['X-Var-3'], 'false', 'GET variables 3') - self.assertEqual(headers['X-Param-Names'], 'var4 var2 var1 ', - 'getParameterNames') - self.assertEqual(headers['X-Param-Values'], 'val4 foo ', - 'getParameterValues') - self.assertEqual(headers['X-Param-Map'], - 'var2= var1=val1 var4=val4,foo ', 'getParameterMap') + self.assertEqual( + headers['X-Param-Names'], 'var4 var2 var1 ', 'getParameterNames' + ) + self.assertEqual( + headers['X-Param-Values'], 'val4 foo ', 'getParameterValues' + ) + self.assertEqual( + headers['X-Param-Map'], + 'var2= var1=val1 var4=val4,foo ', + 'getParameterMap', + ) def test_java_application_post_variables(self): self.load('post_params') - headers = self.post(headers={ - 'Content-Type': 'application/x-www-form-urlencoded', - 'Host': 'localhost', - 'Connection': 'close' - }, body='var1=val1&var2=')['headers'] + headers = self.post( + headers={ + 'Content-Type': 'application/x-www-form-urlencoded', + 'Host': 'localhost', + 'Connection': 'close', + }, + body='var1=val1&var2=', + )['headers'] self.assertEqual(headers['X-Var-1'], 'val1', 'POST variables') self.assertEqual(headers['X-Var-2'], 'true', 'POST variables 2') @@ -68,15 +81,20 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): self.assertEqual(headers['X-Var-1'], 'null', 'variable empty') self.assertEqual(headers['X-Session-New'], 'true', 'session create') - headers = self.get(headers={ - 'Host': 'localhost', - 'Cookie': 'JSESSIONID=' + session_id, - 'Connection': 'close' - }, url='/?var1=val2')['headers'] + headers = self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close', + }, + url='/?var1=val2', + )['headers'] self.assertEqual(headers['X-Var-1'], 'val1', 'variable') self.assertEqual(headers['X-Session-New'], 'false', 'session resume') - self.assertEqual(session_id, headers['X-Session-Id'], 'session same id') + self.assertEqual( + session_id, headers['X-Session-Id'], 'session same id' + ) def test_java_application_session_active(self): self.load('session_inactive') @@ -85,46 +103,63 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): session_id = resp['headers']['X-Session-Id'] self.assertEqual(resp['status'], 200, 'session init') - self.assertEqual(resp['headers']['X-Session-Interval'], '2', - 'session interval') - self.assertLess(abs(self.date_to_sec_epoch( - resp['headers']['X-Session-Last-Access-Time']) - self.sec_epoch()), - 5, 'session last access time') + self.assertEqual( + resp['headers']['X-Session-Interval'], '2', 'session interval' + ) + self.assertLess( + abs( + self.date_to_sec_epoch( + resp['headers']['X-Session-Last-Access-Time'] + ) + - self.sec_epoch() + ), + 5, + 'session last access time', + ) time.sleep(1) - resp = self.get(headers={ - 'Host': 'localhost', - 'Cookie': 'JSESSIONID=' + session_id, - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close', + } + ) - self.assertEqual(resp['headers']['X-Session-Id'], session_id, - 'session active') + self.assertEqual( + resp['headers']['X-Session-Id'], session_id, 'session active' + ) session_id = resp['headers']['X-Session-Id'] time.sleep(1) - resp = self.get(headers={ - 'Host': 'localhost', - 'Cookie': 'JSESSIONID=' + session_id, - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close', + } + ) - self.assertEqual(resp['headers']['X-Session-Id'], session_id, - 'session active 2') + self.assertEqual( + resp['headers']['X-Session-Id'], session_id, 'session active 2' + ) time.sleep(1) - resp = self.get(headers={ - 'Host': 'localhost', - 'Cookie': 'JSESSIONID=' + session_id, - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close', + } + ) - self.assertEqual(resp['headers']['X-Session-Id'], session_id, - 'session active 3') + self.assertEqual( + resp['headers']['X-Session-Id'], session_id, 'session active 3' + ) def test_java_application_session_inactive(self): self.load('session_inactive') @@ -134,14 +169,17 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): time.sleep(3) - resp = self.get(headers={ - 'Host': 'localhost', - 'Cookie': 'JSESSIONID=' + session_id, - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close', + } + ) - self.assertNotEqual(resp['headers']['X-Session-Id'], session_id, - 'session inactive') + self.assertNotEqual( + resp['headers']['X-Session-Id'], session_id, 'session inactive' + ) def test_java_application_session_invalidate(self): self.load('session_invalidate') @@ -149,14 +187,17 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): resp = self.get() session_id = resp['headers']['X-Session-Id'] - resp = self.get(headers={ - 'Host': 'localhost', - 'Cookie': 'JSESSIONID=' + session_id, - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close', + } + ) - self.assertNotEqual(resp['headers']['X-Session-Id'], session_id, - 'session invalidate') + self.assertNotEqual( + resp['headers']['X-Session-Id'], session_id, 'session invalidate' + ) def test_java_application_session_listeners(self): self.load('session_listeners') @@ -164,30 +205,42 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): headers = self.get(url='/test?var1=val1')['headers'] session_id = headers['X-Session-Id'] - self.assertEqual(headers['X-Session-Created'], session_id, - 'session create') - self.assertEqual(headers['X-Attr-Added'], 'var1=val1', - 'attribute add') - - headers = self.get(headers={ - 'Host': 'localhost', - 'Cookie': 'JSESSIONID=' + session_id, - 'Connection': 'close' - }, url='/?var1=val2')['headers'] - - self.assertEqual(session_id, headers['X-Session-Id'], 'session same id') - self.assertEqual(headers['X-Attr-Replaced'], 'var1=val1', - 'attribute replace') + self.assertEqual( + headers['X-Session-Created'], session_id, 'session create' + ) + self.assertEqual(headers['X-Attr-Added'], 'var1=val1', 'attribute add') + + headers = self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close', + }, + url='/?var1=val2', + )['headers'] - headers = self.get(headers={ - 'Host': 'localhost', - 'Cookie': 'JSESSIONID=' + session_id, - 'Connection': 'close' - }, url='/')['headers'] + self.assertEqual( + session_id, headers['X-Session-Id'], 'session same id' + ) + self.assertEqual( + headers['X-Attr-Replaced'], 'var1=val1', 'attribute replace' + ) + + headers = self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'JSESSIONID=' + session_id, + 'Connection': 'close', + }, + url='/', + )['headers'] - self.assertEqual(session_id, headers['X-Session-Id'], 'session same id') - self.assertEqual(headers['X-Attr-Removed'], 'var1=val2', - 'attribute remove') + self.assertEqual( + session_id, headers['X-Session-Id'], 'session same id' + ) + self.assertEqual( + headers['X-Attr-Removed'], 'var1=val2', 'attribute remove' + ) def test_java_application_jsp(self): self.load('jsp') @@ -202,15 +255,23 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): headers = self.get(url='/foo/bar/index.html')['headers'] self.assertEqual(headers['X-Id'], 'servlet1', '#1 Servlet1 request') - self.assertEqual(headers['X-Request-URI'], '/foo/bar/index.html', '#1 request URI') - self.assertEqual(headers['X-Servlet-Path'], '/foo/bar', '#1 servlet path') + self.assertEqual( + headers['X-Request-URI'], '/foo/bar/index.html', '#1 request URI' + ) + self.assertEqual( + headers['X-Servlet-Path'], '/foo/bar', '#1 servlet path' + ) self.assertEqual(headers['X-Path-Info'], '/index.html', '#1 path info') headers = self.get(url='/foo/bar/index.bop')['headers'] self.assertEqual(headers['X-Id'], 'servlet1', '#2 Servlet1 request') - self.assertEqual(headers['X-Request-URI'], '/foo/bar/index.bop', '#2 request URI') - self.assertEqual(headers['X-Servlet-Path'], '/foo/bar', '#2 servlet path') + self.assertEqual( + headers['X-Request-URI'], '/foo/bar/index.bop', '#2 request URI' + ) + self.assertEqual( + headers['X-Servlet-Path'], '/foo/bar', '#2 servlet path' + ) self.assertEqual(headers['X-Path-Info'], '/index.bop', '#2 path info') headers = self.get(url='/baz')['headers'] @@ -223,42 +284,64 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): headers = self.get(url='/baz/index.html')['headers'] self.assertEqual(headers['X-Id'], 'servlet2', '#4 Servlet2 request') - self.assertEqual(headers['X-Request-URI'], '/baz/index.html', '#4 request URI') + self.assertEqual( + headers['X-Request-URI'], '/baz/index.html', '#4 request URI' + ) self.assertEqual(headers['X-Servlet-Path'], '/baz', '#4 servlet path') self.assertEqual(headers['X-Path-Info'], '/index.html', '#4 path info') headers = self.get(url='/catalog')['headers'] self.assertEqual(headers['X-Id'], 'servlet3', '#5 Servlet3 request') - self.assertEqual(headers['X-Request-URI'], '/catalog', '#5 request URI') - self.assertEqual(headers['X-Servlet-Path'], '/catalog', '#5 servlet path') + self.assertEqual( + headers['X-Request-URI'], '/catalog', '#5 request URI' + ) + self.assertEqual( + headers['X-Servlet-Path'], '/catalog', '#5 servlet path' + ) self.assertEqual(headers['X-Path-Info'], 'null', '#5 path info') headers = self.get(url='/catalog/index.html')['headers'] self.assertEqual(headers['X-Id'], 'default', '#6 default request') - self.assertEqual(headers['X-Request-URI'], '/catalog/index.html', '#6 request URI') - self.assertEqual(headers['X-Servlet-Path'], '/catalog/index.html', '#6 servlet path') + self.assertEqual( + headers['X-Request-URI'], '/catalog/index.html', '#6 request URI' + ) + self.assertEqual( + headers['X-Servlet-Path'], '/catalog/index.html', '#6 servlet path' + ) self.assertEqual(headers['X-Path-Info'], 'null', '#6 path info') headers = self.get(url='/catalog/racecar.bop')['headers'] self.assertEqual(headers['X-Id'], 'servlet4', '#7 servlet4 request') - self.assertEqual(headers['X-Request-URI'], '/catalog/racecar.bop', '#7 request URI') - self.assertEqual(headers['X-Servlet-Path'], '/catalog/racecar.bop', '#7 servlet path') + self.assertEqual( + headers['X-Request-URI'], '/catalog/racecar.bop', '#7 request URI' + ) + self.assertEqual( + headers['X-Servlet-Path'], + '/catalog/racecar.bop', + '#7 servlet path', + ) self.assertEqual(headers['X-Path-Info'], 'null', '#7 path info') - headers = self.get( url='/index.bop')['headers'] + headers = self.get(url='/index.bop')['headers'] self.assertEqual(headers['X-Id'], 'servlet4', '#8 servlet4 request') - self.assertEqual(headers['X-Request-URI'], '/index.bop', '#8 request URI') - self.assertEqual(headers['X-Servlet-Path'], '/index.bop', '#8 servlet path') + self.assertEqual( + headers['X-Request-URI'], '/index.bop', '#8 request URI' + ) + self.assertEqual( + headers['X-Servlet-Path'], '/index.bop', '#8 servlet path' + ) self.assertEqual(headers['X-Path-Info'], 'null', '#8 path info') headers = self.get(url='/foo/baz')['headers'] self.assertEqual(headers['X-Id'], 'servlet0', '#9 servlet0 request') - self.assertEqual(headers['X-Request-URI'], '/foo/baz', '#9 request URI') + self.assertEqual( + headers['X-Request-URI'], '/foo/baz', '#9 request URI' + ) self.assertEqual(headers['X-Servlet-Path'], '/foo', '#9 servlet path') self.assertEqual(headers['X-Path-Info'], '/baz', '#9 path info') @@ -272,8 +355,12 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): headers = self.get(url='/index.bop/')['headers'] self.assertEqual(headers['X-Id'], 'default', '#11 default request') - self.assertEqual(headers['X-Request-URI'], '/index.bop/', '#11 request URI') - self.assertEqual(headers['X-Servlet-Path'], '/index.bop/', '#11 servlet path') + self.assertEqual( + headers['X-Request-URI'], '/index.bop/', '#11 request URI' + ) + self.assertEqual( + headers['X-Servlet-Path'], '/index.bop/', '#11 servlet path' + ) self.assertEqual(headers['X-Path-Info'], 'null', '#11 path info') def test_java_application_header(self): @@ -281,10 +368,18 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): headers = self.get()['headers'] - self.assertEqual(headers['X-Set-Utf8-Value'], '????', 'set Utf8 header value') - self.assertEqual(headers['X-Set-Utf8-Name-???'], 'x', 'set Utf8 header name') - self.assertEqual(headers['X-Add-Utf8-Value'], '????', 'add Utf8 header value') - self.assertEqual(headers['X-Add-Utf8-Name-???'], 'y', 'add Utf8 header name') + self.assertEqual( + headers['X-Set-Utf8-Value'], '????', 'set Utf8 header value' + ) + self.assertEqual( + headers['X-Set-Utf8-Name-???'], 'x', 'set Utf8 header name' + ) + self.assertEqual( + headers['X-Add-Utf8-Value'], '????', 'add Utf8 header value' + ) + self.assertEqual( + headers['X-Add-Utf8-Name-???'], 'y', 'add Utf8 header name' + ) self.assertEqual(headers['X-Add-Test'], 'v1', 'add null header') self.assertEqual('X-Set-Test1' in headers, False, 'set null header') self.assertEqual(headers['X-Set-Test2'], '', 'set empty header') @@ -294,52 +389,135 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): headers = self.get(url='/1')['headers'] - self.assertEqual(headers['Content-Type'], 'text/plain;charset=utf-8', '#1 Content-Type header') - self.assertEqual(headers['X-Content-Type'], 'text/plain;charset=utf-8', '#1 response Content-Type') - self.assertEqual(headers['X-Character-Encoding'], 'utf-8', '#1 response charset') + self.assertEqual( + headers['Content-Type'], + 'text/plain;charset=utf-8', + '#1 Content-Type header', + ) + self.assertEqual( + headers['X-Content-Type'], + 'text/plain;charset=utf-8', + '#1 response Content-Type', + ) + self.assertEqual( + headers['X-Character-Encoding'], 'utf-8', '#1 response charset' + ) headers = self.get(url='/2')['headers'] - self.assertEqual(headers['Content-Type'], 'text/plain;charset=iso-8859-1', '#2 Content-Type header') - self.assertEqual(headers['X-Content-Type'], 'text/plain;charset=iso-8859-1', '#2 response Content-Type') - self.assertEqual(headers['X-Character-Encoding'], 'iso-8859-1', '#2 response charset') + self.assertEqual( + headers['Content-Type'], + 'text/plain;charset=iso-8859-1', + '#2 Content-Type header', + ) + self.assertEqual( + headers['X-Content-Type'], + 'text/plain;charset=iso-8859-1', + '#2 response Content-Type', + ) + self.assertEqual( + headers['X-Character-Encoding'], + 'iso-8859-1', + '#2 response charset', + ) headers = self.get(url='/3')['headers'] - self.assertEqual(headers['Content-Type'], 'text/plain;charset=windows-1251', '#3 Content-Type header') - self.assertEqual(headers['X-Content-Type'], 'text/plain;charset=windows-1251', '#3 response Content-Type') - self.assertEqual(headers['X-Character-Encoding'], 'windows-1251', '#3 response charset') + self.assertEqual( + headers['Content-Type'], + 'text/plain;charset=windows-1251', + '#3 Content-Type header', + ) + self.assertEqual( + headers['X-Content-Type'], + 'text/plain;charset=windows-1251', + '#3 response Content-Type', + ) + self.assertEqual( + headers['X-Character-Encoding'], + 'windows-1251', + '#3 response charset', + ) headers = self.get(url='/4')['headers'] - self.assertEqual(headers['Content-Type'], 'text/plain;charset=windows-1251', '#4 Content-Type header') - self.assertEqual(headers['X-Content-Type'], 'text/plain;charset=windows-1251', '#4 response Content-Type') - self.assertEqual(headers['X-Character-Encoding'], 'windows-1251', '#4 response charset') + self.assertEqual( + headers['Content-Type'], + 'text/plain;charset=windows-1251', + '#4 Content-Type header', + ) + self.assertEqual( + headers['X-Content-Type'], + 'text/plain;charset=windows-1251', + '#4 response Content-Type', + ) + self.assertEqual( + headers['X-Character-Encoding'], + 'windows-1251', + '#4 response charset', + ) headers = self.get(url='/5')['headers'] - self.assertEqual(headers['Content-Type'], 'text/plain;charset=iso-8859-1', '#5 Content-Type header') - self.assertEqual(headers['X-Content-Type'], 'text/plain;charset=iso-8859-1', '#5 response Content-Type') - self.assertEqual(headers['X-Character-Encoding'], 'iso-8859-1', '#5 response charset') + self.assertEqual( + headers['Content-Type'], + 'text/plain;charset=iso-8859-1', + '#5 Content-Type header', + ) + self.assertEqual( + headers['X-Content-Type'], + 'text/plain;charset=iso-8859-1', + '#5 response Content-Type', + ) + self.assertEqual( + headers['X-Character-Encoding'], + 'iso-8859-1', + '#5 response charset', + ) headers = self.get(url='/6')['headers'] - self.assertEqual('Content-Type' in headers, False, '#6 no Content-Type header') - self.assertEqual('X-Content-Type' in headers, False, '#6 no response Content-Type') - self.assertEqual(headers['X-Character-Encoding'], 'utf-8', '#6 response charset') - + self.assertEqual( + 'Content-Type' in headers, False, '#6 no Content-Type header' + ) + self.assertEqual( + 'X-Content-Type' in headers, False, '#6 no response Content-Type' + ) + self.assertEqual( + headers['X-Character-Encoding'], 'utf-8', '#6 response charset' + ) headers = self.get(url='/7')['headers'] - self.assertEqual(headers['Content-Type'], 'text/plain;charset=utf-8', '#7 Content-Type header') - self.assertEqual(headers['X-Content-Type'], 'text/plain;charset=utf-8', '#7 response Content-Type') - self.assertEqual(headers['X-Character-Encoding'], 'utf-8', '#7 response charset') + self.assertEqual( + headers['Content-Type'], + 'text/plain;charset=utf-8', + '#7 Content-Type header', + ) + self.assertEqual( + headers['X-Content-Type'], + 'text/plain;charset=utf-8', + '#7 response Content-Type', + ) + self.assertEqual( + headers['X-Character-Encoding'], 'utf-8', '#7 response charset' + ) headers = self.get(url='/8')['headers'] - self.assertEqual(headers['Content-Type'], 'text/html;charset=utf-8', '#8 Content-Type header') - self.assertEqual(headers['X-Content-Type'], 'text/html;charset=utf-8', '#8 response Content-Type') - self.assertEqual(headers['X-Character-Encoding'], 'utf-8', '#8 response charset') + self.assertEqual( + headers['Content-Type'], + 'text/html;charset=utf-8', + '#8 Content-Type header', + ) + self.assertEqual( + headers['X-Content-Type'], + 'text/html;charset=utf-8', + '#8 response Content-Type', + ) + self.assertEqual( + headers['X-Character-Encoding'], 'utf-8', '#8 response charset' + ) def test_java_application_welcome_files(self): self.load('welcome_files') @@ -352,8 +530,12 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): resp = self.get(url='/dir1/') - self.assertEqual('This is index.txt.' in resp['body'], True, 'dir1 index body') - self.assertEqual(resp['headers']['X-TXT-Filter'], '1', 'TXT Filter header') + self.assertEqual( + 'This is index.txt.' in resp['body'], True, 'dir1 index body' + ) + self.assertEqual( + resp['headers']['X-TXT-Filter'], '1', 'TXT Filter header' + ) headers = self.get(url='/dir2/')['headers'] @@ -362,124 +544,216 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): headers = self.get(url='/dir3/')['headers'] - self.assertEqual(headers['X-App-Servlet'], '1', 'URL pattern overrides welcome file') + self.assertEqual( + headers['X-App-Servlet'], '1', 'URL pattern overrides welcome file' + ) headers = self.get(url='/dir4/')['headers'] - self.assertEqual('X-App-Servlet' in headers, False, 'Static welcome file served first') + self.assertEqual( + 'X-App-Servlet' in headers, + False, + 'Static welcome file served first', + ) headers = self.get(url='/dir5/')['headers'] - self.assertEqual(headers['X-App-Servlet'], '1', 'Servlet for welcome file served when no static file found') + self.assertEqual( + headers['X-App-Servlet'], + '1', + 'Servlet for welcome file served when no static file found', + ) def test_java_application_request_listeners(self): self.load('request_listeners') headers = self.get(url='/test1')['headers'] - self.assertEqual(headers['X-Request-Initialized'], '/test1', - 'request initialized event') - self.assertEqual(headers['X-Request-Destroyed'], '', - 'request destroyed event') - self.assertEqual(headers['X-Attr-Added'], '', - 'attribute added event') - self.assertEqual(headers['X-Attr-Removed'], '', - 'attribute removed event') - self.assertEqual(headers['X-Attr-Replaced'], '', - 'attribute replaced event') + self.assertEqual( + headers['X-Request-Initialized'], + '/test1', + 'request initialized event', + ) + self.assertEqual( + headers['X-Request-Destroyed'], '', 'request destroyed event' + ) + self.assertEqual(headers['X-Attr-Added'], '', 'attribute added event') + self.assertEqual( + headers['X-Attr-Removed'], '', 'attribute removed event' + ) + self.assertEqual( + headers['X-Attr-Replaced'], '', 'attribute replaced event' + ) headers = self.get(url='/test2?var1=1')['headers'] - self.assertEqual(headers['X-Request-Initialized'], '/test2', - 'request initialized event') - self.assertEqual(headers['X-Request-Destroyed'], '/test1', - 'request destroyed event') - self.assertEqual(headers['X-Attr-Added'], 'var=1;', - 'attribute added event') - self.assertEqual(headers['X-Attr-Removed'], 'var=1;', - 'attribute removed event') - self.assertEqual(headers['X-Attr-Replaced'], '', - 'attribute replaced event') + self.assertEqual( + headers['X-Request-Initialized'], + '/test2', + 'request initialized event', + ) + self.assertEqual( + headers['X-Request-Destroyed'], '/test1', 'request destroyed event' + ) + self.assertEqual( + headers['X-Attr-Added'], 'var=1;', 'attribute added event' + ) + self.assertEqual( + headers['X-Attr-Removed'], 'var=1;', 'attribute removed event' + ) + self.assertEqual( + headers['X-Attr-Replaced'], '', 'attribute replaced event' + ) headers = self.get(url='/test3?var1=1&var2=2')['headers'] - self.assertEqual(headers['X-Request-Initialized'], '/test3', - 'request initialized event') - self.assertEqual(headers['X-Request-Destroyed'], '/test2', - 'request destroyed event') - self.assertEqual(headers['X-Attr-Added'], 'var=1;', - 'attribute added event') - self.assertEqual(headers['X-Attr-Removed'], 'var=2;', - 'attribute removed event') - self.assertEqual(headers['X-Attr-Replaced'], 'var=1;', - 'attribute replaced event') + self.assertEqual( + headers['X-Request-Initialized'], + '/test3', + 'request initialized event', + ) + self.assertEqual( + headers['X-Request-Destroyed'], '/test2', 'request destroyed event' + ) + self.assertEqual( + headers['X-Attr-Added'], 'var=1;', 'attribute added event' + ) + self.assertEqual( + headers['X-Attr-Removed'], 'var=2;', 'attribute removed event' + ) + self.assertEqual( + headers['X-Attr-Replaced'], 'var=1;', 'attribute replaced event' + ) headers = self.get(url='/test4?var1=1&var2=2&var3=3')['headers'] - self.assertEqual(headers['X-Request-Initialized'], '/test4', - 'request initialized event') - self.assertEqual(headers['X-Request-Destroyed'], '/test3', - 'request destroyed event') - self.assertEqual(headers['X-Attr-Added'], 'var=1;', - 'attribute added event') - self.assertEqual(headers['X-Attr-Removed'], '', - 'attribute removed event') - self.assertEqual(headers['X-Attr-Replaced'], 'var=1;var=2;', - 'attribute replaced event') + self.assertEqual( + headers['X-Request-Initialized'], + '/test4', + 'request initialized event', + ) + self.assertEqual( + headers['X-Request-Destroyed'], '/test3', 'request destroyed event' + ) + self.assertEqual( + headers['X-Attr-Added'], 'var=1;', 'attribute added event' + ) + self.assertEqual( + headers['X-Attr-Removed'], '', 'attribute removed event' + ) + self.assertEqual( + headers['X-Attr-Replaced'], + 'var=1;var=2;', + 'attribute replaced event', + ) def test_java_application_request_uri_forward(self): self.load('forward') - resp = self.get(url='/fwd?uri=%2Fdata%2Ftest%3Furi%3Dnew_uri%26a%3D2%26b%3D3&a=1&c=4') + resp = self.get( + url='/fwd?uri=%2Fdata%2Ftest%3Furi%3Dnew_uri%26a%3D2%26b%3D3&a=1&c=4' + ) headers = resp['headers'] - self.assertEqual(headers['X-REQUEST-Id'], 'fwd', - 'initial request servlet mapping') - self.assertEqual(headers['X-Forward-To'], '/data/test?uri=new_uri&a=2&b=3', - 'forwarding triggered') - self.assertEqual(headers['X-REQUEST-Param-uri'], '/data/test?uri=new_uri&a=2&b=3', - 'original uri parameter') - self.assertEqual(headers['X-REQUEST-Param-a'], '1', - 'original a parameter') - self.assertEqual(headers['X-REQUEST-Param-c'], '4', - 'original c parameter') - - self.assertEqual(headers['X-FORWARD-Id'], 'data', - 'forward request servlet mapping') - self.assertEqual(headers['X-FORWARD-Request-URI'], '/data/test', - 'forward request uri') - self.assertEqual(headers['X-FORWARD-Servlet-Path'], '/data', - 'forward request servlet path') - self.assertEqual(headers['X-FORWARD-Path-Info'], '/test', - 'forward request path info') - self.assertEqual(headers['X-FORWARD-Query-String'], 'uri=new_uri&a=2&b=3', - 'forward request query string') - self.assertEqual(headers['X-FORWARD-Param-uri'], 'new_uri,/data/test?uri=new_uri&a=2&b=3', - 'forward uri parameter') - self.assertEqual(headers['X-FORWARD-Param-a'], '2,1', - 'forward a parameter') - self.assertEqual(headers['X-FORWARD-Param-b'], '3', - 'forward b parameter') - self.assertEqual(headers['X-FORWARD-Param-c'], '4', - 'forward c parameter') - - self.assertEqual(headers['X-javax.servlet.forward.request_uri'], '/fwd', - 'original request uri') - self.assertEqual(headers['X-javax.servlet.forward.context_path'], '', - 'original request context path') - self.assertEqual(headers['X-javax.servlet.forward.servlet_path'], '/fwd', - 'original request servlet path') - self.assertEqual(headers['X-javax.servlet.forward.path_info'], 'null', - 'original request path info') - self.assertEqual(headers['X-javax.servlet.forward.query_string'], 'uri=%2Fdata%2Ftest%3Furi%3Dnew_uri%26a%3D2%26b%3D3&a=1&c=4', - 'original request query') - - self.assertEqual('Before forwarding' in resp['body'], False, - 'discarded data added before forward() call') - self.assertEqual('X-After-Forwarding' in headers, False, - 'cannot add headers after forward() call') - self.assertEqual('After forwarding' in resp['body'], False, - 'cannot add data after forward() call') + self.assertEqual( + headers['X-REQUEST-Id'], 'fwd', 'initial request servlet mapping' + ) + self.assertEqual( + headers['X-Forward-To'], + '/data/test?uri=new_uri&a=2&b=3', + 'forwarding triggered', + ) + self.assertEqual( + headers['X-REQUEST-Param-uri'], + '/data/test?uri=new_uri&a=2&b=3', + 'original uri parameter', + ) + self.assertEqual( + headers['X-REQUEST-Param-a'], '1', 'original a parameter' + ) + self.assertEqual( + headers['X-REQUEST-Param-c'], '4', 'original c parameter' + ) + + self.assertEqual( + headers['X-FORWARD-Id'], 'data', 'forward request servlet mapping' + ) + self.assertEqual( + headers['X-FORWARD-Request-URI'], + '/data/test', + 'forward request uri', + ) + self.assertEqual( + headers['X-FORWARD-Servlet-Path'], + '/data', + 'forward request servlet path', + ) + self.assertEqual( + headers['X-FORWARD-Path-Info'], + '/test', + 'forward request path info', + ) + self.assertEqual( + headers['X-FORWARD-Query-String'], + 'uri=new_uri&a=2&b=3', + 'forward request query string', + ) + self.assertEqual( + headers['X-FORWARD-Param-uri'], + 'new_uri,/data/test?uri=new_uri&a=2&b=3', + 'forward uri parameter', + ) + self.assertEqual( + headers['X-FORWARD-Param-a'], '2,1', 'forward a parameter' + ) + self.assertEqual( + headers['X-FORWARD-Param-b'], '3', 'forward b parameter' + ) + self.assertEqual( + headers['X-FORWARD-Param-c'], '4', 'forward c parameter' + ) + + self.assertEqual( + headers['X-javax.servlet.forward.request_uri'], + '/fwd', + 'original request uri', + ) + self.assertEqual( + headers['X-javax.servlet.forward.context_path'], + '', + 'original request context path', + ) + self.assertEqual( + headers['X-javax.servlet.forward.servlet_path'], + '/fwd', + 'original request servlet path', + ) + self.assertEqual( + headers['X-javax.servlet.forward.path_info'], + 'null', + 'original request path info', + ) + self.assertEqual( + headers['X-javax.servlet.forward.query_string'], + 'uri=%2Fdata%2Ftest%3Furi%3Dnew_uri%26a%3D2%26b%3D3&a=1&c=4', + 'original request query', + ) + + self.assertEqual( + 'Before forwarding' in resp['body'], + False, + 'discarded data added before forward() call', + ) + self.assertEqual( + 'X-After-Forwarding' in headers, + False, + 'cannot add headers after forward() call', + ) + self.assertEqual( + 'After forwarding' in resp['body'], + False, + 'cannot add data after forward() call', + ) def test_java_application_named_dispatcher_forward(self): self.load('forward') @@ -487,39 +761,74 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): resp = self.get(url='/fwd?disp=name&uri=data') headers = resp['headers'] - self.assertEqual(headers['X-REQUEST-Id'], 'fwd', - 'initial request servlet mapping') - self.assertEqual(headers['X-Forward-To'], 'data', - 'forwarding triggered') - - self.assertEqual(headers['X-FORWARD-Id'], 'data', - 'forward request servlet mapping') - self.assertEqual(headers['X-FORWARD-Request-URI'], '/fwd', - 'forward request uri') - self.assertEqual(headers['X-FORWARD-Servlet-Path'], '/fwd', - 'forward request servlet path') - self.assertEqual(headers['X-FORWARD-Path-Info'], 'null', - 'forward request path info') - self.assertEqual(headers['X-FORWARD-Query-String'], 'disp=name&uri=data', - 'forward request query string') - - self.assertEqual(headers['X-javax.servlet.forward.request_uri'], 'null', - 'original request uri') - self.assertEqual(headers['X-javax.servlet.forward.context_path'], 'null', - 'original request context path') - self.assertEqual(headers['X-javax.servlet.forward.servlet_path'], 'null', - 'original request servlet path') - self.assertEqual(headers['X-javax.servlet.forward.path_info'], 'null', - 'original request path info') - self.assertEqual(headers['X-javax.servlet.forward.query_string'], 'null', - 'original request query') - - self.assertEqual('Before forwarding' in resp['body'], False, - 'discarded data added before forward() call') - self.assertEqual('X-After-Forwarding' in headers, False, - 'cannot add headers after forward() call') - self.assertEqual('After forwarding' in resp['body'], False, - 'cannot add data after forward() call') + self.assertEqual( + headers['X-REQUEST-Id'], 'fwd', 'initial request servlet mapping' + ) + self.assertEqual( + headers['X-Forward-To'], 'data', 'forwarding triggered' + ) + + self.assertEqual( + headers['X-FORWARD-Id'], 'data', 'forward request servlet mapping' + ) + self.assertEqual( + headers['X-FORWARD-Request-URI'], '/fwd', 'forward request uri' + ) + self.assertEqual( + headers['X-FORWARD-Servlet-Path'], + '/fwd', + 'forward request servlet path', + ) + self.assertEqual( + headers['X-FORWARD-Path-Info'], 'null', 'forward request path info' + ) + self.assertEqual( + headers['X-FORWARD-Query-String'], + 'disp=name&uri=data', + 'forward request query string', + ) + + self.assertEqual( + headers['X-javax.servlet.forward.request_uri'], + 'null', + 'original request uri', + ) + self.assertEqual( + headers['X-javax.servlet.forward.context_path'], + 'null', + 'original request context path', + ) + self.assertEqual( + headers['X-javax.servlet.forward.servlet_path'], + 'null', + 'original request servlet path', + ) + self.assertEqual( + headers['X-javax.servlet.forward.path_info'], + 'null', + 'original request path info', + ) + self.assertEqual( + headers['X-javax.servlet.forward.query_string'], + 'null', + 'original request query', + ) + + self.assertEqual( + 'Before forwarding' in resp['body'], + False, + 'discarded data added before forward() call', + ) + self.assertEqual( + 'X-After-Forwarding' in headers, + False, + 'cannot add headers after forward() call', + ) + self.assertEqual( + 'After forwarding' in resp['body'], + False, + 'cannot add data after forward() call', + ) def test_java_application_request_uri_include(self): self.load('include') @@ -528,31 +837,55 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): headers = resp['headers'] body = resp['body'] - self.assertEqual(headers['X-REQUEST-Id'], 'inc', - 'initial request servlet mapping') - self.assertEqual(headers['X-Include'], '/data/test', - 'including triggered') - - self.assertEqual('X-INCLUDE-Id' in headers, False, - 'unable to add headers in include request') - - self.assertEqual('javax.servlet.include.request_uri: /data/test' in body, - True, 'include request uri') -# self.assertEqual('javax.servlet.include.context_path: ' in body, -# 'include request context path') - self.assertEqual('javax.servlet.include.servlet_path: /data' in body, - True, 'include request servlet path') - self.assertEqual('javax.servlet.include.path_info: /test' in body, - True, 'include request path info') - self.assertEqual('javax.servlet.include.query_string: null' in body, - True, 'include request query') - - self.assertEqual('Before include' in body, True, - 'preserve data added before include() call') - self.assertEqual(headers['X-After-Include'], 'you-should-see-this', - 'add headers after include() call') - self.assertEqual('After include' in body, True, - 'add data after include() call') + self.assertEqual( + headers['X-REQUEST-Id'], 'inc', 'initial request servlet mapping' + ) + self.assertEqual( + headers['X-Include'], '/data/test', 'including triggered' + ) + + self.assertEqual( + 'X-INCLUDE-Id' in headers, + False, + 'unable to add headers in include request', + ) + + self.assertEqual( + 'javax.servlet.include.request_uri: /data/test' in body, + True, + 'include request uri', + ) + # self.assertEqual('javax.servlet.include.context_path: ' in body, + # 'include request context path') + self.assertEqual( + 'javax.servlet.include.servlet_path: /data' in body, + True, + 'include request servlet path', + ) + self.assertEqual( + 'javax.servlet.include.path_info: /test' in body, + True, + 'include request path info', + ) + self.assertEqual( + 'javax.servlet.include.query_string: null' in body, + True, + 'include request query', + ) + + self.assertEqual( + 'Before include' in body, + True, + 'preserve data added before include() call', + ) + self.assertEqual( + headers['X-After-Include'], + 'you-should-see-this', + 'add headers after include() call', + ) + self.assertEqual( + 'After include' in body, True, 'add data after include() call' + ) def test_java_application_named_dispatcher_include(self): self.load('include') @@ -561,82 +894,134 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): headers = resp['headers'] body = resp['body'] - self.assertEqual(headers['X-REQUEST-Id'], 'inc', - 'initial request servlet mapping') - self.assertEqual(headers['X-Include'], 'data', - 'including triggered') - - self.assertEqual('X-INCLUDE-Id' in headers, False, - 'unable to add headers in include request') - - self.assertEqual('javax.servlet.include.request_uri: null' in body, - True, 'include request uri') -# self.assertEqual('javax.servlet.include.context_path: null' in body, -# 'include request context path') - self.assertEqual('javax.servlet.include.servlet_path: null' in body, - True, 'include request servlet path') - self.assertEqual('javax.servlet.include.path_info: null' in body, - True, 'include request path info') - self.assertEqual('javax.servlet.include.query_string: null' in body, - True, 'include request query') - - self.assertEqual('Before include' in body, True, - 'preserve data added before include() call') - self.assertEqual(headers['X-After-Include'], 'you-should-see-this', - 'add headers after include() call') - self.assertEqual('After include' in body, True, - 'add data after include() call') + self.assertEqual( + headers['X-REQUEST-Id'], 'inc', 'initial request servlet mapping' + ) + self.assertEqual(headers['X-Include'], 'data', 'including triggered') + + self.assertEqual( + 'X-INCLUDE-Id' in headers, + False, + 'unable to add headers in include request', + ) + + self.assertEqual( + 'javax.servlet.include.request_uri: null' in body, + True, + 'include request uri', + ) + # self.assertEqual('javax.servlet.include.context_path: null' in body, + # 'include request context path') + self.assertEqual( + 'javax.servlet.include.servlet_path: null' in body, + True, + 'include request servlet path', + ) + self.assertEqual( + 'javax.servlet.include.path_info: null' in body, + True, + 'include request path info', + ) + self.assertEqual( + 'javax.servlet.include.query_string: null' in body, + True, + 'include request query', + ) + + self.assertEqual( + 'Before include' in body, + True, + 'preserve data added before include() call', + ) + self.assertEqual( + headers['X-After-Include'], + 'you-should-see-this', + 'add headers after include() call', + ) + self.assertEqual( + 'After include' in body, True, 'add data after include() call' + ) def test_java_application_path_translation(self): self.load('path_translation') headers = self.get(url='/pt/test?path=/')['headers'] - self.assertEqual(headers['X-Servlet-Path'], '/pt', - 'matched servlet path') - self.assertEqual(headers['X-Path-Info'], '/test', - 'the rest of the path') - self.assertEqual(headers['X-Path-Translated'], + self.assertEqual( + headers['X-Servlet-Path'], '/pt', 'matched servlet path' + ) + self.assertEqual( + headers['X-Path-Info'], '/test', 'the rest of the path' + ) + self.assertEqual( + headers['X-Path-Translated'], headers['X-Real-Path'] + headers['X-Path-Info'], - 'translated path is the app root + path info') + 'translated path is the app root + path info', + ) self.assertEqual( headers['X-Resource-Paths'].endswith('/WEB-INF/, /index.html]'), - True, 'app root directory content') - self.assertEqual(headers['X-Resource-As-Stream'], 'null', - 'no resource stream for root path') + True, + 'app root directory content', + ) + self.assertEqual( + headers['X-Resource-As-Stream'], + 'null', + 'no resource stream for root path', + ) headers = self.get(url='/test?path=/none')['headers'] - self.assertEqual(headers['X-Servlet-Path'], '/test', - 'matched whole path') - self.assertEqual(headers['X-Path-Info'], 'null', - 'the rest of the path is null, whole path matched') - self.assertEqual(headers['X-Path-Translated'], 'null', - 'translated path is null because path info is null') - self.assertEqual(headers['X-Real-Path'].endswith('/none'), True, - 'read path is not null') - self.assertEqual(headers['X-Resource-Paths'], 'null', - 'no resource found') - self.assertEqual(headers['X-Resource-As-Stream'], 'null', - 'no resource stream') + self.assertEqual( + headers['X-Servlet-Path'], '/test', 'matched whole path' + ) + self.assertEqual( + headers['X-Path-Info'], + 'null', + 'the rest of the path is null, whole path matched', + ) + self.assertEqual( + headers['X-Path-Translated'], + 'null', + 'translated path is null because path info is null', + ) + self.assertEqual( + headers['X-Real-Path'].endswith('/none'), + True, + 'read path is not null', + ) + self.assertEqual( + headers['X-Resource-Paths'], 'null', 'no resource found' + ) + self.assertEqual( + headers['X-Resource-As-Stream'], 'null', 'no resource stream' + ) def test_java_application_query_string(self): self.load('query_string') - self.assertEqual(self.get(url='/?a=b')['headers']['X-Query-String'], - 'a=b', 'query string') + self.assertEqual( + self.get(url='/?a=b')['headers']['X-Query-String'], + 'a=b', + 'query string', + ) def test_java_application_query_empty(self): self.load('query_string') - self.assertEqual(self.get(url='/?')['headers']['X-Query-String'], '', - 'query string empty') + self.assertEqual( + self.get(url='/?')['headers']['X-Query-String'], + '', + 'query string empty', + ) def test_java_application_query_absent(self): self.load('query_string') - self.assertEqual(self.get()['headers']['X-Query-String'], 'null', - 'query string absent') + self.assertEqual( + self.get()['headers']['X-Query-String'], + 'null', + 'query string absent', + ) def test_java_application_empty(self): self.load('empty') @@ -646,19 +1031,30 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): def test_java_application_keepalive_body(self): self.load('mirror') - (resp, sock) = self.post(headers={ - 'Connection': 'keep-alive', - 'Content-Type': 'text/html', - 'Host': 'localhost' - }, start=True, body='0123456789' * 500) + self.assertEqual(self.post()['status'], 200, 'init') + + (resp, sock) = self.post( + headers={ + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + 'Host': 'localhost', + }, + start=True, + body='0123456789' * 500, + read_timeout=1, + ) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') - resp = self.post(headers={ - 'Connection': 'close', - 'Content-Type': 'text/html', - 'Host': 'localhost' - }, sock=sock, body='0123456789') + resp = self.post( + headers={ + 'Connection': 'close', + 'Content-Type': 'text/html', + 'Host': 'localhost', + }, + sock=sock, + body='0123456789', + ) self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') @@ -675,11 +1071,18 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): def test_java_application_get_header(self): self.load('get_header') - self.assertEqual(self.get(headers={ - 'X-Header': 'blah', - 'Content-Type': 'text/html', - 'Host': 'localhost' - })['headers']['X-Reply'], 'blah', 'get header') + self.assertEqual( + self.get( + headers={ + 'X-Header': 'blah', + 'Content-Type': 'text/html', + 'Host': 'localhost', + 'Connection': 'close', + } + )['headers']['X-Reply'], + 'blah', + 'get header', + ) def test_java_application_get_header_empty(self): self.load('get_header') @@ -689,11 +1092,14 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): def test_java_application_get_headers(self): self.load('get_headers') - headers = self.get(headers={ - 'X-Header': ['blah', 'blah'], - 'Content-Type': 'text/html', - 'Host': 'localhost' - })['headers'] + headers = self.get( + headers={ + 'X-Header': ['blah', 'blah'], + 'Content-Type': 'text/html', + 'Host': 'localhost', + 'Connection': 'close', + } + )['headers'] self.assertEqual(headers['X-Reply-0'], 'blah', 'get headers') self.assertEqual(headers['X-Reply-1'], 'blah', 'get headers 2') @@ -701,35 +1107,38 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): def test_java_application_get_headers_empty(self): self.load('get_headers') - self.assertNotIn('X-Reply-0', self.get()['headers'], - 'get headers empty') + self.assertNotIn( + 'X-Reply-0', self.get()['headers'], 'get headers empty' + ) def test_java_application_get_header_names(self): self.load('get_header_names') headers = self.get()['headers'] - self.assertRegex(headers['X-Reply-0'], r'(?:Host|Connection)', - 'get header names') - self.assertRegex(headers['X-Reply-1'], r'(?:Host|Connection)', - 'get header names 2') - self.assertNotEqual(headers['X-Reply-0'], headers['X-Reply-1'], - 'get header names not equal') - - def test_java_application_get_header_names_empty(self): - self.load('get_header_names') - - self.assertNotIn('X-Reply-0', self.get(headers={})['headers'], - 'get header names empty') + self.assertRegex( + headers['X-Reply-0'], r'(?:Host|Connection)', 'get header names' + ) + self.assertRegex( + headers['X-Reply-1'], r'(?:Host|Connection)', 'get header names 2' + ) + self.assertNotEqual( + headers['X-Reply-0'], + headers['X-Reply-1'], + 'get header names not equal', + ) def test_java_application_header_int(self): self.load('header_int') - headers = self.get(headers={ - 'X-Header': '2', - 'Content-Type': 'text/html', - 'Host': 'localhost' - })['headers'] + headers = self.get( + headers={ + 'X-Header': '2', + 'Content-Type': 'text/html', + 'Host': 'localhost', + 'Connection': 'close', + } + )['headers'] self.assertEqual(headers['X-Set-Int'], '1', 'set int header') self.assertEqual(headers['X-Get-Int'], '2', 'get int header') @@ -739,15 +1148,22 @@ class TestUnitJavaApplication(unit.TestUnitApplicationJava): date = 'Fri, 15 Mar 2019 14:45:34 GMT' - headers = self.get(headers={ - 'X-Header': date, - 'Content-Type': 'text/html', - 'Host': 'localhost' - })['headers'] + headers = self.get( + headers={ + 'X-Header': date, + 'Content-Type': 'text/html', + 'Host': 'localhost', + 'Connection': 'close', + } + )['headers'] - self.assertEqual(headers['X-Set-Date'], 'Thu, 01 Jan 1970 00:00:01 GMT', - 'set date header') + self.assertEqual( + headers['X-Set-Date'], + 'Thu, 01 Jan 1970 00:00:01 GMT', + 'set date header', + ) self.assertEqual(headers['X-Get-Date'], date, 'get date header') + if __name__ == '__main__': - TestUnitJavaApplication.main() + TestJavaApplication.main() diff --git a/test/test_node_application.py b/test/test_node_application.py index cd64fefa..0354c978 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -1,17 +1,17 @@ import unittest -import unit +from unit.applications.lang.node import TestApplicationNode -class TestUnitNodeApplication(unit.TestUnitApplicationNode): - def setUpClass(): - u = unit.TestUnit().check_modules('node') +class TestNodeApplication(TestApplicationNode): + prerequisites = ['node'] def test_node_application_basic(self): self.load('basic') resp = self.get() - self.assertEqual(resp['headers']['Content-Type'], 'text/plain', - 'basic header') + self.assertEqual( + resp['headers']['Content-Type'], 'text/plain', 'basic header' + ) self.assertEqual(resp['body'], 'Hello World\n', 'basic body') def test_node_application_seq(self): @@ -25,12 +25,15 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): body = 'Test body string.' - resp = self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'Custom-Header': 'blah', - 'Connection': 'close' - }, body=body) + resp = self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Custom-Header': 'blah', + 'Connection': 'close', + }, + body=body, + ) self.assertEqual(resp['status'], 200, 'status') headers = resp['headers'] @@ -39,24 +42,35 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): date = headers.pop('Date') self.assertEqual(date[-4:], ' GMT', 'date header timezone') - self.assertLess(abs(self.date_to_sec_epoch(date) - self.sec_epoch()), 5, - 'date header') + self.assertLess( + abs(self.date_to_sec_epoch(date) - self.sec_epoch()), + 5, + 'date header', + ) raw_headers = headers.pop('Request-Raw-Headers') - self.assertRegex(raw_headers, r'^(?:Host|localhost|Content-Type|' \ - 'text\/html|Custom-Header|blah|Content-Length|17|Connection|' \ - 'close|,)+$', 'raw headers') - - self.assertDictEqual(headers, { - 'Connection': 'close', - 'Content-Length': str(len(body)), - 'Content-Type': 'text/html', - 'Request-Method': 'POST', - 'Request-Uri': '/', - 'Http-Host': 'localhost', - 'Server-Protocol': 'HTTP/1.1', - 'Custom-Header': 'blah' - }, 'headers') + self.assertRegex( + raw_headers, + r'^(?:Host|localhost|Content-Type|' + 'text\/html|Custom-Header|blah|Content-Length|17|Connection|' + 'close|,)+$', + 'raw headers', + ) + + self.assertDictEqual( + headers, + { + 'Connection': 'close', + 'Content-Length': str(len(body)), + 'Content-Type': 'text/html', + 'Request-Method': 'POST', + 'Request-Uri': '/', + 'Http-Host': 'localhost', + 'Server-Protocol': 'HTTP/1.1', + 'Custom-Header': 'blah', + }, + 'headers', + ) self.assertEqual(resp['body'], body, 'body') def test_node_application_get_variables(self): @@ -70,11 +84,14 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): def test_node_application_post_variables(self): self.load('post_variables') - resp = self.post(headers={ - 'Content-Type': 'application/x-www-form-urlencoded', - 'Host': 'localhost', - 'Connection': 'close' - }, body='var1=val1&var2=&var3') + resp = self.post( + headers={ + 'Content-Type': 'application/x-www-form-urlencoded', + 'Host': 'localhost', + 'Connection': 'close', + }, + body='var1=val1&var2=&var3', + ) self.assertEqual(resp['headers']['X-Var-1'], 'val1', 'POST variables') self.assertEqual(resp['headers']['X-Var-2'], '', 'POST variables 2') @@ -86,41 +103,59 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): resp = self.get() self.assertEqual(resp['status'], 404, '404 status') - self.assertRegex(resp['body'], r'<title>404 Not Found</title>', - '404 body') + self.assertRegex( + resp['body'], r'<title>404 Not Found</title>', '404 body' + ) def test_node_keepalive_body(self): self.load('mirror') - (resp, sock) = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive', - 'Content-Type': 'text/html' - }, start=True, body='0123456789' * 500) + self.assertEqual(self.get()['status'], 200, 'init') + + (resp, sock) = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + }, + start=True, + body='0123456789' * 500, + read_timeout=1, + ) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') - resp = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'close', - 'Content-Type': 'text/html' - }, sock=sock, body='0123456789') + resp = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'Content-Type': 'text/html', + }, + sock=sock, + body='0123456789', + ) self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') def test_node_application_write_buffer(self): self.load('write_buffer') - self.assertEqual(self.get()['body'], '6\r\nbuffer\r\n0\r\n\r\n', - 'write buffer') + self.assertEqual( + self.get()['body'], '6\r\nbuffer\r\n0\r\n\r\n', 'write buffer' + ) def test_node_application_write_callback(self): self.load('write_callback') - self.assertEqual(self.get()['body'], - '5\r\nhello\r\n5\r\nworld\r\n0\r\n\r\n', 'write callback order') - self.assertTrue(self.waitforfiles(self.testdir + '/node/callback'), - 'write callback') + self.assertEqual( + self.get()['body'], + '5\r\nhello\r\n5\r\nworld\r\n0\r\n\r\n', + 'write callback order', + ) + self.assertTrue( + self.waitforfiles(self.testdir + '/node/callback'), + 'write callback', + ) def test_node_application_write_before_write_head(self): self.load('write_before_write_head') @@ -136,17 +171,22 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): def test_node_application_write_return(self): self.load('write_return') - self.assertEqual(self.get()['body'], - '4\r\nbody\r\n4\r\ntrue\r\n0\r\n\r\n', 'write return') + self.assertEqual( + self.get()['body'], + '4\r\nbody\r\n4\r\ntrue\r\n0\r\n\r\n', + 'write return', + ) def test_node_application_remove_header(self): self.load('remove_header') - resp = self.get(headers={ - 'Host': 'localhost', - 'X-Remove': 'X-Header', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Host': 'localhost', + 'X-Remove': 'X-Header', + 'Connection': 'close', + } + ) self.assertEqual(resp['headers']['Was-Header'], 'true', 'was header') self.assertEqual(resp['headers']['Has-Header'], 'false', 'has header') self.assertFalse('X-Header' in resp['headers'], 'remove header') @@ -154,35 +194,48 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): def test_node_application_remove_header_nonexisting(self): self.load('remove_header') - self.assertEqual(self.get(headers={ - 'Host': 'localhost', - 'X-Remove': 'blah', - 'Connection': 'close' - })['headers']['Has-Header'], 'true', 'remove header nonexisting') + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'X-Remove': 'blah', + 'Connection': 'close', + } + )['headers']['Has-Header'], + 'true', + 'remove header nonexisting', + ) def test_node_application_update_header(self): self.load('update_header') - self.assertEqual(self.get()['headers']['X-Header'], 'new', - 'update header') + self.assertEqual( + self.get()['headers']['X-Header'], 'new', 'update header' + ) def test_node_application_set_header_array(self): self.load('set_header_array') - self.assertListEqual(self.get()['headers']['Set-Cookie'], - ['tc=one,two,three', 'tc=four,five,six'], 'set header array') + self.assertListEqual( + self.get()['headers']['Set-Cookie'], + ['tc=one,two,three', 'tc=four,five,six'], + 'set header array', + ) - @unittest.expectedFailure + @unittest.skip('not yet') def test_node_application_status_message(self): self.load('status_message') - self.assertRegex(self.get(raw_resp=True), r'200 blah', 'status message') + self.assertRegex( + self.get(raw_resp=True), r'200 blah', 'status message' + ) def test_node_application_get_header_type(self): self.load('get_header_type') - self.assertEqual(self.get()['headers']['X-Type'], 'number', - 'get header type') + self.assertEqual( + self.get()['headers']['X-Type'], 'number', 'get header type' + ) def test_node_application_header_name_case(self): self.load('header_name_case') @@ -196,58 +249,91 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): def test_node_application_promise_handler(self): self.load('promise_handler') - self.assertEqual(self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'Connection': 'close' - }, body='callback')['status'], 200, 'promise handler request') - self.assertTrue(self.waitforfiles(self.testdir + '/node/callback'), - 'promise handler') + self.assertEqual( + self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Connection': 'close', + }, + body='callback', + )['status'], + 200, + 'promise handler request', + ) + self.assertTrue( + self.waitforfiles(self.testdir + '/node/callback'), + 'promise handler', + ) def test_node_application_promise_handler_write_after_end(self): self.load('promise_handler') - self.assertEqual(self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'X-Write-Call': '1', - 'Connection': 'close' - }, body='callback')['status'], 200, - 'promise handler request write after end') + self.assertEqual( + self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'X-Write-Call': '1', + 'Connection': 'close', + }, + body='callback', + )['status'], + 200, + 'promise handler request write after end', + ) def test_node_application_promise_end(self): self.load('promise_end') - self.assertEqual(self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'Connection': 'close' - }, body='end')['status'], 200, 'promise end request') - self.assertTrue(self.waitforfiles(self.testdir + '/node/callback'), - 'promise end') + self.assertEqual( + self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Connection': 'close', + }, + body='end', + )['status'], + 200, + 'promise end request', + ) + self.assertTrue( + self.waitforfiles(self.testdir + '/node/callback'), 'promise end' + ) def test_node_application_promise_multiple_calls(self): self.load('promise_handler') - self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'Connection': 'close' - }, body='callback1') - - self.assertTrue(self.waitforfiles(self.testdir + '/node/callback1'), - 'promise first call') - - self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'Connection': 'close' - }, body='callback2') - - self.assertTrue(self.waitforfiles(self.testdir + '/node/callback2'), - 'promise second call') - - @unittest.expectedFailure + self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Connection': 'close', + }, + body='callback1', + ) + + self.assertTrue( + self.waitforfiles(self.testdir + '/node/callback1'), + 'promise first call', + ) + + self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Connection': 'close', + }, + body='callback2', + ) + + self.assertTrue( + self.waitforfiles(self.testdir + '/node/callback2'), + 'promise second call', + ) + + @unittest.skip('not yet') def test_node_application_header_name_valid(self): self.load('header_name_valid') @@ -261,28 +347,46 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode): def test_node_application_get_header_names(self): self.load('get_header_names') - self.assertListEqual(self.get()['headers']['X-Names'], - ['date', 'x-header'], 'get header names') + self.assertListEqual( + self.get()['headers']['X-Names'], + ['date', 'x-header'], + 'get header names', + ) def test_node_application_has_header(self): self.load('has_header') - self.assertEqual(self.get(headers={ - 'Host': 'localhost', - 'X-Header': 'length', - 'Connection': 'close' - })['headers']['X-Has-Header'], 'false', 'has header length') - - self.assertEqual(self.get(headers={ - 'Host': 'localhost', - 'X-Header': 'Date', - 'Connection': 'close' - })['headers']['X-Has-Header'], 'false', 'has header date') + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'X-Header': 'length', + 'Connection': 'close', + } + )['headers']['X-Has-Header'], + 'false', + 'has header length', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'X-Header': 'Date', + 'Connection': 'close', + } + )['headers']['X-Has-Header'], + 'false', + 'has header date', + ) def test_node_application_write_multiple(self): self.load('write_multiple') - self.assertEqual(self.get()['body'], 'writewrite2end', 'write multiple') + self.assertEqual( + self.get()['body'], 'writewrite2end', 'write multiple' + ) + if __name__ == '__main__': - TestUnitNodeApplication.main() + TestNodeApplication.main() diff --git a/test/test_perl_application.py b/test/test_perl_application.py index b169baab..bc26b000 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -1,52 +1,64 @@ import unittest -import unit +from unit.applications.lang.perl import TestApplicationPerl -class TestUnitPerlApplication(unit.TestUnitApplicationPerl): - def setUpClass(): - unit.TestUnit().check_modules('perl') +class TestPerlApplication(TestApplicationPerl): + prerequisites = ['perl'] def test_perl_application(self): self.load('variables') body = 'Test body string.' - resp = self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'Custom-Header': 'blah', - 'Connection': 'close' - }, body=body) + resp = self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Custom-Header': 'blah', + 'Connection': 'close', + }, + body=body, + ) self.assertEqual(resp['status'], 200, 'status') headers = resp['headers'] header_server = headers.pop('Server') self.assertRegex(header_server, r'Unit/[\d\.]+', 'server header') - self.assertEqual(headers.pop('Server-Software'), header_server, - 'server software header') + self.assertEqual( + headers.pop('Server-Software'), + header_server, + 'server software header', + ) date = headers.pop('Date') self.assertEqual(date[-4:], ' GMT', 'date header timezone') - self.assertLess(abs(self.date_to_sec_epoch(date) - self.sec_epoch()), 5, - 'date header') - - self.assertDictEqual(headers, { - 'Connection': 'close', - 'Content-Length': str(len(body)), - 'Content-Type': 'text/html', - 'Request-Method': 'POST', - 'Request-Uri': '/', - 'Http-Host': 'localhost', - 'Server-Protocol': 'HTTP/1.1', - 'Custom-Header': 'blah', - 'Psgi-Version': '11', - 'Psgi-Url-Scheme': 'http', - 'Psgi-Multithread': '', - 'Psgi-Multiprocess': '1', - 'Psgi-Run-Once': '', - 'Psgi-Nonblocking': '', - 'Psgi-Streaming': '1' - }, 'headers') + self.assertLess( + abs(self.date_to_sec_epoch(date) - self.sec_epoch()), + 5, + 'date header', + ) + + self.assertDictEqual( + headers, + { + 'Connection': 'close', + 'Content-Length': str(len(body)), + 'Content-Type': 'text/html', + 'Request-Method': 'POST', + 'Request-Uri': '/', + 'Http-Host': 'localhost', + 'Server-Protocol': 'HTTP/1.1', + 'Custom-Header': 'blah', + 'Psgi-Version': '11', + 'Psgi-Url-Scheme': 'http', + 'Psgi-Multithread': '', + 'Psgi-Multiprocess': '1', + 'Psgi-Run-Once': '', + 'Psgi-Nonblocking': '', + 'Psgi-Streaming': '1', + }, + 'headers', + ) self.assertEqual(resp['body'], body, 'body') def test_perl_application_query_string(self): @@ -54,8 +66,11 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): resp = self.get(url='/?var1=val1&var2=val2') - self.assertEqual(resp['headers']['Query-String'], 'var1=val1&var2=val2', - 'Query-String header') + self.assertEqual( + resp['headers']['Query-String'], + 'var1=val1&var2=val2', + 'Query-String header', + ) def test_perl_application_query_string_empty(self): self.load('query_string') @@ -63,25 +78,27 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): resp = self.get(url='/?') self.assertEqual(resp['status'], 200, 'query string empty status') - self.assertEqual(resp['headers']['Query-String'], '', - 'query string empty') + self.assertEqual( + resp['headers']['Query-String'], '', 'query string empty' + ) - @unittest.expectedFailure def test_perl_application_query_string_absent(self): self.load('query_string') resp = self.get() self.assertEqual(resp['status'], 200, 'query string absent status') - self.assertEqual(resp['headers']['Query-String'], '', - 'query string absent') + self.assertEqual( + resp['headers']['Query-String'], '', 'query string absent' + ) - @unittest.expectedFailure + @unittest.skip('not yet') def test_perl_application_server_port(self): self.load('server_port') - self.assertEqual(self.get()['headers']['Server-Port'], '7080', - 'Server-Port header') + self.assertEqual( + self.get()['headers']['Server-Port'], '7080', 'Server-Port header' + ) def test_perl_application_input_read_empty(self): self.load('input_read_empty') @@ -91,15 +108,19 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): def test_perl_application_input_read_parts(self): self.load('input_read_parts') - self.assertEqual(self.post(body='0123456789')['body'], '0123456789', - 'input read parts') + self.assertEqual( + self.post(body='0123456789')['body'], + '0123456789', + 'input read parts', + ) - @unittest.expectedFailure + @unittest.skip('not yet') def test_perl_application_input_read_offset(self): self.load('input_read_offset') - self.assertEqual(self.post(body='0123456789')['body'], '4567', - 'read offset') + self.assertEqual( + self.post(body='0123456789')['body'], '4567', 'read offset' + ) def test_perl_application_input_copy(self): self.load('input_copy') @@ -115,14 +136,18 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): self.stop() self.assertIsNotNone( - self.search_in_log(r'\[error\].+Error in application'), - 'errors print') + self.wait_for_record(r'\[error\].+Error in application'), + 'errors print', + ) def test_perl_application_header_equal_names(self): self.load('header_equal_names') - self.assertListEqual(self.get()['headers']['Set-Cookie'], - ['tc=one,two,three', 'tc=four,five,six'], 'header equal names') + self.assertListEqual( + self.get()['headers']['Set-Cookie'], + ['tc=one,two,three', 'tc=four,five,six'], + 'header equal names', + ) def test_perl_application_header_pairs(self): self.load('header_pairs') @@ -158,12 +183,11 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): self.assertEqual(self.get()['body'], 'body\n', 'body io file') - @unittest.expectedFailure + @unittest.skip('not yet, unsafe') def test_perl_application_syntax_error(self): - self.skip_alerts.extend([ - r'PSGI: Failed to parse script', - r'process \d+ exited on signal' - ]) + self.skip_alerts.extend( + [r'PSGI: Failed to parse script'] + ) self.load('syntax_error') self.assertEqual(self.get()['status'], 500, 'syntax error') @@ -171,19 +195,30 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): def test_perl_keepalive_body(self): self.load('variables') - (resp, sock) = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive', - 'Content-Type': 'text/html' - }, start=True, body='0123456789' * 500) + self.assertEqual(self.get()['status'], 200, 'init') + + (resp, sock) = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + }, + start=True, + body='0123456789' * 500, + read_timeout=1, + ) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') - resp = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'close', - 'Content-Type': 'text/html' - }, sock=sock, body='0123456789') + resp = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'Content-Type': 'text/html', + }, + sock=sock, + body='0123456789', + ) self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') @@ -193,12 +228,14 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): self.assertEqual(self.get()['body'], '21', 'body io fake') self.assertIsNotNone( - self.search_in_log(r'\[error\].+IOFake getline\(\) \$\/ is \d+'), - 'body io fake $/ value') + self.wait_for_record(r'\[error\].+IOFake getline\(\) \$\/ is \d+'), + 'body io fake $/ value', + ) self.assertIsNotNone( - self.search_in_log(r'\[error\].+IOFake close\(\) called'), - 'body io fake close') + self.wait_for_record(r'\[error\].+IOFake close\(\) called'), + 'body io fake close', + ) def test_perl_delayed_response(self): self.load('delayed_response') @@ -216,5 +253,6 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl): self.assertEqual(resp['status'], 200, 'status') self.assertEqual(resp['body'], 'Hello World!', 'body') + if __name__ == '__main__': - TestUnitPerlApplication.main() + TestPerlApplication.main() diff --git a/test/test_php_application.py b/test/test_php_application.py index ac74359d..8032e96e 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -1,11 +1,9 @@ -import unittest -import unit import re +import unittest +from unit.applications.lang.php import TestApplicationPHP -class TestUnitPHPApplication(unit.TestUnitApplicationPHP): - - def setUpClass(): - unit.TestUnit().check_modules('php') +class TestPHPApplication(TestApplicationPHP): + prerequisites = ['php'] def before_disable_functions(self): body = self.get()['body'] @@ -18,38 +16,51 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): body = 'Test body string.' - resp = self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'Custom-Header': 'blah', - 'Connection': 'close' - }, body=body) + resp = self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Custom-Header': 'blah', + 'Connection': 'close', + }, + body=body, + ) self.assertEqual(resp['status'], 200, 'status') headers = resp['headers'] header_server = headers.pop('Server') self.assertRegex(header_server, r'Unit/[\d\.]+', 'server header') - self.assertEqual(headers.pop('Server-Software'), header_server, - 'server software header') + self.assertEqual( + headers.pop('Server-Software'), + header_server, + 'server software header', + ) date = headers.pop('Date') self.assertEqual(date[-4:], ' GMT', 'date header timezone') - self.assertLess(abs(self.date_to_sec_epoch(date) - self.sec_epoch()), 5, - 'date header') + self.assertLess( + abs(self.date_to_sec_epoch(date) - self.sec_epoch()), + 5, + 'date header', + ) if 'X-Powered-By' in headers: headers.pop('X-Powered-By') headers.pop('Content-type') - self.assertDictEqual(headers, { - 'Connection': 'close', - 'Content-Length': str(len(body)), - 'Request-Method': 'POST', - 'Request-Uri': '/', - 'Http-Host': 'localhost', - 'Server-Protocol': 'HTTP/1.1', - 'Custom-Header': 'blah' - }, 'headers') + self.assertDictEqual( + headers, + { + 'Connection': 'close', + 'Content-Length': str(len(body)), + 'Request-Method': 'POST', + 'Request-Uri': '/', + 'Http-Host': 'localhost', + 'Server-Protocol': 'HTTP/1.1', + 'Custom-Header': 'blah', + }, + 'headers', + ) self.assertEqual(resp['body'], body, 'body') def test_php_application_query_string(self): @@ -57,8 +68,11 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): resp = self.get(url='/?var1=val1&var2=val2') - self.assertEqual(resp['headers']['Query-String'], 'var1=val1&var2=val2', - 'query string') + self.assertEqual( + resp['headers']['Query-String'], + 'var1=val1&var2=val2', + 'query string', + ) def test_php_application_query_string_empty(self): self.load('query_string') @@ -66,18 +80,19 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): resp = self.get(url='/?') self.assertEqual(resp['status'], 200, 'query string empty status') - self.assertEqual(resp['headers']['Query-String'], '', - 'query string empty') + self.assertEqual( + resp['headers']['Query-String'], '', 'query string empty' + ) - @unittest.expectedFailure def test_php_application_query_string_absent(self): self.load('query_string') resp = self.get() self.assertEqual(resp['status'], 200, 'query string absent status') - self.assertEqual(resp['headers']['Query-String'], '', - 'query string absent') + self.assertEqual( + resp['headers']['Query-String'], '', 'query string absent' + ) def test_php_application_phpinfo(self): self.load('phpinfo') @@ -93,25 +108,37 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): resp = self.get() self.assertEqual(resp['status'], 404, '404 status') - self.assertRegex(resp['body'], r'<title>404 Not Found</title>', - '404 body') + self.assertRegex( + resp['body'], r'<title>404 Not Found</title>', '404 body' + ) def test_php_application_keepalive_body(self): self.load('mirror') - (resp, sock) = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive', - 'Content-Type': 'text/html' - }, start=True, body='0123456789' * 500) + self.assertEqual(self.get()['status'], 200, 'init') + + (resp, sock) = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + }, + start=True, + body='0123456789' * 500, + read_timeout=1, + ) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') - resp = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'close', - 'Content-Type': 'text/html' - }, sock=sock, body='0123456789') + resp = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'Content-Type': 'text/html', + }, + sock=sock, + body='0123456789', + ) self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') @@ -133,11 +160,14 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): def test_php_application_post_variables(self): self.load('post_variables') - resp = self.post(headers={ - 'Content-Type': 'application/x-www-form-urlencoded', - 'Host': 'localhost', - 'Connection': 'close' - }, body='var1=val1&var2=') + resp = self.post( + headers={ + 'Content-Type': 'application/x-www-form-urlencoded', + 'Host': 'localhost', + 'Connection': 'close', + }, + body='var1=val1&var2=', + ) self.assertEqual(resp['headers']['X-Var-1'], 'val1', 'POST variables') self.assertEqual(resp['headers']['X-Var-2'], '1', 'POST variables 2') self.assertEqual(resp['headers']['X-Var-3'], '', 'POST variables 3') @@ -145,11 +175,13 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): def test_php_application_cookies(self): self.load('cookies') - resp = self.get(headers={ - 'Cookie': 'var=val; var2=val2', - 'Host': 'localhost', - 'Connection': 'close' - }) + resp = self.get( + headers={ + 'Cookie': 'var=val; var2=val2', + 'Host': 'localhost', + 'Connection': 'close', + } + ) self.assertEqual(resp['headers']['X-Cookie-1'], 'val', 'cookie') self.assertEqual(resp['headers']['X-Cookie-2'], 'val2', 'cookie') @@ -157,96 +189,129 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): def test_php_application_ini_precision(self): self.load('ini_precision') - self.assertNotEqual(self.get()['headers']['X-Precision'], '4', - 'ini value default') + self.assertNotEqual( + self.get()['headers']['X-Precision'], '4', 'ini value default' + ) - self.conf({"file": "php.ini"}, 'applications/ini_precision/options') + self.conf( + {"file": "ini/php.ini"}, 'applications/ini_precision/options' + ) - self.assertEqual(self.get()['headers']['X-File'], - self.current_dir + '/php/ini_precision/php.ini', 'ini file') - self.assertEqual(self.get()['headers']['X-Precision'], '4', 'ini value') + self.assertEqual( + self.get()['headers']['X-File'], + self.current_dir + '/php/ini_precision/ini/php.ini', + 'ini file', + ) + self.assertEqual( + self.get()['headers']['X-Precision'], '4', 'ini value' + ) - @unittest.expectedFailure + @unittest.skip('not yet') def test_php_application_ini_admin_user(self): self.load('ini_precision') - self.assertIn('error', self.conf({ - "user": { "precision": "4" }, - "admin": { "precision": "5" } - }, 'applications/ini_precision/options'), 'ini admin user') + self.assertIn( + 'error', + self.conf( + {"user": {"precision": "4"}, "admin": {"precision": "5"}}, + 'applications/ini_precision/options', + ), + 'ini admin user', + ) def test_php_application_ini_admin(self): self.load('ini_precision') - self.conf({ - "file": "php.ini", - "admin": { "precision": "5" } - }, 'applications/ini_precision/options') + self.conf( + {"file": "php.ini", "admin": {"precision": "5"}}, + 'applications/ini_precision/options', + ) - self.assertEqual(self.get()['headers']['X-Precision'], '5', - 'ini value admin') + self.assertEqual( + self.get()['headers']['X-Precision'], '5', 'ini value admin' + ) def test_php_application_ini_user(self): self.load('ini_precision') - self.conf({ - "file": "php.ini", - "user": { "precision": "5" } - }, 'applications/ini_precision/options') + self.conf( + {"file": "php.ini", "user": {"precision": "5"}}, + 'applications/ini_precision/options', + ) - self.assertEqual(self.get()['headers']['X-Precision'], '5', - 'ini value user') + self.assertEqual( + self.get()['headers']['X-Precision'], '5', 'ini value user' + ) def test_php_application_ini_user_2(self): self.load('ini_precision') - self.conf({"file": "php.ini"}, 'applications/ini_precision/options') + self.conf( + {"file": "ini/php.ini"}, 'applications/ini_precision/options' + ) - self.assertEqual(self.get()['headers']['X-Precision'], '4', - 'ini user file') + self.assertEqual( + self.get()['headers']['X-Precision'], '4', 'ini user file' + ) - self.conf({ "precision": "5" }, - 'applications/ini_precision/options/user') + self.conf( + {"precision": "5"}, 'applications/ini_precision/options/user' + ) - self.assertEqual(self.get()['headers']['X-Precision'], '5', - 'ini value user') + self.assertEqual( + self.get()['headers']['X-Precision'], '5', 'ini value user' + ) def test_php_application_ini_set_admin(self): self.load('ini_precision') - self.conf({"admin": { "precision": "5" }}, - 'applications/ini_precision/options') + self.conf( + {"admin": {"precision": "5"}}, 'applications/ini_precision/options' + ) - self.assertEqual(self.get(url='/?precision=6')['headers']['X-Precision'], - '5', 'ini set admin') + self.assertEqual( + self.get(url='/?precision=6')['headers']['X-Precision'], + '5', + 'ini set admin', + ) def test_php_application_ini_set_user(self): self.load('ini_precision') - self.conf({"user": { "precision": "5" }}, - 'applications/ini_precision/options') + self.conf( + {"user": {"precision": "5"}}, 'applications/ini_precision/options' + ) - self.assertEqual(self.get(url='/?precision=6')['headers']['X-Precision'], - '6', 'ini set user') + self.assertEqual( + self.get(url='/?precision=6')['headers']['X-Precision'], + '6', + 'ini set user', + ) def test_php_application_ini_repeat(self): self.load('ini_precision') - self.conf({"user": { "precision": "5" }}, - 'applications/ini_precision/options') + self.conf( + {"user": {"precision": "5"}}, 'applications/ini_precision/options' + ) - self.assertEqual(self.get()['headers']['X-Precision'], '5', 'ini value') + self.assertEqual( + self.get()['headers']['X-Precision'], '5', 'ini value' + ) - self.assertEqual(self.get()['headers']['X-Precision'], '5', - 'ini value repeat') + self.assertEqual( + self.get()['headers']['X-Precision'], '5', 'ini value repeat' + ) def test_php_application_disable_functions_exec(self): self.load('time_exec') self.before_disable_functions() - self.conf({"admin": { "disable_functions": "exec" }}, - 'applications/time_exec/options') + self.conf( + {"admin": {"disable_functions": "exec"}}, + 'applications/time_exec/options', + ) body = self.get()['body'] @@ -258,80 +323,103 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP): self.before_disable_functions() - self.conf({"admin": { "disable_functions": "exec,time" }}, - 'applications/time_exec/options') + self.conf( + {"admin": {"disable_functions": "exec,time"}}, + 'applications/time_exec/options', + ) body = self.get()['body'] self.assertNotRegex(body, r'time: \d+', 'disable_functions comma time') - self.assertNotRegex(body, r'exec: \/\w+', - 'disable_functions comma exec') + self.assertNotRegex( + body, r'exec: \/\w+', 'disable_functions comma exec' + ) def test_php_application_disable_functions_space(self): self.load('time_exec') self.before_disable_functions() - self.conf({"admin": { "disable_functions": "exec time" }}, - 'applications/time_exec/options') + self.conf( + {"admin": {"disable_functions": "exec time"}}, + 'applications/time_exec/options', + ) body = self.get()['body'] self.assertNotRegex(body, r'time: \d+', 'disable_functions space time') - self.assertNotRegex(body, r'exec: \/\w+', - 'disable_functions space exec') + self.assertNotRegex( + body, r'exec: \/\w+', 'disable_functions space exec' + ) def test_php_application_disable_functions_user(self): self.load('time_exec') self.before_disable_functions() - self.conf({"user": { "disable_functions": "exec" }}, - 'applications/time_exec/options') + self.conf( + {"user": {"disable_functions": "exec"}}, + 'applications/time_exec/options', + ) body = self.get()['body'] self.assertRegex(body, r'time: \d+', 'disable_functions user time') - self.assertNotRegex(body, r'exec: \/\w+', 'disable_functions user exec') + self.assertNotRegex( + body, r'exec: \/\w+', 'disable_functions user exec' + ) def test_php_application_disable_functions_nonexistent(self): self.load('time_exec') self.before_disable_functions() - self.conf({"admin": { "disable_functions": "blah" }}, - 'applications/time_exec/options') + self.conf( + {"admin": {"disable_functions": "blah"}}, + 'applications/time_exec/options', + ) body = self.get()['body'] - self.assertRegex(body, r'time: \d+', - 'disable_functions nonexistent time') - self.assertRegex(body, r'exec: \/\w+', - 'disable_functions nonexistent exec') + self.assertRegex( + body, r'time: \d+', 'disable_functions nonexistent time' + ) + self.assertRegex( + body, r'exec: \/\w+', 'disable_functions nonexistent exec' + ) def test_php_application_disable_classes(self): self.load('date_time') - self.assertRegex(self.get()['body'], r'012345', - 'disable_classes before') + self.assertRegex( + self.get()['body'], r'012345', 'disable_classes before' + ) - self.conf({"admin": { "disable_classes": "DateTime" }}, - 'applications/date_time/options') + self.conf( + {"admin": {"disable_classes": "DateTime"}}, + 'applications/date_time/options', + ) - self.assertNotRegex(self.get()['body'], r'012345', - 'disable_classes before') + self.assertNotRegex( + self.get()['body'], r'012345', 'disable_classes before' + ) def test_php_application_disable_classes_user(self): self.load('date_time') - self.assertRegex(self.get()['body'], r'012345', - 'disable_classes before') + self.assertRegex( + self.get()['body'], r'012345', 'disable_classes before' + ) + + self.conf( + {"user": {"disable_classes": "DateTime"}}, + 'applications/date_time/options', + ) - self.conf({"user": { "disable_classes": "DateTime" }}, - 'applications/date_time/options') + self.assertNotRegex( + self.get()['body'], r'012345', 'disable_classes before' + ) - self.assertNotRegex(self.get()['body'], r'012345', - 'disable_classes before') if __name__ == '__main__': - TestUnitPHPApplication.main() + TestPHPApplication.main() diff --git a/test/test_php_basic.py b/test/test_php_basic.py index 1ea46c91..02ff81de 100644 --- a/test/test_php_basic.py +++ b/test/test_php_basic.py @@ -1,27 +1,21 @@ -import unittest -import unit +from unit.control import TestControl -class TestUnitPHPBasic(unit.TestUnitControl): - def setUpClass(): - unit.TestUnit().check_modules('php') +class TestPHPBasic(TestControl): + prerequisites = ['php'] conf_app = { "app": { "type": "php", - "processes": { "spare": 0 }, + "processes": {"spare": 0}, "root": "/app", - "index": "index.php" + "index": "index.php", } } conf_basic = { - "listeners": { - "*:7080": { - "application": "app" - } - }, - "applications": conf_app + "listeners": {"*:7080": {"pass": "applications/app"}}, + "applications": conf_app, } def test_php_get_applications(self): @@ -30,113 +24,146 @@ class TestUnitPHPBasic(unit.TestUnitControl): conf = self.conf_get() self.assertEqual(conf['listeners'], {}, 'listeners') - self.assertEqual(conf['applications'], + self.assertEqual( + conf['applications'], { "app": { "type": "php", - "processes": { "spare": 0 }, + "processes": {"spare": 0}, "root": "/app", - "index": "index.php" + "index": "index.php", } - }, - 'applications') + }, + 'applications', + ) def test_php_get_applications_prefix(self): self.conf(self.conf_app, 'applications') - self.assertEqual(self.conf_get('applications'), + self.assertEqual( + self.conf_get('applications'), { "app": { "type": "php", - "processes": { "spare": 0 }, + "processes": {"spare": 0}, "root": "/app", - "index": "index.php" + "index": "index.php", } }, - 'applications prefix') + 'applications prefix', + ) def test_php_get_applications_prefix_2(self): self.conf(self.conf_app, 'applications') - self.assertEqual(self.conf_get('applications/app'), + self.assertEqual( + self.conf_get('applications/app'), { "type": "php", - "processes": { "spare": 0 }, + "processes": {"spare": 0}, "root": "/app", - "index": "index.php" + "index": "index.php", }, - 'applications prefix 2') + 'applications prefix 2', + ) def test_php_get_applications_prefix_3(self): self.conf(self.conf_app, 'applications') - self.assertEqual(self.conf_get('applications/app/type'), 'php', - 'type') - self.assertEqual(self.conf_get('applications/app/processes/spare'), 0, - 'spare processes') + self.assertEqual(self.conf_get('applications/app/type'), 'php', 'type') + self.assertEqual( + self.conf_get('applications/app/processes/spare'), + 0, + 'spare processes', + ) def test_php_get_listeners(self): self.conf(self.conf_basic) - self.assertEqual(self.conf_get()['listeners'], - {"*:7080":{"application":"app"}}, 'listeners') + self.assertEqual( + self.conf_get()['listeners'], + {"*:7080": {"pass": "applications/app"}}, + 'listeners', + ) def test_php_get_listeners_prefix(self): self.conf(self.conf_basic) - self.assertEqual(self.conf_get('listeners'), - {"*:7080":{"application":"app"}}, 'listeners prefix') + self.assertEqual( + self.conf_get('listeners'), + {"*:7080": {"pass": "applications/app"}}, + 'listeners prefix', + ) def test_php_get_listeners_prefix_2(self): self.conf(self.conf_basic) - self.assertEqual(self.conf_get('listeners/*:7080'), - {"application":"app"}, 'listeners prefix 2') + self.assertEqual( + self.conf_get('listeners/*:7080'), + {"pass": "applications/app"}, + 'listeners prefix 2', + ) def test_php_change_listener(self): self.conf(self.conf_basic) - self.conf({"*:7081":{"application":"app"}}, 'listeners') + self.conf({"*:7081": {"pass": "applications/app"}}, 'listeners') - self.assertEqual(self.conf_get('listeners'), - {"*:7081": {"application":"app"}}, 'change listener') + self.assertEqual( + self.conf_get('listeners'), + {"*:7081": {"pass": "applications/app"}}, + 'change listener', + ) def test_php_add_listener(self): self.conf(self.conf_basic) - self.conf({"application":"app"}, 'listeners/*:7082') + self.conf({"pass": "applications/app"}, 'listeners/*:7082') - self.assertEqual(self.conf_get('listeners'), + self.assertEqual( + self.conf_get('listeners'), { - "*:7080": { - "application": "app" - }, - "*:7082": { - "application": "app" - } + "*:7080": {"pass": "applications/app"}, + "*:7082": {"pass": "applications/app"}, }, - 'add listener') + 'add listener', + ) def test_php_change_application(self): self.conf(self.conf_basic) self.conf('30', 'applications/app/processes/max') - self.assertEqual(self.conf_get('applications/app/processes/max'), 30, - 'change application max') + self.assertEqual( + self.conf_get('applications/app/processes/max'), + 30, + 'change application max', + ) self.conf('"/www"', 'applications/app/root') - self.assertEqual(self.conf_get('applications/app/root'), '/www', - 'change application root') + self.assertEqual( + self.conf_get('applications/app/root'), + '/www', + 'change application root', + ) def test_php_delete(self): self.conf(self.conf_basic) - self.assertIn('error', self.conf_delete('applications/app'), - 'delete app before listener') - self.assertIn('success', self.conf_delete('listeners/*:7080'), - 'delete listener') - self.assertIn('success', self.conf_delete('applications/app'), - 'delete app after listener') - self.assertIn('error', self.conf_delete('applications/app'), - 'delete app again') + self.assertIn( + 'error', + self.conf_delete('applications/app'), + 'delete app before listener', + ) + self.assertIn( + 'success', self.conf_delete('listeners/*:7080'), 'delete listener' + ) + self.assertIn( + 'success', + self.conf_delete('applications/app'), + 'delete app after listener', + ) + self.assertIn( + 'error', self.conf_delete('applications/app'), 'delete app again' + ) + if __name__ == '__main__': - TestUnitPHPBasic.main() + TestPHPBasic.main() diff --git a/test/test_python_application.py b/test/test_python_application.py index a8631085..3484b25e 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -1,51 +1,63 @@ import time import unittest -import unit +from unit.applications.lang.python import TestApplicationPython -class TestUnitPythonApplication(unit.TestUnitApplicationPython): - def setUpClass(): - unit.TestUnit().check_modules('python') +class TestPythonApplication(TestApplicationPython): + prerequisites = ['python'] def test_python_application_variables(self): self.load('variables') body = 'Test body string.' - resp = self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'Custom-Header': 'blah', - 'Connection': 'close' - }, body=body) + resp = self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Custom-Header': 'blah', + 'Connection': 'close', + }, + body=body, + ) self.assertEqual(resp['status'], 200, 'status') headers = resp['headers'] header_server = headers.pop('Server') self.assertRegex(header_server, r'Unit/[\d\.]+', 'server header') - self.assertEqual(headers.pop('Server-Software'), header_server, - 'server software header') + self.assertEqual( + headers.pop('Server-Software'), + header_server, + 'server software header', + ) date = headers.pop('Date') self.assertEqual(date[-4:], ' GMT', 'date header timezone') - self.assertLess(abs(self.date_to_sec_epoch(date) - self.sec_epoch()), 5, - 'date header') - - self.assertDictEqual(headers, { - 'Connection': 'close', - 'Content-Length': str(len(body)), - 'Content-Type': 'text/html', - 'Request-Method': 'POST', - 'Request-Uri': '/', - 'Http-Host': 'localhost', - 'Server-Protocol': 'HTTP/1.1', - 'Custom-Header': 'blah', - 'Wsgi-Version': '(1, 0)', - 'Wsgi-Url-Scheme': 'http', - 'Wsgi-Multithread': 'False', - 'Wsgi-Multiprocess': 'True', - 'Wsgi-Run-Once': 'False' - }, 'headers') + self.assertLess( + abs(self.date_to_sec_epoch(date) - self.sec_epoch()), + 5, + 'date header', + ) + + self.assertDictEqual( + headers, + { + 'Connection': 'close', + 'Content-Length': str(len(body)), + 'Content-Type': 'text/html', + 'Request-Method': 'POST', + 'Request-Uri': '/', + 'Http-Host': 'localhost', + 'Server-Protocol': 'HTTP/1.1', + 'Custom-Header': 'blah', + 'Wsgi-Version': '(1, 0)', + 'Wsgi-Url-Scheme': 'http', + 'Wsgi-Multithread': 'False', + 'Wsgi-Multiprocess': 'True', + 'Wsgi-Run-Once': 'False', + }, + 'headers', + ) self.assertEqual(resp['body'], body, 'body') def test_python_application_query_string(self): @@ -53,8 +65,11 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): resp = self.get(url='/?var1=val1&var2=val2') - self.assertEqual(resp['headers']['Query-String'], 'var1=val1&var2=val2', - 'Query-String header') + self.assertEqual( + resp['headers']['Query-String'], + 'var1=val1&var2=val2', + 'Query-String header', + ) def test_python_application_query_string_empty(self): self.load('query_string') @@ -62,8 +77,9 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): resp = self.get(url='/?') self.assertEqual(resp['status'], 200, 'query string empty status') - self.assertEqual(resp['headers']['Query-String'], '', - 'query string empty') + self.assertEqual( + resp['headers']['Query-String'], '', 'query string empty' + ) def test_python_application_query_string_absent(self): self.load('query_string') @@ -71,141 +87,198 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): resp = self.get() self.assertEqual(resp['status'], 200, 'query string absent status') - self.assertEqual(resp['headers']['Query-String'], '', - 'query string absent') + self.assertEqual( + resp['headers']['Query-String'], '', 'query string absent' + ) - @unittest.expectedFailure + @unittest.skip('not yet') def test_python_application_server_port(self): self.load('server_port') - self.assertEqual(self.get()['headers']['Server-Port'], '7080', - 'Server-Port header') + self.assertEqual( + self.get()['headers']['Server-Port'], '7080', 'Server-Port header' + ) def test_python_application_204_transfer_encoding(self): self.load('204_no_content') - self.assertNotIn('Transfer-Encoding', self.get()['headers'], - '204 header transfer encoding') + self.assertNotIn( + 'Transfer-Encoding', + self.get()['headers'], + '204 header transfer encoding', + ) def test_python_application_ctx_iter_atexit(self): self.load('ctx_iter_atexit') - resp = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'close', - 'Content-Type': 'text/html' - }, body='0123456789') + resp = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'Content-Type': 'text/html', + }, + body='0123456789', + ) self.assertEqual(resp['status'], 200, 'ctx iter status') self.assertEqual(resp['body'], '0123456789', 'ctx iter body') - self.conf({ - "listeners": {}, - "applications": {} - }) + self.conf({"listeners": {}, "applications": {}}) self.stop() - time.sleep(0.2) - - self.assertIsNotNone(self.search_in_log(r'RuntimeError'), - 'ctx iter atexit') + self.assertIsNotNone( + self.wait_for_record(r'RuntimeError'), 'ctx iter atexit' + ) def test_python_keepalive_body(self): self.load('mirror') - (resp, sock) = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive', - 'Content-Type': 'text/html' - }, start=True, body='0123456789' * 500) + self.assertEqual(self.get()['status'], 200, 'init') + + (resp, sock) = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + }, + start=True, + body='0123456789' * 500, + read_timeout=1, + ) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') - resp = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'close', - 'Content-Type': 'text/html' - }, sock=sock, body='0123456789') + resp = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'Content-Type': 'text/html', + }, + sock=sock, + body='0123456789', + ) self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') def test_python_keepalive_reconfigure(self): + self.skip_alerts.extend( + [ + r'pthread_mutex.+failed', + r'failed to apply', + r'process \d+ exited on signal', + ] + ) self.load('mirror') + self.assertEqual(self.get()['status'], 200, 'init') + body = '0123456789' conns = 3 socks = [] for i in range(conns): - (resp, sock) = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive', - 'Content-Type': 'text/html' - }, start=True, body=body) + (resp, sock) = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + }, + start=True, + body=body, + read_timeout=1, + ) self.assertEqual(resp['body'], body, 'keep-alive open') - self.assertIn('success', self.conf({ - "spare": i % 4, - "max": (i % 4) + 1 - }, 'applications/mirror/processes'), 'reconfigure') + self.assertIn( + 'success', + self.conf(str(i + 1), 'applications/mirror/processes'), + 'reconfigure', + ) socks.append(sock) for i in range(conns): - (resp, sock) = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive', - 'Content-Type': 'text/html' - }, start=True, sock=socks[i], body=body) + (resp, sock) = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + }, + start=True, + sock=socks[i], + body=body, + read_timeout=1, + ) self.assertEqual(resp['body'], body, 'keep-alive request') - self.assertIn('success', self.conf({ - "spare": i % 4, - "max": (i % 4) + 1 - }, 'applications/mirror/processes'), 'reconfigure 2') + self.assertIn( + 'success', + self.conf(str(i + 1), 'applications/mirror/processes'), + 'reconfigure 2', + ) for i in range(conns): - resp = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'close', - 'Content-Type': 'text/html' - }, sock=socks[i], body=body) + resp = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'Content-Type': 'text/html', + }, + sock=socks[i], + body=body, + ) self.assertEqual(resp['body'], body, 'keep-alive close') - self.assertIn('success', self.conf({ - "spare": i % 4, - "max": (i % 4) + 1 - }, 'applications/mirror/processes'), 'reconfigure 3') + self.assertIn( + 'success', + self.conf(str(i + 1), 'applications/mirror/processes'), + 'reconfigure 3', + ) def test_python_keepalive_reconfigure_2(self): self.load('mirror') + self.assertEqual(self.get()['status'], 200, 'init') + body = '0123456789' - (resp, sock) = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive', - 'Content-Type': 'text/html' - }, start=True, body=body) + (resp, sock) = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + }, + start=True, + body=body, + read_timeout=1, + ) self.assertEqual(resp['body'], body, 'reconfigure 2 keep-alive 1') self.load('empty') - (resp, sock) = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'close', - 'Content-Type': 'text/html' - }, start=True, sock=sock, body=body) + self.assertEqual(self.get()['status'], 200, 'init') + + (resp, sock) = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'Content-Type': 'text/html', + }, + start=True, + sock=sock, + body=body, + ) self.assertEqual(resp['status'], 200, 'reconfigure 2 keep-alive 2') self.assertEqual(resp['body'], '', 'reconfigure 2 keep-alive 2 body') - self.assertIn('success', self.conf({ - "listeners": {}, - "applications": {} - }), 'reconfigure 2 clear configuration') + self.assertIn( + 'success', + self.conf({"listeners": {}, "applications": {}}), + 'reconfigure 2 clear configuration', + ) resp = self.get(sock=sock) @@ -214,18 +287,30 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython): def test_python_keepalive_reconfigure_3(self): self.load('empty') - (resp, sock) = self.http(b"""GET / HTTP/1.1 -""", start=True, raw=True) + self.assertEqual(self.get()['status'], 200, 'init') + + (resp, sock) = self.http( + b"""GET / HTTP/1.1 +""", + start=True, + raw=True, + read_timeout=5, + ) - self.assertIn('success', self.conf({ - "listeners": {}, - "applications": {} - }), 'reconfigure 3 clear configuration') + self.assertIn( + 'success', + self.conf({"listeners": {}, "applications": {}}), + 'reconfigure 3 clear configuration', + ) - resp = self.http(b"""Host: localhost + resp = self.http( + b"""Host: localhost Connection: close -""", sock=sock, raw=True) +""", + sock=sock, + raw=True, + ) self.assertEqual(resp['status'], 200, 'reconfigure 3') @@ -234,22 +319,21 @@ Connection: close self.get() - self.conf({ - "listeners": {}, - "applications": {} - }) + self.conf({"listeners": {}, "applications": {}}) self.stop() - self.assertIsNotNone(self.search_in_log(r'At exit called\.'), 'atexit') + self.assertIsNotNone( + self.wait_for_record(r'At exit called\.'), 'atexit' + ) - @unittest.expectedFailure + @unittest.skip('not yet') def test_python_application_start_response_exit(self): self.load('start_response_exit') self.assertEqual(self.get()['status'], 500, 'start response exit') - @unittest.expectedFailure + @unittest.skip('not yet') def test_python_application_input_iter(self): self.load('input_iter') @@ -262,39 +346,51 @@ Connection: close body = '0123456789' - resp = self.post(headers={ - 'Host': 'localhost', - 'Input-Length': '5', - 'Connection': 'close' - }, body=body) + resp = self.post( + headers={ + 'Host': 'localhost', + 'Input-Length': '5', + 'Connection': 'close', + }, + body=body, + ) self.assertEqual(resp['body'], body[:5], 'input read length lt body') - resp = self.post(headers={ - 'Host': 'localhost', - 'Input-Length': '15', - 'Connection': 'close' - }, body=body) + resp = self.post( + headers={ + 'Host': 'localhost', + 'Input-Length': '15', + 'Connection': 'close', + }, + body=body, + ) self.assertEqual(resp['body'], body, 'input read length gt body') - resp = self.post(headers={ - 'Host': 'localhost', - 'Input-Length': '0', - 'Connection': 'close' - }, body=body) + resp = self.post( + headers={ + 'Host': 'localhost', + 'Input-Length': '0', + 'Connection': 'close', + }, + body=body, + ) self.assertEqual(resp['body'], '', 'input read length zero') - resp = self.post(headers={ - 'Host': 'localhost', - 'Input-Length': '-1', - 'Connection': 'close' - }, body=body) + resp = self.post( + headers={ + 'Host': 'localhost', + 'Input-Length': '-1', + 'Connection': 'close', + }, + body=body, + ) self.assertEqual(resp['body'], body, 'input read length negative') - @unittest.expectedFailure + @unittest.skip('not yet') def test_python_application_errors_write(self): self.load('errors_write') @@ -303,8 +399,9 @@ Connection: close self.stop() self.assertIsNotNone( - self.search_in_log(r'\[error\].+Error in application\.'), - 'errors write') + self.wait_for_record(r'\[error\].+Error in application\.'), + 'errors write', + ) def test_python_application_body_array(self): self.load('body_array') @@ -321,7 +418,7 @@ Connection: close self.assertEqual(self.get()['body'], 'body\n', 'body io file') - @unittest.expectedFailure + @unittest.skip('not yet') def test_python_application_syntax_error(self): self.skip_alerts.append(r'Python failed to import module "wsgi"') self.load('syntax_error') @@ -335,7 +432,7 @@ Connection: close self.stop() - self.assertIsNotNone(self.search_in_log(r'Close called\.'), 'close') + self.assertIsNotNone(self.wait_for_record(r'Close called\.'), 'close') def test_python_application_close_error(self): self.load('close_error') @@ -344,8 +441,9 @@ Connection: close self.stop() - self.assertIsNotNone(self.search_in_log(r'Close called\.'), - 'close error') + self.assertIsNotNone( + self.wait_for_record(r'Close called\.'), 'close error' + ) def test_python_application_not_iterable(self): self.load('not_iterable') @@ -354,14 +452,18 @@ Connection: close self.stop() - self.assertIsNotNone(self.search_in_log( - r'\[error\].+the application returned not an iterable object'), - 'not iterable') + self.assertIsNotNone( + self.wait_for_record( + r'\[error\].+the application returned not an iterable object' + ), + 'not iterable', + ) def test_python_application_write(self): self.load('write') self.assertEqual(self.get()['body'], '0123456789', 'write') + if __name__ == '__main__': - TestUnitPythonApplication.main() + TestPythonApplication.main() diff --git a/test/test_python_basic.py b/test/test_python_basic.py index b5179dea..9987e886 100644 --- a/test/test_python_basic.py +++ b/test/test_python_basic.py @@ -1,38 +1,35 @@ -import unittest -import unit +from unit.control import TestControl -class TestUnitPythonBasic(unit.TestUnitControl): - def setUpClass(): - unit.TestUnit().check_modules('python') +class TestPythonBasic(TestControl): + prerequisites = ['python'] conf_app = { "app": { "type": "python", - "processes": { "spare": 0 }, + "processes": {"spare": 0}, "path": "/app", - "module": "wsgi" + "module": "wsgi", } } conf_basic = { - "listeners": { - "*:7080": { - "application": "app" - } - }, - "applications": conf_app + "listeners": {"*:7080": {"pass": "applications/app"}}, + "applications": conf_app, } def test_python_get_empty(self): - self.assertEqual(self.conf_get(), - {'listeners': {}, 'applications': {}}, 'empty') + self.assertEqual( + self.conf_get(), {'listeners': {}, 'applications': {}}, 'empty' + ) def test_python_get_prefix_listeners(self): self.assertEqual(self.conf_get('listeners'), {}, 'listeners prefix') def test_python_get_prefix_applications(self): - self.assertEqual(self.conf_get('applications'), {}, 'applications prefix') + self.assertEqual( + self.conf_get('applications'), {}, 'applications prefix' + ) def test_python_get_applications(self): self.conf(self.conf_app, 'applications') @@ -40,113 +37,146 @@ class TestUnitPythonBasic(unit.TestUnitControl): conf = self.conf_get() self.assertEqual(conf['listeners'], {}, 'listeners') - self.assertEqual(conf['applications'], + self.assertEqual( + conf['applications'], { "app": { "type": "python", - "processes": { "spare": 0 }, + "processes": {"spare": 0}, "path": "/app", - "module": "wsgi" + "module": "wsgi", } }, - 'applications') + 'applications', + ) def test_python_get_applications_prefix(self): self.conf(self.conf_app, 'applications') - self.assertEqual(self.conf_get('applications'), + self.assertEqual( + self.conf_get('applications'), { "app": { "type": "python", - "processes": { "spare": 0 }, + "processes": {"spare": 0}, "path": "/app", - "module":"wsgi" + "module": "wsgi", } }, - 'applications prefix') + 'applications prefix', + ) def test_python_get_applications_prefix_2(self): self.conf(self.conf_app, 'applications') - self.assertEqual(self.conf_get('applications/app'), + self.assertEqual( + self.conf_get('applications/app'), { "type": "python", - "processes": { "spare": 0 }, + "processes": {"spare": 0}, "path": "/app", - "module": "wsgi" + "module": "wsgi", }, - 'applications prefix 2') + 'applications prefix 2', + ) def test_python_get_applications_prefix_3(self): self.conf(self.conf_app, 'applications') - self.assertEqual(self.conf_get('applications/app/type'), 'python', - 'type') - self.assertEqual(self.conf_get('applications/app/processes/spare'), 0, - 'spare') + self.assertEqual( + self.conf_get('applications/app/type'), 'python', 'type' + ) + self.assertEqual( + self.conf_get('applications/app/processes/spare'), 0, 'spare' + ) def test_python_get_listeners(self): self.conf(self.conf_basic) - self.assertEqual(self.conf_get()['listeners'], - {"*:7080":{"application":"app"}}, 'listeners') + self.assertEqual( + self.conf_get()['listeners'], + {"*:7080": {"pass": "applications/app"}}, + 'listeners', + ) def test_python_get_listeners_prefix(self): self.conf(self.conf_basic) - self.assertEqual(self.conf_get('listeners'), - {"*:7080":{"application":"app"}}, 'listeners prefix') + self.assertEqual( + self.conf_get('listeners'), + {"*:7080": {"pass": "applications/app"}}, + 'listeners prefix', + ) def test_python_get_listeners_prefix_2(self): self.conf(self.conf_basic) - self.assertEqual(self.conf_get('listeners/*:7080'), - {"application":"app"}, 'listeners prefix 2') + self.assertEqual( + self.conf_get('listeners/*:7080'), + {"pass": "applications/app"}, + 'listeners prefix 2', + ) def test_python_change_listener(self): self.conf(self.conf_basic) - self.conf({"*:7081":{"application":"app"}}, 'listeners') + self.conf({"*:7081": {"pass": "applications/app"}}, 'listeners') - self.assertEqual(self.conf_get('listeners'), - {"*:7081": {"application":"app"}}, 'change listener') + self.assertEqual( + self.conf_get('listeners'), + {"*:7081": {"pass": "applications/app"}}, + 'change listener', + ) def test_python_add_listener(self): self.conf(self.conf_basic) - self.conf({"application":"app"}, 'listeners/*:7082') + self.conf({"pass": "applications/app"}, 'listeners/*:7082') - self.assertEqual(self.conf_get('listeners'), + self.assertEqual( + self.conf_get('listeners'), { - "*:7080": { - "application": "app" - }, - "*:7082": { - "application": "app" - } + "*:7080": {"pass": "applications/app"}, + "*:7082": {"pass": "applications/app"}, }, - 'add listener') + 'add listener', + ) def test_python_change_application(self): self.conf(self.conf_basic) self.conf('30', 'applications/app/processes/max') - self.assertEqual(self.conf_get('applications/app/processes/max'), 30, - 'change application max') + self.assertEqual( + self.conf_get('applications/app/processes/max'), + 30, + 'change application max', + ) self.conf('"/www"', 'applications/app/path') - self.assertEqual(self.conf_get('applications/app/path'), '/www', - 'change application path') + self.assertEqual( + self.conf_get('applications/app/path'), + '/www', + 'change application path', + ) def test_python_delete(self): self.conf(self.conf_basic) - self.assertIn('error', self.conf_delete('applications/app'), - 'delete app before listener') - self.assertIn('success', self.conf_delete('listeners/*:7080'), - 'delete listener') - self.assertIn('success', self.conf_delete('applications/app'), - 'delete app after listener') - self.assertIn('error', self.conf_delete('applications/app'), - 'delete app again') + self.assertIn( + 'error', + self.conf_delete('applications/app'), + 'delete app before listener', + ) + self.assertIn( + 'success', self.conf_delete('listeners/*:7080'), 'delete listener' + ) + self.assertIn( + 'success', + self.conf_delete('applications/app'), + 'delete app after listener', + ) + self.assertIn( + 'error', self.conf_delete('applications/app'), 'delete app again' + ) + if __name__ == '__main__': - TestUnitPythonBasic.main() + TestPythonBasic.main() diff --git a/test/test_python_environment.py b/test/test_python_environment.py index 71e4d5b7..744f4947 100644 --- a/test/test_python_environment.py +++ b/test/test_python_environment.py @@ -1,128 +1,179 @@ -import unittest -import unit +from unit.applications.lang.python import TestApplicationPython -class TestUnitPythonEnvironment(unit.TestUnitApplicationPython): - def setUpClass(): - unit.TestUnit().check_modules('python') +class TestPythonEnvironment(TestApplicationPython): + prerequisites = ['python'] def test_python_environment_name_null(self): self.load('environment') - self.assertIn('error', self.conf({ - "va\0r": "val1" - }, 'applications/environment/environment'), 'name null') + self.assertIn( + 'error', + self.conf( + {"va\0r": "val1"}, 'applications/environment/environment' + ), + 'name null', + ) def test_python_environment_name_equals(self): self.load('environment') - self.assertIn('error', self.conf({ - "var=": "val1" - }, 'applications/environment/environment'), 'name equals') + self.assertIn( + 'error', + self.conf( + {"var=": "val1"}, 'applications/environment/environment' + ), + 'name equals', + ) def test_python_environment_value_null(self): self.load('environment') - self.assertIn('error', self.conf({ - "var": "\0val" - }, 'applications/environment/environment'), 'value null') + self.assertIn( + 'error', + self.conf( + {"var": "\0val"}, 'applications/environment/environment' + ), + 'value null', + ) def test_python_environment_update(self): self.load('environment') - self.conf({ - "var": "val1" - }, 'applications/environment/environment') - - self.assertEqual(self.get(headers={ - 'Host': 'localhost', - 'X-Variables': 'var', - 'Connection': 'close' - })['body'], 'val1,', 'set') - - self.conf({ - "var": "val2" - }, 'applications/environment/environment') - - self.assertEqual(self.get(headers={ - 'Host': 'localhost', - 'X-Variables': 'var', - 'Connection': 'close' - })['body'], 'val2,', 'update') + self.conf({"var": "val1"}, 'applications/environment/environment') + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'X-Variables': 'var', + 'Connection': 'close', + } + )['body'], + 'val1,', + 'set', + ) + + self.conf({"var": "val2"}, 'applications/environment/environment') + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'X-Variables': 'var', + 'Connection': 'close', + } + )['body'], + 'val2,', + 'update', + ) def test_python_environment_replace(self): self.load('environment') - self.conf({ - "var1": "val1" - }, 'applications/environment/environment') - - self.assertEqual(self.get(headers={ - 'Host': 'localhost', - 'X-Variables': 'var1', - 'Connection': 'close' - })['body'], 'val1,', 'set') - - self.conf({ - "var2": "val2" - }, 'applications/environment/environment') - - self.assertEqual(self.get(headers={ - 'Host': 'localhost', - 'X-Variables': 'var1,var2', - 'Connection': 'close' - })['body'], 'val2,', 'replace') + self.conf({"var1": "val1"}, 'applications/environment/environment') + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'X-Variables': 'var1', + 'Connection': 'close', + } + )['body'], + 'val1,', + 'set', + ) + + self.conf({"var2": "val2"}, 'applications/environment/environment') + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'X-Variables': 'var1,var2', + 'Connection': 'close', + } + )['body'], + 'val2,', + 'replace', + ) def test_python_environment_clear(self): self.load('environment') - self.conf({ - "var1": "val1", - "var2": "val2" - }, 'applications/environment/environment') - - self.assertEqual(self.get(headers={ - 'Host': 'localhost', - 'X-Variables': 'var1,var2', - 'Connection': 'close' - })['body'], 'val1,val2,', 'set') + self.conf( + {"var1": "val1", "var2": "val2"}, + 'applications/environment/environment', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'X-Variables': 'var1,var2', + 'Connection': 'close', + } + )['body'], + 'val1,val2,', + 'set', + ) self.conf({}, 'applications/environment/environment') - self.assertEqual(self.get(headers={ - 'Host': 'localhost', - 'X-Variables': 'var1,var2', - 'Connection': 'close' - })['body'], '', 'clear') + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'X-Variables': 'var1,var2', + 'Connection': 'close', + } + )['body'], + '', + 'clear', + ) def test_python_environment_replace_default(self): self.load('environment') - pwd_default = self.get(headers={ - 'Host': 'localhost', - 'X-Variables': 'PWD', - 'Connection': 'close' - })['body'] + pwd_default = self.get( + headers={ + 'Host': 'localhost', + 'X-Variables': 'PWD', + 'Connection': 'close', + } + )['body'] self.assertGreater(len(pwd_default), 1, 'get default') - self.conf({ - "PWD": "new/pwd" - }, 'applications/environment/environment') + self.conf({"PWD": "new/pwd"}, 'applications/environment/environment') - self.assertEqual(self.get(headers={ - 'Host': 'localhost', - 'X-Variables': 'PWD', - 'Connection': 'close' - })['body'], 'new/pwd,', 'replace default') + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'X-Variables': 'PWD', + 'Connection': 'close', + } + )['body'], + 'new/pwd,', + 'replace default', + ) self.conf({}, 'applications/environment/environment') - self.assertEqual(self.get(headers={ - 'Host': 'localhost', - 'X-Variables': 'PWD', - 'Connection': 'close' - })['body'], pwd_default, 'restore default') + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'X-Variables': 'PWD', + 'Connection': 'close', + } + )['body'], + pwd_default, + 'restore default', + ) + if __name__ == '__main__': - TestUnitPythonEnvironment.main() + TestPythonEnvironment.main() diff --git a/test/test_python_procman.py b/test/test_python_procman.py index 2efe59c0..b0c70e53 100644 --- a/test/test_python_procman.py +++ b/test/test_python_procman.py @@ -2,12 +2,11 @@ import re import time import subprocess import unittest -import unit +from unit.applications.lang.python import TestApplicationPython -class TestUnitPythonProcman(unit.TestUnitApplicationPython): - def setUpClass(): - unit.TestUnit().check_modules('python') +class TestPythonProcman(TestApplicationPython): + prerequisites = ['python'] def pids_for_process(self): time.sleep(0.2) @@ -29,55 +28,88 @@ class TestUnitPythonProcman(unit.TestUnitApplicationPython): def test_python_processes_access(self): self.conf('1', 'applications/' + self.app_name + '/processes') - self.assertIn('error', self.conf_get('/applications/' + self.app_name + - '/processes/max'), 'max no access') - self.assertIn('error', self.conf_get('/applications/' + self.app_name + - '/processes/spare'), 'spare no access') - self.assertIn('error', self.conf_get('/applications/' + self.app_name + - '/processes/idle_timeout'), 'idle_timeout no access') + self.assertIn( + 'error', + self.conf_get('/applications/' + self.app_name + '/processes/max'), + 'max no access', + ) + self.assertIn( + 'error', + self.conf_get( + '/applications/' + self.app_name + '/processes/spare' + ), + 'spare no access', + ) + self.assertIn( + 'error', + self.conf_get( + '/applications/' + self.app_name + '/processes/idle_timeout' + ), + 'idle_timeout no access', + ) def test_python_processes_spare_negative(self): - self.assertIn('error', self.conf({ - "spare": -1 - }, 'applications/' + self.app_name + '/processes'), 'negative spare') + self.assertIn( + 'error', + self.conf( + {"spare": -1}, 'applications/' + self.app_name + '/processes' + ), + 'negative spare', + ) def test_python_processes_max_negative(self): - self.assertIn('error', self.conf({ - "max": -1 - }, 'applications/' + self.app_name + '/processes'), 'negative max') + self.assertIn( + 'error', + self.conf( + {"max": -1}, 'applications/' + self.app_name + '/processes' + ), + 'negative max', + ) def test_python_processes_idle_timeout_negative(self): - self.assertIn('error', self.conf({ - "idle_timeout": -1 - }, 'applications/' + self.app_name + '/processes'), - 'negative idle_timeout') + self.assertIn( + 'error', + self.conf( + {"idle_timeout": -1}, + 'applications/' + self.app_name + '/processes', + ), + 'negative idle_timeout', + ) def test_python_processes_spare_gt_max_default(self): - self.assertIn('error', self.conf({"spare": 2}, - 'applications/' + self.app_name + '/processes'), - 'spare greater than max default') + self.assertIn( + 'error', + self.conf( + {"spare": 2}, 'applications/' + self.app_name + '/processes' + ), + 'spare greater than max default', + ) def test_python_processes_spare_gt_max(self): - self.assertIn('error', self.conf({ - "spare": 2, - "max": 1, - "idle_timeout": 1 - }, '/applications/' + self.app_name + '/processes'), - 'spare greater than max') + self.assertIn( + 'error', + self.conf( + {"spare": 2, "max": 1, "idle_timeout": 1}, + '/applications/' + self.app_name + '/processes', + ), + 'spare greater than max', + ) def test_python_processes_max_zero(self): - self.assertIn('error', self.conf({ - "spare": 0, - "max": 0, - "idle_timeout": 1 - }, 'applications/' + self.app_name + '/processes'), 'max 0') + self.assertIn( + 'error', + self.conf( + {"spare": 0, "max": 0, "idle_timeout": 1}, + 'applications/' + self.app_name + '/processes', + ), + 'max 0', + ) def test_python_processes_idle_timeout_zero(self): - self.conf({ - "spare": 0, - "max": 2, - "idle_timeout": 0 - }, 'applications/' + self.app_name + '/processes') + self.conf( + {"spare": 0, "max": 2, "idle_timeout": 0}, + 'applications/' + self.app_name + '/processes', + ) self.get() self.assertEqual(len(self.pids_for_process()), 0, 'idle timeout 0') @@ -101,7 +133,7 @@ class TestUnitPythonProcman(unit.TestUnitApplicationPython): self.stop_all() - @unittest.expectedFailure + @unittest.skip('not yet') def test_python_prefork_same_processes(self): self.conf('2', 'applications/' + self.app_name + '/processes') @@ -114,11 +146,10 @@ class TestUnitPythonProcman(unit.TestUnitApplicationPython): self.assertTrue(pids.issubset(pids_new), 'prefork same processes') def test_python_ondemand(self): - self.conf({ - "spare": 0, - "max": 8, - "idle_timeout": 1 - }, 'applications/' + self.app_name + '/processes') + self.conf( + {"spare": 0, "max": 8, "idle_timeout": 1}, + 'applications/' + self.app_name + '/processes', + ) self.assertEqual(len(self.pids_for_process()), 0, 'on-demand 0') @@ -131,16 +162,17 @@ class TestUnitPythonProcman(unit.TestUnitApplicationPython): time.sleep(1) - self.assertEqual(len(self.pids_for_process()), 0, 'on-demand stop idle') + self.assertEqual( + len(self.pids_for_process()), 0, 'on-demand stop idle' + ) self.stop_all() def test_python_scale_updown(self): - self.conf({ - "spare": 2, - "max": 8, - "idle_timeout": 1 - }, 'applications/' + self.app_name + '/processes') + self.conf( + {"spare": 2, "max": 8, "idle_timeout": 1}, + 'applications/' + self.app_name + '/processes', + ) pids = self.pids_for_process() self.assertEqual(len(pids), 2, 'updown 2') @@ -151,7 +183,9 @@ class TestUnitPythonProcman(unit.TestUnitApplicationPython): self.assertTrue(pids.issubset(pids_new), 'updown 3 only 1 new') self.get() - self.assertSetEqual(self.pids_for_process(), pids_new, 'updown still 3') + self.assertSetEqual( + self.pids_for_process(), pids_new, 'updown still 3' + ) time.sleep(1) @@ -166,11 +200,10 @@ class TestUnitPythonProcman(unit.TestUnitApplicationPython): self.stop_all() def test_python_reconfigure(self): - self.conf({ - "spare": 2, - "max": 6, - "idle_timeout": 1 - }, 'applications/' + self.app_name + '/processes') + self.conf( + {"spare": 2, "max": 6, "idle_timeout": 1}, + 'applications/' + self.app_name + '/processes', + ) pids = self.pids_for_process() self.assertEqual(len(pids), 2, 'reconf 2') @@ -191,11 +224,10 @@ class TestUnitPythonProcman(unit.TestUnitApplicationPython): self.stop_all() def test_python_idle_timeout(self): - self.conf({ - "spare": 0, - "max": 6, - "idle_timeout": 2 - }, 'applications/' + self.app_name + '/processes') + self.conf( + {"spare": 0, "max": 6, "idle_timeout": 2}, + 'applications/' + self.app_name + '/processes', + ) self.get() pids = self.pids_for_process() @@ -209,40 +241,42 @@ class TestUnitPythonProcman(unit.TestUnitApplicationPython): pids_new = self.pids_for_process() self.assertEqual(len(pids_new), 1, 'idle timeout still 1') - self.assertSetEqual(self.pids_for_process(), pids, - 'idle timeout still 1 same pid') + self.assertSetEqual( + self.pids_for_process(), pids, 'idle timeout still 1 same pid' + ) time.sleep(1) self.assertEqual(len(self.pids_for_process()), 0, 'idle timed out') def test_python_processes_connection_keepalive(self): - self.conf({ - "spare": 0, - "max": 6, - "idle_timeout": 2 - }, 'applications/' + self.app_name + '/processes') - - (resp, sock) = self.get(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive' - }, start=True, read_timeout=1) - self.assertEqual(len(self.pids_for_process()), 1, - 'keepalive connection 1') + self.conf( + {"spare": 0, "max": 6, "idle_timeout": 2}, + 'applications/' + self.app_name + '/processes', + ) + + (resp, sock) = self.get( + headers={'Host': 'localhost', 'Connection': 'keep-alive'}, + start=True, + read_timeout=1, + ) + self.assertEqual( + len(self.pids_for_process()), 1, 'keepalive connection 1' + ) time.sleep(2) - self.assertEqual(len(self.pids_for_process()), 0, 'keepalive connection 0') + self.assertEqual( + len(self.pids_for_process()), 0, 'keepalive connection 0' + ) sock.close() def stop_all(self): - self.conf({ - "listeners": {}, - "applications": {} - }) + self.conf({"listeners": {}, "applications": {}}) self.assertEqual(len(self.pids_for_process()), 0, 'stop all') + if __name__ == '__main__': - TestUnitPythonProcman.main() + TestPythonProcman.main() diff --git a/test/test_routing.py b/test/test_routing.py index 07097fc8..ac2e0de8 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -1,458 +1,2889 @@ import unittest -import unit +from unit.applications.proto import TestApplicationProto -class TestUnitRouting(unit.TestUnitApplicationProto): - def setUpClass(): - unit.TestUnit().check_modules('python') +class TestRouting(TestApplicationProto): + prerequisites = ['python'] def setUp(self): super().setUp() - self.conf({ - "listeners": { - "*:7080": { - "pass": "routes" - } - }, - "routes": [{ - "match": { "method": "GET" }, - "action": { "pass": "applications/empty" } - }], - "applications": { - "empty": { - "type": "python", - "processes": { "spare": 0 }, - "path": self.current_dir + '/python/empty', - "working_directory": self.current_dir + '/python/empty', - "module": "wsgi" + self.conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [ + { + "match": {"method": "GET"}, + "action": {"pass": "applications/empty"}, + } + ], + "applications": { + "empty": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + + '/python/empty', + "module": "wsgi", + }, + "mirror": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/mirror', + "working_directory": self.current_dir + + '/python/mirror', + "module": "wsgi", + }, }, - "mirror": { - "type": "python", - "processes": { "spare": 0 }, - "path": self.current_dir + '/python/mirror', - "working_directory": self.current_dir + '/python/mirror', - "module": "wsgi" - } } - }) + ) def test_routes_match_method_positive(self): self.assertEqual(self.get()['status'], 200, 'method positive GET') self.assertEqual(self.post()['status'], 404, 'method positive POST') def test_routes_match_method_positive_many(self): - self.assertIn('success', self.conf([{ - "match": { "method": ["GET", "POST"] }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'method positive many configure') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": ["GET", "POST"]}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'method positive many configure', + ) self.assertEqual(self.get()['status'], 200, 'method positive many GET') - self.assertEqual(self.post()['status'], 200, - 'method positive many POST') - self.assertEqual(self.delete()['status'], 404, - 'method positive many DELETE') + self.assertEqual( + self.post()['status'], 200, 'method positive many POST' + ) + self.assertEqual( + self.delete()['status'], 404, 'method positive many DELETE' + ) def test_routes_match_method_negative(self): - self.assertIn('success', self.conf([{ - "match": { "method": "!GET" }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'method negative configure') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": "!GET"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'method negative configure', + ) self.assertEqual(self.get()['status'], 404, 'method negative GET') self.assertEqual(self.post()['status'], 200, 'method negative POST') def test_routes_match_method_negative_many(self): - self.assertIn('success', self.conf([{ - "match": { "method": ["!GET", "!POST"] }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'method negative many configure') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": ["!GET", "!POST"]}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'method negative many configure', + ) self.assertEqual(self.get()['status'], 404, 'method negative many GET') - self.assertEqual(self.post()['status'], 404, - 'method negative many POST') - self.assertEqual(self.delete()['status'], 200, - 'method negative many DELETE') + self.assertEqual( + self.post()['status'], 404, 'method negative many POST' + ) + self.assertEqual( + self.delete()['status'], 200, 'method negative many DELETE' + ) def test_routes_match_method_wildcard_left(self): - self.assertIn('success', self.conf([{ - "match": { "method": "*ET" }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'method wildcard left configure') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": "*ET"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'method wildcard left configure', + ) self.assertEqual(self.get()['status'], 200, 'method wildcard left GET') - self.assertEqual(self.post()['status'], 404, - 'method wildcard left POST') + self.assertEqual( + self.post()['status'], 404, 'method wildcard left POST' + ) def test_routes_match_method_wildcard_right(self): - self.assertIn('success', self.conf([{ - "match": { "method": "GE*" }, - "action": { "pass": "applications/empty"} - }], 'routes'), 'method wildcard right configure') - - self.assertEqual(self.get()['status'], 200, - 'method wildcard right GET') - self.assertEqual(self.post()['status'], 404, - 'method wildcard right POST') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": "GE*"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'method wildcard right configure', + ) + + self.assertEqual( + self.get()['status'], 200, 'method wildcard right GET' + ) + self.assertEqual( + self.post()['status'], 404, 'method wildcard right POST' + ) def test_routes_match_method_wildcard_left_right(self): - self.assertIn('success', self.conf([{ - "match": { "method": "*GET*" }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'method wildcard left right configure') - - self.assertEqual(self.get()['status'], 200, - 'method wildcard right GET') - self.assertEqual(self.post()['status'], 404, - 'method wildcard right POST') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": "*GET*"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'method wildcard left right configure', + ) + + self.assertEqual( + self.get()['status'], 200, 'method wildcard right GET' + ) + self.assertEqual( + self.post()['status'], 404, 'method wildcard right POST' + ) def test_routes_match_method_wildcard(self): - self.assertIn('success', self.conf([{ - "match": { "method": "*" }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'method wildcard configure') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": "*"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'method wildcard configure', + ) self.assertEqual(self.get()['status'], 200, 'method wildcard') + def test_routes_match_invalid(self): + self.assertIn( + 'error', + self.conf( + [ + { + "match": {"method": "**"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'wildcard invalid', + ) + + self.assertIn( + 'error', + self.conf( + [ + { + "match": {"method": "blah**"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'wildcard invalid 2', + ) + + self.assertIn( + 'error', + self.conf( + [ + { + "match": {"host": "*blah*blah"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'wildcard invalid 3', + ) + + self.assertIn( + 'error', + self.conf( + [ + { + "match": {"host": "blah*blah*blah"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'wildcard invalid 4', + ) + + self.assertIn( + 'error', + self.conf( + [ + { + "match": {"host": "blah*blah*"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'wildcard invalid 5', + ) + + def test_routes_match_wildcard_middle(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"host": "ex*le"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'host wildcard middle configure', + ) + + self.assertEqual( + self.get(headers={'Host': 'example', 'Connection': 'close'})[ + 'status' + ], + 200, + 'host wildcard middle', + ) + + self.assertEqual( + self.get(headers={'Host': 'www.example', 'Connection': 'close'})[ + 'status' + ], + 404, + 'host wildcard middle 2', + ) + + self.assertEqual( + self.get(headers={'Host': 'example.com', 'Connection': 'close'})[ + 'status' + ], + 404, + 'host wildcard middle 3', + ) + + self.assertEqual( + self.get(headers={'Host': 'exampl', 'Connection': 'close'})[ + 'status' + ], + 404, + 'host wildcard middle 4', + ) + def test_routes_match_method_case_insensitive(self): - self.assertIn('success', self.conf([{ - "match": { "method": "get" }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'method case insensitive configure') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": "get"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'method case insensitive configure', + ) self.assertEqual(self.get()['status'], 200, 'method case insensitive') + def test_routes_match_wildcard_left_case_insensitive(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": "*et"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match wildcard case insensitive configure', + ) + + self.assertEqual( + self.get()['status'], 200, 'match wildcard case insensitive' + ) + + def test_routes_match_wildcard_middle_case_insensitive(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": "g*t"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match wildcard case insensitive configure', + ) + + self.assertEqual( + self.get()['status'], 200, 'match wildcard case insensitive' + ) + + def test_routes_match_wildcard_right_case_insensitive(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": "get*"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match wildcard case insensitive configure', + ) + + self.assertEqual( + self.get()['status'], 200, 'match wildcard case insensitive' + ) + + def test_routes_match_wildcard_substring_case_insensitive(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": "*et*"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match wildcard substring case insensitive configure', + ) + + self.assertEqual( + self.get()['status'], + 200, + 'match wildcard substring case insensitive', + ) + + def test_routes_match_wildcard_left_case_sensitive(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"uri": "*blah"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match wildcard left case sensitive configure', + ) + + self.assertEqual( + self.get(url='/blah')['status'], + 200, + 'match wildcard left case sensitive /blah', + ) + + self.assertEqual( + self.get(url='/BLAH')['status'], + 404, + 'match wildcard left case sensitive /BLAH', + ) + + def test_routes_match_wildcard_middle_case_sensitive(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"uri": "/b*h"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match wildcard middle case sensitive configure', + ) + + self.assertEqual( + self.get(url='/blah')['status'], + 200, + 'match wildcard middle case sensitive /blah', + ) + + self.assertEqual( + self.get(url='/BLAH')['status'], + 404, + 'match wildcard middle case sensitive /BLAH', + ) + + def test_routes_match_wildcard_right_case_sensitive(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"uri": "/bla*"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match wildcard right case sensitive configure', + ) + + self.assertEqual( + self.get(url='/blah')['status'], + 200, + 'match wildcard right case sensitive /blah', + ) + + self.assertEqual( + self.get(url='/BLAH')['status'], + 404, + 'match wildcard right case sensitive /BLAH', + ) + + def test_routes_match_wildcard_substring_case_sensitive(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"uri": "*bla*"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match wildcard substring case sensitive configure', + ) + + self.assertEqual( + self.get(url='/blah')['status'], + 200, + 'match wildcard substring case sensitive /blah', + ) + + self.assertEqual( + self.get(url='/BLAH')['status'], + 404, + 'match wildcard substring case sensitive /BLAH', + ) + def test_routes_absent(self): - self.conf({ - "listeners": { - "*:7081": { - "pass": "applications/empty" - } - }, - "applications": { - "empty": { - "type": "python", - "processes": { "spare": 0 }, - "path": self.current_dir + '/python/empty', - "working_directory": self.current_dir + '/python/empty', - "module": "wsgi" - } + self.conf( + { + "listeners": {"*:7081": {"pass": "applications/empty"}}, + "applications": { + "empty": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + + '/python/empty', + "module": "wsgi", + } + }, } - }) + ) self.assertEqual(self.get(port=7081)['status'], 200, 'routes absent') def test_routes_pass_invalid(self): - self.assertIn('error', self.conf({ "pass": "routes/blah" }, - 'listeners/*:7080'), 'routes invalid') + self.assertIn( + 'error', + self.conf({"pass": "routes/blah"}, 'listeners/*:7080'), + 'routes invalid', + ) def test_route_empty(self): - self.assertIn('success', self.conf({ - "listeners": { - "*:7080": { - "pass": "routes/main" - } - }, - "routes": {"main": []}, - "applications": { - "empty": { - "type": "python", - "processes": { "spare": 0 }, - "path": self.current_dir + '/python/empty', - "working_directory": self.current_dir + '/python/empty', - "module": "wsgi" - }, - "mirror": { - "type": "python", - "processes": { "spare": 0 }, - "path": self.current_dir + '/python/mirror', - "working_directory": self.current_dir + '/python/mirror', - "module": "wsgi" + self.assertIn( + 'success', + self.conf( + { + "listeners": {"*:7080": {"pass": "routes/main"}}, + "routes": {"main": []}, + "applications": { + "empty": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + + '/python/empty', + "module": "wsgi", + }, + "mirror": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/mirror', + "working_directory": self.current_dir + + '/python/mirror', + "module": "wsgi", + }, + }, } - } - }), 'route empty configure') + ), + 'route empty configure', + ) self.assertEqual(self.get()['status'], 404, 'route empty') def test_routes_route_empty(self): - self.assertIn('success', self.conf({}, 'listeners'), - 'routes empty listeners configure') + self.assertIn( + 'success', + self.conf({}, 'listeners'), + 'routes empty listeners configure', + ) - self.assertIn('success', self.conf({}, 'routes'), - 'routes empty configure') + self.assertIn( + 'success', self.conf({}, 'routes'), 'routes empty configure' + ) def test_routes_route_match_absent(self): - self.assertIn('success', self.conf([{ - "action": { "pass": "applications/empty" } - }], 'routes'), 'route match absent configure') + self.assertIn( + 'success', + self.conf([{"action": {"pass": "applications/empty"}}], 'routes'), + 'route match absent configure', + ) self.assertEqual(self.get()['status'], 200, 'route match absent') def test_routes_route_action_absent(self): self.skip_alerts.append(r'failed to apply new conf') - self.assertIn('error', self.conf([{ - "match": { "method": "GET" } - }], 'routes'), 'route pass absent configure') + self.assertIn( + 'error', + self.conf([{"match": {"method": "GET"}}], 'routes'), + 'route pass absent configure', + ) def test_routes_route_pass_absent(self): self.skip_alerts.append(r'failed to apply new conf') - self.assertIn('error', self.conf([{ - "match": { "method": "GET" }, - "action": {} - }], 'routes'), 'route pass absent configure') + self.assertIn( + 'error', + self.conf([{"match": {"method": "GET"}, "action": {}}], 'routes'), + 'route pass absent configure', + ) def test_routes_rules_two(self): - self.assertIn('success', self.conf([{ - "match": { "method": "GET" }, - "action": { "pass": "applications/empty" } - }, - { - "match": { "method": "POST" }, - "action": { "pass": "applications/mirror" } - }], 'routes'), 'rules two configure') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": "GET"}, + "action": {"pass": "applications/empty"}, + }, + { + "match": {"method": "POST"}, + "action": {"pass": "applications/mirror"}, + }, + ], + 'routes', + ), + 'rules two configure', + ) self.assertEqual(self.get()['status'], 200, 'rules two match first') - self.assertEqual(self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'Connection': 'close' - }, body='X')['status'], 200, 'rules two match second') + self.assertEqual( + self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Connection': 'close', + }, + body='X', + )['status'], + 200, + 'rules two match second', + ) def test_routes_two(self): - self.assertIn('success', self.conf({ - "listeners": { - "*:7080": { - "pass": "routes/first" - } - }, - "routes": { - "first": [{ - "match": { "method": "GET" }, - "action": { "pass": "routes/second" } - }], - "second": [{ - "match": { "host": "localhost" }, - "action": { "pass": "applications/empty" } - }], - }, - "applications": { - "empty": { - "type": "python", - "processes": { "spare": 0 }, - "path": self.current_dir + '/python/empty', - "working_directory": self.current_dir + '/python/empty', - "module": "wsgi" + self.assertIn( + 'success', + self.conf( + { + "listeners": {"*:7080": {"pass": "routes/first"}}, + "routes": { + "first": [ + { + "match": {"method": "GET"}, + "action": {"pass": "routes/second"}, + } + ], + "second": [ + { + "match": {"host": "localhost"}, + "action": {"pass": "applications/empty"}, + } + ], + }, + "applications": { + "empty": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + + '/python/empty', + "module": "wsgi", + } + }, } - } - }), 'routes two configure') + ), + 'routes two configure', + ) self.assertEqual(self.get()['status'], 200, 'routes two') def test_routes_match_host_positive(self): - self.assertIn('success', self.conf([{ - "match": { "host": "localhost" }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'match host positive configure') - - self.assertEqual(self.get()['status'], 200, - 'match host positive localhost') - - self.assertEqual(self.get(headers={'Connection': 'close'})['status'], - 404, 'match host positive empty') - - self.assertEqual(self.get(headers={ - 'Host': 'localhost.', - 'Connection': 'close' - })['status'], 200, 'match host positive trailing dot') - - self.assertEqual(self.get(headers={ - 'Host': 'www.localhost', - 'Connection': 'close' - })['status'], 404, 'match host positive www.localhost') - - self.assertEqual(self.get(headers={ - 'Host': 'localhost1', - 'Connection': 'close' - })['status'], 404, 'match host positive localhost1') - - self.assertEqual(self.get(headers={ - 'Host': 'example.com', - 'Connection': 'close' - })['status'], 404, 'match host positive example.com') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"host": "localhost"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match host positive configure', + ) + + self.assertEqual( + self.get()['status'], 200, 'match host positive localhost' + ) + + self.assertEqual( + self.get(headers={'Host': 'localhost.', 'Connection': 'close'})[ + 'status' + ], + 200, + 'match host positive trailing dot', + ) + + self.assertEqual( + self.get(headers={'Host': 'www.localhost', 'Connection': 'close'})[ + 'status' + ], + 404, + 'match host positive www.localhost', + ) + + self.assertEqual( + self.get(headers={'Host': 'localhost1', 'Connection': 'close'})[ + 'status' + ], + 404, + 'match host positive localhost1', + ) + + self.assertEqual( + self.get(headers={'Host': 'example.com', 'Connection': 'close'})[ + 'status' + ], + 404, + 'match host positive example.com', + ) + + @unittest.skip('not yet') + def test_routes_match_host_absent(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"host": "localhost"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match host absent configure', + ) + + self.assertEqual( + self.get(headers={'Connection': 'close'})['status'], + 400, + 'match host absent', + ) def test_routes_match_host_ipv4(self): - self.assertIn('success', self.conf([{ - "match": { "host": "127.0.0.1" }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'match host ipv4 configure') - - self.assertEqual(self.get(headers={ - 'Host': '127.0.0.1', - 'Connection': 'close' - })['status'], 200, 'match host ipv4') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"host": "127.0.0.1"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match host ipv4 configure', + ) + + self.assertEqual( + self.get(headers={'Host': '127.0.0.1', 'Connection': 'close'})[ + 'status' + ], + 200, + 'match host ipv4', + ) def test_routes_match_host_ipv6(self): - self.assertIn('success', self.conf([{ - "match": { "host": "[::1]" }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'match host ipv6 configure') - - self.assertEqual(self.get(headers={ - 'Host': '[::1]', - 'Connection': 'close' - })['status'], 200, 'match host ipv6') - - self.assertEqual(self.get(headers={ - 'Host': '[::1]:7080', - 'Connection': 'close' - })['status'], 200, 'match host ipv6 port') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"host": "[::1]"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match host ipv6 configure', + ) + + self.assertEqual( + self.get(headers={'Host': '[::1]', 'Connection': 'close'})[ + 'status' + ], + 200, + 'match host ipv6', + ) + + self.assertEqual( + self.get(headers={'Host': '[::1]:7080', 'Connection': 'close'})[ + 'status' + ], + 200, + 'match host ipv6 port', + ) def test_routes_match_host_positive_many(self): - self.assertIn('success', self.conf([{ - "match": { "host": ["localhost", "example.com"] }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'match host positive many configure') - - self.assertEqual(self.get()['status'], 200, - 'match host positive many localhost') - - self.assertEqual(self.get(headers={ - 'Host': 'example.com', - 'Connection': 'close' - })['status'], 200, 'match host positive many example.com') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"host": ["localhost", "example.com"]}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match host positive many configure', + ) + + self.assertEqual( + self.get()['status'], 200, 'match host positive many localhost' + ) + + self.assertEqual( + self.get(headers={'Host': 'example.com', 'Connection': 'close'})[ + 'status' + ], + 200, + 'match host positive many example.com', + ) def test_routes_match_host_positive_and_negative(self): - self.assertIn('success', self.conf([{ - "match": { "host": ["*example.com", "!www.example.com"] }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'match host positive and negative configure') - - self.assertEqual(self.get()['status'], 404, - 'match host positive and negative localhost') - - self.assertEqual(self.get(headers={ - 'Host': 'example.com', - 'Connection': 'close' - })['status'], 200, 'match host positive and negative example.com') - - self.assertEqual(self.get(headers={ - 'Host': 'www.example.com', - 'Connection': 'close' - })['status'], 404, 'match host positive and negative www.example.com') - - self.assertEqual(self.get(headers={ - 'Host': '!www.example.com', - 'Connection': 'close' - })['status'], 200, 'match host positive and negative !www.example.com') + self.assertIn( + 'success', + self.conf( + [ + { + "match": { + "host": ["*example.com", "!www.example.com"] + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match host positive and negative configure', + ) + + self.assertEqual( + self.get()['status'], + 404, + 'match host positive and negative localhost', + ) + + self.assertEqual( + self.get(headers={'Host': 'example.com', 'Connection': 'close'})[ + 'status' + ], + 200, + 'match host positive and negative example.com', + ) + + self.assertEqual( + self.get( + headers={'Host': 'www.example.com', 'Connection': 'close'} + )['status'], + 404, + 'match host positive and negative www.example.com', + ) + + self.assertEqual( + self.get( + headers={'Host': '!www.example.com', 'Connection': 'close'} + )['status'], + 200, + 'match host positive and negative !www.example.com', + ) def test_routes_match_host_positive_and_negative_wildcard(self): - self.assertIn('success', self.conf([{ - "match": { "host": ["*example*", "!www.example*"] }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'match host positive and negative wildcard configure') - - self.assertEqual(self.get(headers={ - 'Host': 'example.com', - 'Connection': 'close' - })['status'], 200, - 'match host positive and negative wildcard example.com') - - self.assertEqual(self.get(headers={ - 'Host': 'www.example.com', - 'Connection': 'close' - })['status'], 404, - 'match host positive and negative wildcard www.example.com') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"host": ["*example*", "!www.example*"]}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match host positive and negative wildcard configure', + ) + + self.assertEqual( + self.get(headers={'Host': 'example.com', 'Connection': 'close'})[ + 'status' + ], + 200, + 'match host positive and negative wildcard example.com', + ) + + self.assertEqual( + self.get( + headers={'Host': 'www.example.com', 'Connection': 'close'} + )['status'], + 404, + 'match host positive and negative wildcard www.example.com', + ) def test_routes_match_host_case_insensitive(self): - self.assertIn('success', self.conf([{ - "match": { "host": "Example.com" }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'host case insensitive configure') - - self.assertEqual(self.get(headers={ - 'Host': 'example.com', - 'Connection': 'close' - })['status'], 200, 'host case insensitive example.com') - - self.assertEqual(self.get(headers={ - 'Host': 'EXAMPLE.COM', - 'Connection': 'close' - })['status'], 200, 'host case insensitive EXAMPLE.COM') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"host": "Example.com"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'host case insensitive configure', + ) + + self.assertEqual( + self.get(headers={'Host': 'example.com', 'Connection': 'close'})[ + 'status' + ], + 200, + 'host case insensitive example.com', + ) + + self.assertEqual( + self.get(headers={'Host': 'EXAMPLE.COM', 'Connection': 'close'})[ + 'status' + ], + 200, + 'host case insensitive EXAMPLE.COM', + ) def test_routes_match_host_port(self): - self.assertIn('success', self.conf([{ - "match": { "host": "example.com" }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'match host port configure') - - self.assertEqual(self.get(headers={ - 'Host': 'example.com:7080', - 'Connection': 'close' - })['status'], 200, 'match host port') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"host": "example.com"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match host port configure', + ) + + self.assertEqual( + self.get( + headers={'Host': 'example.com:7080', 'Connection': 'close'} + )['status'], + 200, + 'match host port', + ) + + def test_routes_match_host_empty(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"host": ""}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match host empty configure', + ) + + self.assertEqual( + self.get(headers={'Host': '', 'Connection': 'close'})['status'], + 200, + 'match host empty', + ) + self.assertEqual( + self.get(http_10=True, headers={})['status'], + 200, + 'match host empty 2', + ) + self.assertEqual(self.get()['status'], 404, 'match host empty 3') def test_routes_match_uri_positive(self): - self.assertIn('success', self.conf([{ - "match": { "uri": "/" }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'match uri positive configure') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"uri": "/"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match uri positive configure', + ) self.assertEqual(self.get()['status'], 200, 'match uri positive') - self.assertEqual(self.get(url='/blah')['status'], 404, - 'match uri positive blah') - self.assertEqual(self.get(url='/#blah')['status'], 200, - 'match uri positive #blah') - self.assertEqual(self.get(url='/?var')['status'], 200, - 'match uri params') - self.assertEqual(self.get(url='//')['status'], 200, - 'match uri adjacent slashes') - self.assertEqual(self.get(url='/blah/../')['status'], 200, - 'match uri relative path') - self.assertEqual(self.get(url='/./')['status'], 200, - 'match uri relative path') + self.assertEqual( + self.get(url='/blah')['status'], 404, 'match uri positive blah' + ) + self.assertEqual( + self.get(url='/#blah')['status'], 200, 'match uri positive #blah' + ) + self.assertEqual( + self.get(url='/?var')['status'], 200, 'match uri params' + ) + self.assertEqual( + self.get(url='//')['status'], 200, 'match uri adjacent slashes' + ) + self.assertEqual( + self.get(url='/blah/../')['status'], 200, 'match uri relative path' + ) + self.assertEqual( + self.get(url='/./')['status'], 200, 'match uri relative path' + ) def test_routes_match_uri_case_sensitive(self): - self.assertIn('success', self.conf([{ - "match": { "uri": "/BLAH" }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'match uri case sensitive configure') - - self.assertEqual(self.get(url='/blah')['status'], 404, - 'match uri case sensitive blah') - self.assertEqual(self.get(url='/BlaH')['status'], 404, - 'match uri case sensitive BlaH') - self.assertEqual(self.get(url='/BLAH')['status'], 200, - 'match uri case sensitive BLAH') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"uri": "/BLAH"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match uri case sensitive configure', + ) + + self.assertEqual( + self.get(url='/blah')['status'], + 404, + 'match uri case sensitive blah', + ) + self.assertEqual( + self.get(url='/BlaH')['status'], + 404, + 'match uri case sensitive BlaH', + ) + self.assertEqual( + self.get(url='/BLAH')['status'], + 200, + 'match uri case sensitive BLAH', + ) def test_routes_match_uri_normalize(self): - self.assertIn('success', self.conf([{ - "match": { "uri": "/blah" }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'match uri normalize configure') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"uri": "/blah"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match uri normalize configure', + ) + + self.assertEqual( + self.get(url='/%62%6c%61%68')['status'], 200, 'match uri normalize' + ) + + def test_routes_match_empty_array(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"uri": []}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match empty array configure', + ) + + self.assertEqual( + self.get(url='/blah')['status'], + 200, + 'match empty array', + ) + + def test_routes_reconfigure(self): + self.assertIn('success', self.conf([], 'routes'), 'routes redefine') + self.assertEqual(self.get()['status'], 404, 'routes redefine request') + + self.assertIn( + 'success', + self.conf([{"action": {"pass": "applications/empty"}}], 'routes'), + 'routes redefine 2', + ) + self.assertEqual( + self.get()['status'], 200, 'routes redefine request 2' + ) + + self.assertIn('success', self.conf([], 'routes'), 'routes redefine 3') + self.assertEqual( + self.get()['status'], 404, 'routes redefine request 3' + ) + + self.assertIn( + 'success', + self.conf( + { + "listeners": {"*:7080": {"pass": "routes/main"}}, + "routes": { + "main": [{"action": {"pass": "applications/empty"}}] + }, + "applications": { + "empty": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + + '/python/empty', + "module": "wsgi", + } + }, + } + ), + 'routes redefine 4', + ) + self.assertEqual( + self.get()['status'], 200, 'routes redefine request 4' + ) + + self.assertIn( + 'success', self.conf_delete('routes/main/0'), 'routes redefine 5' + ) + self.assertEqual( + self.get()['status'], 404, 'routes redefine request 5' + ) + + self.assertIn( + 'success', + self.conf_post( + {"action": {"pass": "applications/empty"}}, 'routes/main' + ), + 'routes redefine 6', + ) + self.assertEqual( + self.get()['status'], 200, 'routes redefine request 6' + ) + + self.assertIn( + 'error', + self.conf( + {"action": {"pass": "applications/empty"}}, 'routes/main/2' + ), + 'routes redefine 7', + ) + self.assertIn( + 'success', + self.conf( + {"action": {"pass": "applications/empty"}}, 'routes/main/1' + ), + 'routes redefine 8', + ) + + self.assertEqual( + len(self.conf_get('routes/main')), 2, 'routes redefine conf 8' + ) + self.assertEqual( + self.get()['status'], 200, 'routes redefine request 8' + ) + + def test_routes_edit(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": "GET"}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'routes edit configure', + ) + + self.assertEqual(self.get()['status'], 200, 'routes edit GET') + self.assertEqual(self.post()['status'], 404, 'routes edit POST') + + self.assertIn( + 'success', + self.conf_post( + { + "match": {"method": "POST"}, + "action": {"pass": "applications/empty"}, + }, + 'routes', + ), + 'routes edit configure 2', + ) + self.assertEqual( + 'GET', + self.conf_get('routes/0/match/method'), + 'routes edit configure 2 check', + ) + self.assertEqual( + 'POST', + self.conf_get('routes/1/match/method'), + 'routes edit configure 2 check 2', + ) + + self.assertEqual(self.get()['status'], 200, 'routes edit GET 2') + self.assertEqual(self.post()['status'], 200, 'routes edit POST 2') + + self.assertIn( + 'success', + self.conf_delete('routes/0'), + 'routes edit configure 3', + ) + + self.assertEqual(self.get()['status'], 404, 'routes edit GET 3') + self.assertEqual(self.post()['status'], 200, 'routes edit POST 3') + + self.assertIn( + 'error', + self.conf_delete('routes/1'), + 'routes edit configure invalid', + ) + self.assertIn( + 'error', + self.conf_delete('routes/-1'), + 'routes edit configure invalid 2', + ) + self.assertIn( + 'error', + self.conf_delete('routes/blah'), + 'routes edit configure invalid 3', + ) + + self.assertEqual(self.get()['status'], 404, 'routes edit GET 4') + self.assertEqual(self.post()['status'], 200, 'routes edit POST 4') + + self.assertIn( + 'success', + self.conf_delete('routes/0'), + 'routes edit configure 5', + ) + + self.assertEqual(self.get()['status'], 404, 'routes edit GET 5') + self.assertEqual(self.post()['status'], 404, 'routes edit POST 5') + + self.assertIn( + 'success', + self.conf_post( + { + "match": {"method": "POST"}, + "action": {"pass": "applications/empty"}, + }, + 'routes', + ), + 'routes edit configure 6', + ) + + self.assertEqual(self.get()['status'], 404, 'routes edit GET 6') + self.assertEqual(self.post()['status'], 200, 'routes edit POST 6') + + self.assertIn( + 'success', + self.conf( + { + "listeners": {"*:7080": {"pass": "routes/main"}}, + "routes": { + "main": [{"action": {"pass": "applications/empty"}}] + }, + "applications": { + "empty": { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + + '/python/empty', + "module": "wsgi", + } + }, + } + ), + 'route edit configure 7', + ) + + self.assertIn( + 'error', + self.conf_delete('routes/0'), + 'routes edit configure invalid 4', + ) + self.assertIn( + 'error', + self.conf_delete('routes/main'), + 'routes edit configure invalid 5', + ) + + self.assertEqual(self.get()['status'], 200, 'routes edit GET 7') + + self.assertIn( + 'success', + self.conf_delete('listeners/*:7080'), + 'route edit configure 8', + ) + self.assertIn( + 'success', + self.conf_delete('routes/main'), + 'route edit configure 9', + ) + + def test_match_edit(self): + self.skip_alerts.append(r'failed to apply new conf') - self.assertEqual(self.get(url='/%62%6c%61%68')['status'], 200, - 'match uri normalize') + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"method": ["GET", "POST"]}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match edit configure', + ) + + self.assertEqual(self.get()['status'], 200, 'match edit GET') + self.assertEqual(self.post()['status'], 200, 'match edit POST') + self.assertEqual(self.put()['status'], 404, 'match edit PUT') + + self.assertIn( + 'success', + self.conf_post('\"PUT\"', 'routes/0/match/method'), + 'match edit configure 2', + ) + self.assertListEqual( + ['GET', 'POST', 'PUT'], + self.conf_get('routes/0/match/method'), + 'match edit configure 2 check', + ) + + self.assertEqual(self.get()['status'], 200, 'match edit GET 2') + self.assertEqual(self.post()['status'], 200, 'match edit POST 2') + self.assertEqual(self.put()['status'], 200, 'match edit PUT 2') + + self.assertIn( + 'success', + self.conf_delete('routes/0/match/method/1'), + 'match edit configure 3', + ) + self.assertListEqual( + ['GET', 'PUT'], + self.conf_get('routes/0/match/method'), + 'match edit configure 3 check', + ) + + self.assertEqual(self.get()['status'], 200, 'match edit GET 3') + self.assertEqual(self.post()['status'], 404, 'match edit POST 3') + self.assertEqual(self.put()['status'], 200, 'match edit PUT 3') + + self.assertIn( + 'success', + self.conf_delete('routes/0/match/method/1'), + 'match edit configure 4', + ) + self.assertListEqual( + ['GET'], + self.conf_get('routes/0/match/method'), + 'match edit configure 4 check', + ) + + self.assertEqual(self.get()['status'], 200, 'match edit GET 4') + self.assertEqual(self.post()['status'], 404, 'match edit POST 4') + self.assertEqual(self.put()['status'], 404, 'match edit PUT 4') + + self.assertIn( + 'error', + self.conf_delete('routes/0/match/method/1'), + 'match edit configure invalid', + ) + self.assertIn( + 'error', + self.conf_delete('routes/0/match/method/-1'), + 'match edit configure invalid 2', + ) + self.assertIn( + 'error', + self.conf_delete('routes/0/match/method/blah'), + 'match edit configure invalid 3', + ) + self.assertListEqual( + ['GET'], + self.conf_get('routes/0/match/method'), + 'match edit configure 5 check', + ) + + self.assertEqual(self.get()['status'], 200, 'match edit GET 5') + self.assertEqual(self.post()['status'], 404, 'match edit POST 5') + self.assertEqual(self.put()['status'], 404, 'match edit PUT 5') + + self.assertIn( + 'success', + self.conf_delete('routes/0/match/method/0'), + 'match edit configure 6', + ) + self.assertListEqual( + [], + self.conf_get('routes/0/match/method'), + 'match edit configure 6 check', + ) + + self.assertEqual(self.get()['status'], 200, 'match edit GET 6') + self.assertEqual(self.post()['status'], 200, 'match edit POST 6') + self.assertEqual(self.put()['status'], 200, 'match edit PUT 6') + + self.assertIn( + 'success', + self.conf('"GET"', 'routes/0/match/method'), + 'match edit configure 7', + ) + + self.assertEqual(self.get()['status'], 200, 'match edit GET 7') + self.assertEqual(self.post()['status'], 404, 'match edit POST 7') + self.assertEqual(self.put()['status'], 404, 'match edit PUT 7') + + self.assertIn( + 'error', + self.conf_delete('routes/0/match/method/0'), + 'match edit configure invalid 5', + ) + self.assertIn( + 'error', + self.conf({}, 'routes/0/action'), + 'match edit configure invalid 6', + ) + + self.assertIn( + 'success', + self.conf({}, 'routes/0/match'), + 'match edit configure 8', + ) + + self.assertEqual(self.get()['status'], 200, 'match edit GET 8') def test_routes_match_rules(self): - self.assertIn('success', self.conf([{ - "match": { - "method": "GET", - "host": "localhost", - "uri": "/" - }, - "action": { "pass": "applications/empty" } - }], 'routes'), 'routes match rules configure') + self.assertIn( + 'success', + self.conf( + [ + { + "match": { + "method": "GET", + "host": "localhost", + "uri": "/", + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'routes match rules configure', + ) self.assertEqual(self.get()['status'], 200, 'routes match rules') def test_routes_loop(self): - self.assertIn('success', self.conf([{ - "match": { "uri": "/" }, - "action": { "pass": "routes" } - }], 'routes'), 'routes loop configure') + self.assertIn( + 'success', + self.conf( + [{"match": {"uri": "/"}, "action": {"pass": "routes"}}], + 'routes', + ), + 'routes loop configure', + ) self.assertEqual(self.get()['status'], 500, 'routes loop') + def test_routes_match_headers(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"headers": {"host": "localhost"}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match headers configure', + ) + + self.assertEqual(self.get()['status'], 200, 'match headers') + self.assertEqual( + self.get( + headers={ + "Host": "Localhost", + "Connection": "close", + } + )['status'], + 200, + 'match headers case insensitive', + ) + self.assertEqual( + self.get( + headers={ + "Host": "localhost.com", + "Connection": "close", + } + )['status'], + 404, + 'match headers exact', + ) + self.assertEqual( + self.get( + headers={ + "Host": "llocalhost", + "Connection": "close", + } + )['status'], + 404, + 'match headers exact 2', + ) + self.assertEqual( + self.get( + headers={ + "Host": "host", + "Connection": "close", + } + )['status'], + 404, + 'match headers exact 3', + ) + + def test_routes_match_headers_multiple(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": { + "headers": {"host": "localhost", "x-blah": "test"} + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match headers multiple configure', + ) + + self.assertEqual(self.get()['status'], 404, 'match headers multiple') + + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "X-blah": "test", + "Connection": "close", + } + )['status'], + 200, + 'match headers multiple 2', + ) + + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "X-blah": "", + "Connection": "close", + } + )['status'], + 404, + 'match headers multiple 3', + ) + + def test_routes_match_headers_multiple_values(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"headers": {"x-blah": "test"}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match headers multiple values configure', + ) + + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "X-blah": ["test", "test", "test"], + "Connection": "close", + } + )['status'], + 200, + 'match headers multiple values', + ) + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "X-blah": ["test", "blah", "test"], + "Connection": "close", + } + )['status'], + 404, + 'match headers multiple values 2', + ) + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "X-blah": ["test", "", "test"], + "Connection": "close", + } + )['status'], + 404, + 'match headers multiple values 3', + ) + + def test_routes_match_headers_multiple_rules(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"headers": {"x-blah": ["test", "blah"]}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match headers multiple rules configure', + ) + + self.assertEqual( + self.get()['status'], 404, 'match headers multiple rules' + ) + + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "X-blah": "test", + "Connection": "close", + } + )['status'], + 200, + 'match headers multiple rules 2', + ) + + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "X-blah": "blah", + "Connection": "close", + } + )['status'], + 200, + 'match headers multiple rules 3', + ) + + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "X-blah": ["test", "blah", "test"], + "Connection": "close", + } + )['status'], + 200, + 'match headers multiple rules 4', + ) + + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "X-blah": ["blah", ""], + "Connection": "close", + } + )['status'], + 404, + 'match headers multiple rules 5', + ) + + def test_routes_match_headers_case_insensitive(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"headers": {"X-BLAH": "TEST"}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match headers case insensitive configure', + ) + + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "x-blah": "test", + "Connection": "close", + } + )['status'], + 200, + 'match headers case insensitive', + ) + + def test_routes_match_headers_invalid(self): + self.assertIn( + 'error', + self.conf( + [ + { + "match": {"headers": ["blah"]}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match headers invalid', + ) + + self.assertIn( + 'error', + self.conf( + [ + { + "match": {"headers": {"foo": ["bar", {}]}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match headers invalid 2', + ) + + def test_routes_match_headers_empty_rule(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"headers": {"host": ""}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match headers empty rule configure', + ) + + self.assertEqual(self.get()['status'], 404, 'match headers empty rule') + + self.assertEqual( + self.get(headers={"Host": "", "Connection": "close"})['status'], + 200, + 'match headers empty rule 2', + ) + + def test_routes_match_headers_rule_field_empty(self): + self.assertIn( + 'error', + self.conf( + [ + { + "match": {"headers": {"": "blah"}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match headers rule field empty configure', + ) + + def test_routes_match_headers_empty(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"headers": {}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match headers empty configure', + ) + + self.assertEqual(self.get()['status'], 200, 'match headers empty') + + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"headers": []}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match headers array empty configure 2', + ) + + self.assertEqual( + self.get()['status'], 200, 'match headers array empty 2' + ) + + def test_routes_match_headers_rule_array_empty(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"headers": {"blah": []}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match headers rule array empty configure', + ) + + self.assertEqual( + self.get()['status'], 404, 'match headers rule array empty' + ) + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "blah": "foo", + "Connection": "close", + } + )['status'], 200, 'match headers rule array empty 2' + ) + + def test_routes_match_headers_array(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": { + "headers": [ + {"x-header1": "foo*"}, + {"x-header2": "bar"}, + {"x-header3": ["foo", "bar"]}, + {"x-header1": "bar", "x-header4": "foo"}, + ] + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match headers array configure', + ) + + self.assertEqual(self.get()['status'], 404, 'match headers array') + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "x-header1": "foo123", + "Connection": "close", + } + )['status'], + 200, + 'match headers array 2', + ) + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "x-header2": "bar", + "Connection": "close", + } + )['status'], + 200, + 'match headers array 3', + ) + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "x-header3": "bar", + "Connection": "close", + } + )['status'], + 200, + 'match headers array 4', + ) + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "x-header1": "bar", + "Connection": "close", + } + )['status'], + 404, + 'match headers array 5', + ) + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "x-header1": "bar", + "x-header4": "foo", + "Connection": "close", + } + )['status'], + 200, + 'match headers array 6', + ) + + self.assertIn( + 'success', + self.conf_delete('routes/0/match/headers/1'), + 'match headers array configure 2', + ) + + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "x-header2": "bar", + "Connection": "close", + } + )['status'], + 404, + 'match headers array 7', + ) + self.assertEqual( + self.get( + headers={ + "Host": "localhost", + "x-header3": "foo", + "Connection": "close", + } + )['status'], + 200, + 'match headers array 8', + ) + + def test_routes_match_arguments(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"arguments": {"foo": "bar"}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments configure', + ) + + self.assertEqual(self.get()['status'], 404, 'match arguments') + self.assertEqual( + self.get(url='/?foo=bar')['status'], 200, 'match arguments 2' + ) + + self.assertEqual( + self.get(url='/?Foo=bar')['status'], + 404, + 'match arguments case sensitive', + ) # FAIL + self.assertEqual( + self.get(url='/?foo=Bar')['status'], + 404, + 'match arguments case sensitive 2', + ) # FAIL + self.assertEqual( + self.get(url='/?foo=bar1')['status'], + 404, + 'match arguments exact', + ) + self.assertEqual( + self.get(url='/?1foo=bar')['status'], + 404, + 'match arguments exact 2', + ) + + def test_routes_match_arguments_empty(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"arguments": {}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments empty configure', + ) + + self.assertEqual(self.get()['status'], 200, 'match arguments empty') + + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"arguments": []}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments empty configure 2', + ) + + self.assertEqual(self.get()['status'], 200, 'match arguments empty 2') + + def test_routes_match_arguments_invalid(self): + self.assertIn( + 'error', + self.conf( + [ + { + "match": {"arguments": ["var"]}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments invalid', + ) + + self.assertIn( + 'error', + self.conf( + [ + { + "match": {"arguments": [{"var1": {}}]}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments invalid 2', + ) + + self.assertIn( + 'error', + self.conf( + [ + { + "match": { + "arguments": { + "": "bar" + } + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments invalid 3', + ) + + @unittest.skip('not yet') + def test_routes_match_arguments_space(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": { + "arguments": { + "foo": "bar " + } + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments space configure', + ) + + self.assertEqual( + self.get(url='/?foo=bar &')['status'], + 200, + 'match arguments space', + ) + self.assertEqual( + self.get(url='/?foo=bar+&')['status'], + 200, + 'match arguments space 2', + ) # FAIL + self.assertEqual( + self.get(url='/?foo=bar%20&')['status'], + 200, + 'match arguments space 3', + ) # FAIL + + @unittest.skip('not yet') + def test_routes_match_arguments_plus(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": { + "arguments": [ + {"foo": "bar+"} + ] + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments plus configure', + ) + + self.assertEqual( + self.get(url='/?foo=bar+&')['status'], + 200, + 'match arguments plus', + ) + self.assertEqual( + self.get(url='/?foo=bar%2B&')['status'], + 200, + 'match arguments plus 2', + ) # FAIL + + @unittest.skip('not yet') + def test_routes_match_arguments_hex(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": { + "arguments": [ + {"foo": "bar"} + ] + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments hex configure', + ) + + self.assertEqual( + self.get(url='/?%66%6F%6f=%62%61%72&')['status'], + 200, + 'match arguments hex', + ) # FAIL + + def test_routes_match_arguments_chars(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": { + "arguments": { + "foo": "-._()[],;" + } + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments chars configure', + ) + + self.assertEqual( + self.get(url='/?foo=-._()[],;')['status'], + 200, + 'match arguments chars', + ) + + def test_routes_match_arguments_complex(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": { + "arguments": { + "foo": "" + } + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments complex configure', + ) + + self.assertEqual( + self.get(url='/?foo')['status'], + 200, + 'match arguments complex', + ) + self.assertEqual( + self.get(url='/?blah=blah&foo=')['status'], + 200, + 'match arguments complex 2', + ) + self.assertEqual( + self.get(url='/?&&&foo&&&')['status'], + 200, + 'match arguments complex 3', + ) + self.assertEqual( + self.get(url='/?foo&foo=bar&foo')['status'], + 404, + 'match arguments complex 4', + ) + self.assertEqual( + self.get(url='/?foo=&foo')['status'], + 200, + 'match arguments complex 5', + ) + self.assertEqual( + self.get(url='/?&=&foo&==&')['status'], + 200, + 'match arguments complex 6', + ) + self.assertEqual( + self.get(url='/?&=&bar&==&')['status'], + 404, + 'match arguments complex 7', + ) + + def test_routes_match_arguments_multiple(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": { + "arguments": {"foo": "bar", "blah": "test"} + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments multiple configure', + ) + + self.assertEqual(self.get()['status'], 404, 'match arguments multiple') + + self.assertEqual( + self.get(url='/?foo=bar&blah=test')['status'], + 200, + 'match arguments multiple 2', + ) + + self.assertEqual( + self.get(url='/?foo=bar&blah')['status'], + 404, + 'match arguments multiple 3', + ) + + def test_routes_match_arguments_multiple_rules(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"arguments": {"foo": ["bar", "blah"]}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments multiple rules configure', + ) + + self.assertEqual( + self.get()['status'], 404, 'match arguments multiple rules' + ) + + self.assertEqual( + self.get(url='/?foo=bar')['status'], + 200, + 'match arguments multiple rules 2', + ) + + self.assertEqual( + self.get(url='/?foo=blah')['status'], + 200, + 'match arguments multiple rules 3', + ) + + self.assertEqual( + self.get(url='/?foo=blah&foo=bar&foo=blah')['status'], + 200, + 'match arguments multiple rules 4', + ) + + self.assertEqual( + self.get(url='/?foo=blah&foo=bar&foo=')['status'], + 404, + 'match arguments multiple rules 5', + ) + + def test_routes_match_arguments_array(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": { + "arguments": [ + {"var1": "val1*"}, + {"var2": "val2"}, + {"var3": ["foo", "bar"]}, + {"var1": "bar", "var4": "foo"}, + ] + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match arguments array configure', + ) + + self.assertEqual(self.get()['status'], 404, 'match arguments array') + self.assertEqual( + self.get(url='/?var1=val123')['status'], + 200, + 'match arguments array 2', + ) + self.assertEqual( + self.get(url='/?var2=val2')['status'], + 200, + 'match arguments array 3', + ) + self.assertEqual( + self.get(url='/?var3=bar')['status'], + 200, + 'match arguments array 4', + ) + self.assertEqual( + self.get(url='/?var1=bar')['status'], + 404, + 'match arguments array 5', + ) + self.assertEqual( + self.get(url='/?var1=bar&var4=foo')['status'], + 200, + 'match arguments array 6', + ) + + self.assertIn( + 'success', + self.conf_delete('routes/0/match/arguments/1'), + 'match arguments array configure 2', + ) + + self.assertEqual( + self.get(url='/?var2=val2')['status'], + 404, + 'match arguments array 7', + ) + self.assertEqual( + self.get(url='/?var3=foo')['status'], + 200, + 'match arguments array 8', + ) + + def test_routes_match_cookies(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"cookies": {"foO": "bar"}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match cookie configure', + ) + + self.assertEqual(self.get()['status'], 404, 'match cookie') + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'foo=bar', + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies 2', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': ['foo=bar', 'blah=blah'], + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies 3', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'foo=bar; blah=blah', + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies 4', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'Foo=bar', + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies case insensitive', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'foo=Bar', + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies case insensitive 2', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'foo=bar1', + 'Connection': 'close', + }, + )['status'], + 404, + 'match cookies exact', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'foo=bar;', + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies exact 2', + ) + + def test_routes_match_cookies_empty(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"cookies": {}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match cookies empty configure', + ) + + self.assertEqual(self.get()['status'], 200, 'match cookies empty') + + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"cookies": []}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match cookies empty configure 2', + ) + + self.assertEqual(self.get()['status'], 200, 'match cookies empty 2') + + def test_routes_match_cookies_invalid(self): + self.assertIn( + 'error', + self.conf( + [ + { + "match": {"cookies": ["var"]}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match cookies invalid', + ) + + self.assertIn( + 'error', + self.conf( + [ + { + "match": {"cookies": [{"foo": {}}]}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match cookies invalid 2', + ) + + def test_routes_match_cookies_multiple(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": { + "cookies": {"foo": "bar", "blah": "blah"} + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match cookies multiple configure', + ) + + self.assertEqual(self.get()['status'], 404, 'match cookies multiple') + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'foo=bar; blah=blah', + 'Connection': 'close', + } + )['status'], + 200, + 'match cookies multiple 2', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': ['foo=bar', 'blah=blah'], + 'Connection': 'close', + } + )['status'], + 200, + 'match cookies multiple 3', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': ['foo=bar; blah', 'blah'], + 'Connection': 'close', + } + )['status'], + 404, + 'match cookies multiple 4', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': ['foo=bar; blah=test', 'blah=blah'], + 'Connection': 'close', + } + )['status'], + 404, + 'match cookies multiple 5', + ) + + def test_routes_match_cookies_multiple_values(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"cookies": {"blah": "blah"}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match cookies multiple values configure', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': ['blah=blah', 'blah=blah', 'blah=blah'], + 'Connection': 'close', + } + )['status'], + 200, + 'match headers multiple values', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': ['blah=blah', 'blah=test', 'blah=blah'], + 'Connection': 'close', + } + )['status'], + 404, + 'match cookies multiple values 2', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': ['blah=blah; blah=', 'blah=blah'], + 'Connection': 'close', + } + )['status'], + 404, + 'match cookies multiple values 3', + ) + + def test_routes_match_cookies_multiple_rules(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": {"cookies": {"blah": ["test", "blah"]}}, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match cookies multiple rules configure', + ) + + self.assertEqual( + self.get()['status'], 404, 'match cookies multiple rules' + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'blah=test', + 'Connection': 'close', + } + )['status'], + 200, + 'match cookies multiple rules 2', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'blah=blah', + 'Connection': 'close', + } + )['status'], + 200, + 'match cookies multiple rules 3', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': ['blah=blah', 'blah=test', 'blah=blah'], + 'Connection': 'close', + } + )['status'], + 200, + 'match cookies multiple rules 4', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': ['blah=blah; blah=test', 'blah=blah'], + 'Connection': 'close', + } + )['status'], + 200, + 'match cookies multiple rules 5', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': ['blah=blah', 'blah'], # invalid cookie + 'Connection': 'close', + } + )['status'], + 200, + 'match cookies multiple rules 6', + ) + + def test_routes_match_cookies_array(self): + self.assertIn( + 'success', + self.conf( + [ + { + "match": { + "cookies": [ + {"var1": "val1*"}, + {"var2": "val2"}, + {"var3": ["foo", "bar"]}, + {"var1": "bar", "var4": "foo"}, + ] + }, + "action": {"pass": "applications/empty"}, + } + ], + 'routes', + ), + 'match cookies array configure', + ) + + self.assertEqual(self.get()['status'], 404, 'match cookies array') + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'var1=val123', + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies array 2', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'var2=val2', + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies array 3', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'var3=bar', + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies array 4', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'var3=bar;', + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies array 5', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'var1=bar', + 'Connection': 'close', + }, + )['status'], + 404, + 'match cookies array 6', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'var1=bar; var4=foo;', + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies array 7', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': ['var1=bar', 'var4=foo'], + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies array 8', + ) + + self.assertIn( + 'success', + self.conf_delete('routes/0/match/cookies/1'), + 'match cookies array configure 2', + ) + + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'var2=val2', + 'Connection': 'close', + }, + )['status'], + 404, + 'match cookies array 9', + ) + self.assertEqual( + self.get( + headers={ + 'Host': 'localhost', + 'Cookie': 'var3=foo', + 'Connection': 'close', + }, + )['status'], + 200, + 'match cookies array 10', + ) + if __name__ == '__main__': - TestUnitRouting.main() + TestRouting.main() diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index 262fc497..67db8a8e 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -1,53 +1,65 @@ import unittest -import unit +from unit.applications.lang.ruby import TestApplicationRuby -class TestUnitRubyApplication(unit.TestUnitApplicationRuby): - def setUpClass(): - unit.TestUnit().check_modules('ruby') +class TestRubyApplication(TestApplicationRuby): + prerequisites = ['ruby'] def test_ruby_application(self): self.load('variables') body = 'Test body string.' - resp = self.post(headers={ - 'Host': 'localhost', - 'Content-Type': 'text/html', - 'Custom-Header': 'blah', - 'Connection': 'close' - }, body=body) + resp = self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Custom-Header': 'blah', + 'Connection': 'close', + }, + body=body, + ) self.assertEqual(resp['status'], 200, 'status') headers = resp['headers'] header_server = headers.pop('Server') self.assertRegex(header_server, r'Unit/[\d\.]+', 'server header') - self.assertEqual(headers.pop('Server-Software'), header_server, - 'server software header') + self.assertEqual( + headers.pop('Server-Software'), + header_server, + 'server software header', + ) date = headers.pop('Date') self.assertEqual(date[-4:], ' GMT', 'date header timezone') - self.assertLess(abs(self.date_to_sec_epoch(date) - self.sec_epoch()), 5, - 'date header') - - self.assertDictEqual(headers, { - 'Connection': 'close', - 'Content-Length': str(len(body)), - 'Content-Type': 'text/html', - 'Request-Method': 'POST', - 'Request-Uri': '/', - 'Http-Host': 'localhost', - 'Server-Protocol': 'HTTP/1.1', - 'Custom-Header': 'blah', - 'Rack-Version': '13', - 'Rack-Url-Scheme': 'http', - 'Rack-Multithread': 'false', - 'Rack-Multiprocess': 'true', - 'Rack-Run-Once': 'false', - 'Rack-Hijack-Q': 'false', - 'Rack-Hijack': '', - 'Rack-Hijack-IO': '' - }, 'headers') + self.assertLess( + abs(self.date_to_sec_epoch(date) - self.sec_epoch()), + 5, + 'date header', + ) + + self.assertDictEqual( + headers, + { + 'Connection': 'close', + 'Content-Length': str(len(body)), + 'Content-Type': 'text/html', + 'Request-Method': 'POST', + 'Request-Uri': '/', + 'Http-Host': 'localhost', + 'Server-Protocol': 'HTTP/1.1', + 'Custom-Header': 'blah', + 'Rack-Version': '13', + 'Rack-Url-Scheme': 'http', + 'Rack-Multithread': 'false', + 'Rack-Multiprocess': 'true', + 'Rack-Run-Once': 'false', + 'Rack-Hijack-Q': 'false', + 'Rack-Hijack': '', + 'Rack-Hijack-IO': '', + }, + 'headers', + ) self.assertEqual(resp['body'], body, 'body') def test_ruby_application_query_string(self): @@ -55,8 +67,11 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): resp = self.get(url='/?var1=val1&var2=val2') - self.assertEqual(resp['headers']['Query-String'], 'var1=val1&var2=val2', - 'Query-String header') + self.assertEqual( + resp['headers']['Query-String'], + 'var1=val1&var2=val2', + 'Query-String header', + ) def test_ruby_application_query_string_empty(self): self.load('query_string') @@ -64,25 +79,27 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): resp = self.get(url='/?') self.assertEqual(resp['status'], 200, 'query string empty status') - self.assertEqual(resp['headers']['Query-String'], '', - 'query string empty') + self.assertEqual( + resp['headers']['Query-String'], '', 'query string empty' + ) - @unittest.expectedFailure def test_ruby_application_query_string_absent(self): self.load('query_string') resp = self.get() self.assertEqual(resp['status'], 200, 'query string absent status') - self.assertEqual(resp['headers']['Query-String'], '', - 'query string absent') + self.assertEqual( + resp['headers']['Query-String'], '', 'query string absent' + ) - @unittest.expectedFailure + @unittest.skip('not yet') def test_ruby_application_server_port(self): self.load('server_port') - self.assertEqual(self.get()['headers']['Server-Port'], '7080', - 'Server-Port header') + self.assertEqual( + self.get()['headers']['Server-Port'], '7080', 'Server-Port header' + ) def test_ruby_application_status_int(self): self.load('status_int') @@ -97,20 +114,29 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): def test_ruby_application_input_read_parts(self): self.load('input_read_parts') - self.assertEqual(self.post(body='0123456789')['body'], '012345678', - 'input read parts') + self.assertEqual( + self.post(body='0123456789')['body'], + '012345678', + 'input read parts', + ) def test_ruby_application_input_read_buffer(self): self.load('input_read_buffer') - self.assertEqual(self.post(body='0123456789')['body'], '0123456789', - 'input read buffer') + self.assertEqual( + self.post(body='0123456789')['body'], + '0123456789', + 'input read buffer', + ) def test_ruby_application_input_read_buffer_not_empty(self): self.load('input_read_buffer_not_empty') - self.assertEqual(self.post(body='0123456789')['body'], '0123456789', - 'input read buffer not empty') + self.assertEqual( + self.post(body='0123456789')['body'], + '0123456789', + 'input read buffer not empty', + ) def test_ruby_application_input_gets(self): self.load('input_gets') @@ -122,8 +148,9 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): def test_ruby_application_input_gets_2(self): self.load('input_gets') - self.assertEqual(self.post(body='01234\n56789\n')['body'], '01234\n', - 'input gets 2') + self.assertEqual( + self.post(body='01234\n56789\n')['body'], '01234\n', 'input gets 2' + ) def test_ruby_application_input_gets_all(self): self.load('input_gets_all') @@ -139,7 +166,7 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): self.assertEqual(self.post(body=body)['body'], body, 'input each') - @unittest.expectedFailure + @unittest.skip('not yet') def test_ruby_application_input_rewind(self): self.load('input_rewind') @@ -147,14 +174,16 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): self.assertEqual(self.post(body=body)['body'], body, 'input rewind') - @unittest.expectedFailure + @unittest.skip('not yet') def test_ruby_application_syntax_error(self): - self.skip_alerts.extend([ - r'Failed to parse rack script', - r'syntax error', - r'new_from_string', - r'parse_file' - ]) + self.skip_alerts.extend( + [ + r'Failed to parse rack script', + r'syntax error', + r'new_from_string', + r'parse_file', + ] + ) self.load('syntax_error') self.assertEqual(self.get()['status'], 500, 'syntax error') @@ -167,8 +196,9 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): self.stop() self.assertIsNotNone( - self.search_in_log(r'\[error\].+Error in application'), - 'errors puts') + self.wait_for_record(r'\[error\].+Error in application'), + 'errors puts', + ) def test_ruby_application_errors_puts_int(self): self.load('errors_puts_int') @@ -178,8 +208,8 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): self.stop() self.assertIsNotNone( - self.search_in_log(r'\[error\].+1234567890'), - 'errors puts int') + self.wait_for_record(r'\[error\].+1234567890'), 'errors puts int' + ) def test_ruby_application_errors_write(self): self.load('errors_write') @@ -189,14 +219,14 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): self.stop() self.assertIsNotNone( - self.search_in_log(r'\[error\].+Error in application'), - 'errors write') + self.wait_for_record(r'\[error\].+Error in application'), + 'errors write', + ) def test_ruby_application_errors_write_to_s_custom(self): self.load('errors_write_to_s_custom') - self.assertEqual(self.get()['status'], 200, - 'errors write to_s custom') + self.assertEqual(self.get()['status'], 200, 'errors write to_s custom') def test_ruby_application_errors_write_int(self): self.load('errors_write_int') @@ -206,45 +236,47 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): self.stop() self.assertIsNotNone( - self.search_in_log(r'\[error\].+1234567890'), - 'errors write int') + self.wait_for_record(r'\[error\].+1234567890'), 'errors write int' + ) def test_ruby_application_at_exit(self): self.load('at_exit') self.get() - self.conf({ - "listeners": {}, - "applications": {} - }) + self.conf({"listeners": {}, "applications": {}}) self.stop() self.assertIsNotNone( - self.search_in_log(r'\[error\].+At exit called\.'), 'at exit') + self.wait_for_record(r'\[error\].+At exit called\.'), 'at exit' + ) def test_ruby_application_header_custom(self): self.load('header_custom') resp = self.post(body="\ntc=one,two\ntc=three,four,\n\n") - self.assertEqual(resp['headers']['Custom-Header'], - ['', 'tc=one,two', 'tc=three,four,', '', ''], 'header custom') + self.assertEqual( + resp['headers']['Custom-Header'], + ['', 'tc=one,two', 'tc=three,four,', '', ''], + 'header custom', + ) - @unittest.expectedFailure + @unittest.skip('not yet') def test_ruby_application_header_custom_non_printable(self): self.load('header_custom') - self.assertEqual(self.post(body='\b')['status'], 500, - 'header custom non printable') + self.assertEqual( + self.post(body='\b')['status'], 500, 'header custom non printable' + ) def test_ruby_application_header_status(self): self.load('header_status') self.assertEqual(self.get()['status'], 200, 'header status') - @unittest.expectedFailure + @unittest.skip('not yet') def test_ruby_application_header_rack(self): self.load('header_rack') @@ -267,7 +299,7 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): self.assertEqual(self.post(body=body)['body'], body, 'body large') - @unittest.expectedFailure + @unittest.skip('not yet') def test_ruby_application_body_each_error(self): self.load('body_each_error') @@ -276,8 +308,9 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): self.stop() self.assertIsNotNone( - self.search_in_log(r'\[error\].+Failed to run ruby script'), - 'body each error') + self.wait_for_record(r'\[error\].+Failed to run ruby script'), + 'body each error', + ) def test_ruby_application_body_file(self): self.load('body_file') @@ -287,21 +320,33 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby): def test_ruby_keepalive_body(self): self.load('mirror') - (resp, sock) = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive', - 'Content-Type': 'text/html' - }, start=True, body='0123456789' * 500) + self.assertEqual(self.get()['status'], 200, 'init') + + (resp, sock) = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + }, + start=True, + body='0123456789' * 500, + read_timeout=1, + ) self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1') - resp = self.post(headers={ - 'Host': 'localhost', - 'Connection': 'close', - 'Content-Type': 'text/html' - }, sock=sock, body='0123456789') + resp = self.post( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'Content-Type': 'text/html', + }, + sock=sock, + body='0123456789', + ) self.assertEqual(resp['body'], '0123456789', 'keep-alive 2') + if __name__ == '__main__': - TestUnitRubyApplication.main() + TestRubyApplication.main() diff --git a/test/test_settings.py b/test/test_settings.py index 13bfad49..98063440 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -1,47 +1,72 @@ import time import socket import unittest -import unit +from unit.applications.lang.python import TestApplicationPython -class TestUnitSettings(unit.TestUnitApplicationPython): - def setUpClass(): - unit.TestUnit().check_modules('python') +class TestSettings(TestApplicationPython): + prerequisites = ['python'] def test_settings_header_read_timeout(self): self.load('empty') - self.conf({'http': { 'header_read_timeout': 2 }}, 'settings') + self.conf({'http': {'header_read_timeout': 2}}, 'settings') - (resp, sock) = self.http(b"""GET / HTTP/1.1 -""", start=True, read_timeout=1, raw=True) + (resp, sock) = self.http( + b"""GET / HTTP/1.1 +""", + start=True, + read_timeout=1, + raw=True, + ) time.sleep(3) - resp = self.http(b"""Host: localhost + resp = self.http( + b"""Host: localhost Connection: close -""", sock=sock, raw=True) +""", + sock=sock, + raw=True, + ) self.assertEqual(resp['status'], 408, 'status header read timeout') def test_settings_header_read_timeout_update(self): self.load('empty') - self.conf({'http': { 'header_read_timeout': 4 }}, 'settings') + self.conf({'http': {'header_read_timeout': 4}}, 'settings') - (resp, sock) = self.http(b"""GET / HTTP/1.1 -""", start=True, read_timeout=1, raw=True, no_recv=True) + (resp, sock) = self.http( + b"""GET / HTTP/1.1 +""", + start=True, + raw=True, + no_recv=True, + ) time.sleep(2) - (resp, sock) = self.http(b"""Host: localhost -""", start=True, sock=sock, read_timeout=1, raw=True, no_recv=True) + (resp, sock) = self.http( + b"""Host: localhost +""", + start=True, + sock=sock, + raw=True, + no_recv=True, + ) time.sleep(2) - (resp, sock) = self.http(b"""X-Blah: blah -""", start=True, sock=sock, read_timeout=1, raw=True) + (resp, sock) = self.http( + b"""X-Blah: blah +""", + start=True, + sock=sock, + read_timeout=1, + raw=True, + ) if len(resp) != 0: sock.close() @@ -49,24 +74,35 @@ Connection: close else: time.sleep(2) - resp = self.http(b"""Connection: close + resp = self.http( + b"""Connection: close -""", sock=sock, raw=True) +""", + sock=sock, + raw=True, + ) - self.assertEqual(resp['status'], 408, - 'status header read timeout update') + self.assertEqual( + resp['status'], 408, 'status header read timeout update' + ) def test_settings_body_read_timeout(self): self.load('empty') - self.conf({'http': { 'body_read_timeout': 2 }}, 'settings') + self.conf({'http': {'body_read_timeout': 2}}, 'settings') - (resp, sock) = self.http(b"""POST / HTTP/1.1 + (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) @@ -77,37 +113,46 @@ Connection: close def test_settings_body_read_timeout_update(self): self.load('empty') - self.conf({'http': { 'body_read_timeout': 4 }}, 'settings') + self.conf({'http': {'body_read_timeout': 4}}, 'settings') - (resp, sock) = self.http(b"""POST / HTTP/1.1 + (resp, sock) = self.http( + b"""POST / HTTP/1.1 Host: localhost Content-Length: 10 Connection: close -""", start=True, read_timeout=1, raw=True) +""", + start=True, + read_timeout=1, + raw=True, + ) time.sleep(2) - (resp, sock) = self.http(b"""012""", start=True, sock=sock, - read_timeout=1, raw=True) + (resp, sock) = self.http( + b"""012""", start=True, sock=sock, read_timeout=1, raw=True + ) time.sleep(2) - (resp, sock) = self.http(b"""345""", start=True, sock=sock, - read_timeout=1, raw=True) + (resp, sock) = self.http( + b"""345""", start=True, sock=sock, read_timeout=1, raw=True + ) time.sleep(2) resp = self.http(b"""6789""", sock=sock, raw=True) - self.assertEqual(resp['status'], 200, 'status body read timeout update') + self.assertEqual( + resp['status'], 200, 'status body read timeout update' + ) def test_settings_send_timeout(self): self.load('mirror') data_len = 1048576 - self.conf({'http': { 'send_timeout': 1 }}, 'settings') + self.conf({'http': {'send_timeout': 1}}, 'settings') addr = self.testdir + '/sock' @@ -122,7 +167,9 @@ Content-Type: text/html Content-Length: %d Connection: close -""" % data_len + ('X' * data_len) +""" % data_len + ( + 'X' * data_len + ) sock.sendall(req.encode()) @@ -140,35 +187,42 @@ Connection: close def test_settings_idle_timeout(self): self.load('empty') - self.conf({'http': { 'idle_timeout': 2 }}, 'settings') + self.assertEqual(self.get()['status'], 200, 'init') - (resp, sock) = self.get(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive' - }, start=True, read_timeout=1) + self.conf({'http': {'idle_timeout': 2}}, 'settings') + + (resp, sock) = self.get( + headers={'Host': 'localhost', 'Connection': 'keep-alive'}, + start=True, + read_timeout=1, + ) time.sleep(3) - resp = self.get(headers={ - 'Host': 'localhost', - 'Connection': 'close' - }, sock=sock) + resp = self.get( + headers={'Host': 'localhost', 'Connection': 'close'}, sock=sock + ) self.assertEqual(resp['status'], 408, 'status idle timeout') def test_settings_max_body_size(self): self.load('empty') - self.conf({'http': { 'max_body_size': 5 }}, 'settings') + self.conf({'http': {'max_body_size': 5}}, 'settings') self.assertEqual(self.post(body='01234')['status'], 200, 'status size') - self.assertEqual(self.post(body='012345')['status'], 413, - 'status size max') + self.assertEqual( + self.post(body='012345')['status'], 413, 'status size max' + ) - @unittest.expectedFailure + @unittest.skip('not yet') def test_settings_negative_value(self): - self.assertIn('error', self.conf({'http': { 'max_body_size': -1 }}, - 'settings'), 'settings negative value') + self.assertIn( + 'error', + self.conf({'http': {'max_body_size': -1}}, 'settings'), + 'settings negative value', + ) + if __name__ == '__main__': - TestUnitSettings.main() + TestSettings.main() diff --git a/test/test_tls.py b/test/test_tls.py index 2131bf30..f055aa24 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -3,40 +3,32 @@ import ssl import time import subprocess import unittest -import unit +from unit.applications.tls import TestApplicationTLS -class TestUnitTLS(unit.TestUnitApplicationTLS): - def setUpClass(): - unit.TestUnit().check_modules('python', 'openssl') +class TestTLS(TestApplicationTLS): + prerequisites = ['python', 'openssl'] def findall(self, pattern): with open(self.testdir + '/unit.log', 'r', errors='ignore') as f: return re.findall(pattern, f.read()) - def wait_for_record(self, pattern): - for i in range(50): - with open(self.testdir + '/unit.log', 'r', errors='ignore') as f: - if re.search(pattern, f.read()) is not None: - break - - time.sleep(0.1) - 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, application='empty', cert='default', port=7080): - self.conf({ - "application": application, - "tls": { - "certificate": cert - } - }, 'listeners/*:' + str(port)) + self.conf( + { + "pass": "applications/" + application, + "tls": {"certificate": cert} + }, + 'listeners/*:' + str(port), + ) def remove_tls(self, application='empty', port=7080): - self.conf({ - "application": application - }, 'listeners/*:' + str(port)) + self.conf( + {"pass": "applications/" + application}, 'listeners/*:' + str(port) + ) def test_tls_listener_option_add(self): self.load('empty') @@ -65,8 +57,11 @@ class TestUnitTLS(unit.TestUnitApplicationTLS): self.certificate() - self.assertIn('success', self.conf_delete('/certificates/default'), - 'remove certificate') + self.assertIn( + 'success', + self.conf_delete('/certificates/default'), + 'remove certificate', + ) def test_tls_certificate_remove_used(self): self.load('empty') @@ -75,8 +70,11 @@ class TestUnitTLS(unit.TestUnitApplicationTLS): self.add_tls() - self.assertIn('error', self.conf_delete('/certificates/default'), - 'remove certificate') + self.assertIn( + 'error', + self.conf_delete('/certificates/default'), + 'remove certificate', + ) def test_tls_certificate_remove_nonexisting(self): self.load('empty') @@ -85,10 +83,13 @@ class TestUnitTLS(unit.TestUnitApplicationTLS): self.add_tls() - self.assertIn('error', self.conf_delete('/certificates/blah'), - 'remove nonexistings certificate') + self.assertIn( + 'error', + self.conf_delete('/certificates/blah'), + 'remove nonexistings certificate', + ) - @unittest.expectedFailure + @unittest.skip('not yet') def test_tls_certificate_update(self): self.load('empty') @@ -100,18 +101,20 @@ class TestUnitTLS(unit.TestUnitApplicationTLS): self.certificate() - self.assertNotEqual(cert_old, self.get_server_certificate(), - 'update certificate') + self.assertNotEqual( + cert_old, self.get_server_certificate(), 'update certificate' + ) - @unittest.expectedFailure + @unittest.skip('not yet') def test_tls_certificate_key_incorrect(self): self.load('empty') self.certificate('first', False) self.certificate('second', False) - self.assertIn('error', self.certificate_load('first', 'second'), - 'key incorrect') + self.assertIn( + 'error', self.certificate_load('first', 'second'), 'key incorrect' + ) def test_tls_certificate_change(self): self.load('empty') @@ -125,33 +128,53 @@ class TestUnitTLS(unit.TestUnitApplicationTLS): self.add_tls(cert='new') - self.assertNotEqual(cert_old, self.get_server_certificate(), - 'change certificate') + self.assertNotEqual( + cert_old, self.get_server_certificate(), 'change certificate' + ) def test_tls_certificate_key_rsa(self): self.load('empty') self.certificate() - self.assertEqual(self.conf_get('/certificates/default/key'), - 'RSA (1024 bits)', 'certificate key rsa') + self.assertEqual( + self.conf_get('/certificates/default/key'), + 'RSA (1024 bits)', + 'certificate key rsa', + ) def test_tls_certificate_key_ec(self): self.load('empty') - subprocess.call(['openssl', 'ecparam', '-noout', '-genkey', - '-out', self.testdir + '/ec.key', - '-name', 'prime256v1']) - - subprocess.call(['openssl', 'req', '-x509', '-new', - '-config', self.testdir + '/openssl.conf', - '-key', self.testdir + '/ec.key', '-subj', '/CN=ec/', - '-out', self.testdir + '/ec.crt']) + subprocess.call( + [ + 'openssl', + 'ecparam', + '-noout', + '-genkey', + '-out', self.testdir + '/ec.key', + '-name', 'prime256v1', + ] + ) + + subprocess.call( + [ + 'openssl', + 'req', + '-x509', + '-new', + '-subj', '/CN=ec/', + '-config', self.testdir + '/openssl.conf', + '-key', self.testdir + '/ec.key', + '-out', self.testdir + '/ec.crt', + ] + ) self.certificate_load('ec') - self.assertEqual(self.conf_get('/certificates/ec/key'), 'ECDH', - 'certificate key ec') + self.assertEqual( + self.conf_get('/certificates/ec/key'), 'ECDH', 'certificate key ec' + ) def test_tls_certificate_chain_options(self): self.load('empty') @@ -164,36 +187,64 @@ class TestUnitTLS(unit.TestUnitApplicationTLS): cert = chain[0] - self.assertEqual(cert['subject']['common_name'], 'default', - 'certificate subject common name') - self.assertEqual(cert['issuer']['common_name'], 'default', - 'certificate issuer common name') - - self.assertLess(abs(self.sec_epoch() - - self.openssl_date_to_sec_epoch(cert['validity']['since'])), 5, - 'certificate validity since') self.assertEqual( - self.openssl_date_to_sec_epoch(cert['validity']['until']) - - self.openssl_date_to_sec_epoch(cert['validity']['since']), 2592000, - 'certificate validity until') + cert['subject']['common_name'], + 'default', + 'certificate subject common name', + ) + self.assertEqual( + cert['issuer']['common_name'], + 'default', + 'certificate issuer common name', + ) + + self.assertLess( + abs( + self.sec_epoch() + - self.openssl_date_to_sec_epoch(cert['validity']['since']) + ), + 5, + 'certificate validity since', + ) + self.assertEqual( + self.openssl_date_to_sec_epoch(cert['validity']['until']) + - self.openssl_date_to_sec_epoch(cert['validity']['since']), + 2592000, + 'certificate validity until', + ) def test_tls_certificate_chain(self): self.load('empty') self.certificate('root', False) - subprocess.call(['openssl', 'req', '-new', '-config', - self.testdir + '/openssl.conf', '-subj', '/CN=int/', - '-out', self.testdir + '/int.csr', - '-keyout', self.testdir + '/int.key']) - - subprocess.call(['openssl', 'req', '-new', '-config', - self.testdir + '/openssl.conf', '-subj', '/CN=end/', - '-out', self.testdir + '/end.csr', - '-keyout', self.testdir + '/end.key']) + subprocess.call( + [ + 'openssl', + 'req', + '-new', + '-subj', '/CN=int/', + '-config', self.testdir + '/openssl.conf', + '-out', self.testdir + '/int.csr', + '-keyout', self.testdir + '/int.key', + ] + ) + + subprocess.call( + [ + 'openssl', + 'req', + '-new', + '-subj', '/CN=end/', + '-config', self.testdir + '/openssl.conf', + '-out', self.testdir + '/end.csr', + '-keyout', self.testdir + '/end.key', + ] + ) with open(self.testdir + '/ca.conf', 'w') as f: - f.write("""[ ca ] + f.write( + """[ ca ] default_ca = myca [ myca ] @@ -209,11 +260,13 @@ x509_extensions = myca_extensions commonName = supplied [ myca_extensions ] -basicConstraints = critical,CA:TRUE""" % { - 'dir': self.testdir, - 'database': self.testdir + '/certindex', - 'certserial': self.testdir + '/certserial' - }) +basicConstraints = critical,CA:TRUE""" + % { + 'dir': self.testdir, + 'database': self.testdir + '/certindex', + 'certserial': self.testdir + '/certserial', + } + ) with open(self.testdir + '/certserial', 'w') as f: f.write('1000') @@ -221,26 +274,42 @@ basicConstraints = critical,CA:TRUE""" % { with open(self.testdir + '/certindex', 'w') as f: f.write('') - subprocess.call(['openssl', 'ca', '-batch', - '-config', self.testdir + '/ca.conf', - '-keyfile', self.testdir + '/root.key', - '-cert', self.testdir + '/root.crt', - '-subj', '/CN=int/', - '-in', self.testdir + '/int.csr', - '-out', self.testdir + '/int.crt']) - - subprocess.call(['openssl', 'ca', '-batch', - '-config', self.testdir + '/ca.conf', - '-keyfile', self.testdir + '/int.key', - '-cert', self.testdir + '/int.crt', - '-subj', '/CN=end/', - '-in', self.testdir + '/end.csr', - '-out', self.testdir + '/end.crt']) - - with open(self.testdir + '/end-int.crt', 'wb') as crt, \ - open(self.testdir + '/end.crt', 'rb') as end, \ - open(self.testdir + '/int.crt', 'rb') as int: - crt.write(end.read() + int.read()) + subprocess.call( + [ + 'openssl', + 'ca', + '-batch', + '-subj', '/CN=int/', + '-config', self.testdir + '/ca.conf', + '-keyfile', self.testdir + '/root.key', + '-cert', self.testdir + '/root.crt', + '-in', self.testdir + '/int.csr', + '-out', self.testdir + '/int.crt', + ] + ) + + subprocess.call( + [ + 'openssl', + 'ca', + '-batch', + '-subj', '/CN=end/', + '-config', self.testdir + '/ca.conf', + '-keyfile', self.testdir + '/int.key', + '-cert', self.testdir + '/int.crt', + '-in', self.testdir + '/end.csr', + '-out', self.testdir + '/end.crt', + ] + ) + + crt_path = self.testdir + '/end-int.crt' + end_path = self.testdir + '/end.crt' + int_path = self.testdir + '/int.crt' + + with open(crt_path, 'wb') as crt, \ + open(end_path, 'rb') as end, \ + open(int_path, 'rb') as int: + crt.write(end.read() + int.read()) self.context = ssl.create_default_context() self.context.check_hostname = False @@ -249,15 +318,24 @@ basicConstraints = critical,CA:TRUE""" % { # incomplete chain - self.assertIn('success', self.certificate_load('end', 'end'), - 'certificate chain end upload') + self.assertIn( + 'success', + self.certificate_load('end', 'end'), + 'certificate chain end upload', + ) chain = self.conf_get('/certificates/end/chain') self.assertEqual(len(chain), 1, 'certificate chain end length') - self.assertEqual(chain[0]['subject']['common_name'], 'end', - 'certificate chain end subject common name') - self.assertEqual(chain[0]['issuer']['common_name'], 'int', - 'certificate chain end issuer common name') + self.assertEqual( + chain[0]['subject']['common_name'], + 'end', + 'certificate chain end subject common name', + ) + self.assertEqual( + chain[0]['issuer']['common_name'], + 'int', + 'certificate chain end issuer common name', + ) self.add_tls(cert='end') @@ -270,153 +348,249 @@ basicConstraints = critical,CA:TRUE""" % { # intermediate - self.assertIn('success', self.certificate_load('int', 'int'), - 'certificate chain int upload') + self.assertIn( + 'success', + self.certificate_load('int', 'int'), + 'certificate chain int upload', + ) chain = self.conf_get('/certificates/int/chain') self.assertEqual(len(chain), 1, 'certificate chain int length') - self.assertEqual(chain[0]['subject']['common_name'], 'int', - 'certificate chain int subject common name') - self.assertEqual(chain[0]['issuer']['common_name'], 'root', - 'certificate chain int issuer common name') + self.assertEqual( + chain[0]['subject']['common_name'], + 'int', + 'certificate chain int subject common name', + ) + self.assertEqual( + chain[0]['issuer']['common_name'], + 'root', + 'certificate chain int issuer common name', + ) self.add_tls(cert='int') - self.assertEqual(self.get_ssl()['status'], 200, - 'certificate chain intermediate') + self.assertEqual( + self.get_ssl()['status'], 200, 'certificate chain intermediate' + ) # intermediate server - self.assertIn('success', self.certificate_load('end-int', 'end'), - 'certificate chain end-int upload') + self.assertIn( + 'success', + self.certificate_load('end-int', 'end'), + 'certificate chain end-int upload', + ) chain = self.conf_get('/certificates/end-int/chain') self.assertEqual(len(chain), 2, 'certificate chain end-int length') - self.assertEqual(chain[0]['subject']['common_name'], 'end', - 'certificate chain end-int int subject common name') - self.assertEqual(chain[0]['issuer']['common_name'], 'int', - 'certificate chain end-int int issuer common name') - self.assertEqual(chain[1]['subject']['common_name'], 'int', - 'certificate chain end-int end subject common name') - self.assertEqual(chain[1]['issuer']['common_name'], 'root', - 'certificate chain end-int end issuer common name') + self.assertEqual( + chain[0]['subject']['common_name'], + 'end', + 'certificate chain end-int int subject common name', + ) + self.assertEqual( + chain[0]['issuer']['common_name'], + 'int', + 'certificate chain end-int int issuer common name', + ) + self.assertEqual( + chain[1]['subject']['common_name'], + 'int', + 'certificate chain end-int end subject common name', + ) + self.assertEqual( + chain[1]['issuer']['common_name'], + 'root', + 'certificate chain end-int end issuer common name', + ) self.add_tls(cert='end-int') - self.assertEqual(self.get_ssl()['status'], 200, - 'certificate chain intermediate server') + self.assertEqual( + self.get_ssl()['status'], + 200, + 'certificate chain intermediate server', + ) - @unittest.expectedFailure + @unittest.skip('not yet') def test_tls_reconfigure(self): self.load('empty') + self.assertEqual(self.get()['status'], 200, 'init') + self.certificate() - (resp, sock) = self.get(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive' - }, start=True) + (resp, sock) = self.get( + headers={'Host': 'localhost', 'Connection': 'keep-alive'}, + start=True, + read_timeout=1, + ) self.assertEqual(resp['status'], 200, 'initial status') self.add_tls() - self.assertEqual(self.get(sock=sock)['status'], 200, - 'reconfigure status') - self.assertEqual(self.get_ssl()['status'], 200, - 'reconfigure tls status') + self.assertEqual( + self.get(sock=sock)['status'], 200, 'reconfigure status' + ) + self.assertEqual( + self.get_ssl()['status'], 200, 'reconfigure tls status' + ) def test_tls_keepalive(self): self.load('mirror') + self.assertEqual(self.get()['status'], 200, 'init') + self.certificate() self.add_tls(application='mirror') - (resp, sock) = self.post_ssl(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive', - 'Content-Type': 'text/html' - }, start=True, body='0123456789') + (resp, sock) = self.post_ssl( + headers={ + 'Host': 'localhost', + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + }, + start=True, + body='0123456789', + read_timeout=1, + ) self.assertEqual(resp['body'], '0123456789', 'keepalive 1') - resp = self.post_ssl(headers={ - 'Host': 'localhost', - 'Connection': 'close', - 'Content-Type': 'text/html' - }, sock=sock, body='0123456789') + resp = self.post_ssl( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'Content-Type': 'text/html', + }, + sock=sock, + body='0123456789', + ) self.assertEqual(resp['body'], '0123456789', 'keepalive 2') - @unittest.expectedFailure + @unittest.skip('not yet') def test_tls_keepalive_certificate_remove(self): self.load('empty') + self.assertEqual(self.get()['status'], 200, 'init') + self.certificate() self.add_tls() - (resp, sock) = self.get_ssl(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive' - }, start=True) + (resp, sock) = self.get_ssl( + headers={'Host': 'localhost', 'Connection': 'keep-alive'}, + start=True, + read_timeout=1, + ) - self.conf({ - "application": "empty" - }, 'listeners/*:7080') + self.conf({"pass": "applications/empty"}, 'listeners/*:7080') self.conf_delete('/certificates/default') try: - resp = self.get_ssl(headers={ - 'Host': 'localhost', - 'Connection': 'close' - }, sock=sock) + resp = self.get_ssl( + headers={'Host': 'localhost', 'Connection': 'close'}, sock=sock + ) except: resp = None self.assertEqual(resp, None, 'keepalive remove certificate') - @unittest.expectedFailure + @unittest.skip('not yet') def test_tls_certificates_remove_all(self): self.load('empty') self.certificate() - self.assertIn('success', self.conf_delete('/certificates'), - 'remove all certificates') + self.assertIn( + 'success', + self.conf_delete('/certificates'), + 'remove all certificates', + ) def test_tls_application_respawn(self): self.skip_alerts.append(r'process \d+ exited on signal 9') self.load('mirror') + self.assertEqual(self.get()['status'], 200, 'init') + self.certificate() self.conf('1', 'applications/mirror/processes') self.add_tls(application='mirror') - (resp, sock) = self.post_ssl(headers={ - 'Host': 'localhost', - 'Connection': 'keep-alive', - 'Content-Type': 'text/html' - }, start=True, body='0123456789') + (resp, sock) = self.post_ssl( + headers={ + 'Host': 'localhost', + 'Connection': 'keep-alive', + 'Content-Type': 'text/html', + }, + start=True, + body='0123456789', + read_timeout=1, + ) app_id = self.findall(r'(\d+)#\d+ "mirror" application started')[0] subprocess.call(['kill', '-9', app_id]) - self.wait_for_record(re.compile(' (?!' + app_id + - '#)(\d+)#\d+ "mirror" application started')) + self.wait_for_record( + re.compile( + ' (?!' + app_id + '#)(\d+)#\d+ "mirror" application started' + ) + ) - resp = self.post_ssl(headers={ - 'Host': 'localhost', - 'Connection': 'close', - 'Content-Type': 'text/html' - }, sock=sock, body='0123456789') + resp = self.post_ssl( + headers={ + 'Host': 'localhost', + 'Connection': 'close', + 'Content-Type': 'text/html', + }, + sock=sock, + body='0123456789', + ) self.assertEqual(resp['status'], 200, 'application respawn status') - self.assertEqual(resp['body'], '0123456789', 'application respawn body') + self.assertEqual( + resp['body'], '0123456789', 'application respawn body' + ) + + def test_tls_url_scheme(self): + self.load('variables') + + self.assertEqual( + self.post( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Custom-Header': '', + 'Connection': 'close', + } + )['headers']['Wsgi-Url-Scheme'], + 'http', + 'url scheme http', + ) + + self.certificate() + + self.add_tls(application='variables') + + self.assertEqual( + self.post_ssl( + headers={ + 'Host': 'localhost', + 'Content-Type': 'text/html', + 'Custom-Header': '', + 'Connection': 'close', + } + )['headers']['Wsgi-Url-Scheme'], + 'https', + 'url scheme https', + ) if __name__ == '__main__': - TestUnitTLS.main() + TestTLS.main() diff --git a/test/unit.py b/test/unit.py deleted file mode 100644 index 6cca7f48..00000000 --- a/test/unit.py +++ /dev/null @@ -1,763 +0,0 @@ -import os -import re -import ssl -import sys -import json -import time -import shutil -import socket -import select -import argparse -import platform -import tempfile -import unittest -import subprocess -from multiprocessing import Process - -class TestUnit(unittest.TestCase): - - pardir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) - architecture = platform.architecture()[0] - maxDiff = None - - detailed = False - save_log = False - - def __init__(self, methodName='runTest'): - super().__init__(methodName) - - if re.match(r'.*\/run\.py$', sys.argv[0]): - args, rest = TestUnit._parse_args() - - TestUnit._set_args(args) - - @classmethod - def main(cls): - args, rest = TestUnit._parse_args() - - for i, arg in enumerate(rest): - if arg[:5] == 'test_': - rest[i] = cls.__name__ + '.' + arg - - sys.argv = sys.argv[:1] + rest - - TestUnit._set_args(args) - - unittest.main() - - def setUp(self): - self._run() - - def tearDown(self): - self.stop() - - # detect errors and failures for current test - - def list2reason(exc_list): - if exc_list and exc_list[-1][0] is self: - return exc_list[-1][1] - - if hasattr(self, '_outcome'): - result = self.defaultTestResult() - self._feedErrorsToResult(result, self._outcome.errors) - else: - result = getattr(self, '_outcomeForDoCleanups', - self._resultForDoCleanups) - - success = not list2reason(result.errors) \ - and not list2reason(result.failures) - - # check unit.log for alerts - - with open(self.testdir + '/unit.log', 'r', encoding='utf-8', - errors='ignore') as f: - self._check_alerts(f.read()) - - # remove unit.log - - if not TestUnit.save_log and success: - shutil.rmtree(self.testdir) - - else: - self._print_path_to_log() - - def check_modules(self, *modules): - self._run() - - for i in range(50): - with open(self.testdir + '/unit.log', 'r') as f: - log = f.read() - m = re.search('controller started', log) - - if m is None: - time.sleep(0.1) - else: - break - - if m is None: - self.stop() - exit("Unit is writing log too long") - - current_dir = os.path.dirname(os.path.abspath(__file__)) - - missed_module = '' - for module in modules: - if module == 'go': - env = os.environ.copy() - env['GOPATH'] = self.pardir + '/go' - - try: - process = subprocess.Popen(['go', 'build', '-o', - self.testdir + '/go/check_module', - current_dir + '/go/empty/app.go'], env=env) - process.communicate() - - m = module if process.returncode == 0 else None - - except: - m = None - - elif module == 'node': - if os.path.isdir(self.pardir + '/node/node_modules'): - m = module - else: - m = None - - elif module == 'openssl': - try: - subprocess.check_output(['which', 'openssl']) - - output = subprocess.check_output([ - self.pardir + '/build/unitd', '--version'], - stderr=subprocess.STDOUT) - - m = re.search('--openssl', output.decode()) - - except: - m = None - - else: - m = re.search('module: ' + module, log) - - if m is None: - missed_module = module - break - - self.stop() - self._check_alerts(log) - shutil.rmtree(self.testdir) - - if missed_module: - raise unittest.SkipTest('Unit has no ' + missed_module + ' module') - - def stop(self): - if self._started: - self._stop() - - def _run(self): - self.testdir = tempfile.mkdtemp(prefix='unit-test-') - - os.mkdir(self.testdir + '/state') - - print() - - def _run_unit(): - subprocess.call([self.pardir + '/build/unitd', - '--no-daemon', - '--modules', self.pardir + '/build', - '--state', self.testdir + '/state', - '--pid', self.testdir + '/unit.pid', - '--log', self.testdir + '/unit.log', - '--control', 'unix:' + self.testdir + '/control.unit.sock']) - - self._p = Process(target=_run_unit) - self._p.start() - - if not self.waitforfiles(self.testdir + '/unit.pid', - self.testdir + '/unit.log', self.testdir + '/control.unit.sock'): - exit("Could not start unit") - - self._started = True - - self.skip_alerts = [r'read signalfd\(4\) failed', r'sendmsg.+failed', - r'recvmsg.+failed'] - self.skip_sanitizer = False - - def _stop(self): - with open(self.testdir + '/unit.pid', 'r') as f: - pid = f.read().rstrip() - - subprocess.call(['kill', '-s', 'QUIT', pid]) - - for i in range(50): - if not os.path.exists(self.testdir + '/unit.pid'): - break - time.sleep(0.1) - - if os.path.exists(self.testdir + '/unit.pid'): - exit("Could not terminate unit") - - self._started = False - - self._p.join(timeout=1) - self._terminate_process(self._p) - - def _terminate_process(self, process): - if process.is_alive(): - process.terminate() - process.join(timeout=5) - - if process.is_alive(): - exit("Could not terminate process " + process.pid) - - if process.exitcode: - exit("Child process terminated with code " + str(process.exitcode)) - - def _check_alerts(self, log): - found = False - - alerts = re.findall('.+\[alert\].+', log) - - if alerts: - print('All alerts/sanitizer errors found in log:') - [print(alert) for alert in alerts] - found = True - - if self.skip_alerts: - for skip in self.skip_alerts: - alerts = [al for al in alerts if re.search(skip, al) is None] - - if alerts: - self._print_path_to_log() - self.assertFalse(alerts, 'alert(s)') - - if not self.skip_sanitizer: - sanitizer_errors = re.findall('.+Sanitizer.+', log) - - if sanitizer_errors: - self._print_path_to_log() - self.assertFalse(sanitizer_errors, 'sanitizer error(s)') - - if found: - print('skipped.') - - def waitforfiles(self, *files): - for i in range(50): - wait = False - ret = False - - for f in files: - if not os.path.exists(f): - wait = True - break - - if wait: - time.sleep(0.1) - - else: - ret = True - break - - return ret - - @staticmethod - def _parse_args(): - parser = argparse.ArgumentParser(add_help=False) - - parser.add_argument('-d', '--detailed', dest='detailed', - action='store_true', help='Detailed output for tests') - parser.add_argument('-l', '--log', dest='save_log', - action='store_true', help='Save unit.log after the test execution') - - return parser.parse_known_args() - - @staticmethod - def _set_args(args): - TestUnit.detailed = args.detailed - TestUnit.save_log = args.save_log - - def _print_path_to_log(self): - print('Path to unit.log:\n' + self.testdir + '/unit.log') - -class TestUnitHTTP(TestUnit): - - def http(self, start_str, **kwargs): - sock_type = 'ipv4' if 'sock_type' not in kwargs else kwargs['sock_type'] - port = 7080 if 'port' not in kwargs else kwargs['port'] - url = '/' if 'url' not in kwargs else kwargs['url'] - http = 'HTTP/1.0' if 'http_10' in kwargs else 'HTTP/1.1' - - headers = ({ - 'Host': 'localhost', - 'Connection': 'close' - } if 'headers' not in kwargs else kwargs['headers']) - - body = b'' if 'body' not in kwargs else kwargs['body'] - crlf = '\r\n' - - if 'addr' not in kwargs: - addr = '::1' if sock_type == 'ipv6' else '127.0.0.1' - else: - addr = kwargs['addr'] - - sock_types = { - 'ipv4': socket.AF_INET, - 'ipv6': socket.AF_INET6, - 'unix': socket.AF_UNIX - } - - if 'sock' not in kwargs: - sock = socket.socket(sock_types[sock_type], socket.SOCK_STREAM) - - if sock_type == sock_types['ipv4'] or sock_type == sock_types['ipv6']: - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - - if 'wrapper' in kwargs: - sock = kwargs['wrapper'](sock) - - connect_args = addr if sock_type == 'unix' else (addr, port) - try: - sock.connect(connect_args) - except ConnectionRefusedError: - sock.close() - return None - - else: - sock = kwargs['sock'] - - if 'raw' not in kwargs: - req = ' '.join([start_str, url, http]) + crlf - - if body is not b'': - if isinstance(body, str): - body = body.encode() - - if 'Content-Length' not in headers: - headers['Content-Length'] = len(body) - - for header, value in headers.items(): - if isinstance(value, list): - for v in value: - req += header + ': ' + str(v) + crlf - - else: - req += header + ': ' + str(value) + crlf - - req = (req + crlf).encode() + body - - else: - req = start_str - - sock.sendall(req) - - if TestUnit.detailed: - print('>>>', req, sep='\n') - - resp = '' - - if 'no_recv' not in kwargs: - enc = 'utf-8' if 'encoding' not in kwargs else kwargs['encoding'] - read_timeout = 5 if 'read_timeout' not in kwargs else kwargs['read_timeout'] - resp = self.recvall(sock, read_timeout=read_timeout).decode(enc) - - if TestUnit.detailed: - print('<<<', resp.encode('utf-8'), sep='\n') - - if 'raw_resp' not in kwargs: - resp = self._resp_to_dict(resp) - - if 'start' not in kwargs: - sock.close() - return resp - - return (resp, sock) - - def delete(self, **kwargs): - return self.http('DELETE', **kwargs) - - def get(self, **kwargs): - return self.http('GET', **kwargs) - - def post(self, **kwargs): - return self.http('POST', **kwargs) - - def put(self, **kwargs): - return self.http('PUT', **kwargs) - - def recvall(self, sock, read_timeout=5, buff_size=4096): - data = b'' - while select.select([sock], [], [], read_timeout)[0]: - try: - part = sock.recv(buff_size) - except: - break - - data += part - - if not len(part): - break - - return data - - def _resp_to_dict(self, resp): - m = re.search('(.*?\x0d\x0a?)\x0d\x0a?(.*)', resp, re.M | re.S) - - if not m: - return {} - - headers_text, body = m.group(1), m.group(2) - - p = re.compile('(.*?)\x0d\x0a?', re.M | re.S) - headers_lines = p.findall(headers_text) - - status = re.search('^HTTP\/\d\.\d\s(\d+)|$', headers_lines.pop(0)).group(1) - - headers = {} - for line in headers_lines: - m = re.search('(.*)\:\s(.*)', line) - - if m.group(1) not in headers: - headers[m.group(1)] = m.group(2) - elif isinstance(headers[m.group(1)], list): - headers[m.group(1)].append(m.group(2)) - else: - headers[m.group(1)] = [headers[m.group(1)], m.group(2)] - - return { - 'status': int(status), - 'headers': headers, - 'body': body - } - -class TestUnitControl(TestUnitHTTP): - - # TODO socket reuse - # TODO http client - - def conf(self, conf, path='/config'): - if isinstance(conf, dict) or isinstance(conf, list): - conf = json.dumps(conf) - - if path[:1] != '/': - path = '/config/' + path - - return json.loads(self.put( - url=path, - body=conf, - sock_type='unix', - addr=self.testdir + '/control.unit.sock' - )['body']) - - def conf_get(self, path='/config'): - if path[:1] != '/': - path = '/config/' + path - - return json.loads(self.get( - url=path, - sock_type='unix', - addr=self.testdir + '/control.unit.sock' - )['body']) - - def conf_delete(self, path='/config'): - if path[:1] != '/': - path = '/config/' + path - - return json.loads(self.delete( - url=path, - sock_type='unix', - addr=self.testdir + '/control.unit.sock' - )['body']) - -class TestUnitApplicationProto(TestUnitControl): - - current_dir = os.path.dirname(os.path.abspath(__file__)) - - def sec_epoch(self): - return time.mktime(time.gmtime()) - - def date_to_sec_epoch(self, date, template='%a, %d %b %Y %H:%M:%S %Z'): - return time.mktime(time.strptime(date, template)) - - def search_in_log(self, pattern): - with open(self.testdir + '/unit.log', 'r', errors='ignore') as f: - return re.search(pattern, f.read()) - -class TestUnitApplicationPython(TestUnitApplicationProto): - def load(self, script, name=None): - if name is None: - name = script - - self.conf({ - "listeners": { - "*:7080": { - "application": name - } - }, - "applications": { - name: { - "type": "python", - "processes": { "spare": 0 }, - "path": self.current_dir + '/python/' + script, - "working_directory": self.current_dir + '/python/' + script, - "module": "wsgi" - } - } - }) - -class TestUnitApplicationRuby(TestUnitApplicationProto): - def load(self, script, name='config.ru'): - self.conf({ - "listeners": { - "*:7080": { - "application": script - } - }, - "applications": { - script: { - "type": "ruby", - "processes": { "spare": 0 }, - "working_directory": self.current_dir + '/ruby/' + script, - "script": self.current_dir + '/ruby/' + script + '/' + name - } - } - }) - -class TestUnitApplicationPHP(TestUnitApplicationProto): - def load(self, script, name='index.php'): - self.conf({ - "listeners": { - "*:7080": { - "application": script - } - }, - "applications": { - script: { - "type": "php", - "processes": { "spare": 0 }, - "root": self.current_dir + '/php/' + script, - "working_directory": self.current_dir + '/php/' + script, - "index": name - } - } - }) - -class TestUnitApplicationGo(TestUnitApplicationProto): - def load(self, script, name='app'): - - if not os.path.isdir(self.testdir + '/go'): - os.mkdir(self.testdir + '/go') - - env = os.environ.copy() - env['GOPATH'] = self.pardir + '/go' - process = subprocess.Popen(['go', 'build', '-o', - self.testdir + '/go/' + name, - self.current_dir + '/go/' + script + '/' + name + '.go'], - env=env) - process.communicate() - - self.conf({ - "listeners": { - "*:7080": { - "application": script - } - }, - "applications": { - script: { - "type": "external", - "processes": { "spare": 0 }, - "working_directory": self.current_dir + '/go/' + script, - "executable": self.testdir + '/go/' + name - } - } - }) - -class TestUnitApplicationNode(TestUnitApplicationProto): - def load(self, script, name='app.js'): - - # copy application - - shutil.copytree(self.current_dir + '/node/' + script, - self.testdir + '/node') - - # link modules - - os.symlink(self.pardir + '/node/node_modules', - self.testdir + '/node/node_modules') - - self.conf({ - "listeners": { - "*:7080": { - "application": script - } - }, - "applications": { - script: { - "type": "external", - "processes": { "spare": 0 }, - "working_directory": self.testdir + '/node', - "executable": name - } - } - }) - -class TestUnitApplicationJava(TestUnitApplicationProto): - def load(self, script, name='app'): - - app_path = self.testdir + '/java' - web_inf_path = app_path + '/WEB-INF/' - classes_path = web_inf_path + 'classes/' - - script_path = self.current_dir + '/java/' + script + '/' - - if not os.path.isdir(app_path): - os.makedirs(app_path) - - src = [] - - for f in os.listdir(script_path): - if f.endswith('.java'): - src.append(script_path + f) - continue - - if f.startswith('.') or f == 'Makefile': - continue - - if os.path.isdir(script_path + f): - if f == 'WEB-INF': - continue - - shutil.copytree(script_path + f, app_path + '/' + f) - continue - - if f == 'web.xml': - if not os.path.isdir(web_inf_path): - os.makedirs(web_inf_path) - - shutil.copy2(script_path + f, web_inf_path) - else: - shutil.copy2(script_path + f, app_path) - - if src: - if not os.path.isdir(classes_path): - os.makedirs(classes_path) - - javac = ['javac', '-encoding', 'utf-8', '-d', classes_path, - '-classpath', - self.pardir + '/build/tomcat-servlet-api-9.0.13.jar'] - javac.extend(src) - - process = subprocess.Popen(javac) - process.communicate() - - self.conf({ - "listeners": { - "*:7080": { - "application": script - } - }, - "applications": { - script: { - "unit_jars": self.pardir + '/build', - "type": "java", - "processes": { "spare": 0 }, - "working_directory": script_path, - "webapp": app_path - } - } - }) - -class TestUnitApplicationPerl(TestUnitApplicationProto): - def load(self, script, name='psgi.pl'): - self.conf({ - "listeners": { - "*:7080": { - "application": script - } - }, - "applications": { - script: { - "type": "perl", - "processes": { "spare": 0 }, - "working_directory": self.current_dir + '/perl/' + script, - "script": self.current_dir + '/perl/' + script + '/' + name - } - } - }) - -class TestUnitApplicationTLS(TestUnitApplicationProto): - def __init__(self, test): - super().__init__(test) - - self.context = ssl.create_default_context() - self.context.check_hostname = False - self.context.verify_mode = ssl.CERT_NONE - - def certificate(self, name='default', load=True): - subprocess.call(['openssl', 'req', '-x509', '-new', '-config', - self.testdir + '/openssl.conf', '-subj', '/CN=' + name + '/', - '-out', self.testdir + '/' + name + '.crt', - '-keyout', self.testdir + '/' + name + '.key']) - - if load: - self.certificate_load(name) - - def certificate_load(self, crt, key=None): - if key is None: - key = crt - - with open(self.testdir + '/' + key + '.key', 'rb') as k, \ - open(self.testdir + '/' + crt + '.crt', 'rb') as c: - return self.conf(k.read() + c.read(), '/certificates/' + crt) - - def get_ssl(self, **kwargs): - return self.get(wrapper=self.context.wrap_socket, - **kwargs) - - def post_ssl(self, **kwargs): - return self.post(wrapper=self.context.wrap_socket, - **kwargs) - - def get_server_certificate(self, addr=('127.0.0.1', 7080)): - - ssl_list = dir(ssl) - - if 'PROTOCOL_TLS' in ssl_list: - ssl_version = ssl.PROTOCOL_TLS - - elif 'PROTOCOL_TLSv1_2' in ssl_list: - ssl_version = ssl.PROTOCOL_TLSv1_2 - - else: - ssl_version = ssl.PROTOCOL_TLSv1_1 - - return ssl.get_server_certificate(addr, ssl_version=ssl_version) - - def load(self, script, name=None): - if name is None: - name = script - - # create default openssl configuration - - with open(self.testdir + '/openssl.conf', 'w') as f: - f.write("""[ req ] -default_bits = 1024 -encrypt_key = no -distinguished_name = req_distinguished_name -[ req_distinguished_name ]""") - - self.conf({ - "listeners": { - "*:7080": { - "application": name - } - }, - "applications": { - name: { - "type": "python", - "processes": { "spare": 0 }, - "path": self.current_dir + '/python/' + script, - "working_directory": self.current_dir + '/python/' + script, - "module": "wsgi" - } - } - }) diff --git a/test/unit/__init__.py b/test/unit/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/unit/__init__.py diff --git a/test/unit/applications/__init__.py b/test/unit/applications/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/unit/applications/__init__.py diff --git a/test/unit/applications/lang/__init__.py b/test/unit/applications/lang/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/unit/applications/lang/__init__.py diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py new file mode 100644 index 00000000..e4ab8ffa --- /dev/null +++ b/test/unit/applications/lang/go.py @@ -0,0 +1,40 @@ +import os +from subprocess import Popen +from unit.applications.proto import TestApplicationProto + + +class TestApplicationGo(TestApplicationProto): + def load(self, script, name='app'): + + if not os.path.isdir(self.testdir + '/go'): + os.mkdir(self.testdir + '/go') + + go_app_path = self.current_dir + '/go/' + + env = os.environ.copy() + env['GOPATH'] = self.pardir + '/go' + process = Popen( + [ + 'go', + 'build', + '-o', + self.testdir + '/go/' + name, + go_app_path + script + '/' + name + '.go', + ], + env=env, + ) + process.communicate() + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "applications/" + script}}, + "applications": { + script: { + "type": "external", + "processes": {"spare": 0}, + "working_directory": go_app_path + script, + "executable": self.testdir + '/go/' + name, + } + }, + } + ) diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py new file mode 100644 index 00000000..c4390f15 --- /dev/null +++ b/test/unit/applications/lang/java.py @@ -0,0 +1,74 @@ +import os +import shutil +from subprocess import Popen +from unit.applications.proto import TestApplicationProto + + +class TestApplicationJava(TestApplicationProto): + def load(self, script, name='app'): + + app_path = self.testdir + '/java' + web_inf_path = app_path + '/WEB-INF/' + classes_path = web_inf_path + 'classes/' + + script_path = self.current_dir + '/java/' + script + '/' + + if not os.path.isdir(app_path): + os.makedirs(app_path) + + src = [] + + for f in os.listdir(script_path): + if f.endswith('.java'): + src.append(script_path + f) + continue + + if f.startswith('.') or f == 'Makefile': + continue + + if os.path.isdir(script_path + f): + if f == 'WEB-INF': + continue + + shutil.copytree(script_path + f, app_path + '/' + f) + continue + + if f == 'web.xml': + if not os.path.isdir(web_inf_path): + os.makedirs(web_inf_path) + + shutil.copy2(script_path + f, web_inf_path) + else: + shutil.copy2(script_path + f, app_path) + + if src: + if not os.path.isdir(classes_path): + os.makedirs(classes_path) + + tomcat_jar = self.pardir + '/build/tomcat-servlet-api-9.0.13.jar' + + javac = [ + 'javac', + '-encoding', 'utf-8', + '-d', classes_path, + '-classpath', tomcat_jar, + ] + javac.extend(src) + + process = Popen(javac) + process.communicate() + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "applications/" + script}}, + "applications": { + script: { + "unit_jars": self.pardir + '/build', + "type": "java", + "processes": {"spare": 0}, + "working_directory": script_path, + "webapp": app_path, + } + }, + } + ) diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py new file mode 100644 index 00000000..931c6596 --- /dev/null +++ b/test/unit/applications/lang/node.py @@ -0,0 +1,34 @@ +import os +import shutil +from unit.applications.proto import TestApplicationProto + + +class TestApplicationNode(TestApplicationProto): + def load(self, script, name='app.js'): + + # copy application + + shutil.copytree( + self.current_dir + '/node/' + script, self.testdir + '/node' + ) + + # link modules + + os.symlink( + self.pardir + '/node/node_modules', + self.testdir + '/node/node_modules', + ) + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "applications/" + script}}, + "applications": { + script: { + "type": "external", + "processes": {"spare": 0}, + "working_directory": self.testdir + '/node', + "executable": name, + } + }, + } + ) diff --git a/test/unit/applications/lang/perl.py b/test/unit/applications/lang/perl.py new file mode 100644 index 00000000..8aaf33a4 --- /dev/null +++ b/test/unit/applications/lang/perl.py @@ -0,0 +1,20 @@ +from unit.applications.proto import TestApplicationProto + + +class TestApplicationPerl(TestApplicationProto): + def load(self, script, name='psgi.pl'): + script_path = self.current_dir + '/perl/' + script + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "applications/" + script}}, + "applications": { + script: { + "type": "perl", + "processes": {"spare": 0}, + "working_directory": script_path, + "script": script_path + '/' + name, + } + }, + } + ) diff --git a/test/unit/applications/lang/php.py b/test/unit/applications/lang/php.py new file mode 100644 index 00000000..99d84164 --- /dev/null +++ b/test/unit/applications/lang/php.py @@ -0,0 +1,21 @@ +from unit.applications.proto import TestApplicationProto + + +class TestApplicationPHP(TestApplicationProto): + def load(self, script, name='index.php'): + script_path = self.current_dir + '/php/' + script + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "applications/" + script}}, + "applications": { + script: { + "type": "php", + "processes": {"spare": 0}, + "root": script_path, + "working_directory": script_path, + "index": name, + } + }, + } + ) diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py new file mode 100644 index 00000000..d1b5b839 --- /dev/null +++ b/test/unit/applications/lang/python.py @@ -0,0 +1,24 @@ +from unit.applications.proto import TestApplicationProto + + +class TestApplicationPython(TestApplicationProto): + def load(self, script, name=None): + if name is None: + name = script + + script_path = self.current_dir + '/python/' + script + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "applications/" + name}}, + "applications": { + name: { + "type": "python", + "processes": {"spare": 0}, + "path": script_path, + "working_directory": script_path, + "module": "wsgi", + } + }, + } + ) diff --git a/test/unit/applications/lang/ruby.py b/test/unit/applications/lang/ruby.py new file mode 100644 index 00000000..c2d8633e --- /dev/null +++ b/test/unit/applications/lang/ruby.py @@ -0,0 +1,20 @@ +from unit.applications.proto import TestApplicationProto + + +class TestApplicationRuby(TestApplicationProto): + def load(self, script, name='config.ru'): + script_path = self.current_dir + '/ruby/' + script + + self._load_conf( + { + "listeners": {"*:7080": {"pass": "applications/" + script}}, + "applications": { + script: { + "type": "ruby", + "processes": {"spare": 0}, + "working_directory": script_path, + "script": script_path + '/' + name, + } + }, + } + ) diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py new file mode 100644 index 00000000..4105473f --- /dev/null +++ b/test/unit/applications/proto.py @@ -0,0 +1,31 @@ +import re +import time +from unit.control import TestControl + + +class TestApplicationProto(TestControl): + def sec_epoch(self): + return time.mktime(time.gmtime()) + + def date_to_sec_epoch(self, date, template='%a, %d %b %Y %H:%M:%S %Z'): + return time.mktime(time.strptime(date, template)) + + def search_in_log(self, pattern, name='unit.log'): + with open(self.testdir + '/' + name, 'r', errors='ignore') as f: + return re.search(pattern, f.read()) + + def wait_for_record(self, pattern, name='unit.log'): + for i in range(50): + found = self.search_in_log(pattern, name) + + if found is not None: + break + + time.sleep(0.1) + + return found + + def _load_conf(self, conf): + self.assertIn( + 'success', self.conf(conf), 'load application configuration' + ) diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py new file mode 100644 index 00000000..83cc1a03 --- /dev/null +++ b/test/unit/applications/tls.py @@ -0,0 +1,92 @@ +import ssl +import subprocess +from unit.applications.proto import TestApplicationProto + + +class TestApplicationTLS(TestApplicationProto): + def __init__(self, test): + super().__init__(test) + + self.context = ssl.create_default_context() + self.context.check_hostname = False + self.context.verify_mode = ssl.CERT_NONE + + def certificate(self, name='default', load=True): + subprocess.call( + [ + 'openssl', + 'req', + '-x509', + '-new', + '-subj', '/CN=' + name + '/', + '-config', self.testdir + '/openssl.conf', + '-out', self.testdir + '/' + name + '.crt', + '-keyout', self.testdir + '/' + name + '.key', + ] + ) + + if load: + self.certificate_load(name) + + def certificate_load(self, crt, key=None): + if key is None: + key = crt + + key_path = self.testdir + '/' + key + '.key' + crt_path = self.testdir + '/' + crt + '.crt' + + with open(key_path, 'rb') as k, open(crt_path, 'rb') as c: + return self.conf(k.read() + c.read(), '/certificates/' + crt) + + def get_ssl(self, **kwargs): + return self.get(wrapper=self.context.wrap_socket, **kwargs) + + def post_ssl(self, **kwargs): + return self.post(wrapper=self.context.wrap_socket, **kwargs) + + def get_server_certificate(self, addr=('127.0.0.1', 7080)): + + ssl_list = dir(ssl) + + if 'PROTOCOL_TLS' in ssl_list: + ssl_version = ssl.PROTOCOL_TLS + + elif 'PROTOCOL_TLSv1_2' in ssl_list: + ssl_version = ssl.PROTOCOL_TLSv1_2 + + else: + ssl_version = ssl.PROTOCOL_TLSv1_1 + + return ssl.get_server_certificate(addr, ssl_version=ssl_version) + + def load(self, script, name=None): + if name is None: + name = script + + # create default openssl configuration + + with open(self.testdir + '/openssl.conf', 'w') as f: + f.write( + """[ req ] +default_bits = 1024 +encrypt_key = no +distinguished_name = req_distinguished_name +[ req_distinguished_name ]""" + ) + + script_path = self.current_dir + '/python/' + script + + self.conf( + { + "listeners": {"*:7080": {"pass": "applications/" + name}}, + "applications": { + name: { + "type": "python", + "processes": {"spare": 0}, + "path": script_path, + "working_directory": script_path, + "module": "wsgi", + } + }, + } + ) diff --git a/test/unit/control.py b/test/unit/control.py new file mode 100644 index 00000000..0b344ed5 --- /dev/null +++ b/test/unit/control.py @@ -0,0 +1,61 @@ +import json +from unit.http import TestHTTP + + +def args_handler(conf_func): + def args_wrapper(self, *args): + argcount = conf_func.__code__.co_argcount + url_default = '/config' + conf = None + + if argcount == 2: + url = args[0] if len(args) == 1 else url_default + + elif argcount == 3: + conf = args[0] + + if isinstance(conf, dict) or isinstance(conf, list): + conf = json.dumps(conf) + + url = args[1] if len(args) == 2 else url_default + + url = url if url.startswith('/') else url_default + '/' + url + arguments = (self, url) if conf is None else (self, conf, url) + + return json.loads(conf_func(*arguments)) + + return args_wrapper + + +class TestControl(TestHTTP): + + # TODO socket reuse + # TODO http client + + @args_handler + def conf(self, conf, url): + return self.put(**self._get_args(url, conf))['body'] + + @args_handler + def conf_get(self, url): + return self.get(**self._get_args(url))['body'] + + @args_handler + def conf_delete(self, url): + return self.delete(**self._get_args(url))['body'] + + @args_handler + def conf_post(self, conf, url): + return self.post(**self._get_args(url, conf))['body'] + + def _get_args(self, url, conf=None): + args = { + 'url': url, + 'sock_type': 'unix', + 'addr': self.testdir + '/control.unit.sock', + } + + if conf is not None: + args['body'] = conf + + return args diff --git a/test/unit/http.py b/test/unit/http.py new file mode 100644 index 00000000..1ce86e5a --- /dev/null +++ b/test/unit/http.py @@ -0,0 +1,162 @@ +import re +import socket +import select +from unit.main import TestUnit + + +class TestHTTP(TestUnit): + def http(self, start_str, **kwargs): + sock_type = ( + 'ipv4' if 'sock_type' not in kwargs else kwargs['sock_type'] + ) + port = 7080 if 'port' not in kwargs else kwargs['port'] + url = '/' if 'url' not in kwargs else kwargs['url'] + http = 'HTTP/1.0' if 'http_10' in kwargs else 'HTTP/1.1' + + headers = ( + {'Host': 'localhost', 'Connection': 'close'} + if 'headers' not in kwargs + else kwargs['headers'] + ) + + body = b'' if 'body' not in kwargs else kwargs['body'] + crlf = '\r\n' + + if 'addr' not in kwargs: + addr = '::1' if sock_type == 'ipv6' else '127.0.0.1' + else: + addr = kwargs['addr'] + + sock_types = { + 'ipv4': socket.AF_INET, + 'ipv6': socket.AF_INET6, + 'unix': socket.AF_UNIX, + } + + if 'sock' not in kwargs: + sock = socket.socket(sock_types[sock_type], socket.SOCK_STREAM) + + if ( + sock_type == sock_types['ipv4'] + or sock_type == sock_types['ipv6'] + ): + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + + if 'wrapper' in kwargs: + sock = kwargs['wrapper'](sock) + + connect_args = addr if sock_type == 'unix' else (addr, port) + try: + sock.connect(connect_args) + except ConnectionRefusedError: + sock.close() + return None + + else: + sock = kwargs['sock'] + + if 'raw' not in kwargs: + req = ' '.join([start_str, url, http]) + crlf + + if body is not b'': + if isinstance(body, str): + body = body.encode() + + if 'Content-Length' not in headers: + headers['Content-Length'] = len(body) + + for header, value in headers.items(): + if isinstance(value, list): + for v in value: + req += header + ': ' + str(v) + crlf + + else: + req += header + ': ' + str(value) + crlf + + req = (req + crlf).encode() + body + + else: + req = start_str + + sock.sendall(req) + + if TestUnit.detailed: + print('>>>', req, sep='\n') + + resp = '' + + if 'no_recv' not in kwargs: + enc = 'utf-8' if 'encoding' not in kwargs else kwargs['encoding'] + read_timeout = ( + 30 if 'read_timeout' not in kwargs else kwargs['read_timeout'] + ) + resp = self.recvall(sock, read_timeout=read_timeout).decode(enc) + + if TestUnit.detailed: + print('<<<', resp.encode('utf-8'), sep='\n') + + if 'raw_resp' not in kwargs: + resp = self._resp_to_dict(resp) + + if 'start' not in kwargs: + sock.close() + return resp + + return (resp, sock) + + def delete(self, **kwargs): + return self.http('DELETE', **kwargs) + + def get(self, **kwargs): + return self.http('GET', **kwargs) + + def post(self, **kwargs): + return self.http('POST', **kwargs) + + def put(self, **kwargs): + return self.http('PUT', **kwargs) + + def recvall(self, sock, read_timeout=30, buff_size=4096): + data = b'' + while select.select([sock], [], [], read_timeout)[0]: + try: + part = sock.recv(buff_size) + except: + break + + data += part + + if not len(part): + break + + return data + + def _resp_to_dict(self, resp): + m = re.search('(.*?\x0d\x0a?)\x0d\x0a?(.*)', resp, re.M | re.S) + + if not m: + return {} + + headers_text, body = m.group(1), m.group(2) + + p = re.compile('(.*?)\x0d\x0a?', re.M | re.S) + headers_lines = p.findall(headers_text) + + status = re.search( + '^HTTP\/\d\.\d\s(\d+)|$', headers_lines.pop(0) + ).group(1) + + headers = {} + for line in headers_lines: + m = re.search('(.*)\:\s(.*)', line) + + if m.group(1) not in headers: + headers[m.group(1)] = m.group(2) + + elif isinstance(headers[m.group(1)], list): + headers[m.group(1)].append(m.group(2)) + + else: + headers[m.group(1)] = [headers[m.group(1)], m.group(2)] + + return {'status': int(status), 'headers': headers, 'body': body} diff --git a/test/unit/main.py b/test/unit/main.py new file mode 100644 index 00000000..49806fe7 --- /dev/null +++ b/test/unit/main.py @@ -0,0 +1,324 @@ +import os +import re +import sys +import time +import fcntl +import shutil +import argparse +import platform +import tempfile +import unittest +import subprocess +from multiprocessing import Process + + +class TestUnit(unittest.TestCase): + + current_dir = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir) + ) + pardir = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir, os.pardir) + ) + architecture = platform.architecture()[0] + maxDiff = None + + detailed = False + save_log = False + + def __init__(self, methodName='runTest'): + super().__init__(methodName) + + if re.match(r'.*\/run\.py$', sys.argv[0]): + args, rest = TestUnit._parse_args() + + TestUnit._set_args(args) + + @classmethod + def main(cls): + args, rest = TestUnit._parse_args() + + for i, arg in enumerate(rest): + if arg[:5] == 'test_': + rest[i] = cls.__name__ + '.' + arg + + sys.argv = sys.argv[:1] + rest + + TestUnit._set_args(args) + + unittest.main() + + @classmethod + def setUpClass(cls): + TestUnit().check_modules(*cls.prerequisites) + + def setUp(self): + self._run() + + def tearDown(self): + self.stop() + + # detect errors and failures for current test + + def list2reason(exc_list): + if exc_list and exc_list[-1][0] is self: + return exc_list[-1][1] + + if hasattr(self, '_outcome'): + result = self.defaultTestResult() + self._feedErrorsToResult(result, self._outcome.errors) + else: + result = getattr( + self, '_outcomeForDoCleanups', self._resultForDoCleanups + ) + + success = not list2reason(result.errors) and not list2reason( + result.failures + ) + + # check unit.log for alerts + + unit_log = self.testdir + '/unit.log' + + with open(unit_log, 'r', encoding='utf-8', errors='ignore') as f: + self._check_alerts(f.read()) + + # remove unit.log + + if not TestUnit.save_log and success: + shutil.rmtree(self.testdir) + + else: + self._print_path_to_log() + + def check_modules(self, *modules): + self._run() + + for i in range(50): + with open(self.testdir + '/unit.log', 'r') as f: + log = f.read() + m = re.search('controller started', log) + + if m is None: + time.sleep(0.1) + else: + break + + if m is None: + self.stop() + exit("Unit is writing log too long") + + missed_module = '' + for module in modules: + if module == 'go': + env = os.environ.copy() + env['GOPATH'] = self.pardir + '/go' + + try: + process = subprocess.Popen( + [ + 'go', + 'build', + '-o', + self.testdir + '/go/check_module', + self.current_dir + '/go/empty/app.go', + ], + env=env, + ) + process.communicate() + + m = module if process.returncode == 0 else None + + except: + m = None + + elif module == 'node': + if os.path.isdir(self.pardir + '/node/node_modules'): + m = module + else: + m = None + + elif module == 'openssl': + try: + subprocess.check_output(['which', 'openssl']) + + output = subprocess.check_output( + [self.unitd, '--version'], + stderr=subprocess.STDOUT, + ) + + m = re.search('--openssl', output.decode()) + + except: + m = None + + else: + m = re.search('module: ' + module, log) + + if m is None: + missed_module = module + break + + self.stop() + self._check_alerts(log) + shutil.rmtree(self.testdir) + + if missed_module: + raise unittest.SkipTest('Unit has no ' + missed_module + ' module') + + def stop(self): + if self._started: + self._stop() + + def _run(self): + self.unitd = self.pardir + '/build/unitd' + + if not os.path.isfile(self.unitd): + exit("Could not find unit") + + self.testdir = tempfile.mkdtemp(prefix='unit-test-') + + os.mkdir(self.testdir + '/state') + + print() + + def _run_unit(): + subprocess.call( + [ + self.unitd, + '--no-daemon', + '--modules', self.pardir + '/build', + '--state', self.testdir + '/state', + '--pid', self.testdir + '/unit.pid', + '--log', self.testdir + '/unit.log', + '--control', 'unix:' + self.testdir + '/control.unit.sock', + ] + ) + + self._p = Process(target=_run_unit) + self._p.start() + + if not self.waitforfiles( + self.testdir + '/unit.pid', + self.testdir + '/unit.log', + self.testdir + '/control.unit.sock', + ): + exit("Could not start unit") + + self._started = True + + self.skip_alerts = [ + r'read signalfd\(4\) failed', + r'sendmsg.+failed', + r'recvmsg.+failed', + ] + self.skip_sanitizer = False + + def _stop(self): + with open(self.testdir + '/unit.pid', 'r') as f: + pid = f.read().rstrip() + + subprocess.call(['kill', '-s', 'QUIT', pid]) + + for i in range(150): + if not os.path.exists(self.testdir + '/unit.pid'): + break + time.sleep(0.1) + + if os.path.exists(self.testdir + '/unit.pid'): + exit("Could not terminate unit") + + self._started = False + + self._p.join(timeout=1) + self._terminate_process(self._p) + + def _terminate_process(self, process): + if process.is_alive(): + process.terminate() + process.join(timeout=5) + + if process.is_alive(): + exit("Could not terminate process " + process.pid) + + if process.exitcode: + exit("Child process terminated with code " + str(process.exitcode)) + + def _check_alerts(self, log): + found = False + + alerts = re.findall('.+\[alert\].+', log) + + if alerts: + print('All alerts/sanitizer errors found in log:') + [print(alert) for alert in alerts] + found = True + + if self.skip_alerts: + for skip in self.skip_alerts: + alerts = [al for al in alerts if re.search(skip, al) is None] + + if alerts: + self._print_path_to_log() + self.assertFalse(alerts, 'alert(s)') + + if not self.skip_sanitizer: + sanitizer_errors = re.findall('.+Sanitizer.+', log) + + if sanitizer_errors: + self._print_path_to_log() + self.assertFalse(sanitizer_errors, 'sanitizer error(s)') + + if found: + print('skipped.') + + def waitforfiles(self, *files): + for i in range(50): + wait = False + ret = False + + for f in files: + if not os.path.exists(f): + wait = True + break + + if wait: + time.sleep(0.1) + + else: + ret = True + break + + return ret + + @staticmethod + def _parse_args(): + parser = argparse.ArgumentParser(add_help=False) + + parser.add_argument( + '-d', + '--detailed', + dest='detailed', + action='store_true', + help='Detailed output for tests', + ) + parser.add_argument( + '-l', + '--log', + dest='save_log', + action='store_true', + help='Save unit.log after the test execution', + ) + + return parser.parse_known_args() + + @staticmethod + def _set_args(args): + TestUnit.detailed = args.detailed + TestUnit.save_log = args.save_log + + if TestUnit.detailed: + fcntl.fcntl(sys.stdout.fileno(), fcntl.F_SETFL, 0) + + def _print_path_to_log(self): + print('Path to unit.log:\n' + self.testdir + '/unit.log') @@ -1,5 +1,5 @@ # Copyright (C) NGINX, Inc. -NXT_VERSION=1.8.0 -NXT_VERNUM=10800 +NXT_VERSION=1.9.0 +NXT_VERNUM=10900 |