summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrei Belov <defan@nginx.com>2019-03-01 18:30:09 +0300
committerAndrei Belov <defan@nginx.com>2019-03-01 18:30:09 +0300
commit3c3720cba7154bc168cbd00c74817626bb53e140 (patch)
treeda1500f7c6bd5e90ecf45299b6f4b19a29d521cd
parent315a864c27aa27a48c013c4a1ef67a099ffea894 (diff)
parentdf02b03824065389c73213b19736140442cf63bc (diff)
downloadunit-3c3720cba7154bc168cbd00c74817626bb53e140.tar.gz
unit-3c3720cba7154bc168cbd00c74817626bb53e140.tar.bz2
Merged with the default branch.
-rw-r--r--.hgtags1
-rw-r--r--CHANGES28
-rw-r--r--auto/make28
-rw-r--r--auto/modules/conf4
-rw-r--r--auto/modules/go2
-rw-r--r--auto/modules/java458
-rw-r--r--auto/modules/java_get_jar33
-rw-r--r--auto/modules/nodejs10
-rw-r--r--auto/sources1
-rwxr-xr-xconfigure1
-rw-r--r--docs/Makefile3
-rw-r--r--docs/changes.xml141
-rw-r--r--pkg/Makefile4
-rw-r--r--pkg/deb/Makefile57
-rw-r--r--pkg/deb/Makefile.jsc-common30
-rw-r--r--pkg/deb/Makefile.jsc1071
-rw-r--r--pkg/deb/Makefile.jsc1171
-rw-r--r--pkg/deb/Makefile.jsc871
-rw-r--r--pkg/deb/debian.module/control-noarch.in23
-rw-r--r--pkg/deb/debian.module/copyright.unit-jsc-common872
-rw-r--r--pkg/deb/debian.module/copyright.unit-jsc1026
-rw-r--r--pkg/deb/debian.module/copyright.unit-jsc1126
-rw-r--r--pkg/deb/debian.module/copyright.unit-jsc826
-rw-r--r--pkg/deb/debian.module/rules-noarch.in107
-rwxr-xr-xpkg/deb/debian.module/rules.in9
-rw-r--r--pkg/deb/debian.module/unit.example-jsc-app12
-rw-r--r--pkg/deb/debian.module/unit.example-jsc10-config15
-rw-r--r--pkg/deb/debian.module/unit.example-jsc11-config15
-rw-r--r--pkg/deb/debian.module/unit.example-jsc8-config15
-rw-r--r--pkg/deb/debian.module/unit.example-jsc9-config15
-rw-r--r--pkg/deb/debian/rules.in6
-rw-r--r--pkg/docker/Dockerfile.full2
-rw-r--r--pkg/docker/Dockerfile.go1.7-dev2
-rw-r--r--pkg/docker/Dockerfile.go1.8-dev2
-rw-r--r--pkg/docker/Dockerfile.minimal2
-rw-r--r--pkg/docker/Dockerfile.perl5.242
-rw-r--r--pkg/docker/Dockerfile.php7.02
-rw-r--r--pkg/docker/Dockerfile.python2.72
-rw-r--r--pkg/docker/Dockerfile.python3.52
-rw-r--r--pkg/docker/Dockerfile.ruby2.32
-rw-r--r--pkg/docker/Makefile6
-rw-r--r--pkg/npm/Makefile12
-rw-r--r--pkg/rpm/Makefile52
-rw-r--r--pkg/rpm/Makefile.go2
-rw-r--r--pkg/rpm/Makefile.jsc-common41
-rw-r--r--pkg/rpm/Makefile.jsc1169
-rw-r--r--pkg/rpm/Makefile.jsc869
-rw-r--r--pkg/rpm/Makefile.perl2
-rw-r--r--pkg/rpm/Makefile.php2
-rw-r--r--pkg/rpm/Makefile.python2
-rw-r--r--pkg/rpm/Makefile.python272
-rw-r--r--pkg/rpm/Makefile.python342
-rw-r--r--pkg/rpm/Makefile.python352
-rw-r--r--pkg/rpm/Makefile.python362
-rw-r--r--pkg/rpm/Makefile.python3757
-rw-r--r--pkg/rpm/Makefile.ruby2
-rw-r--r--pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc-common872
-rw-r--r--pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc1025
-rw-r--r--pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc1125
-rw-r--r--pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc825
-rw-r--r--pkg/rpm/rpmbuild/SOURCES/unit.example-jsc-app12
-rw-r--r--pkg/rpm/rpmbuild/SOURCES/unit.example-jsc11-config15
-rw-r--r--pkg/rpm/rpmbuild/SOURCES/unit.example-jsc8-config15
-rw-r--r--pkg/rpm/rpmbuild/SOURCES/unit.example-python37-config17
-rw-r--r--pkg/rpm/rpmbuild/SOURCES/unit.logrotate10
-rw-r--r--pkg/rpm/rpmbuild/SOURCES/unit.service20
-rw-r--r--pkg/rpm/rpmbuild/SOURCES/unit.sysconf2
-rw-r--r--pkg/rpm/unit.module.spec.in17
-rw-r--r--pkg/rpm/unit.spec.in49
-rw-r--r--src/go/unit/nxt_cgo_lib.c7
-rw-r--r--src/java/README.JSR-34016
-rw-r--r--src/java/nginx/unit/Context.java3502
-rw-r--r--src/java/nginx/unit/DynamicDispatcherRequest.java8
-rw-r--r--src/java/nginx/unit/DynamicPathRequest.java15
-rw-r--r--src/java/nginx/unit/ForwardRequestWrapper.java150
-rw-r--r--src/java/nginx/unit/HeaderNamesEnumeration.java42
-rw-r--r--src/java/nginx/unit/HeadersEnumeration.java40
-rw-r--r--src/java/nginx/unit/IncludeRequestWrapper.java88
-rw-r--r--src/java/nginx/unit/IncludeResponseWrapper.java117
-rw-r--r--src/java/nginx/unit/InitParams.java7
-rw-r--r--src/java/nginx/unit/InputStream.java90
-rw-r--r--src/java/nginx/unit/JspPropertyGroup.java169
-rw-r--r--src/java/nginx/unit/OutputStream.java68
-rw-r--r--src/java/nginx/unit/Request.java1123
-rw-r--r--src/java/nginx/unit/RequestAttrProxy.java40
-rw-r--r--src/java/nginx/unit/Response.java817
-rw-r--r--src/java/nginx/unit/Session.java251
-rw-r--r--src/java/nginx/unit/SessionAttrProxy.java40
-rw-r--r--src/java/nginx/unit/Taglib.java44
-rw-r--r--src/java/nginx/unit/UnitSessionCookieConfig.java110
-rw-r--r--src/java/nxt_jni.c175
-rw-r--r--src/java/nxt_jni.h55
-rw-r--r--src/java/nxt_jni_Context.c164
-rw-r--r--src/java/nxt_jni_Context.h23
-rw-r--r--src/java/nxt_jni_HeaderNamesEnumeration.c153
-rw-r--r--src/java/nxt_jni_HeaderNamesEnumeration.h19
-rw-r--r--src/java/nxt_jni_HeadersEnumeration.c148
-rw-r--r--src/java/nxt_jni_HeadersEnumeration.h19
-rw-r--r--src/java/nxt_jni_InputStream.c230
-rw-r--r--src/java/nxt_jni_InputStream.h15
-rw-r--r--src/java/nxt_jni_OutputStream.c236
-rw-r--r--src/java/nxt_jni_OutputStream.h17
-rw-r--r--src/java/nxt_jni_Request.c658
-rw-r--r--src/java/nxt_jni_Request.h18
-rw-r--r--src/java/nxt_jni_Response.c1105
-rw-r--r--src/java/nxt_jni_Response.h18
-rw-r--r--src/java/nxt_jni_Thread.c94
-rw-r--r--src/java/nxt_jni_Thread.h20
-rw-r--r--src/java/nxt_jni_URLClassLoader.c187
-rw-r--r--src/java/nxt_jni_URLClassLoader.h27
-rw-r--r--src/nodejs/unit-http/unit.h2
-rw-r--r--src/nxt_application.c14
-rw-r--r--src/nxt_application.h14
-rw-r--r--src/nxt_conf.c23
-rw-r--r--src/nxt_conf.h3
-rw-r--r--src/nxt_conf_validation.c319
-rw-r--r--src/nxt_conn.h3
-rw-r--r--src/nxt_conn_read.c7
-rw-r--r--src/nxt_conn_write.c5
-rw-r--r--src/nxt_event_conn_job_sendfile.c6
-rw-r--r--src/nxt_external.c1
-rw-r--r--src/nxt_h1proto.c10
-rw-r--r--src/nxt_http.h33
-rw-r--r--src/nxt_http_request.c193
-rw-r--r--src/nxt_http_route.c849
-rw-r--r--src/nxt_java.c462
-rw-r--r--src/nxt_main.h5
-rw-r--r--src/nxt_main_process.c66
-rw-r--r--src/nxt_openssl.c6
-rw-r--r--src/nxt_php_sapi.c38
-rw-r--r--src/nxt_port_rpc.c2
-rw-r--r--src/nxt_port_socket.c11
-rw-r--r--src/nxt_python_wsgi.c36
-rw-r--r--src/nxt_router.c126
-rw-r--r--src/nxt_router.h10
-rw-r--r--src/nxt_string.c13
-rw-r--r--src/nxt_string.h2
-rw-r--r--src/nxt_timer.c11
-rw-r--r--src/nxt_unit.c95
-rw-r--r--src/nxt_unit.h2
-rw-r--r--src/nxt_unit_field.h1
-rw-r--r--src/nxt_unit_request.h3
-rw-r--r--src/perl/nxt_perl_psgi.c341
-rw-r--r--src/ruby/nxt_ruby.c35
-rw-r--r--test/java/content_type/app.java89
-rw-r--r--test/java/cookies/app.java30
-rw-r--r--test/java/empty/app.java18
-rw-r--r--test/java/filter/app.java54
-rw-r--r--test/java/forward/app.java138
-rw-r--r--test/java/forward/index.html1
-rw-r--r--test/java/forward/web.xml38
-rw-r--r--test/java/get_header/app.java21
-rw-r--r--test/java/get_header_names/app.java27
-rw-r--r--test/java/get_headers/app.java27
-rw-r--r--test/java/get_params/app.java50
-rw-r--r--test/java/header/app.java34
-rw-r--r--test/java/header_date/app.java22
-rw-r--r--test/java/header_int/app.java22
-rw-r--r--test/java/include/app.java136
-rw-r--r--test/java/include/index.html1
-rw-r--r--test/java/include/web.xml37
-rw-r--r--test/java/jsp/index.jsp2
-rw-r--r--test/java/mirror/app.java37
-rw-r--r--test/java/path_translation/app.java56
-rw-r--r--test/java/path_translation/index.html1
-rw-r--r--test/java/post_params/app.java22
-rw-r--r--test/java/query_string/app.java20
-rw-r--r--test/java/request_listeners/app.java79
-rw-r--r--test/java/session/app.java30
-rw-r--r--test/java/session_inactive/app.java27
-rw-r--r--test/java/session_invalidate/app.java23
-rw-r--r--test/java/session_listeners/app.java80
-rw-r--r--test/java/session_listeners/web.xml14
-rw-r--r--test/java/url_pattern/app.java39
-rw-r--r--test/java/url_pattern/web.xml75
-rw-r--r--test/java/welcome_files/app.java67
-rw-r--r--test/java/welcome_files/dir1/index.txt1
-rw-r--r--test/java/welcome_files/dir2/default.jsp3
-rw-r--r--test/java/welcome_files/dir2/index.html1
-rw-r--r--test/java/welcome_files/dir3/index.txt1
-rw-r--r--test/java/welcome_files/dir4/index.html1
-rw-r--r--test/java/welcome_files/index.htm1
-rw-r--r--test/java/welcome_files/web.xml27
-rwxr-xr-xtest/node/write_before_write_head/app.js1
-rw-r--r--test/perl/body_io_fake/IOFake.pm33
-rw-r--r--test/perl/body_io_fake/psgi.pl11
-rw-r--r--test/perl/delayed_response/psgi.pl10
-rw-r--r--test/perl/streaming_body/psgi.pl13
-rw-r--r--test/php/date_time/index.php3
-rw-r--r--test/php/highlight_file_exec/index.php4
-rw-r--r--test/php/query_string/index.php4
-rw-r--r--test/php/time_exec/index.php4
-rw-r--r--test/python/host/wsgi.py7
-rw-r--r--test/test_access_log.py8
-rw-r--r--test/test_configuration.py49
-rw-r--r--test/test_go_application.py25
-rw-r--r--test/test_http_header.py241
-rw-r--r--test/test_java_application.py753
-rw-r--r--test/test_node_application.py54
-rw-r--r--test/test_perl_application.py62
-rw-r--r--test/test_php_application.py157
-rw-r--r--test/test_python_application.py65
-rw-r--r--test/test_python_procman.py2
-rw-r--r--test/test_routing.py458
-rw-r--r--test/test_ruby_application.py32
-rw-r--r--test/test_settings.py31
-rw-r--r--test/test_tls.py44
-rw-r--r--test/unit.py113
-rw-r--r--version5
209 files changed, 19382 insertions, 658 deletions
diff --git a/.hgtags b/.hgtags
index cfb28e33..3f559140 100644
--- a/.hgtags
+++ b/.hgtags
@@ -19,3 +19,4 @@ d411e7fdee9e03036adb652f8d9f4c45a420bdd5 1.6
abb8cfb421f608df1c23f5c333c5f049a79a681a 1.7-1
0f04ef991fbc1dadbc590ab7fb229d4f3d6357bc 1.7.1
fe0d5eb09b66e77a2b66455faa51d3fa04146d3d 1.7.1-1
+0a18a14d169f156f8e2daca35aa86d5a6dd9b1ae 1.8.0
diff --git a/CHANGES b/CHANGES
index 72a21c3f..5398910e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,4 +1,32 @@
+Changes with Unit 1.8.0 01 Mar 2019
+
+ *) Change: now three numbers are always used for versioning: major,
+ minor, and patch versions.
+
+ *) Change: now QUERY_STRING is always defined even if the request does
+ not include the query component.
+
+ *) Feature: basic internal request routing by Host, URI, and method.
+
+ *) Feature: experimental support for Java Servlet Containers.
+
+ *) Bugfix: segmentation fault might have occurred in the router process.
+
+ *) Bugfix: various potential memory leaks.
+
+ *) Bugfix: TLS connections might have stalled.
+
+ *) Bugfix: some Perl applications might have failed to send the response
+ body.
+
+ *) Bugfix: some compilers with specific flags might have produced
+ non-functioning builds; the bug had appeared in 1.5.
+
+ *) Bugfix: Node.js package had wrong version number when installed from
+ sources.
+
+
Changes with Unit 1.7.1 07 Feb 2019
*) Security: a heap memory buffer overflow might have been caused in the
diff --git a/auto/make b/auto/make
index 4f716b93..1eee2a78 100644
--- a/auto/make
+++ b/auto/make
@@ -67,6 +67,22 @@ done
$echo >> $NXT_MAKEFILE
+# The version file.
+
+cat << END >> $NXT_MAKEFILE
+
+include version
+
+$NXT_VERSION_H: version
+ $echo '#define NXT_VERSION "\$(NXT_VERSION)"' > $NXT_VERSION_H
+ $echo '#define NXT_VERNUM \$(NXT_VERNUM)' >> $NXT_VERSION_H
+
+$NXT_BUILD_DIR/src/nxt_unit.o: $NXT_VERSION_H
+$NXT_BUILD_DIR/src/nxt_lib.o: $NXT_VERSION_H
+
+END
+
+
# Shared and static library.
cat << END >> $NXT_MAKEFILE
@@ -82,14 +98,6 @@ $NXT_BUILD_DIR/$NXT_LIB_STATIC: \$(NXT_LIB_OBJS)
$NXT_STATIC_LINK $NXT_BUILD_DIR/$NXT_LIB_STATIC \\
\$(NXT_LIB_OBJS)
-$NXT_BUILD_DIR/nxt_unit_version.h: src/nxt_main.h
- $echo -n '#define NXT_UNIT_VERNUM ' > $NXT_BUILD_DIR/nxt_unit_version.h
- grep 'define NXT_VERNUM' src/nxt_main.h \\
- | sed -e 's/[^0-9]//g' >> $NXT_BUILD_DIR/nxt_unit_version.h
-
-$NXT_BUILD_DIR/src/nxt_unit.o: $NXT_BUILD_DIR/nxt_unit_version.h
-$NXT_BUILD_DIR/src/nxt_lib.o: $NXT_BUILD_DIR/nxt_unit_version.h
-
$NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC: \$(NXT_LIB_UNIT_OBJS)
$NXT_STATIC_LINK $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC \\
\$(NXT_LIB_UNIT_OBJS)
@@ -296,7 +304,7 @@ libunit-install: $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC
install -d \$(DESTDIR)$NXT_INCDIR
install -p -m u=rw,go=r src/nxt_unit.h src/nxt_unit_field.h \
src/nxt_unit_request.h src/nxt_unit_response.h src/nxt_unit_sptr.h \
- src/nxt_unit_typedefs.h $NXT_BUILD_DIR/nxt_unit_version.h \
+ src/nxt_unit_typedefs.h $NXT_BUILD_DIR/nxt_version.h \
\$(DESTDIR)$NXT_INCDIR/
libunit-uninstall:
@@ -308,7 +316,7 @@ libunit-uninstall:
\$(DESTDIR)$NXT_INCDIR/nxt_unit_response.h \
\$(DESTDIR)$NXT_INCDIR/nxt_unit_sptr.h \
\$(DESTDIR)$NXT_INCDIR/nxt_unit_typedefs.h \
- \$(DESTDIR)$NXT_INCDIR/nxt_unit_version.h
+ \$(DESTDIR)$NXT_INCDIR/nxt_version.h
@rmdir -p \$(DESTDIR)$NXT_INCDIR 2>/dev/null || true
END
diff --git a/auto/modules/conf b/auto/modules/conf
index 409b4bea..7e004703 100644
--- a/auto/modules/conf
+++ b/auto/modules/conf
@@ -29,6 +29,10 @@ case "$nxt_module" in
. auto/modules/nodejs
;;
+ java)
+ . auto/modules/java
+ ;;
+
*)
echo
echo $0: error: invalid module \"$nxt_module\".
diff --git a/auto/modules/go b/auto/modules/go
index e9b2321d..62c3743f 100644
--- a/auto/modules/go
+++ b/auto/modules/go
@@ -103,7 +103,7 @@ ${NXT_GO}:
${NXT_GO}-install: ${NXT_GO}-install-build
-${NXT_GO}-install-src: ${NXT_BUILD_DIR}/nxt_unit_version.h
+${NXT_GO}-install-src: ${NXT_VERSION_H}
install -d \$(DESTDIR)\$(NXT_GO_DST)/src/nginx/unit
install -p -m644 ./src/*.h ./build/*.h ./src/go/unit/* \
./src/nxt_unit.c ./src/nxt_lvlhsh.c ./src/nxt_murmur_hash.c \
diff --git a/auto/modules/java b/auto/modules/java
new file mode 100644
index 00000000..27030da1
--- /dev/null
+++ b/auto/modules/java
@@ -0,0 +1,458 @@
+
+# Copyright (C) NGINX, Inc.
+
+
+shift
+
+NXT_JAVA_HOME=${JAVA_HOME-}
+NXT_JAR_REPO=http://central.maven.org/maven2/
+NXT_JAR_LOCAL_REPO=$HOME/.m2/repository/
+
+for nxt_option; do
+
+ case "$nxt_option" in
+ -*=*) value=`echo "$nxt_option" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;;
+ *) value="" ;;
+ esac
+
+ case "$nxt_option" in
+
+ --module=*) NXT_JAVA_MODULE="$value" ;;
+ --home=*) NXT_JAVA_HOME="$value" ;;
+ --lib-path=*) NXT_JAVA_LIB_PATH="$value" ;;
+ --repo=*) NXT_JAR_REPO="$value" ;;
+ --local-repo=*) NXT_JAR_LOCAL_REPO="$value" ;;
+ --jars=*) NXT_JARS="$value" ;;
+
+ --help)
+ cat << END
+
+ --module=NAME set unit Java module name
+ --home=DIR set Java home directory
+ --lib-path=DIRECTORY set directory path to libjvm.so library
+ --repo=URL set Maven remote repository URL
+ default: "$NXT_JAR_REPO"
+ --local-repo=DIR set local repository directory
+ default: "$NXT_JAR_LOCAL_REPO"
+ --jars=DIR set jars install/search directory
+
+END
+ exit 0
+ ;;
+
+ *)
+ echo
+ echo $0: error: invalid Java option \"$nxt_option\"
+ echo
+ exit 1
+ ;;
+ esac
+
+done
+
+
+if [ ! -f $NXT_AUTOCONF_DATA ]; then
+ echo
+ echo Please run common $0 before configuring module \"$nxt_module\".
+ echo
+ exit 1
+fi
+
+. $NXT_AUTOCONF_DATA
+
+
+NXT_JARS=${NXT_JARS=$NXT_MODULES}
+NXT_JAVA_MODULE=${NXT_JAVA_MODULE=java}
+NXT_JAVA_LIB_PATH=${NXT_JAVA_LIB_PATH=}
+
+$echo "configuring Java module"
+$echo "configuring Java module ..." >> $NXT_AUTOCONF_ERR
+
+if [ -n "${NXT_JAVA_HOME}" ]; then
+ $echo "using java.home ${NXT_JAVA_HOME}"
+ $echo "using java.home ${NXT_JAVA_HOME}" >> $NXT_AUTOCONF_ERR
+
+ if [ ! -d "${NXT_JAVA_HOME}" ]; then
+ $echo
+ $echo $0: error: Java home directory not found.
+ $echo
+ exit 1
+ fi
+
+ NXT_JAVA="${NXT_JAVA_HOME}/bin/java"
+
+else
+ $echo -n "checking for java executable"
+ $echo "checking for java executable ..." >> $NXT_AUTOCONF_ERR
+
+ NXT_JAVA=`which java || :`
+ if [ -z "$NXT_JAVA" -o ! -x "$NXT_JAVA" ]; then
+ $echo
+ $echo $0: error: java executable not found.
+ $echo
+ exit 1
+ fi
+
+ $echo " found $NXT_JAVA"
+ $echo "found $NXT_JAVA" >> $NXT_AUTOCONF_ERR
+
+ "$NXT_JAVA" -version
+
+ $echo -n "checking java.home"
+ $echo "checking java.home ..." >> $NXT_AUTOCONF_ERR
+
+ NXT_JAVA_HOME=`$NXT_JAVA -XshowSettings 2>&1 | grep -F -e java.home | sed -e 's/^.*= //'`
+ if [ -z "$NXT_JAVA_HOME" ]; then
+ $echo
+ $echo $0: error: java.home not found.
+ $echo
+ exit 1
+ fi
+
+ $echo " $NXT_JAVA_HOME"
+ $echo "got java.home $NXT_JAVA_HOME" >> $NXT_AUTOCONF_ERR
+
+ if [ ! -x "${NXT_JAVA_HOME}/bin/javac" ]; then
+ NXT_JAVA_HOME_=${NXT_JAVA_HOME%/jre}
+ if [ -x "${NXT_JAVA_HOME_}/bin/javac" ]; then
+ $echo "adjust java.home $NXT_JAVA_HOME_"
+ $echo "adjust java.home $NXT_JAVA_HOME_" >> $NXT_AUTOCONF_ERR
+
+ NXT_JAVA_HOME="$NXT_JAVA_HOME_"
+ fi
+ fi
+fi
+
+NXT_JAVAC="${NXT_JAVA_HOME}/bin/javac"
+
+if [ ! -x "$NXT_JAVAC" ]; then
+ $echo
+ $echo $0: error: javac not found.
+ $echo
+ exit 1
+fi
+
+NXT_JAVA_INCLUDE="-I${NXT_JAVA_HOME}/include"
+
+case "$NXT_SYSTEM" in
+ Linux)
+ NXT_JAVA_INCLUDE="${NXT_JAVA_INCLUDE} -I${NXT_JAVA_HOME}/include/linux"
+ ;;
+ Darwin)
+ NXT_JAVA_INCLUDE="${NXT_JAVA_INCLUDE} -I${NXT_JAVA_HOME}/include/darwin"
+ ;;
+ FreeBSD)
+ NXT_JAVA_INCLUDE="${NXT_JAVA_INCLUDE} -I${NXT_JAVA_HOME}/include/freebsd"
+ ;;
+esac
+
+if [ -z "$NXT_JAVA_LIB_PATH" ]; then
+ $echo -n "checking library path"
+ $echo "checking library path ..." >> $NXT_AUTOCONF_ERR
+
+ if [ ! -x "$NXT_JAVA" ]; then
+ $echo
+ $echo $0: error: java executable not found.
+ $echo
+ exit 1
+ fi
+
+ NXT_JAVA_LIB_PATH=`$NXT_JAVA -XshowSettings 2>&1 | grep -F -e sun.boot.library.path | sed -e 's/^.*= //'`
+
+ if [ -z "$NXT_JAVA_LIB_PATH" ]; then
+ $echo
+ $echo $0: error: library path not found.
+ $echo
+ exit 1
+ fi
+
+ NXT_JAVA_LIB_PATH="${NXT_JAVA_LIB_PATH}/server"
+
+ $echo " $NXT_JAVA_LIB_PATH"
+ $echo "got library path $NXT_JAVA_LIB_PATH" >> $NXT_AUTOCONF_ERR
+fi
+
+NXT_JAVA_LDFLAGS="-L${NXT_JAVA_LIB_PATH} -Wl,-rpath ${NXT_JAVA_LIB_PATH} -ljvm"
+
+
+nxt_found=no
+
+nxt_feature="JNI"
+nxt_feature_name=""
+nxt_feature_run=no
+nxt_feature_incs="${NXT_JAVA_INCLUDE}"
+nxt_feature_libs="${NXT_JAVA_LDFLAGS}"
+nxt_feature_test="
+ #include <jni.h>
+
+ int main() {
+ JNI_CreateJavaVM(NULL, NULL, NULL);
+ return 0;
+ }"
+
+. auto/feature
+
+
+if [ $nxt_found = no ]; then
+ $echo
+ $echo $0: error: no JNI found.
+ $echo
+ exit 1;
+fi
+
+NXT_JAVA_VERSION=`$NXT_JAVAC -version 2>&1`
+NXT_JAVA_VERSION=${NXT_JAVA_VERSION#javac }
+NXT_JAVA_INCLUDE="$NXT_JAVA_INCLUDE -I$NXT_BUILD_DIR/$NXT_JAVA_MODULE -DNXT_JAVA_VERSION=$NXT_JAVA_VERSION"
+
+if grep ^$NXT_JAVA_MODULE: $NXT_MAKEFILE 2>&1 > /dev/null; then
+ $echo
+ $echo $0: error: duplicate \"$NXT_JAVA_MODULE\" module configured.
+ $echo
+ exit 1;
+fi
+
+. ./version
+
+NXT_UNIT_JAR=nginx-unit-jsc-${NXT_JAVA_MODULE}-$NXT_VERSION.jar
+
+NXT_JAVA_BUILD_CP=$NXT_BUILD_DIR/$NXT_JAVA_MODULE
+NXT_JAVA_INSTALL_JARS=
+NXT_JAVA_UNINSTALL_JARS=
+
+NXT_JAVA_JARS=$NXT_BUILD_DIR/$NXT_JAVA_MODULE/nxt_jars.h
+mkdir -p $NXT_BUILD_DIR/$NXT_JAVA_MODULE
+
+cat << END > $NXT_JAVA_JARS
+#ifndef _NXT_JAVA_JARS_INCLUDED_
+#define _NXT_JAVA_JARS_INCLUDED_
+
+#define NXT_JARS "$NXT_JARS"
+
+static const char *nxt_java_system_jars[] = {
+END
+
+NXT_TOMCAT_VERSION=9.0.13
+
+NXT_JAR_VERSION=$NXT_TOMCAT_VERSION
+
+NXT_JAR_NAME=tomcat-servlet-api
+NXT_JAR_NAMESPACE=org/apache/tomcat/
+. auto/modules/java_get_jar
+
+NXT_JAR_NAME=tomcat-el-api
+. auto/modules/java_get_jar
+
+NXT_JAR_NAME=tomcat-jsp-api
+. auto/modules/java_get_jar
+
+NXT_JAR_NAME=tomcat-jasper
+. auto/modules/java_get_jar
+
+NXT_JAR_NAME=tomcat-jasper-el
+. auto/modules/java_get_jar
+
+NXT_JAR_NAME=tomcat-juli
+. auto/modules/java_get_jar
+
+NXT_JAR_NAME=tomcat-api
+. auto/modules/java_get_jar
+
+NXT_JAR_NAME=tomcat-util-scan
+. auto/modules/java_get_jar
+
+NXT_JAR_NAME=tomcat-util
+. auto/modules/java_get_jar
+
+NXT_JAR_NAME=ecj
+NXT_JAR_VERSION=3.13.102
+NXT_JAR_NAMESPACE=org/eclipse/jdt/
+. auto/modules/java_get_jar
+
+cat << END >> $NXT_JAVA_JARS
+ NULL
+};
+
+static const char *nxt_java_unit_jars[] = {
+ "$NXT_UNIT_JAR",
+END
+
+NXT_JAR_VERSION=9.4.12.v20180830
+NXT_JAR_NAMESPACE=org/eclipse/jetty/
+
+NXT_JAR_NAME=jetty-util
+. auto/modules/java_get_jar
+
+NXT_JAR_NAME=jetty-server
+. auto/modules/java_get_jar
+
+NXT_JAR_NAME=jetty-http
+. auto/modules/java_get_jar
+
+NXT_JAR_NAME=classgraph
+NXT_JAR_VERSION=4.4.11
+NXT_JAR_NAMESPACE=io/github/classgraph/
+. auto/modules/java_get_jar
+
+cat << END >> $NXT_JAVA_JARS
+ NULL
+};
+
+#endif /* _NXT_JAVA_JARS_INCLUDED_ */
+END
+
+$echo " + Java module: ${NXT_JAVA_MODULE}.unit.so"
+
+. auto/cc/deps
+
+$echo >> $NXT_MAKEFILE
+
+NXT_JAVA_MODULE_SRCS=" \
+ src/nxt_java.c \
+ src/java/nxt_jni.c \
+ src/java/nxt_jni_Context.c \
+ src/java/nxt_jni_HeaderNamesEnumeration.c \
+ src/java/nxt_jni_HeadersEnumeration.c \
+ src/java/nxt_jni_InputStream.c \
+ src/java/nxt_jni_OutputStream.c \
+ src/java/nxt_jni_Request.c \
+ src/java/nxt_jni_Response.c \
+ src/java/nxt_jni_Thread.c \
+ src/java/nxt_jni_URLClassLoader.c \
+"
+
+# The Java module object files.
+
+nxt_objs=$NXT_BUILD_DIR/src/nxt_unit.o
+
+for nxt_src in $NXT_JAVA_MODULE_SRCS; do
+
+ nxt_obj=${nxt_src%.c}-$NXT_JAVA_MODULE.o
+ nxt_dep=${nxt_src%.c}-$NXT_JAVA_MODULE.dep
+ nxt_dep_flags=`nxt_gen_dep_flags`
+ nxt_dep_post=`nxt_gen_dep_post`
+ nxt_objs="$nxt_objs $NXT_BUILD_DIR/$nxt_obj"
+
+ cat << END >> $NXT_MAKEFILE
+
+$NXT_BUILD_DIR/$nxt_obj: $nxt_src
+ mkdir -p $NXT_BUILD_DIR/src/java
+ \$(CC) -c \$(CFLAGS) \$(NXT_INCS) $NXT_JAVA_INCLUDE \\
+ $nxt_dep_flags \\
+ -o $NXT_BUILD_DIR/$nxt_obj $nxt_src
+ $nxt_dep_post
+
+-include $NXT_BUILD_DIR/$nxt_dep
+
+END
+
+done
+
+NXT_JAVA_SRCS=" \
+ src/java/nginx/unit/Context.java \
+ src/java/nginx/unit/DynamicDispatcherRequest.java \
+ src/java/nginx/unit/DynamicPathRequest.java \
+ src/java/nginx/unit/ForwardRequestWrapper.java \
+ src/java/nginx/unit/HeaderNamesEnumeration.java \
+ src/java/nginx/unit/HeadersEnumeration.java \
+ src/java/nginx/unit/IncludeRequestWrapper.java \
+ src/java/nginx/unit/IncludeResponseWrapper.java \
+ src/java/nginx/unit/InitParams.java \
+ src/java/nginx/unit/InputStream.java \
+ src/java/nginx/unit/JspPropertyGroup.java \
+ src/java/nginx/unit/OutputStream.java \
+ src/java/nginx/unit/Request.java \
+ src/java/nginx/unit/RequestAttrProxy.java \
+ src/java/nginx/unit/Response.java \
+ src/java/nginx/unit/Session.java \
+ src/java/nginx/unit/SessionAttrProxy.java \
+ src/java/nginx/unit/Taglib.java \
+ src/java/nginx/unit/UnitSessionCookieConfig.java \
+"
+
+cat << END >> $NXT_MAKEFILE
+
+.PHONY: ${NXT_JAVA_MODULE}
+.PHONY: ${NXT_JAVA_MODULE}-install
+.PHONY: ${NXT_JAVA_MODULE}-uninstall
+
+all: ${NXT_JAVA_MODULE}
+
+${NXT_JAVA_MODULE}: $NXT_BUILD_DIR/${NXT_JAVA_MODULE}.unit.so \
+ $NXT_BUILD_DIR/$NXT_UNIT_JAR
+
+$NXT_BUILD_DIR/${NXT_JAVA_MODULE}.unit.so: $nxt_objs
+ \$(NXT_MODULE_LINK) -o $NXT_BUILD_DIR/${NXT_JAVA_MODULE}.unit.so \\
+ $nxt_objs $NXT_JAVA_LDFLAGS $NXT_LD_OPT
+
+
+install: ${NXT_JAVA_MODULE}-install
+
+${NXT_JAVA_MODULE}-install: $NXT_BUILD_DIR/${NXT_JAVA_MODULE}.unit.so \\
+ $NXT_BUILD_DIR/$NXT_UNIT_JAR java-shared-install
+ install -d \$(DESTDIR)$NXT_MODULES
+ install -p $NXT_BUILD_DIR/${NXT_JAVA_MODULE}.unit.so \\
+ \$(DESTDIR)$NXT_MODULES/
+ install -d \$(DESTDIR)$NXT_JARS
+ install -p -m 0644 $NXT_BUILD_DIR/$NXT_UNIT_JAR \$(DESTDIR)$NXT_JARS/
+
+
+uninstall: ${NXT_JAVA_MODULE}-uninstall
+
+${NXT_JAVA_MODULE}-uninstall: java-shared-uninstall
+ rm -f \$(DESTDIR)$NXT_MODULES/${NXT_JAVA_MODULE}.unit.so
+ @rmdir -p \$(DESTDIR)$NXT_MODULES 2>/dev/null || true
+ rm -f \$(DESTDIR)$NXT_JARS/$NXT_UNIT_JAR
+ @rmdir -p \$(DESTDIR)$NXT_JARS 2>/dev/null || true
+
+END
+
+if ! grep ^$NXT_BUILD_DIR/$NXT_UNIT_JAR: $NXT_MAKEFILE 2>&1 > /dev/null; then
+
+ cat << END >> $NXT_MAKEFILE
+
+.INTERMEDIATE: $NXT_BUILD_DIR/$NXT_JAVA_MODULE/.classes
+
+NXT_JAVA_SRCS = $NXT_JAVA_SRCS
+
+$NXT_BUILD_DIR/$NXT_JAVA_MODULE/.classes: \$(NXT_JAVA_SRCS)
+ rm -rf $NXT_BUILD_DIR/$NXT_JAVA_MODULE/nginx
+ $NXT_JAVAC -d $NXT_BUILD_DIR/$NXT_JAVA_MODULE -cp $NXT_JAVA_BUILD_CP \\
+ \$(NXT_JAVA_SRCS)
+
+$NXT_BUILD_DIR/$NXT_JAVA_MODULE/META-INF/LICENSE: LICENSE
+ mkdir -p $NXT_BUILD_DIR/$NXT_JAVA_MODULE/META-INF
+ cp -p LICENSE \$@
+
+$NXT_BUILD_DIR/$NXT_JAVA_MODULE/META-INF/NOTICE: NOTICE
+ mkdir -p $NXT_BUILD_DIR/$NXT_JAVA_MODULE/META-INF
+ cp -p NOTICE \$@
+
+
+$NXT_BUILD_DIR/$NXT_UNIT_JAR: $NXT_BUILD_DIR/$NXT_JAVA_MODULE/.classes \\
+ $NXT_BUILD_DIR/$NXT_JAVA_MODULE/META-INF/LICENSE \\
+ $NXT_BUILD_DIR/$NXT_JAVA_MODULE/META-INF/NOTICE
+ $NXT_JAVA_HOME/bin/jar c -C $NXT_BUILD_DIR/$NXT_JAVA_MODULE META-INF \\
+ -C $NXT_BUILD_DIR/$NXT_JAVA_MODULE nginx/unit > \$@
+
+END
+
+fi
+
+if ! grep ^java-shared-install: $NXT_MAKEFILE 2>&1 > /dev/null; then
+
+ cat << END >> $NXT_MAKEFILE
+
+.PHONY: java-shared-install
+.PHONY: java-shared-uninstall
+
+java-shared-install: $NXT_JAVA_INSTALL_JARS
+ install -d \$(DESTDIR)$NXT_JARS
+ install -p -m 0644 $NXT_JAVA_INSTALL_JARS \$(DESTDIR)$NXT_JARS/
+
+java-shared-uninstall:
+ rm -f $NXT_JAVA_UNINSTALL_JARS
+ @rmdir -p \$(DESTDIR)$NXT_JARS 2>/dev/null || true
+
+END
+
+fi
diff --git a/auto/modules/java_get_jar b/auto/modules/java_get_jar
new file mode 100644
index 00000000..c61d0a53
--- /dev/null
+++ b/auto/modules/java_get_jar
@@ -0,0 +1,33 @@
+
+# Copyright (C) NGINX, Inc.
+
+# NXT_JAR_NAME=
+# NXT_JAR_VERSION=
+# NXT_JAR_NAMESPACE=
+# NXT_JAR_REPO=http://central.maven.org/maven2/
+# NXT_JAR_LOCAL_REPO=$HOME/.m2/repository/
+
+NXT_JAR_FILE=${NXT_JAR_NAME}-${NXT_JAR_VERSION}.jar
+NXT_JAR_LOCAL="${NXT_JAR_LOCAL_REPO}${NXT_JAR_NAMESPACE}${NXT_JAR_NAME}/${NXT_JAR_VERSION}/${NXT_JAR_FILE}"
+NXT_JAR_URL=${NXT_JAR_REPO}${NXT_JAR_NAMESPACE}${NXT_JAR_NAME}/${NXT_JAR_VERSION}/${NXT_JAR_FILE}
+
+if [ ! -f "$NXT_BUILD_DIR/$NXT_JAR_FILE" ]; then
+ if [ ! -f "$NXT_JAR_LOCAL" ]; then
+ $echo "getting remote $NXT_JAR_FILE ... "
+ $echo "getting remote $NXT_JAR_FILE ..." >> $NXT_AUTOCONF_ERR
+
+ mkdir -p "${NXT_JAR_LOCAL_REPO}${NXT_JAR_NAMESPACE}${NXT_JAR_NAME}/${NXT_JAR_VERSION}/"
+ curl --progress-bar "$NXT_JAR_URL" -o "$NXT_JAR_LOCAL"
+ else
+ $echo "getting local $NXT_JAR_FILE"
+ $echo "getting local $NXT_JAR_FILE ..." >> $NXT_AUTOCONF_ERR
+ fi
+
+ cp "$NXT_JAR_LOCAL" "$NXT_BUILD_DIR/$NXT_JAR_FILE"
+fi
+
+NXT_JAVA_BUILD_CP="${NXT_JAVA_BUILD_CP}:$NXT_BUILD_DIR/$NXT_JAR_FILE"
+NXT_JAVA_INSTALL_JARS="$NXT_JAVA_INSTALL_JARS $NXT_BUILD_DIR/$NXT_JAR_FILE"
+NXT_JAVA_UNINSTALL_JARS="$NXT_JAVA_UNINSTALL_JARS \$(DESTDIR)$NXT_JARS/$NXT_JAR_FILE"
+
+$echo " \"$NXT_JAR_FILE\"," >> $NXT_JAVA_JARS
diff --git a/auto/modules/nodejs b/auto/modules/nodejs
index e0208f5d..edaf99ac 100644
--- a/auto/modules/nodejs
+++ b/auto/modules/nodejs
@@ -145,7 +145,6 @@ cat << END >> $NXT_MAKEFILE
.PHONY: ${NXT_NODE}-build
.PHONY: ${NXT_NODE}-publish
-
${NXT_NODE}: ${NXT_NODE}-copy $NXT_BUILD_DIR/$NXT_LIB_UNIT_STATIC
${NXT_NODE_EXPORTS} && \\
cd ${NXT_NODE_TMP} && ${NXT_NODE_GYP} configure build clean
@@ -154,13 +153,14 @@ ${NXT_NODE}-copy: ${NXT_NODE_VERSION_FILE}
mkdir -p ${NXT_BUILD_DIR}/src/
cp -rp src/nodejs/* ${NXT_BUILD_DIR}/src/${NXT_NODE}
-${NXT_NODE_VERSION_FILE}: src/nxt_main.h
+${NXT_NODE_VERSION_FILE}: ${NXT_VERSION_H}
mkdir -p ${NXT_NODE_TMP}
- $echo -n '#define NXT_NODE_VERNUM ' > $NXT_NODE_VERSION_FILE
- grep 'define NXT_VERNUM' src/nxt_main.h \\
- | sed -e 's/[^0-9]//g' >> $NXT_NODE_VERSION_FILE
+ $echo '#define NXT_NODE_VERNUM \$(NXT_VERNUM)' > $NXT_NODE_VERSION_FILE
${NXT_NODE_TARBALL}: ${NXT_NODE}-copy
+ sed -e 's/"version"\s*:.*/"version": "\$(NXT_VERSION)",/' \
+ ${NXT_NODE_TMP}/package.json > ${NXT_NODE_TMP}/package.json.tmp
+ mv ${NXT_NODE_TMP}/package.json.tmp ${NXT_NODE_TMP}/package.json
tar -zcvf ${NXT_NODE_TARBALL} -C ${NXT_NODE_TMP} .
diff --git a/auto/sources b/auto/sources
index bc979fe2..4c4fd742 100644
--- a/auto/sources
+++ b/auto/sources
@@ -82,6 +82,7 @@ NXT_LIB_SRCS=" \
src/nxt_http_request.c \
src/nxt_http_response.c \
src/nxt_http_error.c \
+ src/nxt_http_route.c \
src/nxt_application.c \
src/nxt_external.c \
src/nxt_port_hash.c \
diff --git a/configure b/configure
index 13f2db6b..335a8c88 100755
--- a/configure
+++ b/configure
@@ -25,6 +25,7 @@ NXT_AUTOTEST=$NXT_BUILD_DIR/autotest
NXT_AUTOCONF_ERR=$NXT_BUILD_DIR/autoconf.err
NXT_AUTOCONF_DATA=$NXT_BUILD_DIR/autoconf.data
NXT_AUTO_CONFIG_H=$NXT_BUILD_DIR/nxt_auto_config.h
+NXT_VERSION_H=$NXT_BUILD_DIR/nxt_version.h
NXT_MAKEFILE=$NXT_BUILD_DIR/Makefile
CC=${CC:-cc}
diff --git a/docs/Makefile b/docs/Makefile
index 1f0ba451..ef9e596e 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -11,7 +11,8 @@ PACKAGES= unit \
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-ruby \
+ unit-jsc-common unit-jsc8 unit-jsc10 unit-jsc11
all: changes changelogs
diff --git a/docs/changes.xml b/docs/changes.xml
index ea4298ba..e4b8158d 100644
--- a/docs/changes.xml
+++ b/docs/changes.xml
@@ -5,6 +5,147 @@
<change_log title="unit">
+<changes apply="unit-jsc-common" ver="1.8.0" rev="1"
+ date="2019-03-01" time="18:00:00 +0300"
+ packager="Andrei Belov &lt;defan@nginx.com&gt;">
+
+<change>
+<para>
+Initial release of Java common packages for NGINX Unit.
+</para>
+</change>
+
+</changes>
+
+
+<changes apply="unit-jsc8" ver="1.8.0" rev="1"
+ date="2019-03-01" time="18:00:00 +0300"
+ packager="Andrei Belov &lt;defan@nginx.com&gt;">
+
+<change>
+<para>
+Initial release of Java 8 module for NGINX Unit.
+</para>
+</change>
+
+</changes>
+
+
+<changes apply="unit-jsc10" ver="1.8.0" rev="1"
+ date="2019-03-01" time="18:00:00 +0300"
+ packager="Andrei Belov &lt;defan@nginx.com&gt;">
+
+<change>
+<para>
+Initial release of Java 10 module for NGINX Unit.
+</para>
+</change>
+
+</changes>
+
+
+<changes apply="unit-jsc11" ver="1.8.0" rev="1"
+ date="2019-03-01" time="18:00:00 +0300"
+ packager="Andrei Belov &lt;defan@nginx.com&gt;">
+
+<change>
+<para>
+Initial release of Java 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"
+ ver="1.8.0" rev="1"
+ date="2019-03-01" time="18:00:00 +0300"
+ packager="Andrei Belov &lt;defan@nginx.com&gt;">
+
+<change>
+<para>
+NGINX Unit updated to 1.8.0.
+</para>
+</change>
+
+</changes>
+
+
+<changes apply="unit" ver="1.8.0" rev="1"
+ date="2019-03-01" time="18:00:00 +0300"
+ packager="Andrei Belov &lt;defan@nginx.com&gt;">
+
+<change type="change">
+<para>
+now three numbers are always used for versioning: major, minor,
+and patch versions.
+</para>
+</change>
+
+<change type="change">
+<para>
+now QUERY_STRING is always defined even if the request does not include
+the query component.
+</para>
+</change>
+
+<change type="feature">
+<para>
+basic internal request routing by Host, URI, and method.
+</para>
+</change>
+
+<change type="feature">
+<para>
+experimental support for Java Servlet Containers.
+</para>
+</change>
+
+<change type="bugfix">
+<para>
+segmentation fault might have occurred in the router process.
+</para>
+</change>
+
+<change type="bugfix">
+<para>
+various potential memory leaks.
+</para>
+</change>
+
+<change type="bugfix">
+<para>
+TLS connections might have stalled.
+</para>
+</change>
+
+<change type="bugfix">
+<para>
+some Perl applications might have failed to send the response body.
+</para>
+</change>
+
+<change type="bugfix">
+<para>
+some compilers with specific flags might have produced non-functioning builds;
+the bug had appeared in 1.5.
+</para>
+</change>
+
+<change type="bugfix">
+<para>
+Node.js package had wrong version number when installed from sources.
+</para>
+</change>
+
+</changes>
+
+
<changes apply="unit-php
unit-python unit-python2.7
unit-python3.4 unit-python3.5 unit-python3.6 unit-python3.7
diff --git a/pkg/Makefile b/pkg/Makefile
index 6001a034..7926606d 100644
--- a/pkg/Makefile
+++ b/pkg/Makefile
@@ -1,8 +1,8 @@
#!/usr/bin/make
-VERSION ?= $(shell grep 'define NXT_VERSION' ../src/nxt_main.h \
- | sed -e 's/^.*"\(.*\)".*/\1/')
+include ../version
+VERSION ?= $(NXT_VERSION)
RELEASE ?= 1
default:
diff --git a/pkg/deb/Makefile b/pkg/deb/Makefile
index 1a16b6ee..f481ff02 100644
--- a/pkg/deb/Makefile
+++ b/pkg/deb/Makefile
@@ -1,11 +1,10 @@
#!/usr/bin/make
-DEFAULT_VERSION := $(shell grep 'define NXT_VERSION' ../../src/nxt_main.h \
- | sed -e 's/^.*"\(.*\)".*/\1/')
+include ../../version
DEFAULT_RELEASE := 1
-VERSION ?= $(DEFAULT_VERSION)
+VERSION ?= $(NXT_VERSION)
RELEASE ?= $(DEFAULT_RELEASE)
SRCDIR= unit-$(VERSION)
@@ -27,6 +26,9 @@ include Makefile.go19
include Makefile.go110
include Makefile.perl
include Makefile.ruby
+include Makefile.jsc-common
+include Makefile.jsc8
+include Makefile.jsc11
endif
# Ubuntu 18.04
@@ -38,28 +40,9 @@ include Makefile.go19
include Makefile.go110
include Makefile.perl
include Makefile.ruby
-endif
-
-# Ubuntu 17.10
-ifeq ($(CODENAME),artful)
-include Makefile.php
-include Makefile.python27
-include Makefile.python36
-include Makefile.go18
-include Makefile.go19
-include Makefile.perl
-include Makefile.ruby
-endif
-
-# Ubuntu 17.04
-ifeq ($(CODENAME),zesty)
-include Makefile.php
-include Makefile.python27
-include Makefile.python35
-include Makefile.go17
-include Makefile.go18
-include Makefile.perl
-include Makefile.ruby
+include Makefile.jsc-common
+include Makefile.jsc8
+include Makefile.jsc10
endif
# Ubuntu 16.04
@@ -70,6 +53,8 @@ include Makefile.python35
include Makefile.go
include Makefile.perl
include Makefile.ruby
+include Makefile.jsc-common
+include Makefile.jsc8
endif
# Ubuntu 14.04
@@ -88,6 +73,8 @@ include Makefile.go17
include Makefile.go18
include Makefile.perl
include Makefile.ruby
+include Makefile.jsc-common
+include Makefile.jsc8
endif
# Debian 8
@@ -171,7 +158,7 @@ endif
debuild/unit_$(VERSION).orig.tar.gz: | debuild/$(SRCDIR)/debian
cd ../.. && tar -czf pkg/deb/debuild/$(SRCDIR).tar.gz \
--transform "s#^#$(SRCDIR)/#" \
- LICENSE NOTICE CHANGES README configure auto src test
+ LICENSE NOTICE CHANGES README configure auto src test version
mv debuild/$(SRCDIR).tar.gz debuild/unit_$(VERSION).orig.tar.gz
cd debuild && tar zxf unit_$(VERSION).orig.tar.gz
@@ -201,7 +188,11 @@ else
-e "s#%%CODENAME%%#$(CODENAME)#g" \
> $@/$(SRCDIR)/debian/changelog
endif
- cp debian/copyright debuild-$*/$(SRCDIR)/debian/
+ if [ -f debian.module/copyright.unit-$(MODULE_SUFFIX_$*) ]; then \
+ cp debian.module/copyright.unit-$(MODULE_SUFFIX_$*) debuild-$*/$(SRCDIR)/debian/copyright ; \
+ else \
+ cp debian/copyright debuild-$*/$(SRCDIR)/debian/ ; \
+ fi
@{ \
set -e ; \
for src in $(MODULE_SOURCES_$*); do \
@@ -210,8 +201,9 @@ endif
definitions=`echo "$$MODULE_DEFINITIONS_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \
prebuild=`echo "$$MODULE_PREBUILD_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \
preinstall=`echo "$$MODULE_PREINSTALL_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \
+ postinstall=`echo "$$MODULE_POSTINSTALL_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \
post=`echo "$$MODULE_POST_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \
- cat debian.module/control.in | sed \
+ cat debian.module/$(if $(MODULE_NOARCH_$*),control-noarch.in,control.in) | sed \
-e "s#%%NAME%%#unit-$(MODULE_SUFFIX_$*)#g" \
-e "s#%%SUMMARY%%#$(MODULE_SUMMARY_$*)#g" \
-e "s#%%CODENAME%%#$(CODENAME)#g" \
@@ -222,7 +214,7 @@ endif
-e "s#%%MODULE_BUILD_DEPENDS%%#$(MODULE_BUILD_DEPENDS_$*)#g" \
-e "s#%%MODULE_DEPENDS%%#$(MODULE_DEPENDS_$*)#g" \
> $@/$(SRCDIR)/debian/control ; \
- cat debian.module/rules.in | sed \
+ cat debian.module/$(if $(MODULE_NOARCH_$*),rules-noarch.in,rules.in) | sed \
-e "s#%%NAME%%#unit-$(MODULE_SUFFIX_$*)#g" \
-e "s#%%CODENAME%%#$(CODENAME)#g" \
-e "s#%%UNIT_VERSION%%#$(VERSION)#g" \
@@ -234,6 +226,7 @@ endif
-e "s#%%MODULE_DEFINITIONS%%#$${definitions}#g" \
-e "s#%%MODULE_PREBUILD%%#$${prebuild}#g" \
-e "s#%%MODULE_PREINSTALL%%#$${preinstall}#g" \
+ -e "s#%%MODULE_POSTINSTALL%%#$${postinstall}#g" \
> $@/$(SRCDIR)/debian/rules ; \
cat debian.module/preinst.in | sed \
-e "s#%%MODULE_POST%%#$$post#g" \
@@ -250,8 +243,9 @@ unit-%: check-build-depends-% | debuild-%
test: unit modules
@{ \
- for so in `find debuild-*/unit-$(VERSION)/debian/build-unit/ -type f -name "*.so"` ; do \
+ for so in `find debuild-*/unit-$(VERSION)/debian/build-unit/ -type f \( -name "*.so" -o -name "*.jar" \)`; do \
soname=`basename $${so}` ; \
+ test "$${soname}" = "java.unit.so" && continue ; \
test -h debuild/unit-$(VERSION)/debian/build-unit/build/$${soname} || \
ln -fs `pwd`/$${so} debuild/unit-$(VERSION)/debian/build-unit/build/$${soname} ; \
done ; \
@@ -260,8 +254,9 @@ test: unit modules
test-debug: unit modules
@{ \
- for so in `find debuild-*/unit-$(VERSION)/debian/build-unit-debug/ -type f -name "*.so"` ; do \
+ for so in `find debuild-*/unit-$(VERSION)/debian/build-unit-debug/ -type f \( -name "*.so" -o -name "*.jar" \)`; do \
soname=`basename $${so}` ; \
+ test "$${soname}" = "java.unit.so" && continue ; \
test -h debuild/unit-$(VERSION)/debian/build-unit-debug/build/$${soname} || \
ln -fs `pwd`/$${so} debuild/unit-$(VERSION)/debian/build-unit-debug/build/$${soname} ; \
done ; \
diff --git a/pkg/deb/Makefile.jsc-common b/pkg/deb/Makefile.jsc-common
new file mode 100644
index 00000000..42fdb12f
--- /dev/null
+++ b/pkg/deb/Makefile.jsc-common
@@ -0,0 +1,30 @@
+MODULES+= jsc_common
+MODULE_SUFFIX_jsc_common= jsc-common
+
+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/
+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+= $(BUILD_DEPENDS_jsc_common)
+
+MODULE_NOARCH_jsc_common= true
+
+define MODULE_POST_jsc_common
+cat <<BANNER
+----------------------------------------------------------------------
+
+The $(MODULE_SUMMARY_jsc_common) have been installed.
+
+Please find licenses and related information at:
+ /usr/share/doc/unit-jsc-common/copyright
+
+----------------------------------------------------------------------
+BANNER
+endef
+export MODULE_POST_jsc_common
diff --git a/pkg/deb/Makefile.jsc10 b/pkg/deb/Makefile.jsc10
new file mode 100644
index 00000000..43ded86b
--- /dev/null
+++ b/pkg/deb/Makefile.jsc10
@@ -0,0 +1,71 @@
+MODULES+= jsc10
+MODULE_SUFFIX_jsc10= jsc10
+
+MODULE_SUMMARY_jsc10= Java 10 module for NGINX Unit
+
+MODULE_VERSION_jsc10= $(VERSION)
+MODULE_RELEASE_jsc10= 1
+
+MODULE_CONFARGS_jsc10= java --module=java10 --home=/usr/lib/jvm/java-11-openjdk-$$\(DEB_HOST_ARCH\) --jars=/usr/share/unit-jsc-common/
+MODULE_MAKEARGS_jsc10= java10
+MODULE_INSTARGS_jsc10= java10-install
+
+MODULE_SOURCES_jsc10= unit.example-jsc-app \
+ unit.example-jsc10-config
+
+BUILD_DEPENDS_jsc10= openjdk-11-jdk-headless openjdk-11-jre-headless
+BUILD_DEPENDS+= $(BUILD_DEPENDS_jsc10)
+
+MODULE_BUILD_DEPENDS_jsc10=,openjdk-11-jdk-headless
+MODULE_DEPENDS_jsc10=,openjdk-11-jre-headless,unit-jsc-common (= $(MODULE_VERSION_jsc_common)-$(MODULE_RELEASE_jsc_common)~$(CODENAME))
+
+define MODULE_PREINSTALL_jsc10
+ mkdir -p debian/unit-jsc10/usr/share/doc/unit-jsc10/examples/jsc-app
+ install -m 644 -p debian/unit.example-jsc-app debian/unit-jsc10/usr/share/doc/unit-jsc10/examples/jsc-app/index.jsp
+ install -m 644 -p debian/unit.example-jsc10-config debian/unit-jsc10/usr/share/doc/unit-jsc10/examples/unit.config
+ install -m 644 -p src/java/README.JSR-340 debian/unit-jsc10/usr/share/doc/unit-jsc10/
+endef
+export MODULE_PREINSTALL_jsc10
+
+define MODULE_POSTINSTALL_jsc10
+ cd $$\(BUILDDIR_unit\) \&\& \
+ DESTDIR=$$\(INSTALLDIR\) make java-shared-uninstall
+endef
+export MODULE_POSTINSTALL_jsc10
+
+define MODULE_POST_jsc10
+cat <<BANNER
+----------------------------------------------------------------------
+
+The $(MODULE_SUMMARY_jsc10) has been installed.
+
+To check out the sample app, run these commands:
+
+ sudo service unit restart
+ cd /usr/share/doc/unit-$(MODULE_SUFFIX_jsc10)/examples
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ curl http://localhost:8800/
+
+Online documentation is available at https://unit.nginx.org
+
+NOTICE:
+
+This version of Unit code is made available in support of the open source
+development process. This is an intermediate build made available for
+testing purposes only. This Unit code is untested and presumed incompatible
+with the JSR 340 Java Servlet 3.1 specification. You should not deploy or
+write to this code. You should instead deploy and write production
+applications on pre-built binaries that have been tested and certified
+to meet the JSR-340 compatibility requirements such as certified binaries
+published for the JSR-340 reference implementation available at
+https://javaee.github.io/glassfish/.
+
+Redistribution of any Intermediate Build must retain this notice.
+
+Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+Other names may be trademarks of their respective owners.
+
+----------------------------------------------------------------------
+BANNER
+endef
+export MODULE_POST_jsc10
diff --git a/pkg/deb/Makefile.jsc11 b/pkg/deb/Makefile.jsc11
new file mode 100644
index 00000000..40f60f17
--- /dev/null
+++ b/pkg/deb/Makefile.jsc11
@@ -0,0 +1,71 @@
+MODULES+= jsc11
+MODULE_SUFFIX_jsc11= jsc11
+
+MODULE_SUMMARY_jsc11= Java 11 module for NGINX Unit
+
+MODULE_VERSION_jsc11= $(VERSION)
+MODULE_RELEASE_jsc11= 1
+
+MODULE_CONFARGS_jsc11= java --module=java11 --home=/usr/lib/jvm/java-11-openjdk-$$\(DEB_HOST_ARCH\) --jars=/usr/share/unit-jsc-common/
+MODULE_MAKEARGS_jsc11= java11
+MODULE_INSTARGS_jsc11= java11-install
+
+MODULE_SOURCES_jsc11= unit.example-jsc-app \
+ unit.example-jsc11-config
+
+BUILD_DEPENDS_jsc11= openjdk-11-jdk-headless openjdk-11-jre-headless
+BUILD_DEPENDS+= $(BUILD_DEPENDS_jsc11)
+
+MODULE_BUILD_DEPENDS_jsc11=,openjdk-11-jdk-headless
+MODULE_DEPENDS_jsc11=,openjdk-11-jre-headless,unit-jsc-common (= $(MODULE_VERSION_jsc_common)-$(MODULE_RELEASE_jsc_common)~$(CODENAME))
+
+define MODULE_PREINSTALL_jsc11
+ mkdir -p debian/unit-jsc11/usr/share/doc/unit-jsc11/examples/jsc-app
+ install -m 644 -p debian/unit.example-jsc-app debian/unit-jsc11/usr/share/doc/unit-jsc11/examples/jsc-app/index.jsp
+ install -m 644 -p debian/unit.example-jsc11-config debian/unit-jsc11/usr/share/doc/unit-jsc11/examples/unit.config
+ install -m 644 -p src/java/README.JSR-340 debian/unit-jsc11/usr/share/doc/unit-jsc11/
+endef
+export MODULE_PREINSTALL_jsc11
+
+define MODULE_POSTINSTALL_jsc11
+ cd $$\(BUILDDIR_unit\) \&\& \
+ DESTDIR=$$\(INSTALLDIR\) make java-shared-uninstall
+endef
+export MODULE_POSTINSTALL_jsc11
+
+define MODULE_POST_jsc11
+cat <<BANNER
+----------------------------------------------------------------------
+
+The $(MODULE_SUMMARY_jsc11) has been installed.
+
+To check out the sample app, run these commands:
+
+ sudo service unit restart
+ cd /usr/share/doc/unit-$(MODULE_SUFFIX_jsc11)/examples
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ curl http://localhost:8800/
+
+Online documentation is available at https://unit.nginx.org
+
+NOTICE:
+
+This version of Unit code is made available in support of the open source
+development process. This is an intermediate build made available for
+testing purposes only. This Unit code is untested and presumed incompatible
+with the JSR 340 Java Servlet 3.1 specification. You should not deploy or
+write to this code. You should instead deploy and write production
+applications on pre-built binaries that have been tested and certified
+to meet the JSR-340 compatibility requirements such as certified binaries
+published for the JSR-340 reference implementation available at
+https://javaee.github.io/glassfish/.
+
+Redistribution of any Intermediate Build must retain this notice.
+
+Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+Other names may be trademarks of their respective owners.
+
+----------------------------------------------------------------------
+BANNER
+endef
+export MODULE_POST_jsc11
diff --git a/pkg/deb/Makefile.jsc8 b/pkg/deb/Makefile.jsc8
new file mode 100644
index 00000000..d7eed96b
--- /dev/null
+++ b/pkg/deb/Makefile.jsc8
@@ -0,0 +1,71 @@
+MODULES+= jsc8
+MODULE_SUFFIX_jsc8= jsc8
+
+MODULE_SUMMARY_jsc8= Java 8 module for NGINX Unit
+
+MODULE_VERSION_jsc8= $(VERSION)
+MODULE_RELEASE_jsc8= 1
+
+MODULE_CONFARGS_jsc8= java --module=java8 --home=/usr/lib/jvm/java-8-openjdk-$$\(DEB_HOST_ARCH\) --jars=/usr/share/unit-jsc-common/
+MODULE_MAKEARGS_jsc8= java8
+MODULE_INSTARGS_jsc8= java8-install
+
+MODULE_SOURCES_jsc8= unit.example-jsc-app \
+ unit.example-jsc8-config
+
+BUILD_DEPENDS_jsc8= openjdk-8-jdk-headless openjdk-8-jre-headless
+BUILD_DEPENDS+= $(BUILD_DEPENDS_jsc8)
+
+MODULE_BUILD_DEPENDS_jsc8=,openjdk-8-jdk-headless
+MODULE_DEPENDS_jsc8=,openjdk-8-jre-headless,unit-jsc-common (= $(MODULE_VERSION_jsc_common)-$(MODULE_RELEASE_jsc_common)~$(CODENAME))
+
+define MODULE_PREINSTALL_jsc8
+ mkdir -p debian/unit-jsc8/usr/share/doc/unit-jsc8/examples/jsc-app
+ install -m 644 -p debian/unit.example-jsc-app debian/unit-jsc8/usr/share/doc/unit-jsc8/examples/jsc-app/index.jsp
+ install -m 644 -p debian/unit.example-jsc8-config debian/unit-jsc8/usr/share/doc/unit-jsc8/examples/unit.config
+ install -m 644 -p src/java/README.JSR-340 debian/unit-jsc8/usr/share/doc/unit-jsc8/
+endef
+export MODULE_PREINSTALL_jsc8
+
+define MODULE_POSTINSTALL_jsc8
+ cd $$\(BUILDDIR_unit\) \&\& \
+ DESTDIR=$$\(INSTALLDIR\) make java-shared-uninstall
+endef
+export MODULE_POSTINSTALL_jsc8
+
+define MODULE_POST_jsc8
+cat <<BANNER
+----------------------------------------------------------------------
+
+The $(MODULE_SUMMARY_jsc8) has been installed.
+
+To check out the sample app, run these commands:
+
+ sudo service unit restart
+ cd /usr/share/doc/unit-$(MODULE_SUFFIX_jsc8)/examples
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ curl http://localhost:8800/
+
+Online documentation is available at https://unit.nginx.org
+
+NOTICE:
+
+This version of Unit code is made available in support of the open source
+development process. This is an intermediate build made available for
+testing purposes only. This Unit code is untested and presumed incompatible
+with the JSR 340 Java Servlet 3.1 specification. You should not deploy or
+write to this code. You should instead deploy and write production
+applications on pre-built binaries that have been tested and certified
+to meet the JSR-340 compatibility requirements such as certified binaries
+published for the JSR-340 reference implementation available at
+https://javaee.github.io/glassfish/.
+
+Redistribution of any Intermediate Build must retain this notice.
+
+Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+Other names may be trademarks of their respective owners.
+
+----------------------------------------------------------------------
+BANNER
+endef
+export MODULE_POST_jsc8
diff --git a/pkg/deb/debian.module/control-noarch.in b/pkg/deb/debian.module/control-noarch.in
new file mode 100644
index 00000000..e22bb49a
--- /dev/null
+++ b/pkg/deb/debian.module/control-noarch.in
@@ -0,0 +1,23 @@
+Source: %%NAME%%
+Section: admin
+Priority: extra
+Maintainer: Andrei Belov <defan@nginx.com>
+Build-Depends: debhelper (>= 9),
+ linux-libc-dev%%MODULE_BUILD_DEPENDS%%
+Standards-Version: 3.9.5
+Homepage: https://unit.nginx.org
+
+Package: %%NAME%%
+Section: admin
+Architecture: all
+Depends: lsb-base,
+ ${misc:Depends},
+ unit (= %%UNIT_VERSION%%-%%UNIT_RELEASE%%~%%CODENAME%%)%%MODULE_DEPENDS%%
+Description: %%SUMMARY%%
+ NGINX Unit is a runtime and delivery environment for modern distributed
+ applications. It runs the application code in multiple languages
+ (PHP, Python, Go, etc.), and tightly couples it with traffic delivery
+ in and out of the application. Take this application server and proxy
+ directly in the cloud / container environments and fully control your app
+ dynamically via an API.
+ This package contains %%SUMMARY%%.
diff --git a/pkg/deb/debian.module/copyright.unit-jsc-common b/pkg/deb/debian.module/copyright.unit-jsc-common
new file mode 100644
index 00000000..accb1834
--- /dev/null
+++ b/pkg/deb/debian.module/copyright.unit-jsc-common
@@ -0,0 +1,872 @@
+
+The unit-jsc-common package includes supplementary Java archives (JARs)
+required by Java servlet container module for NGINX Unit.
+
+Java is a registered trademark of Oracle and/or its affiliates.
+
+The following software components distributed under corresponding licenses:
+
+ * classgraph-4.4.11.jar: MIT
+ * ecj-3.13.102.jar: EPL 1.0
+ * jetty-http-9.4.12.v20180830.jar: Apache 2.0 + EPL 1.0
+ * jetty-server-9.4.12.v20180830.jar: Apache 2.0 + EPL 1.0
+ * jetty-util-9.4.12.v20180830.jar: Apache 2.0 + EPL 1.0
+ * tomcat-api-9.0.13.jar: Apache 2.0
+ * tomcat-el-api-9.0.13.jar: Apache 2.0
+ * tomcat-jasper-9.0.13.jar: Apache 2.0
+ * tomcat-jasper-el-9.0.13.jar: Apache 2.0
+ * tomcat-jsp-api-9.0.13.jar: Apache 2.0
+ * tomcat-juli-9.0.13.jar: Apache 2.0
+ * tomcat-servlet-api-9.0.13.jar: Apache 2.0 + CDDL 1.0
+ * tomcat-util-9.0.13.jar: Apache 2.0
+ * tomcat-util-scan-9.0.13.jar: Apache 2.0
+
+Licenses could be found by the following links and below in this file:
+
+ - MIT (The MIT License):
+ http://www.opensource.org/licenses/MIT
+
+ - EPL (Eclipse Public License) v1.0:
+ https://www.eclipse.org/legal/epl-v10.html
+
+ - Apache 2.0:
+ http://www.apache.org/licenses/LICENSE-2.0.html
+
+ - CDDL (Common Development and Distribution License) v1.0:
+ https://opensource.org/licenses/CDDL-1.0
+
+
+====[ MIT license - begin ]===================================================
+
+The MIT License (MIT)
+
+Copyright (c) 2015 Luke Hutchison
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+====[ MIT license - end ]=====================================================
+
+
+====[ EPL license - begin ]===================================================
+
+Eclipse Public License - v1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
+CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and documentation
+distributed under this Agreement, and
+
+b) in the case of each subsequent Contributor:
+
+i) changes to the Program, and
+
+ii) additions to the Program;
+
+where such changes and/or additions to the Program originate from and are
+distributed by that particular Contributor. A Contribution 'originates' from a
+Contributor if it was added to the Program by such Contributor itself or
+anyone acting on such Contributor's behalf. Contributions do not include
+additions to the Program which: (i) are separate modules of software
+distributed in conjunction with the Program under their own license agreement,
+and (ii) are not derivative works of the Program. "Contributor" means any
+person or entity that distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which are
+necessarily infringed by the use or sale of its Contribution alone or when
+combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this
+Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement,
+including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby grants
+Recipient a non-exclusive, worldwide, royalty-free copyright license to
+reproduce, prepare derivative works of, publicly display, publicly perform,
+distribute and sublicense the Contribution of such Contributor, if any, and
+such derivative works, in source code and object code form.
+
+b) Subject to the terms of this Agreement, each Contributor hereby grants
+Recipient a non-exclusive, worldwide, royalty-free patent license under
+Licensed Patents to make, use, sell, offer to sell, import and otherwise
+transfer the Contribution of such Contributor, if any, in source code and
+object code form. This patent license shall apply to the combination of the
+Contribution and the Program if, at the time the Contribution is added by the
+Contributor, such addition of the Contribution causes such combination to be
+covered by the Licensed Patents. The patent license shall not apply to any
+other combinations which include the Contribution. No hardware per se is
+licensed hereunder.
+
+c) Recipient understands that although each Contributor grants the licenses to
+its Contributions set forth herein, no assurances are provided by any
+Contributor that the Program does not infringe the patent or other
+intellectual property rights of any other entity. Each Contributor disclaims
+any liability to Recipient for claims brought by any other entity based on
+infringement of intellectual property rights or otherwise. As a condition to
+exercising the rights and licenses granted hereunder, each Recipient hereby
+assumes sole responsibility to secure any other intellectual property rights
+needed, if any. For example, if a third party patent license is required to
+allow Recipient to distribute the Program, it is Recipient's responsibility to
+acquire that license before distributing the Program.
+
+d) Each Contributor represents that to its knowledge it has sufficient
+copyright rights in its Contribution, if any, to grant the copyright license
+set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under
+its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+
+b) its license agreement:
+
+i) effectively disclaims on behalf of all Contributors all warranties and
+conditions, express and implied, including warranties or conditions of title
+and non-infringement, and implied warranties or conditions of merchantability
+and fitness for a particular purpose;
+
+ii) effectively excludes on behalf of all Contributors all liability for
+damages, including direct, indirect, special, incidental and consequential
+damages, such as lost profits;
+
+iii) states that any provisions which differ from this Agreement are offered
+by that Contributor alone and not by any other party; and
+
+iv) states that source code for the Program is available from such
+Contributor, and informs licensees how to obtain it in a reasonable manner on
+or through a medium customarily used for software exchange.
+
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+
+b) a copy of this Agreement must be included with each copy of the Program.
+
+Contributors may not remove or alter any copyright notices contained within
+the Program.
+
+Each Contributor must identify itself as the originator of its Contribution,
+if any, in a manner that reasonably allows subsequent Recipients to identify
+the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with
+respect to end users, business partners and the like. While this license is
+intended to facilitate the commercial use of the Program, the Contributor who
+includes the Program in a commercial product offering should do so in a manner
+which does not create potential liability for other Contributors. Therefore,
+if a Contributor includes the Program in a commercial product offering, such
+Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
+every other Contributor ("Indemnified Contributor") against any losses,
+damages and costs (collectively "Losses") arising from claims, lawsuits and
+other legal actions brought by a third party against the Indemnified
+Contributor to the extent caused by the acts or omissions of such Commercial
+Contributor in connection with its distribution of the Program in a commercial
+product offering. The obligations in this section do not apply to any claims
+or Losses relating to any actual or alleged intellectual property
+infringement. In order to qualify, an Indemnified Contributor must: a)
+promptly notify the Commercial Contributor in writing of such claim, and b)
+allow the Commercial Contributor to control, and cooperate with the Commercial
+Contributor in, the defense and any related settlement negotiations. The
+Indemnified Contributor may participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial product
+offering, Product X. That Contributor is then a Commercial Contributor. If
+that Commercial Contributor then makes performance claims, or offers
+warranties related to Product X, those performance claims and warranties are
+such Commercial Contributor's responsibility alone. Under this section, the
+Commercial Contributor would have to defend claims against the other
+Contributors related to those performance claims and warranties, and if a
+court requires any other Contributor to pay any damages as a result, the
+Commercial Contributor must pay those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
+Recipient is solely responsible for determining the appropriateness of using
+and distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement , including but not limited to the
+risks and costs of program errors, compliance with applicable laws, damage to
+or loss of data, programs or equipment, and unavailability or interruption of
+operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
+CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
+LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of the
+remainder of the terms of this Agreement, and without further action by the
+parties hereto, such provision shall be reformed to the minimum extent
+necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Program itself
+(excluding combinations of the Program with other software or hardware)
+infringes such Recipient's patent(s), then such Recipient's rights granted
+under Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to
+comply with any of the material terms or conditions of this Agreement and does
+not cure such failure in a reasonable period of time after becoming aware of
+such noncompliance. If all Recipient's rights under this Agreement terminate,
+Recipient agrees to cease use and distribution of the Program as soon as
+reasonably practicable. However, Recipient's obligations under this Agreement
+and any licenses granted by Recipient relating to the Program shall continue
+and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement, but in
+order to avoid inconsistency the Agreement is copyrighted and may only be
+modified in the following manner. The Agreement Steward reserves the right to
+publish new versions (including revisions) of this Agreement from time to
+time. No one other than the Agreement Steward has the right to modify this
+Agreement. The Eclipse Foundation is the initial Agreement Steward. The
+Eclipse Foundation may assign the responsibility to serve as the Agreement
+Steward to a suitable separate entity. Each new version of the Agreement will
+be given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new version of the
+Agreement is published, Contributor may elect to distribute the Program
+(including its Contributions) under the new version. Except as expressly
+stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
+licenses to the intellectual property of any Contributor under this Agreement,
+whether expressly, by implication, estoppel or otherwise. All rights in the
+Program not expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and the
+intellectual property laws of the United States of America. No party to this
+Agreement will bring a legal action under this Agreement more than one year
+after the cause of action arose. Each party waives its rights to a jury trial
+in any resulting litigation.
+
+====[ EPL license - begin ]===================================================
+
+
+====[ Apache 2.0 license - begin ]============================================
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+====[ Apache 2.0 license - end ]==============================================
+
+
+====[ CDDL v1.0 license - start ]=============================================
+
+COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0
+
+1. Definitions.
+
+ 1.1. "Contributor" means each individual or entity that creates
+ or contributes to the creation of Modifications.
+
+ 1.2. "Contributor Version" means the combination of the Original
+ Software, prior Modifications used by a Contributor (if any),
+ and the Modifications made by that particular Contributor.
+
+ 1.3. "Covered Software" means (a) the Original Software, or (b)
+ Modifications, or (c) the combination of files containing
+ Original Software with files containing Modifications, in
+ each case including portions thereof.
+
+ 1.4. "Executable" means the Covered Software in any form other
+ than Source Code.
+
+ 1.5. "Initial Developer" means the individual or entity that first
+ makes Original Software available under this License.
+
+ 1.6. "Larger Work" means a work which combines Covered Software or
+ portions thereof with code not governed by the terms of this
+ License.
+
+ 1.7. "License" means this document.
+
+ 1.8. "Licensable" means having the right to grant, to the maximum
+ extent possible, whether at the time of the initial grant or
+ subsequently acquired, any and all of the rights conveyed
+ herein.
+
+ 1.9. "Modifications" means the Source Code and Executable form of
+ any of the following:
+
+ A. Any file that results from an addition to, deletion from or
+ modification of the contents of a file containing Original
+ Software or previous Modifications;
+
+ B. Any new file that contains any part of the Original
+ Software or previous Modifications; or
+
+ C. Any new file that is contributed or otherwise made
+ available under the terms of this License.
+
+ 1.10. "Original Software" means the Source Code and Executable
+ form of computer software code that is originally released
+ under this License.
+
+ 1.11. "Patent Claims" means any patent claim(s), now owned or
+ hereafter acquired, including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by
+ grantor.
+
+ 1.12. "Source Code" means (a) the common form of computer software
+ code in which modifications are made and (b) associated
+ documentation included in or with such code.
+
+ 1.13. "You" (or "Your") means an individual or a legal entity
+ exercising rights under, and complying with all of the terms
+ of, this License. For legal entities, "You" includes any
+ entity which controls, is controlled by, or is under common
+ control with You. For purposes of this definition,
+ "control" means (a) the power, direct or indirect, to cause
+ the direction or management of such entity, whether by
+ contract or otherwise, or (b) ownership of more than fifty
+ percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants.
+
+ 2.1. The Initial Developer Grant.
+
+ Conditioned upon Your compliance with Section 3.1 below and
+ subject to third party intellectual property claims, the Initial
+ Developer hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Initial Developer, to use,
+ reproduce, modify, display, perform, sublicense and
+ distribute the Original Software (or portions thereof),
+ with or without Modifications, and/or as part of a Larger
+ Work; and
+
+ (b) under Patent Claims infringed by the making, using or
+ selling of Original Software, to make, have made, use,
+ practice, sell, and offer for sale, and/or otherwise
+ dispose of the Original Software (or portions thereof).
+
+ (c) The licenses granted in Sections 2.1(a) and (b) are
+ effective on the date Initial Developer first distributes
+ or otherwise makes the Original Software available to a
+ third party under the terms of this License.
+
+ (d) Notwithstanding Section 2.1(b) above, no patent license is
+ granted: (1) for code that You delete from the Original
+ Software, or (2) for infringements caused by: (i) the
+ modification of the Original Software, or (ii) the
+ combination of the Original Software with other software
+ or devices.
+
+ 2.2. Contributor Grant.
+
+ Conditioned upon Your compliance with Section 3.1 below and
+ subject to third party intellectual property claims, each
+ Contributor hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Contributor to use, reproduce,
+ modify, display, perform, sublicense and distribute the
+ Modifications created by such Contributor (or portions
+ thereof), either on an unmodified basis, with other
+ Modifications, as Covered Software and/or as part of a
+ Larger Work; and
+
+ (b) under Patent Claims infringed by the making, using, or
+ selling of Modifications made by that Contributor either
+ alone and/or in combination with its Contributor Version
+ (or portions of such combination), to make, use, sell,
+ offer for sale, have made, and/or otherwise dispose of:
+ (1) Modifications made by that Contributor (or portions
+ thereof); and (2) the combination of Modifications made by
+ that Contributor with its Contributor Version (or portions
+ of such combination).
+
+ (c) The licenses granted in Sections 2.2(a) and 2.2(b) are
+ effective on the date Contributor first distributes or
+ otherwise makes the Modifications available to a third
+ party.
+
+ (d) Notwithstanding Section 2.2(b) above, no patent license is
+ granted: (1) for any code that Contributor has deleted
+ from the Contributor Version; (2) for infringements caused
+ by: (i) third party modifications of Contributor Version,
+ or (ii) the combination of Modifications made by that
+ Contributor with other software (except as part of the
+ Contributor Version) or other devices; or (3) under Patent
+ Claims infringed by Covered Software in the absence of
+ Modifications made by that Contributor.
+
+3. Distribution Obligations.
+
+ 3.1. Availability of Source Code.
+
+ Any Covered Software that You distribute or otherwise make
+ available in Executable form must also be made available in Source
+ Code form and that Source Code form must be distributed only under
+ the terms of this License. You must include a copy of this
+ License with every copy of the Source Code form of the Covered
+ Software You distribute or otherwise make available. You must
+ inform recipients of any such Covered Software in Executable form
+ as to how they can obtain such Covered Software in Source Code
+ form in a reasonable manner on or through a medium customarily
+ used for software exchange.
+
+ 3.2. Modifications.
+
+ The Modifications that You create or to which You contribute are
+ governed by the terms of this License. You represent that You
+ believe Your Modifications are Your original creation(s) and/or
+ You have sufficient rights to grant the rights conveyed by this
+ License.
+
+ 3.3. Required Notices.
+
+ You must include a notice in each of Your Modifications that
+ identifies You as the Contributor of the Modification. You may
+ not remove or alter any copyright, patent or trademark notices
+ contained within the Covered Software, or any notices of licensing
+ or any descriptive text giving attribution to any Contributor or
+ the Initial Developer.
+
+ 3.4. Application of Additional Terms.
+
+ You may not offer or impose any terms on any Covered Software in
+ Source Code form that alters or restricts the applicable version
+ of this License or the recipients' rights hereunder. You may
+ choose to offer, and to charge a fee for, warranty, support,
+ indemnity or liability obligations to one or more recipients of
+ Covered Software. However, you may do so only on Your own behalf,
+ and not on behalf of the Initial Developer or any Contributor.
+ You must make it absolutely clear that any such warranty, support,
+ indemnity or liability obligation is offered by You alone, and You
+ hereby agree to indemnify the Initial Developer and every
+ Contributor for any liability incurred by the Initial Developer or
+ such Contributor as a result of warranty, support, indemnity or
+ liability terms You offer.
+
+ 3.5. Distribution of Executable Versions.
+
+ You may distribute the Executable form of the Covered Software
+ under the terms of this License or under the terms of a license of
+ Your choice, which may contain terms different from this License,
+ provided that You are in compliance with the terms of this License
+ and that the license for the Executable form does not attempt to
+ limit or alter the recipient's rights in the Source Code form from
+ the rights set forth in this License. If You distribute the
+ Covered Software in Executable form under a different license, You
+ must make it absolutely clear that any terms which differ from
+ this License are offered by You alone, not by the Initial
+ Developer or Contributor. You hereby agree to indemnify the
+ Initial Developer and every Contributor for any liability incurred
+ by the Initial Developer or such Contributor as a result of any
+ such terms You offer.
+
+ 3.6. Larger Works.
+
+ You may create a Larger Work by combining Covered Software with
+ other code not governed by the terms of this License and
+ distribute the Larger Work as a single product. In such a case,
+ You must make sure the requirements of this License are fulfilled
+ for the Covered Software.
+
+4. Versions of the License.
+
+ 4.1. New Versions.
+
+ Sun Microsystems, Inc. is the initial license steward and may
+ publish revised and/or new versions of this License from time to
+ time. Each version will be given a distinguishing version number.
+ Except as provided in Section 4.3, no one other than the license
+ steward has the right to modify this License.
+
+ 4.2. Effect of New Versions.
+
+ You may always continue to use, distribute or otherwise make the
+ Covered Software available under the terms of the version of the
+ License under which You originally received the Covered Software.
+ If the Initial Developer includes a notice in the Original
+ Software prohibiting it from being distributed or otherwise made
+ available under any subsequent version of the License, You must
+ distribute and make the Covered Software available under the terms
+ of the version of the License under which You originally received
+ the Covered Software. Otherwise, You may also choose to use,
+ distribute or otherwise make the Covered Software available under
+ the terms of any subsequent version of the License published by
+ the license steward.
+
+ 4.3. Modified Versions.
+
+ When You are an Initial Developer and You want to create a new
+ license for Your Original Software, You may create and use a
+ modified version of this License if You: (a) rename the license
+ and remove any references to the name of the license steward
+ (except to note that the license differs from this License); and
+ (b) otherwise make it clear that the license contains terms which
+ differ from this License.
+
+5. DISCLAIMER OF WARRANTY.
+
+ COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS"
+ BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
+ INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED
+ SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR
+ PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND
+ PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY
+ COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE
+ INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY
+ NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF
+ WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
+ ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
+ DISCLAIMER.
+
+6. TERMINATION.
+
+ 6.1. This License and the rights granted hereunder will terminate
+ automatically if You fail to comply with terms herein and fail to
+ cure such breach within 30 days of becoming aware of the breach.
+ Provisions which, by their nature, must remain in effect beyond
+ the termination of this License shall survive.
+
+ 6.2. If You assert a patent infringement claim (excluding
+ declaratory judgment actions) against Initial Developer or a
+ Contributor (the Initial Developer or Contributor against whom You
+ assert such claim is referred to as "Participant") alleging that
+ the Participant Software (meaning the Contributor Version where
+ the Participant is a Contributor or the Original Software where
+ the Participant is the Initial Developer) directly or indirectly
+ infringes any patent, then any and all rights granted directly or
+ indirectly to You by such Participant, the Initial Developer (if
+ the Initial Developer is not the Participant) and all Contributors
+ under Sections 2.1 and/or 2.2 of this License shall, upon 60 days
+ notice from Participant terminate prospectively and automatically
+ at the expiration of such 60 day notice period, unless if within
+ such 60 day period You withdraw Your claim with respect to the
+ Participant Software against such Participant either unilaterally
+ or pursuant to a written agreement with Participant.
+
+ 6.3. In the event of termination under Sections 6.1 or 6.2 above,
+ all end user licenses that have been validly granted by You or any
+ distributor hereunder prior to termination (excluding licenses
+ granted to You by any distributor) shall survive termination.
+
+7. LIMITATION OF LIABILITY.
+
+ UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+ (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE
+ INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF
+ COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE
+ LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT
+ LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK
+ STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+ COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+ INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+ LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL
+ INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT
+ APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO
+ NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR
+ CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT
+ APPLY TO YOU.
+
+8. U.S. GOVERNMENT END USERS.
+
+ The Covered Software is a "commercial item," as that term is
+ defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial
+ computer software" (as that term is defined at 48
+ C.F.R. 252.227-7014(a)(1)) and "commercial computer software
+ documentation" as such terms are used in 48 C.F.R. 12.212
+ (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48
+ C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all
+ U.S. Government End Users acquire Covered Software with only those
+ rights set forth herein. This U.S. Government Rights clause is in
+ lieu of, and supersedes, any other FAR, DFAR, or other clause or
+ provision that addresses Government rights in computer software
+ under this License.
+
+9. MISCELLANEOUS.
+
+ This License represents the complete agreement concerning subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. This License shall be governed
+ by the law of the jurisdiction specified in a notice contained
+ within the Original Software (except to the extent applicable law,
+ if any, provides otherwise), excluding such jurisdiction's
+ conflict-of-law provisions. Any litigation relating to this
+ License shall be subject to the jurisdiction of the courts located
+ in the jurisdiction and venue specified in a notice contained
+ within the Original Software, with the losing party responsible
+ for costs, including, without limitation, court costs and
+ reasonable attorneys' fees and expenses. The application of the
+ United Nations Convention on Contracts for the International Sale
+ of Goods is expressly excluded. Any law or regulation which
+ provides that the language of a contract shall be construed
+ against the drafter shall not apply to this License. You agree
+ that You alone are responsible for compliance with the United
+ States export administration regulations (and the export control
+ laws and regulation of any other countries) when You use,
+ distribute or otherwise make available any Covered Software.
+
+10. RESPONSIBILITY FOR CLAIMS.
+
+ As between Initial Developer and the Contributors, each party is
+ responsible for claims and damages arising, directly or
+ indirectly, out of its utilization of rights under this License
+ and You agree to work with Initial Developer and Contributors to
+ distribute such responsibility on an equitable basis. Nothing
+ herein is intended or shall be deemed to constitute any admission
+ of liability.
+
+--------------------------------------------------------------------
+
+NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND
+DISTRIBUTION LICENSE (CDDL)
+
+For Covered Software in this distribution, this License shall
+be governed by the laws of the State of California (excluding
+conflict-of-law provisions).
+
+Any litigation relating to this License shall be subject to the
+jurisdiction of the Federal Courts of the Northern District of
+California and the state courts of the State of California, with
+venue lying in Santa Clara County, California.
+
+====[ CDDL v1.0 license - end ]===============================================
diff --git a/pkg/deb/debian.module/copyright.unit-jsc10 b/pkg/deb/debian.module/copyright.unit-jsc10
new file mode 100644
index 00000000..dbad728b
--- /dev/null
+++ b/pkg/deb/debian.module/copyright.unit-jsc10
@@ -0,0 +1,26 @@
+
+ NGINX Unit.
+
+ Copyright 2017-2019 NGINX, Inc.
+ Copyright 2017-2019 Igor Sysoev
+ Copyright 2017-2019 Valentin V. Bartenev
+ Copyright 2017-2019 Max Romanov
+ Copyright 2018-2019 Alexander Borisov
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ /usr/share/common-licenses/Apache-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ The unit-jsc10 package provides Java servlet container module
+ for NGINX Unit.
+
+ Java is a registered trademark of Oracle and/or its affiliates.
diff --git a/pkg/deb/debian.module/copyright.unit-jsc11 b/pkg/deb/debian.module/copyright.unit-jsc11
new file mode 100644
index 00000000..413c0094
--- /dev/null
+++ b/pkg/deb/debian.module/copyright.unit-jsc11
@@ -0,0 +1,26 @@
+
+ NGINX Unit.
+
+ Copyright 2017-2019 NGINX, Inc.
+ Copyright 2017-2019 Igor Sysoev
+ Copyright 2017-2019 Valentin V. Bartenev
+ Copyright 2017-2019 Max Romanov
+ Copyright 2018-2019 Alexander Borisov
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ /usr/share/common-licenses/Apache-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ The unit-jsc11 package provides Java servlet container module
+ for NGINX Unit.
+
+ Java is a registered trademark of Oracle and/or its affiliates.
diff --git a/pkg/deb/debian.module/copyright.unit-jsc8 b/pkg/deb/debian.module/copyright.unit-jsc8
new file mode 100644
index 00000000..96b62102
--- /dev/null
+++ b/pkg/deb/debian.module/copyright.unit-jsc8
@@ -0,0 +1,26 @@
+
+ NGINX Unit.
+
+ Copyright 2017-2019 NGINX, Inc.
+ Copyright 2017-2019 Igor Sysoev
+ Copyright 2017-2019 Valentin V. Bartenev
+ Copyright 2017-2019 Max Romanov
+ Copyright 2018-2019 Alexander Borisov
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ /usr/share/common-licenses/Apache-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ The unit-jsc8 package provides Java servlet container module
+ for NGINX Unit.
+
+ Java is a registered trademark of Oracle and/or its affiliates.
diff --git a/pkg/deb/debian.module/rules-noarch.in b/pkg/deb/debian.module/rules-noarch.in
new file mode 100644
index 00000000..823675ba
--- /dev/null
+++ b/pkg/deb/debian.module/rules-noarch.in
@@ -0,0 +1,107 @@
+#!/usr/bin/make -f
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+export DEB_BUILD_MAINT_OPTIONS=hardening=+all,-pie
+export DEB_CFLAGS_MAINT_APPEND=-Wp,-D_FORTIFY_SOURCE=2 -fPIC
+DPKG_EXPORT_BUILDFLAGS = 1
+include /usr/share/dpkg/buildflags.mk
+
+BUILDDIR_unit = $(CURDIR)/debian/build-unit
+BUILDDIR_unit_debug = $(CURDIR)/debian/build-unit-debug
+INSTALLDIR = $(CURDIR)/debian/%%NAME%%
+BASEDIR = $(CURDIR)
+
+%%MODULE_DEFINITIONS%%
+
+config.env.%:
+ dh_testdir
+ mkdir -p $(BUILDDIR_$*)
+ cp -Pa $(CURDIR)/auto $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/configure $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/src $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/test $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/version $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/CHANGES $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/LICENSE $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/NOTICE $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/README $(BUILDDIR_$*)/
+ touch $@
+
+configure.unit: config.env.unit
+ cd $(BUILDDIR_unit) && \
+ CFLAGS= ./configure \
+ %%CONFIGURE_ARGS%% \
+ --modules=/usr/lib/unit/modules \
+ --cc-opt="$(CFLAGS)" && \
+ ./configure %%MODULE_CONFARGS%%
+ touch $@
+
+configure.unit_debug: config.env.unit_debug
+ cd $(BUILDDIR_unit_debug) && \
+ CFLAGS= ./configure \
+ %%CONFIGURE_ARGS%% \
+ --modules=/usr/lib/unit/debug-modules \
+ --cc-opt="$(CFLAGS)" \
+ --debug && \
+ ./configure %%MODULE_CONFARGS%%
+ touch $@
+
+build-arch.%: configure.%
+ dh_testdir
+ $(MAKE) -C $(BUILDDIR_$*) %%MODULE_MAKEARGS%%
+ touch $@
+
+build-indep:
+ dh_testdir
+ touch $@
+
+build-arch: build-arch.unit build-arch.unit_debug
+ dh_testdir
+ touch $@
+
+build: build-arch build-indep
+ dh_testdir
+ touch $@
+
+clean:
+ dh_testdir
+ dh_testroot
+ dh_clean
+ find $(CURDIR) -maxdepth 1 -size 0 -delete
+ rm -rf $(BUILDDIR_unit) $(BUILDDIR_unit_debug)
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_prep
+ dh_installdirs
+ dh_installinit
+ dh_installlogrotate
+%%MODULE_PREINSTALL%%
+ cd $(BUILDDIR_unit) && \
+ DESTDIR=$(INSTALLDIR) make %%MODULE_INSTARGS%%
+ cd $(BUILDDIR_unit_debug) && \
+ DESTDIR=$(INSTALLDIR) make %%MODULE_INSTARGS%%
+%%MODULE_POSTINSTALL%%
+
+binary-indep: build install
+ dh_testdir
+ dh_testroot
+ dh_installdocs
+ dh_installchangelogs
+ dh_link
+ dh_compress
+ dh_fixperms
+ dh_installdeb
+ dh_perl
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+binary-arch: install
+
+binary: binary-indep binary-arch
+
+.PHONY: clean binary-indep binary-arch binary install build
diff --git a/pkg/deb/debian.module/rules.in b/pkg/deb/debian.module/rules.in
index 22e381fe..1391e01a 100755
--- a/pkg/deb/debian.module/rules.in
+++ b/pkg/deb/debian.module/rules.in
@@ -22,6 +22,11 @@ config.env.%:
cp -Pa $(CURDIR)/configure $(BUILDDIR_$*)/
cp -Pa $(CURDIR)/src $(BUILDDIR_$*)/
cp -Pa $(CURDIR)/test $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/version $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/CHANGES $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/LICENSE $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/NOTICE $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/README $(BUILDDIR_$*)/
touch $@
configure.unit: config.env.unit
@@ -65,6 +70,7 @@ clean:
dh_testroot
dh_clean
find $(CURDIR) -maxdepth 1 -size 0 -delete
+ rm -rf $(BUILDDIR_unit) $(BUILDDIR_unit_debug)
install: build
dh_testdir
@@ -78,12 +84,13 @@ install: build
DESTDIR=$(INSTALLDIR) make %%MODULE_INSTARGS%%
cd $(BUILDDIR_unit_debug) && \
DESTDIR=$(INSTALLDIR) make %%MODULE_INSTARGS%%
+%%MODULE_POSTINSTALL%%
binary-indep: build install
dh_testdir
dh_testroot
dh_installdocs
- dh_installchangelogs
+ dh_installchangelogs
dh_link
dh_strip --dbg-package=%%NAME%%-dbg
dh_shlibdeps
diff --git a/pkg/deb/debian.module/unit.example-jsc-app b/pkg/deb/debian.module/unit.example-jsc-app
new file mode 100644
index 00000000..be01e123
--- /dev/null
+++ b/pkg/deb/debian.module/unit.example-jsc-app
@@ -0,0 +1,12 @@
+<%@ page contentType="text/plain"%><%@ page import="java.util.*" %>This is plain text response for "<%= request.getMethod() %> <%= request.getRequestURI() %>".
+Here is the list of all system properties:
+<%
+ Properties p = System.getProperties();
+ Enumeration keys = p.keys();
+ while (keys.hasMoreElements()) {
+ String key = (String) keys.nextElement();
+ String value = (String) p.get(key);
+ out.println(" " + key + " : " + value);
+ }
+%>
+<% response.addHeader("X-Unit-JSP", "ok"); %>
diff --git a/pkg/deb/debian.module/unit.example-jsc10-config b/pkg/deb/debian.module/unit.example-jsc10-config
new file mode 100644
index 00000000..6929356d
--- /dev/null
+++ b/pkg/deb/debian.module/unit.example-jsc10-config
@@ -0,0 +1,15 @@
+{
+ "applications": {
+ "example_java10": {
+ "processes": 1,
+ "type": "java 10",
+ "webapp": "/usr/share/doc/unit-jsc10/examples/jsc-app"
+ }
+ },
+
+ "listeners": {
+ "*:8800": {
+ "application": "example_java10"
+ }
+ }
+}
diff --git a/pkg/deb/debian.module/unit.example-jsc11-config b/pkg/deb/debian.module/unit.example-jsc11-config
new file mode 100644
index 00000000..6c1d9549
--- /dev/null
+++ b/pkg/deb/debian.module/unit.example-jsc11-config
@@ -0,0 +1,15 @@
+{
+ "applications": {
+ "example_java11": {
+ "processes": 1,
+ "type": "java 11",
+ "webapp": "/usr/share/doc/unit-jsc11/examples/jsc-app"
+ }
+ },
+
+ "listeners": {
+ "*:8800": {
+ "application": "example_java11"
+ }
+ }
+}
diff --git a/pkg/deb/debian.module/unit.example-jsc8-config b/pkg/deb/debian.module/unit.example-jsc8-config
new file mode 100644
index 00000000..0254677b
--- /dev/null
+++ b/pkg/deb/debian.module/unit.example-jsc8-config
@@ -0,0 +1,15 @@
+{
+ "applications": {
+ "example_java8": {
+ "processes": 1,
+ "type": "java 1.8.0",
+ "webapp": "/usr/share/doc/unit-jsc8/examples/jsc-app"
+ }
+ },
+
+ "listeners": {
+ "*:8800": {
+ "application": "example_java8"
+ }
+ }
+}
diff --git a/pkg/deb/debian.module/unit.example-jsc9-config b/pkg/deb/debian.module/unit.example-jsc9-config
new file mode 100644
index 00000000..c64a1aff
--- /dev/null
+++ b/pkg/deb/debian.module/unit.example-jsc9-config
@@ -0,0 +1,15 @@
+{
+ "applications": {
+ "example_java9": {
+ "processes": 1,
+ "type": "java 9",
+ "webapp": "/usr/share/doc/unit-jsc9/examples/jsc-app"
+ }
+ },
+
+ "listeners": {
+ "*:8800": {
+ "application": "example_java9"
+ }
+ }
+}
diff --git a/pkg/deb/debian/rules.in b/pkg/deb/debian/rules.in
index 7eab391d..bee9223f 100644
--- a/pkg/deb/debian/rules.in
+++ b/pkg/deb/debian/rules.in
@@ -26,6 +26,11 @@ config.env.%:
cp -Pa $(CURDIR)/configure $(BUILDDIR_$*)/
cp -Pa $(CURDIR)/src $(BUILDDIR_$*)/
cp -Pa $(CURDIR)/test $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/version $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/CHANGES $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/LICENSE $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/NOTICE $(BUILDDIR_$*)/
+ cp -Pa $(CURDIR)/README $(BUILDDIR_$*)/
touch $@
configure.unit: config.env.unit
@@ -83,6 +88,7 @@ clean:
dh_testroot
dh_clean
find $(CURDIR) -maxdepth 1 -size 0 -delete
+ rm -rf $(BUILDDIR_unit) $(BUILDDIR_unit_debug)
install: build do.tests
dh_testdir
diff --git a/pkg/docker/Dockerfile.full b/pkg/docker/Dockerfile.full
index 99b18fc1..7e688710 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.7.1-1~stretch
+ENV UNIT_VERSION 1.8.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 eb75e7b0..c0245ea7 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.7.1-1~stretch
+ENV UNIT_VERSION 1.8.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 c60853ac..3c13c018 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.7.1-1~stretch
+ENV UNIT_VERSION 1.8.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.minimal b/pkg/docker/Dockerfile.minimal
index f944da7f..b48f8410 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.7.1-1~stretch
+ENV UNIT_VERSION 1.8.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.perl5.24 b/pkg/docker/Dockerfile.perl5.24
index ee3ba4bf..4d5f502e 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.7.1-1~stretch
+ENV UNIT_VERSION 1.8.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.php7.0 b/pkg/docker/Dockerfile.php7.0
index 4468dafd..0afcf28a 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.7.1-1~stretch
+ENV UNIT_VERSION 1.8.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.python2.7 b/pkg/docker/Dockerfile.python2.7
index dec5d3cf..57619e26 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.7.1-1~stretch
+ENV UNIT_VERSION 1.8.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.python3.5 b/pkg/docker/Dockerfile.python3.5
index 0d93fb07..410f395c 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.7.1-1~stretch
+ENV UNIT_VERSION 1.8.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Dockerfile.ruby2.3 b/pkg/docker/Dockerfile.ruby2.3
index ff9474cc..7b674178 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.7.1-1~stretch
+ENV UNIT_VERSION 1.8.0-1~stretch
RUN set -x \
&& apt-get update \
diff --git a/pkg/docker/Makefile b/pkg/docker/Makefile
index 140ac5b3..cf6de78d 100644
--- a/pkg/docker/Makefile
+++ b/pkg/docker/Makefile
@@ -1,10 +1,10 @@
#!/usr/bin/make
-DEFAULT_VERSION := $(shell grep 'define NXT_VERSION' ../../src/nxt_main.h \
- | sed -e 's/^.*"\(.*\)".*/\1/')
+include ../../version
+
DEFAULT_RELEASE := 1
-VERSION ?= $(DEFAULT_VERSION)
+VERSION ?= $(NXT_VERSION)
RELEASE ?= $(DEFAULT_RELEASE)
CODENAME := stretch
diff --git a/pkg/npm/Makefile b/pkg/npm/Makefile
index dfa9ccdc..ef8ef7b5 100644
--- a/pkg/npm/Makefile
+++ b/pkg/npm/Makefile
@@ -1,13 +1,9 @@
#!/usr/bin/make
-DEFAULT_VERSION := $(shell grep 'define NXT_VERSION' ../../src/nxt_main.h \
- | sed -e 's/^.*"\(.*\)".*/\1/')
+include ../../version
-DEFAULT_VERNUM := $(shell grep 'define NXT_VERNUM' ../../src/nxt_main.h \
- | sed -e 's/[^0-9]//g')
-
-VERSION ?= $(DEFAULT_VERSION)
-VERNUM ?= $(DEFAULT_VERNUM)
+VERSION ?= $(NXT_VERSION)
+VERNUM ?= $(NXT_VERNUM)
NPM ?= npm
default:
@@ -17,7 +13,7 @@ copy:
cp -rp ../../src/nodejs/unit-http .
echo '#define NXT_NODE_VERNUM ${VERNUM}' > unit-http/version.h
mv unit-http/binding_pub.gyp unit-http/binding.gyp
- sed -e 's/"version"\s*:.*/"version": "${VERSION}.0",/' \
+ sed -e 's/"version"\s*:.*/"version": "${VERSION}",/' \
unit-http/package.json > unit-http/package.json.tmp
mv unit-http/package.json.tmp unit-http/package.json
diff --git a/pkg/rpm/Makefile b/pkg/rpm/Makefile
index 40b6016a..d94890f2 100644
--- a/pkg/rpm/Makefile
+++ b/pkg/rpm/Makefile
@@ -1,16 +1,15 @@
#!/usr/bin/make
-DEFAULT_VERSION := $(shell grep 'define NXT_VERSION' ../../src/nxt_main.h \
- | sed -e 's/^.*"\(.*\)".*/\1/')
+include ../../version
DEFAULT_RELEASE := 1
-VERSION ?= $(DEFAULT_VERSION)
+VERSION ?= $(NXT_VERSION)
RELEASE ?= $(DEFAULT_RELEASE)
-ifeq ($(shell rpm --eval "%{?rhel}"), 6)
+ifeq ($(shell test `rpm --eval '0%{?rhel} -eq 6 -a 0%{?amzn} -eq 0'`; echo $$?), 0)
OSVER = centos6
-else ifeq ($(shell rpm --eval "%{?rhel}"), 7)
+else ifeq ($(shell test `rpm --eval '0%{?rhel} -eq 7 -a 0%{?amzn} -eq 0'`; echo $$?), 0)
OSVER = centos7
else ifeq ($(shell rpm --eval "%{?amzn}"), 1)
OSVER = amazonlinux1
@@ -50,13 +49,18 @@ ifeq ($(OSVER), centos6)
include Makefile.php
include Makefile.python
include Makefile.go
+include Makefile.jsc-common
+include Makefile.jsc8
endif
-ifneq (,$(findstring $(OSVER),centos7 amazonlinux2))
+ifeq ($(OSVER), centos7)
include Makefile.php
include Makefile.python
include Makefile.go
include Makefile.perl
+include Makefile.jsc-common
+include Makefile.jsc8
+include Makefile.jsc11
endif
ifeq ($(OSVER), amazonlinux1)
@@ -67,6 +71,17 @@ include Makefile.python35
include Makefile.python36
include Makefile.go
include Makefile.perl
+include Makefile.jsc-common
+include Makefile.jsc8
+endif
+
+ifeq ($(OSVER), amazonlinux2)
+include Makefile.php
+include Makefile.python
+include Makefile.go
+include Makefile.perl
+include Makefile.jsc-common
+include Makefile.jsc8
endif
ifeq ($(OSVER), opensuse-leap)
@@ -95,16 +110,23 @@ endif
ifeq ($(OSVER), fedora)
include Makefile.php
include Makefile.python27
+ifeq ($(shell test `rpm --eval '0%{?fedora} -ge 29'`; echo $$?),0)
+include Makefile.python37
+else
include Makefile.python36
+endif
include Makefile.go
include Makefile.perl
include Makefile.ruby
+include Makefile.jsc-common
+include Makefile.jsc8
+include Makefile.jsc11
endif
CONFIGURE_ARGS=\
--prefix=/usr \
--state=%{_sharedstatedir}/unit \
- --control="unix:/var/run/control.unit.sock" \
+ --control="unix:/var/run/unit/control.sock" \
--pid=/var/run/unit.pid \
--log=/var/log/unit.log \
--tests \
@@ -167,7 +189,7 @@ endif
rpmbuild/SOURCES/unit-$(VERSION).tar.gz:
cd ../.. && tar -czf pkg/rpm/rpmbuild/SOURCES/unit-$(VERSION).tar.gz \
--transform "s#^#unit-$(VERSION)/#" \
- LICENSE NOTICE CHANGES README configure auto src test
+ LICENSE NOTICE CHANGES README configure auto src test version
unit: check-build-depends-unit rpmbuild/SPECS/unit.spec rpmbuild/SOURCES/unit-$(VERSION).tar.gz
@echo "===> Building $@ package" ; \
@@ -188,9 +210,10 @@ rpmbuild/SPECS/unit-%.spec: unit.module.spec.in ../../docs/changes.xml | rpmbuil
sources="$${sources}\n$${s}" ; \
i=$$(($${i}+1)) ; \
done ; \
- pkgname=$(shell echo $@ | cut -d '/' -f 3 | cut -d '.' -f 1) ; \
+ pkgname=$(shell echo $@ | cut -d '/' -f 3 | tr '_' '-' | cut -d '.' -f 1) ; \
definitions=`echo "$$MODULE_DEFINITIONS_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \
preinstall=`echo "$$MODULE_PREINSTALL_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \
+ postinstall=`echo "$$MODULE_POSTINSTALL_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \
files=`echo "$$MODULE_FILES_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \
post=`echo "$$MODULE_POST_$*" | sed -e ':a' -e 'N' -e '$$!ba' -e "s/\n/\$$CR/g"` ; \
cat unit.module.spec.in | sed \
@@ -207,6 +230,7 @@ rpmbuild/SPECS/unit-%.spec: unit.module.spec.in ../../docs/changes.xml | rpmbuil
-e "s#%%MODULE_INSTARGS%%#$(MODULE_INSTARGS_$*)#g" \
-e "s#%%MODULE_DEFINITIONS%%#$${definitions}#g" \
-e "s#%%MODULE_PREINSTALL%%#$${preinstall}#g" \
+ -e "s#%%MODULE_POSTINSTALL%%#$${postinstall}#g" \
-e "s#%%MODULE_FILES%%#$${files}#g" \
-e "s#%%MODULE_POST%%#$${post}#g" \
> $@.tmp ; \
@@ -218,14 +242,15 @@ rpmbuild/SPECS/unit-%.spec: unit.module.spec.in ../../docs/changes.xml | rpmbuil
mv $@.tmp $@
unit-%: check-build-depends-% rpmbuild/SPECS/unit-%.spec rpmbuild/SOURCES/unit-$(VERSION).tar.gz
- @echo "===> Building $@ package" ; \
+ @echo "===> Building $(subst _,-,$@) package" ; \
rpmbuild -D "_topdir `pwd`/rpmbuild" -ba rpmbuild/SPECS/$@.spec && \
- ln -s rpmbuild/BUILD/$@-$(VERSION)/build $@
+ ln -s rpmbuild/BUILD/$(subst _,-,$@)-$(VERSION)/build $@
test: unit modules
@{ \
- for so in `find rpmbuild/BUILD/*/build-nodebug/ -type f -name "*.so"`; do \
+ for so in `find rpmbuild/BUILD/*/build-nodebug/ -type f \( -name "*.so" -o -name "*.jar" \)`; do \
soname=`basename $${so}` ; \
+ test "$${soname}" = "java.unit.so" && continue ; \
test -h rpmbuild/BUILD/unit-$(VERSION)/build-nodebug/$${soname} || \
ln -fs `pwd`/$${so} rpmbuild/BUILD/unit-$(VERSION)/build-nodebug/$${soname} ; \
done ; \
@@ -234,8 +259,9 @@ test: unit modules
test-debug: unit modules
@{ \
- for so in `find rpmbuild/BUILD/*/build-debug/ -type f -name "*.so"`; do \
+ for so in `find rpmbuild/BUILD/*/build-debug/ -type f \( -name "*.so" -o -name "*.jar" \)`; do \
soname=`basename $${so}` ; \
+ test "$${soname}" = "java.unit.so" && continue ; \
test -h rpmbuild/BUILD/unit-$(VERSION)/build-debug/$${soname} || \
ln -fs `pwd`/$${so} rpmbuild/BUILD/unit-$(VERSION)/build-debug/$${soname} ; \
done ; \
diff --git a/pkg/rpm/Makefile.go b/pkg/rpm/Makefile.go
index f031c7ba..09dffd21 100644
--- a/pkg/rpm/Makefile.go
+++ b/pkg/rpm/Makefile.go
@@ -64,7 +64,7 @@ To check the sample app, run these commands:
GOPATH=%{gopath} go build -o /tmp/go-app /usr/share/doc/unit-go/examples/go-app/let-my-people.go
sudo service unit start
cd /usr/share/doc/%{name}/examples
- sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config
curl http://localhost:8500/
Online documentation is available at https://unit.nginx.org
diff --git a/pkg/rpm/Makefile.jsc-common b/pkg/rpm/Makefile.jsc-common
new file mode 100644
index 00000000..d73ed06c
--- /dev/null
+++ b/pkg/rpm/Makefile.jsc-common
@@ -0,0 +1,41 @@
+MODULES+= jsc_common
+MODULE_SUFFIX_jsc_common= jsc-common
+
+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-1.8.0 --jars=/usr/share/unit-jsc-common/
+MODULE_MAKEARGS_jsc_common= java
+MODULE_INSTARGS_jsc_common= java-shared-install
+
+MODULE_SOURCES_jsc_common= COPYRIGHT.unit-jsc-common
+
+BUILD_DEPENDS_jsc_common= java-1.8.0-openjdk-devel
+BUILD_DEPENDS+= $(BUILD_DEPENDS_jsc_common)
+
+define MODULE_DEFINITIONS_jsc_common
+BuildArch: noarch
+endef
+export MODULE_DEFINITIONS_jsc_common
+
+define MODULE_FILES_jsc_common
+%dir %{_datadir}/unit-jsc-common
+%{_datadir}/unit-jsc-common/*
+endef
+export MODULE_FILES_jsc_common
+
+define MODULE_POST_jsc_common
+cat <<BANNER
+----------------------------------------------------------------------
+
+The $(MODULE_SUMMARY_jsc_common) have been installed.
+
+Please find licenses and related information here:
+ /usr/share/doc/unit-jsc-common/COPYRIGHT
+
+----------------------------------------------------------------------
+BANNER
+endef
+export MODULE_POST_jsc_common
diff --git a/pkg/rpm/Makefile.jsc11 b/pkg/rpm/Makefile.jsc11
new file mode 100644
index 00000000..1dadaba0
--- /dev/null
+++ b/pkg/rpm/Makefile.jsc11
@@ -0,0 +1,69 @@
+MODULES+= jsc11
+MODULE_SUFFIX_jsc11= jsc11
+
+MODULE_SUMMARY_jsc11= Java 11 module for NGINX Unit
+
+MODULE_VERSION_jsc11= $(VERSION)
+MODULE_RELEASE_jsc11= 1
+
+MODULE_CONFARGS_jsc11= java --module=java11 --home=/usr/lib/jvm/java-11 --jars=/usr/share/unit-jsc-common/
+MODULE_MAKEARGS_jsc11= java11
+MODULE_INSTARGS_jsc11= java11-install
+
+MODULE_SOURCES_jsc11= unit.example-jsc-app \
+ unit.example-jsc11-config
+
+BUILD_DEPENDS_jsc11= java-11-openjdk-devel
+BUILD_DEPENDS+= $(BUILD_DEPENDS_jsc11)
+
+define MODULE_DEFINITIONS_jsc11
+Requires: unit-jsc-common == $(MODULE_VERSION_jsc_common)-$(MODULE_RELEASE_jsc_common)%{?dist}.ngx
+endef
+export MODULE_DEFINITIONS_jsc11
+
+define MODULE_PREINSTALL_jsc11
+%{__mkdir} -p %{buildroot}%{_datadir}/doc/unit-jsc11/examples/jsc-app
+%{__install} -m 644 -p %{SOURCE100} \
+ %{buildroot}%{_datadir}/doc/unit-jsc11/examples/jsc-app/index.jsp
+%{__install} -m 644 -p %{SOURCE101} \
+ %{buildroot}%{_datadir}/doc/unit-jsc11/examples/unit.config
+%{__install} -m 644 -p %{bdir}/src/java/README.JSR-340 \
+ %{buildroot}%{_datadir}/doc/unit-jsc8/
+endef
+export MODULE_PREINSTALL_jsc11
+
+define MODULE_POSTINSTALL_jsc11
+DESTDIR=%{buildroot} make java-shared-uninstall
+endef
+export MODULE_POSTINSTALL_jsc11
+
+define MODULE_FILES_jsc11
+%{_libdir}/unit/modules/*
+%{_libdir}/unit/debug-modules/*
+%dir %{_datadir}/doc/unit-jsc11
+%{_datadir}/doc/unit-jsc11/*
+%{_datadir}/unit-jsc-common/*
+endef
+export MODULE_FILES_jsc11
+
+define MODULE_POST_jsc11
+cat <<BANNER
+----------------------------------------------------------------------
+
+The $(MODULE_SUMMARY_jsc11) has been installed.
+
+To check out the sample app, run these commands:
+
+ sudo service unit restart
+ cd /usr/share/doc/%{name}/examples
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config
+ curl http://localhost:8800/
+
+Online documentation is available at https://unit.nginx.org
+
+`cat /usr/share/doc/unit-jsc11/README.JSR-340`
+
+----------------------------------------------------------------------
+BANNER
+endef
+export MODULE_POST_jsc11
diff --git a/pkg/rpm/Makefile.jsc8 b/pkg/rpm/Makefile.jsc8
new file mode 100644
index 00000000..919e8f06
--- /dev/null
+++ b/pkg/rpm/Makefile.jsc8
@@ -0,0 +1,69 @@
+MODULES+= jsc8
+MODULE_SUFFIX_jsc8= jsc8
+
+MODULE_SUMMARY_jsc8= Java 8 module for NGINX Unit
+
+MODULE_VERSION_jsc8= $(VERSION)
+MODULE_RELEASE_jsc8= 1
+
+MODULE_CONFARGS_jsc8= java --module=java8 --home=/usr/lib/jvm/java-1.8.0 --jars=/usr/share/unit-jsc-common/
+MODULE_MAKEARGS_jsc8= java8
+MODULE_INSTARGS_jsc8= java8-install
+
+MODULE_SOURCES_jsc8= unit.example-jsc-app \
+ unit.example-jsc8-config
+
+BUILD_DEPENDS_jsc8= java-1.8.0-openjdk-devel
+BUILD_DEPENDS+= $(BUILD_DEPENDS_jsc8)
+
+define MODULE_DEFINITIONS_jsc8
+Requires: unit-jsc-common == $(MODULE_VERSION_jsc_common)-$(MODULE_RELEASE_jsc_common)%{?dist}.ngx
+endef
+export MODULE_DEFINITIONS_jsc8
+
+define MODULE_PREINSTALL_jsc8
+%{__mkdir} -p %{buildroot}%{_datadir}/doc/unit-jsc8/examples/jsc-app
+%{__install} -m 644 -p %{SOURCE100} \
+ %{buildroot}%{_datadir}/doc/unit-jsc8/examples/jsc-app/index.jsp
+%{__install} -m 644 -p %{SOURCE101} \
+ %{buildroot}%{_datadir}/doc/unit-jsc8/examples/unit.config
+%{__install} -m 644 -p %{bdir}/src/java/README.JSR-340 \
+ %{buildroot}%{_datadir}/doc/unit-jsc8/
+endef
+export MODULE_PREINSTALL_jsc8
+
+define MODULE_POSTINSTALL_jsc8
+DESTDIR=%{buildroot} make java-shared-uninstall
+endef
+export MODULE_POSTINSTALL_jsc8
+
+define MODULE_FILES_jsc8
+%{_libdir}/unit/modules/*
+%{_libdir}/unit/debug-modules/*
+%dir %{_datadir}/doc/unit-jsc8
+%{_datadir}/doc/unit-jsc8/*
+%{_datadir}/unit-jsc-common/*
+endef
+export MODULE_FILES_jsc8
+
+define MODULE_POST_jsc8
+cat <<BANNER
+----------------------------------------------------------------------
+
+The $(MODULE_SUMMARY_jsc8) has been installed.
+
+To check out the sample app, run these commands:
+
+ sudo service unit restart
+ cd /usr/share/doc/%{name}/examples
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config
+ curl http://localhost:8800/
+
+Online documentation is available at https://unit.nginx.org
+
+`cat /usr/share/doc/unit-jsc8/README.JSR-340`
+
+----------------------------------------------------------------------
+BANNER
+endef
+export MODULE_POST_jsc8
diff --git a/pkg/rpm/Makefile.perl b/pkg/rpm/Makefile.perl
index 84c15260..f59b7353 100644
--- a/pkg/rpm/Makefile.perl
+++ b/pkg/rpm/Makefile.perl
@@ -51,7 +51,7 @@ To check out the sample app, run these commands:
sudo service unit start
cd /usr/share/doc/%{name}/examples
- sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config
curl http://localhost:8600/
Online documentation is available at https://unit.nginx.org
diff --git a/pkg/rpm/Makefile.php b/pkg/rpm/Makefile.php
index eedcce4e..8f39efc0 100644
--- a/pkg/rpm/Makefile.php
+++ b/pkg/rpm/Makefile.php
@@ -46,7 +46,7 @@ To check out the sample app, run these commands:
sudo service unit start
cd /usr/share/doc/%{name}/examples
- sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config
curl http://localhost:8300/
Online documentation is available at https://unit.nginx.org
diff --git a/pkg/rpm/Makefile.python b/pkg/rpm/Makefile.python
index bb408bb5..334d62c1 100644
--- a/pkg/rpm/Makefile.python
+++ b/pkg/rpm/Makefile.python
@@ -46,7 +46,7 @@ To check the sample app, run these commands:
sudo service unit start
cd /usr/share/doc/%{name}/examples
- sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config
curl http://localhost:8400/
Online documentation is available at https://unit.nginx.org
diff --git a/pkg/rpm/Makefile.python27 b/pkg/rpm/Makefile.python27
index c6a2ec65..005eff17 100644
--- a/pkg/rpm/Makefile.python27
+++ b/pkg/rpm/Makefile.python27
@@ -48,7 +48,7 @@ To check the sample app, run these commands:
sudo service unit start
cd /usr/share/doc/%{name}/examples
- sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config
curl http://localhost:8400/
Online documentation is available at https://unit.nginx.org
diff --git a/pkg/rpm/Makefile.python34 b/pkg/rpm/Makefile.python34
index 1dc10f9c..83c0bdb6 100644
--- a/pkg/rpm/Makefile.python34
+++ b/pkg/rpm/Makefile.python34
@@ -46,7 +46,7 @@ To check the sample app, run these commands:
sudo service unit start
cd /usr/share/doc/%{name}/examples
- sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config
curl http://localhost:8400/
Online documentation is available at https://unit.nginx.org
diff --git a/pkg/rpm/Makefile.python35 b/pkg/rpm/Makefile.python35
index 3e4e0a2e..6f866771 100644
--- a/pkg/rpm/Makefile.python35
+++ b/pkg/rpm/Makefile.python35
@@ -41,7 +41,7 @@ To check the sample app, run these commands:
sudo service unit start
cd /usr/share/doc/%{name}/examples
- sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config
curl http://localhost:8400/
Online documentation is available at https://unit.nginx.org
diff --git a/pkg/rpm/Makefile.python36 b/pkg/rpm/Makefile.python36
index d674d8fe..25e33968 100644
--- a/pkg/rpm/Makefile.python36
+++ b/pkg/rpm/Makefile.python36
@@ -46,7 +46,7 @@ To check the sample app, run these commands:
sudo service unit start
cd /usr/share/doc/%{name}/examples
- sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config
curl http://localhost:8400/
Online documentation is available at https://unit.nginx.org
diff --git a/pkg/rpm/Makefile.python37 b/pkg/rpm/Makefile.python37
new file mode 100644
index 00000000..ed9462b8
--- /dev/null
+++ b/pkg/rpm/Makefile.python37
@@ -0,0 +1,57 @@
+MODULES+= python37
+MODULE_SUFFIX_python37= python3.7
+
+MODULE_SUMMARY_python37= Python 3.7 module for NGINX Unit
+
+MODULE_VERSION_python37= $(VERSION)
+MODULE_RELEASE_python37= 1
+
+MODULE_CONFARGS_python37= python --config=python3.7-config
+MODULE_MAKEARGS_python37= python3.7
+MODULE_INSTARGS_python37= python3.7-install
+
+MODULE_SOURCES_python37= unit.example-python-app \
+ unit.example-python37-config
+
+ifneq (,$(findstring $(OSVER),opensuse-tumbleweed sles fedora))
+BUILD_DEPENDS_python37= python3-devel
+else
+BUILD_DEPENDS_python37= python37-devel
+endif
+
+BUILD_DEPENDS+= $(BUILD_DEPENDS_python37)
+
+define MODULE_PREINSTALL_python37
+%{__mkdir} -p %{buildroot}%{_datadir}/doc/unit-python37/examples/python-app
+%{__install} -m 644 -p %{SOURCE100} \
+ %{buildroot}%{_datadir}/doc/unit-python37/examples/python-app/wsgi.py
+%{__install} -m 644 -p %{SOURCE101} \
+ %{buildroot}%{_datadir}/doc/unit-python37/examples/unit.config
+endef
+export MODULE_PREINSTALL_python37
+
+define MODULE_FILES_python37
+%{_libdir}/unit/modules/*
+%{_libdir}/unit/debug-modules/*
+endef
+export MODULE_FILES_python37
+
+define MODULE_POST_python37
+cat <<BANNER
+----------------------------------------------------------------------
+
+The $(MODULE_SUMMARY_python37) has been installed.
+
+To check the sample app, run these commands:
+
+ sudo service unit start
+ cd /usr/share/doc/%{name}/examples
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config
+ curl http://localhost:8400/
+
+Online documentation is available at https://unit.nginx.org
+
+----------------------------------------------------------------------
+BANNER
+endef
+export MODULE_POST_python37
diff --git a/pkg/rpm/Makefile.ruby b/pkg/rpm/Makefile.ruby
index 92416338..51c2949d 100644
--- a/pkg/rpm/Makefile.ruby
+++ b/pkg/rpm/Makefile.ruby
@@ -55,7 +55,7 @@ To check the sample app, run these commands:
sudo service unit start
cd /usr/share/doc/%{name}/examples
- sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/control.unit.sock http://localhost/config
+ sudo curl -X PUT --data-binary @unit.config --unix-socket /var/run/unit/control.sock http://localhost/config
curl http://localhost:8700/
Online documentation is available at https://unit.nginx.org
diff --git a/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc-common b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc-common
new file mode 100644
index 00000000..accb1834
--- /dev/null
+++ b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc-common
@@ -0,0 +1,872 @@
+
+The unit-jsc-common package includes supplementary Java archives (JARs)
+required by Java servlet container module for NGINX Unit.
+
+Java is a registered trademark of Oracle and/or its affiliates.
+
+The following software components distributed under corresponding licenses:
+
+ * classgraph-4.4.11.jar: MIT
+ * ecj-3.13.102.jar: EPL 1.0
+ * jetty-http-9.4.12.v20180830.jar: Apache 2.0 + EPL 1.0
+ * jetty-server-9.4.12.v20180830.jar: Apache 2.0 + EPL 1.0
+ * jetty-util-9.4.12.v20180830.jar: Apache 2.0 + EPL 1.0
+ * tomcat-api-9.0.13.jar: Apache 2.0
+ * tomcat-el-api-9.0.13.jar: Apache 2.0
+ * tomcat-jasper-9.0.13.jar: Apache 2.0
+ * tomcat-jasper-el-9.0.13.jar: Apache 2.0
+ * tomcat-jsp-api-9.0.13.jar: Apache 2.0
+ * tomcat-juli-9.0.13.jar: Apache 2.0
+ * tomcat-servlet-api-9.0.13.jar: Apache 2.0 + CDDL 1.0
+ * tomcat-util-9.0.13.jar: Apache 2.0
+ * tomcat-util-scan-9.0.13.jar: Apache 2.0
+
+Licenses could be found by the following links and below in this file:
+
+ - MIT (The MIT License):
+ http://www.opensource.org/licenses/MIT
+
+ - EPL (Eclipse Public License) v1.0:
+ https://www.eclipse.org/legal/epl-v10.html
+
+ - Apache 2.0:
+ http://www.apache.org/licenses/LICENSE-2.0.html
+
+ - CDDL (Common Development and Distribution License) v1.0:
+ https://opensource.org/licenses/CDDL-1.0
+
+
+====[ MIT license - begin ]===================================================
+
+The MIT License (MIT)
+
+Copyright (c) 2015 Luke Hutchison
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+====[ MIT license - end ]=====================================================
+
+
+====[ EPL license - begin ]===================================================
+
+Eclipse Public License - v1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
+CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and documentation
+distributed under this Agreement, and
+
+b) in the case of each subsequent Contributor:
+
+i) changes to the Program, and
+
+ii) additions to the Program;
+
+where such changes and/or additions to the Program originate from and are
+distributed by that particular Contributor. A Contribution 'originates' from a
+Contributor if it was added to the Program by such Contributor itself or
+anyone acting on such Contributor's behalf. Contributions do not include
+additions to the Program which: (i) are separate modules of software
+distributed in conjunction with the Program under their own license agreement,
+and (ii) are not derivative works of the Program. "Contributor" means any
+person or entity that distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which are
+necessarily infringed by the use or sale of its Contribution alone or when
+combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this
+Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement,
+including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby grants
+Recipient a non-exclusive, worldwide, royalty-free copyright license to
+reproduce, prepare derivative works of, publicly display, publicly perform,
+distribute and sublicense the Contribution of such Contributor, if any, and
+such derivative works, in source code and object code form.
+
+b) Subject to the terms of this Agreement, each Contributor hereby grants
+Recipient a non-exclusive, worldwide, royalty-free patent license under
+Licensed Patents to make, use, sell, offer to sell, import and otherwise
+transfer the Contribution of such Contributor, if any, in source code and
+object code form. This patent license shall apply to the combination of the
+Contribution and the Program if, at the time the Contribution is added by the
+Contributor, such addition of the Contribution causes such combination to be
+covered by the Licensed Patents. The patent license shall not apply to any
+other combinations which include the Contribution. No hardware per se is
+licensed hereunder.
+
+c) Recipient understands that although each Contributor grants the licenses to
+its Contributions set forth herein, no assurances are provided by any
+Contributor that the Program does not infringe the patent or other
+intellectual property rights of any other entity. Each Contributor disclaims
+any liability to Recipient for claims brought by any other entity based on
+infringement of intellectual property rights or otherwise. As a condition to
+exercising the rights and licenses granted hereunder, each Recipient hereby
+assumes sole responsibility to secure any other intellectual property rights
+needed, if any. For example, if a third party patent license is required to
+allow Recipient to distribute the Program, it is Recipient's responsibility to
+acquire that license before distributing the Program.
+
+d) Each Contributor represents that to its knowledge it has sufficient
+copyright rights in its Contribution, if any, to grant the copyright license
+set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under
+its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+
+b) its license agreement:
+
+i) effectively disclaims on behalf of all Contributors all warranties and
+conditions, express and implied, including warranties or conditions of title
+and non-infringement, and implied warranties or conditions of merchantability
+and fitness for a particular purpose;
+
+ii) effectively excludes on behalf of all Contributors all liability for
+damages, including direct, indirect, special, incidental and consequential
+damages, such as lost profits;
+
+iii) states that any provisions which differ from this Agreement are offered
+by that Contributor alone and not by any other party; and
+
+iv) states that source code for the Program is available from such
+Contributor, and informs licensees how to obtain it in a reasonable manner on
+or through a medium customarily used for software exchange.
+
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+
+b) a copy of this Agreement must be included with each copy of the Program.
+
+Contributors may not remove or alter any copyright notices contained within
+the Program.
+
+Each Contributor must identify itself as the originator of its Contribution,
+if any, in a manner that reasonably allows subsequent Recipients to identify
+the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with
+respect to end users, business partners and the like. While this license is
+intended to facilitate the commercial use of the Program, the Contributor who
+includes the Program in a commercial product offering should do so in a manner
+which does not create potential liability for other Contributors. Therefore,
+if a Contributor includes the Program in a commercial product offering, such
+Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
+every other Contributor ("Indemnified Contributor") against any losses,
+damages and costs (collectively "Losses") arising from claims, lawsuits and
+other legal actions brought by a third party against the Indemnified
+Contributor to the extent caused by the acts or omissions of such Commercial
+Contributor in connection with its distribution of the Program in a commercial
+product offering. The obligations in this section do not apply to any claims
+or Losses relating to any actual or alleged intellectual property
+infringement. In order to qualify, an Indemnified Contributor must: a)
+promptly notify the Commercial Contributor in writing of such claim, and b)
+allow the Commercial Contributor to control, and cooperate with the Commercial
+Contributor in, the defense and any related settlement negotiations. The
+Indemnified Contributor may participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial product
+offering, Product X. That Contributor is then a Commercial Contributor. If
+that Commercial Contributor then makes performance claims, or offers
+warranties related to Product X, those performance claims and warranties are
+such Commercial Contributor's responsibility alone. Under this section, the
+Commercial Contributor would have to defend claims against the other
+Contributors related to those performance claims and warranties, and if a
+court requires any other Contributor to pay any damages as a result, the
+Commercial Contributor must pay those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
+Recipient is solely responsible for determining the appropriateness of using
+and distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement , including but not limited to the
+risks and costs of program errors, compliance with applicable laws, damage to
+or loss of data, programs or equipment, and unavailability or interruption of
+operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
+CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
+LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of the
+remainder of the terms of this Agreement, and without further action by the
+parties hereto, such provision shall be reformed to the minimum extent
+necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Program itself
+(excluding combinations of the Program with other software or hardware)
+infringes such Recipient's patent(s), then such Recipient's rights granted
+under Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to
+comply with any of the material terms or conditions of this Agreement and does
+not cure such failure in a reasonable period of time after becoming aware of
+such noncompliance. If all Recipient's rights under this Agreement terminate,
+Recipient agrees to cease use and distribution of the Program as soon as
+reasonably practicable. However, Recipient's obligations under this Agreement
+and any licenses granted by Recipient relating to the Program shall continue
+and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement, but in
+order to avoid inconsistency the Agreement is copyrighted and may only be
+modified in the following manner. The Agreement Steward reserves the right to
+publish new versions (including revisions) of this Agreement from time to
+time. No one other than the Agreement Steward has the right to modify this
+Agreement. The Eclipse Foundation is the initial Agreement Steward. The
+Eclipse Foundation may assign the responsibility to serve as the Agreement
+Steward to a suitable separate entity. Each new version of the Agreement will
+be given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new version of the
+Agreement is published, Contributor may elect to distribute the Program
+(including its Contributions) under the new version. Except as expressly
+stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
+licenses to the intellectual property of any Contributor under this Agreement,
+whether expressly, by implication, estoppel or otherwise. All rights in the
+Program not expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and the
+intellectual property laws of the United States of America. No party to this
+Agreement will bring a legal action under this Agreement more than one year
+after the cause of action arose. Each party waives its rights to a jury trial
+in any resulting litigation.
+
+====[ EPL license - begin ]===================================================
+
+
+====[ Apache 2.0 license - begin ]============================================
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+====[ Apache 2.0 license - end ]==============================================
+
+
+====[ CDDL v1.0 license - start ]=============================================
+
+COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0
+
+1. Definitions.
+
+ 1.1. "Contributor" means each individual or entity that creates
+ or contributes to the creation of Modifications.
+
+ 1.2. "Contributor Version" means the combination of the Original
+ Software, prior Modifications used by a Contributor (if any),
+ and the Modifications made by that particular Contributor.
+
+ 1.3. "Covered Software" means (a) the Original Software, or (b)
+ Modifications, or (c) the combination of files containing
+ Original Software with files containing Modifications, in
+ each case including portions thereof.
+
+ 1.4. "Executable" means the Covered Software in any form other
+ than Source Code.
+
+ 1.5. "Initial Developer" means the individual or entity that first
+ makes Original Software available under this License.
+
+ 1.6. "Larger Work" means a work which combines Covered Software or
+ portions thereof with code not governed by the terms of this
+ License.
+
+ 1.7. "License" means this document.
+
+ 1.8. "Licensable" means having the right to grant, to the maximum
+ extent possible, whether at the time of the initial grant or
+ subsequently acquired, any and all of the rights conveyed
+ herein.
+
+ 1.9. "Modifications" means the Source Code and Executable form of
+ any of the following:
+
+ A. Any file that results from an addition to, deletion from or
+ modification of the contents of a file containing Original
+ Software or previous Modifications;
+
+ B. Any new file that contains any part of the Original
+ Software or previous Modifications; or
+
+ C. Any new file that is contributed or otherwise made
+ available under the terms of this License.
+
+ 1.10. "Original Software" means the Source Code and Executable
+ form of computer software code that is originally released
+ under this License.
+
+ 1.11. "Patent Claims" means any patent claim(s), now owned or
+ hereafter acquired, including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by
+ grantor.
+
+ 1.12. "Source Code" means (a) the common form of computer software
+ code in which modifications are made and (b) associated
+ documentation included in or with such code.
+
+ 1.13. "You" (or "Your") means an individual or a legal entity
+ exercising rights under, and complying with all of the terms
+ of, this License. For legal entities, "You" includes any
+ entity which controls, is controlled by, or is under common
+ control with You. For purposes of this definition,
+ "control" means (a) the power, direct or indirect, to cause
+ the direction or management of such entity, whether by
+ contract or otherwise, or (b) ownership of more than fifty
+ percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants.
+
+ 2.1. The Initial Developer Grant.
+
+ Conditioned upon Your compliance with Section 3.1 below and
+ subject to third party intellectual property claims, the Initial
+ Developer hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Initial Developer, to use,
+ reproduce, modify, display, perform, sublicense and
+ distribute the Original Software (or portions thereof),
+ with or without Modifications, and/or as part of a Larger
+ Work; and
+
+ (b) under Patent Claims infringed by the making, using or
+ selling of Original Software, to make, have made, use,
+ practice, sell, and offer for sale, and/or otherwise
+ dispose of the Original Software (or portions thereof).
+
+ (c) The licenses granted in Sections 2.1(a) and (b) are
+ effective on the date Initial Developer first distributes
+ or otherwise makes the Original Software available to a
+ third party under the terms of this License.
+
+ (d) Notwithstanding Section 2.1(b) above, no patent license is
+ granted: (1) for code that You delete from the Original
+ Software, or (2) for infringements caused by: (i) the
+ modification of the Original Software, or (ii) the
+ combination of the Original Software with other software
+ or devices.
+
+ 2.2. Contributor Grant.
+
+ Conditioned upon Your compliance with Section 3.1 below and
+ subject to third party intellectual property claims, each
+ Contributor hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Contributor to use, reproduce,
+ modify, display, perform, sublicense and distribute the
+ Modifications created by such Contributor (or portions
+ thereof), either on an unmodified basis, with other
+ Modifications, as Covered Software and/or as part of a
+ Larger Work; and
+
+ (b) under Patent Claims infringed by the making, using, or
+ selling of Modifications made by that Contributor either
+ alone and/or in combination with its Contributor Version
+ (or portions of such combination), to make, use, sell,
+ offer for sale, have made, and/or otherwise dispose of:
+ (1) Modifications made by that Contributor (or portions
+ thereof); and (2) the combination of Modifications made by
+ that Contributor with its Contributor Version (or portions
+ of such combination).
+
+ (c) The licenses granted in Sections 2.2(a) and 2.2(b) are
+ effective on the date Contributor first distributes or
+ otherwise makes the Modifications available to a third
+ party.
+
+ (d) Notwithstanding Section 2.2(b) above, no patent license is
+ granted: (1) for any code that Contributor has deleted
+ from the Contributor Version; (2) for infringements caused
+ by: (i) third party modifications of Contributor Version,
+ or (ii) the combination of Modifications made by that
+ Contributor with other software (except as part of the
+ Contributor Version) or other devices; or (3) under Patent
+ Claims infringed by Covered Software in the absence of
+ Modifications made by that Contributor.
+
+3. Distribution Obligations.
+
+ 3.1. Availability of Source Code.
+
+ Any Covered Software that You distribute or otherwise make
+ available in Executable form must also be made available in Source
+ Code form and that Source Code form must be distributed only under
+ the terms of this License. You must include a copy of this
+ License with every copy of the Source Code form of the Covered
+ Software You distribute or otherwise make available. You must
+ inform recipients of any such Covered Software in Executable form
+ as to how they can obtain such Covered Software in Source Code
+ form in a reasonable manner on or through a medium customarily
+ used for software exchange.
+
+ 3.2. Modifications.
+
+ The Modifications that You create or to which You contribute are
+ governed by the terms of this License. You represent that You
+ believe Your Modifications are Your original creation(s) and/or
+ You have sufficient rights to grant the rights conveyed by this
+ License.
+
+ 3.3. Required Notices.
+
+ You must include a notice in each of Your Modifications that
+ identifies You as the Contributor of the Modification. You may
+ not remove or alter any copyright, patent or trademark notices
+ contained within the Covered Software, or any notices of licensing
+ or any descriptive text giving attribution to any Contributor or
+ the Initial Developer.
+
+ 3.4. Application of Additional Terms.
+
+ You may not offer or impose any terms on any Covered Software in
+ Source Code form that alters or restricts the applicable version
+ of this License or the recipients' rights hereunder. You may
+ choose to offer, and to charge a fee for, warranty, support,
+ indemnity or liability obligations to one or more recipients of
+ Covered Software. However, you may do so only on Your own behalf,
+ and not on behalf of the Initial Developer or any Contributor.
+ You must make it absolutely clear that any such warranty, support,
+ indemnity or liability obligation is offered by You alone, and You
+ hereby agree to indemnify the Initial Developer and every
+ Contributor for any liability incurred by the Initial Developer or
+ such Contributor as a result of warranty, support, indemnity or
+ liability terms You offer.
+
+ 3.5. Distribution of Executable Versions.
+
+ You may distribute the Executable form of the Covered Software
+ under the terms of this License or under the terms of a license of
+ Your choice, which may contain terms different from this License,
+ provided that You are in compliance with the terms of this License
+ and that the license for the Executable form does not attempt to
+ limit or alter the recipient's rights in the Source Code form from
+ the rights set forth in this License. If You distribute the
+ Covered Software in Executable form under a different license, You
+ must make it absolutely clear that any terms which differ from
+ this License are offered by You alone, not by the Initial
+ Developer or Contributor. You hereby agree to indemnify the
+ Initial Developer and every Contributor for any liability incurred
+ by the Initial Developer or such Contributor as a result of any
+ such terms You offer.
+
+ 3.6. Larger Works.
+
+ You may create a Larger Work by combining Covered Software with
+ other code not governed by the terms of this License and
+ distribute the Larger Work as a single product. In such a case,
+ You must make sure the requirements of this License are fulfilled
+ for the Covered Software.
+
+4. Versions of the License.
+
+ 4.1. New Versions.
+
+ Sun Microsystems, Inc. is the initial license steward and may
+ publish revised and/or new versions of this License from time to
+ time. Each version will be given a distinguishing version number.
+ Except as provided in Section 4.3, no one other than the license
+ steward has the right to modify this License.
+
+ 4.2. Effect of New Versions.
+
+ You may always continue to use, distribute or otherwise make the
+ Covered Software available under the terms of the version of the
+ License under which You originally received the Covered Software.
+ If the Initial Developer includes a notice in the Original
+ Software prohibiting it from being distributed or otherwise made
+ available under any subsequent version of the License, You must
+ distribute and make the Covered Software available under the terms
+ of the version of the License under which You originally received
+ the Covered Software. Otherwise, You may also choose to use,
+ distribute or otherwise make the Covered Software available under
+ the terms of any subsequent version of the License published by
+ the license steward.
+
+ 4.3. Modified Versions.
+
+ When You are an Initial Developer and You want to create a new
+ license for Your Original Software, You may create and use a
+ modified version of this License if You: (a) rename the license
+ and remove any references to the name of the license steward
+ (except to note that the license differs from this License); and
+ (b) otherwise make it clear that the license contains terms which
+ differ from this License.
+
+5. DISCLAIMER OF WARRANTY.
+
+ COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS"
+ BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
+ INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED
+ SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR
+ PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND
+ PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY
+ COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE
+ INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY
+ NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF
+ WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
+ ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
+ DISCLAIMER.
+
+6. TERMINATION.
+
+ 6.1. This License and the rights granted hereunder will terminate
+ automatically if You fail to comply with terms herein and fail to
+ cure such breach within 30 days of becoming aware of the breach.
+ Provisions which, by their nature, must remain in effect beyond
+ the termination of this License shall survive.
+
+ 6.2. If You assert a patent infringement claim (excluding
+ declaratory judgment actions) against Initial Developer or a
+ Contributor (the Initial Developer or Contributor against whom You
+ assert such claim is referred to as "Participant") alleging that
+ the Participant Software (meaning the Contributor Version where
+ the Participant is a Contributor or the Original Software where
+ the Participant is the Initial Developer) directly or indirectly
+ infringes any patent, then any and all rights granted directly or
+ indirectly to You by such Participant, the Initial Developer (if
+ the Initial Developer is not the Participant) and all Contributors
+ under Sections 2.1 and/or 2.2 of this License shall, upon 60 days
+ notice from Participant terminate prospectively and automatically
+ at the expiration of such 60 day notice period, unless if within
+ such 60 day period You withdraw Your claim with respect to the
+ Participant Software against such Participant either unilaterally
+ or pursuant to a written agreement with Participant.
+
+ 6.3. In the event of termination under Sections 6.1 or 6.2 above,
+ all end user licenses that have been validly granted by You or any
+ distributor hereunder prior to termination (excluding licenses
+ granted to You by any distributor) shall survive termination.
+
+7. LIMITATION OF LIABILITY.
+
+ UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+ (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE
+ INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF
+ COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE
+ LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
+ CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT
+ LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK
+ STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+ COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+ INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+ LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL
+ INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT
+ APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO
+ NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR
+ CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT
+ APPLY TO YOU.
+
+8. U.S. GOVERNMENT END USERS.
+
+ The Covered Software is a "commercial item," as that term is
+ defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial
+ computer software" (as that term is defined at 48
+ C.F.R. 252.227-7014(a)(1)) and "commercial computer software
+ documentation" as such terms are used in 48 C.F.R. 12.212
+ (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48
+ C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all
+ U.S. Government End Users acquire Covered Software with only those
+ rights set forth herein. This U.S. Government Rights clause is in
+ lieu of, and supersedes, any other FAR, DFAR, or other clause or
+ provision that addresses Government rights in computer software
+ under this License.
+
+9. MISCELLANEOUS.
+
+ This License represents the complete agreement concerning subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. This License shall be governed
+ by the law of the jurisdiction specified in a notice contained
+ within the Original Software (except to the extent applicable law,
+ if any, provides otherwise), excluding such jurisdiction's
+ conflict-of-law provisions. Any litigation relating to this
+ License shall be subject to the jurisdiction of the courts located
+ in the jurisdiction and venue specified in a notice contained
+ within the Original Software, with the losing party responsible
+ for costs, including, without limitation, court costs and
+ reasonable attorneys' fees and expenses. The application of the
+ United Nations Convention on Contracts for the International Sale
+ of Goods is expressly excluded. Any law or regulation which
+ provides that the language of a contract shall be construed
+ against the drafter shall not apply to this License. You agree
+ that You alone are responsible for compliance with the United
+ States export administration regulations (and the export control
+ laws and regulation of any other countries) when You use,
+ distribute or otherwise make available any Covered Software.
+
+10. RESPONSIBILITY FOR CLAIMS.
+
+ As between Initial Developer and the Contributors, each party is
+ responsible for claims and damages arising, directly or
+ indirectly, out of its utilization of rights under this License
+ and You agree to work with Initial Developer and Contributors to
+ distribute such responsibility on an equitable basis. Nothing
+ herein is intended or shall be deemed to constitute any admission
+ of liability.
+
+--------------------------------------------------------------------
+
+NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND
+DISTRIBUTION LICENSE (CDDL)
+
+For Covered Software in this distribution, this License shall
+be governed by the laws of the State of California (excluding
+conflict-of-law provisions).
+
+Any litigation relating to this License shall be subject to the
+jurisdiction of the Federal Courts of the Northern District of
+California and the state courts of the State of California, with
+venue lying in Santa Clara County, California.
+
+====[ CDDL v1.0 license - end ]===============================================
diff --git a/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc10 b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc10
new file mode 100644
index 00000000..665785b2
--- /dev/null
+++ b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc10
@@ -0,0 +1,25 @@
+
+ NGINX Unit.
+
+ Copyright 2017-2019 NGINX, Inc.
+ Copyright 2017-2019 Igor Sysoev
+ Copyright 2017-2019 Valentin V. Bartenev
+ Copyright 2017-2019 Max Romanov
+ Copyright 2018-2019 Alexander Borisov
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ The unit-jsc10 package provides Java servlet container module
+ for NGINX Unit.
+
+ Java is a registered trademark of Oracle and/or its affiliates.
diff --git a/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc11 b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc11
new file mode 100644
index 00000000..e92a8245
--- /dev/null
+++ b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc11
@@ -0,0 +1,25 @@
+
+ NGINX Unit.
+
+ Copyright 2017-2019 NGINX, Inc.
+ Copyright 2017-2019 Igor Sysoev
+ Copyright 2017-2019 Valentin V. Bartenev
+ Copyright 2017-2019 Max Romanov
+ Copyright 2018-2019 Alexander Borisov
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ The unit-jsc11 package provides Java servlet container module
+ for NGINX Unit.
+
+ Java is a registered trademark of Oracle and/or its affiliates.
diff --git a/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc8 b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc8
new file mode 100644
index 00000000..1e7dbff6
--- /dev/null
+++ b/pkg/rpm/rpmbuild/SOURCES/COPYRIGHT.unit-jsc8
@@ -0,0 +1,25 @@
+
+ NGINX Unit.
+
+ Copyright 2017-2019 NGINX, Inc.
+ Copyright 2017-2019 Igor Sysoev
+ Copyright 2017-2019 Valentin V. Bartenev
+ Copyright 2017-2019 Max Romanov
+ Copyright 2018-2019 Alexander Borisov
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ The unit-jsc8 package provides Java servlet container module
+ for NGINX Unit.
+
+ Java is a registered trademark of Oracle and/or its affiliates.
diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc-app b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc-app
new file mode 100644
index 00000000..be01e123
--- /dev/null
+++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc-app
@@ -0,0 +1,12 @@
+<%@ page contentType="text/plain"%><%@ page import="java.util.*" %>This is plain text response for "<%= request.getMethod() %> <%= request.getRequestURI() %>".
+Here is the list of all system properties:
+<%
+ Properties p = System.getProperties();
+ Enumeration keys = p.keys();
+ while (keys.hasMoreElements()) {
+ String key = (String) keys.nextElement();
+ String value = (String) p.get(key);
+ out.println(" " + key + " : " + value);
+ }
+%>
+<% response.addHeader("X-Unit-JSP", "ok"); %>
diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc11-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc11-config
new file mode 100644
index 00000000..6c1d9549
--- /dev/null
+++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc11-config
@@ -0,0 +1,15 @@
+{
+ "applications": {
+ "example_java11": {
+ "processes": 1,
+ "type": "java 11",
+ "webapp": "/usr/share/doc/unit-jsc11/examples/jsc-app"
+ }
+ },
+
+ "listeners": {
+ "*:8800": {
+ "application": "example_java11"
+ }
+ }
+}
diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc8-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc8-config
new file mode 100644
index 00000000..0254677b
--- /dev/null
+++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-jsc8-config
@@ -0,0 +1,15 @@
+{
+ "applications": {
+ "example_java8": {
+ "processes": 1,
+ "type": "java 1.8.0",
+ "webapp": "/usr/share/doc/unit-jsc8/examples/jsc-app"
+ }
+ },
+
+ "listeners": {
+ "*:8800": {
+ "application": "example_java8"
+ }
+ }
+}
diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.example-python37-config b/pkg/rpm/rpmbuild/SOURCES/unit.example-python37-config
new file mode 100644
index 00000000..ada7ae5b
--- /dev/null
+++ b/pkg/rpm/rpmbuild/SOURCES/unit.example-python37-config
@@ -0,0 +1,17 @@
+{
+ "applications": {
+ "example_python": {
+ "type": "python 3.7",
+ "user": "nobody",
+ "processes": 2,
+ "path": "/usr/share/doc/unit-python37/examples/python-app",
+ "module": "wsgi"
+ }
+ },
+
+ "listeners": {
+ "*:8400": {
+ "application": "example_python"
+ }
+ }
+}
diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.logrotate b/pkg/rpm/rpmbuild/SOURCES/unit.logrotate
new file mode 100644
index 00000000..8fb00199
--- /dev/null
+++ b/pkg/rpm/rpmbuild/SOURCES/unit.logrotate
@@ -0,0 +1,10 @@
+/var/log/unit/*.log {
+ missingok
+ nocreate
+ notifempty
+ postrotate
+ if [ -f /var/run/unit/unit.pid ]; then
+ /bin/kill -SIGUSR1 `cat /var/run/unit/unit.pid`
+ fi
+ endscript
+}
diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.service b/pkg/rpm/rpmbuild/SOURCES/unit.service
index 4aaf70cd..f888685f 100644
--- a/pkg/rpm/rpmbuild/SOURCES/unit.service
+++ b/pkg/rpm/rpmbuild/SOURCES/unit.service
@@ -1,14 +1,26 @@
+# Modifying this file in-place is not recommended, because changes
+# will be overwritten during package upgrades. To customize the
+# behaviour, run "systemctl edit unit" to create an override unit.
+
+# For example, to change options given to the unitd binary at startup,
+# create an override unit (as is done by systemctl edit) and enter
+# the following:
+
+# [Service]
+# Environment="UNITD_OPTIONS=--log /var/log/unit/unit.log --pid /run/unit/unit.pid"
+
[Unit]
Description=NGINX Unit
Wants=network-online.target
After=network-online.target
[Service]
-Type=forking
-PIDFile=/run/unit.pid
-EnvironmentFile=-/etc/sysconfig/unit
-ExecStart=/usr/sbin/unitd $UNITD_OPTIONS
+Type=simple
+Environment="UNITD_OPTIONS=--log /var/log/unit/unit.log --pid /run/unit/unit.pid"
+ExecStart=/usr/sbin/unitd $UNITD_OPTIONS --no-daemon
ExecReload=
+RuntimeDirectory=unit
+RuntimeDirectoryMode=0755
[Install]
WantedBy=multi-user.target
diff --git a/pkg/rpm/rpmbuild/SOURCES/unit.sysconf b/pkg/rpm/rpmbuild/SOURCES/unit.sysconf
index 0b28558f..9146bdac 100644
--- a/pkg/rpm/rpmbuild/SOURCES/unit.sysconf
+++ b/pkg/rpm/rpmbuild/SOURCES/unit.sysconf
@@ -1 +1 @@
-UNITD_OPTIONS="--log /var/log/unit.log --pid /run/unit.pid"
+UNITD_OPTIONS="--log /var/log/unit/unit.log --pid /var/run/unit/unit.pid"
diff --git a/pkg/rpm/unit.module.spec.in b/pkg/rpm/unit.module.spec.in
index d86469f4..ab55d2a4 100644
--- a/pkg/rpm/unit.module.spec.in
+++ b/pkg/rpm/unit.module.spec.in
@@ -48,6 +48,10 @@ This package contains %%SUMMARY%%.
%debug_package
%endif
+%if 0%{?fedora}
+%define _debugsource_template %{nil}
+%endif
+
%prep
%setup -qcTn %{name}-%{unit_version}
tar --strip-components=1 -zxf %{SOURCE0}
@@ -72,16 +76,29 @@ make %%MODULE_MAKEARGS%%
%install
%{__rm} -rf %{buildroot}
%{__mkdir} -p %{buildroot}%{_datadir}/doc/%%NAME%%
+if [ `basename %{SOURCE100}` == COPYRIGHT.%{name} ]; then
+%{__install} -m 644 -p %{SOURCE100} \
+ %{buildroot}%{_datadir}/doc/%%NAME%%/COPYRIGHT
+else
%{__install} -m 644 -p NOTICE \
%{buildroot}%{_datadir}/doc/%%NAME%%/COPYRIGHT
+fi
%%MODULE_PREINSTALL%%
%{__ln_s} build-debug build
DESTDIR=%{buildroot} make %%MODULE_INSTARGS%%
%{__rm} -f build
%{__ln_s} build-nodebug build
DESTDIR=%{buildroot} make %%MODULE_INSTARGS%%
+%%MODULE_POSTINSTALL%%
%check
+%{__rm} -rf %{buildroot}/usr/src
+cd %{bdir}
+grep -v 'usr/src' debugfiles.list > debugfiles.list.new && mv debugfiles.list.new debugfiles.list
+cat /dev/null > debugsources.list
+%if 0%{?suse_version} >= 1500
+cat /dev/null > debugsourcefiles.list
+%endif
%clean
%{__rm} -rf %{buildroot}
diff --git a/pkg/rpm/unit.spec.in b/pkg/rpm/unit.spec.in
index 31833f6b..2d5c1bd1 100644
--- a/pkg/rpm/unit.spec.in
+++ b/pkg/rpm/unit.spec.in
@@ -1,25 +1,31 @@
# distribution specific definitions
-%define use_systemd (0%{?rhel} && 0%{?rhel} >= 7) || (0%{?suse_version} >= 1315)
+%define use_systemd (0%{?rhel} >= 7 || 0%{?fedora} >= 19 || 0%{?suse_version} >= 1315)
%define bdir %{_builddir}/%{name}-%{version}
%define dotests 0
%if ( 0%{?rhel} == 5 || 0%{?rhel} == 6 )
Requires: initscripts >= 8.36
-BuildRequires: openssl-devel
%endif
-%if 0%{?rhel} == 7
-Requires: systemd
-BuildRequires: systemd-units
+%if %{use_systemd}
+BuildRequires: systemd
+Requires(post): systemd
+Requires(preun): systemd
+Requires(postun): systemd
+%endif
+
+%if 0%{?rhel}%{?fedora}
+BuildRequires: gcc
BuildRequires: openssl-devel
+%endif
+
+%if 0%{?rhel}
%if 0%{?amzn} == 0
-%define dist .el7
+%define dist .el%{?rhel}
%endif
%endif
%if 0%{?suse_version} >= 1315
-BuildRequires: systemd
-Requires: systemd
BuildRequires: libopenssl-devel
%endif
@@ -50,6 +56,7 @@ Source1: unit.service
Source2: unit.init
Source3: unit.sysconf
Source4: unit.example.config
+Source5: unit.logrotate
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
@@ -65,6 +72,10 @@ dynamically via an API.
%debug_package
%endif
+%if 0%{?fedora}
+%define _debugsource_template %{nil}
+%endif
+
%package devel
Summary: NGINX Unit (development files)
Version: %%VERSION%%
@@ -108,9 +119,16 @@ DESTDIR=%{buildroot} make unitd-install libunit-install
%{__mkdir} -p %{buildroot}%{_libdir}/unit/modules
%{__mkdir} -p %{buildroot}%{_libdir}/unit/debug-modules
%{__mkdir} -p %{buildroot}%{_sharedstatedir}/unit
+%{__mkdir} -p %{buildroot}%{_localstatedir}/log/unit
+%{__mkdir} -p %{buildroot}%{_localstatedir}/run/unit
+%if ! %{use_systemd}
%{__mkdir} -p %{buildroot}%{_sysconfdir}/sysconfig
%{__install} -m 644 -p %{SOURCE3} \
- %{buildroot}%{_sysconfdir}/sysconfig/unit
+ %{buildroot}%{_sysconfdir}/sysconfig/unitd
+%endif
+%{__mkdir} -p %{buildroot}%{_sysconfdir}/logrotate.d
+%{__install} -m 644 -p %{SOURCE5} \
+ %{buildroot}%{_sysconfdir}/logrotate.d/unit
%{__mkdir} -p %{buildroot}%{_sysconfdir}/unit
%{__mkdir} -p %{buildroot}%{_datadir}/doc/unit/examples
%{__install} -m 644 -p %{SOURCE4} \
@@ -138,6 +156,13 @@ export QA_SKIP_BUILD_ROOT
%if %{dotests}
cd %{bdir} && make tests && ./build/tests
%endif
+%{__rm} -rf %{buildroot}/usr/src
+cd %{bdir}
+grep -v 'usr/src' debugfiles.list > debugfiles.list.new && mv debugfiles.list.new debugfiles.list
+cat /dev/null > debugsources.list
+%if 0%{?suse_version} >= 1500
+cat /dev/null > debugsourcefiles.list
+%endif
%clean
%{__rm} -rf %{buildroot}
@@ -190,11 +215,13 @@ fi
%defattr(-,root,root,-)
%attr(0755,root,root) %{_sbindir}/unitd
%attr(0755,root,root) %{_sbindir}/unitd-debug
-%config(noreplace) %{_sysconfdir}/sysconfig/unit
%dir %{_sysconfdir}/unit
%if %{use_systemd}
%{_unitdir}/unit.service
+%dir %attr(0755,root,root) %ghost %{_localstatedir}/run/unit
%else
+%config(noreplace) %{_sysconfdir}/sysconfig/unitd
+%dir %attr(0755,root,root) %{_localstatedir}/run/unit
%{_initrddir}/unit
%endif
%dir %{_datadir}/doc/unit
@@ -202,6 +229,8 @@ fi
%dir %{_libdir}/unit/modules
%dir %{_libdir}/unit/debug-modules
%dir %{_sharedstatedir}/unit
+%dir %attr(0700,root,root) %{_localstatedir}/log/unit
+%config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
%files devel
%{_libdir}/libunit.a
diff --git a/src/go/unit/nxt_cgo_lib.c b/src/go/unit/nxt_cgo_lib.c
index 172bef88..98a23482 100644
--- a/src/go/unit/nxt_cgo_lib.c
+++ b/src/go/unit/nxt_cgo_lib.c
@@ -75,14 +75,11 @@ nxt_cgo_request_handler(nxt_unit_request_info_t *req)
nxt_go_request_add_header(go_req,
nxt_cgo_str_init(&name, &f->name, f->name_length),
nxt_cgo_str_init(&value, &f->value, f->value_length));
-
- if (f->hash == NXT_UNIT_HASH_HOST) {
- host = value;
- }
}
nxt_go_request_set_content_length(go_req, r->content_length);
- nxt_go_request_set_host(go_req, &host);
+ nxt_go_request_set_host(go_req,
+ nxt_cgo_str_init(&host, &r->server_name, r->server_name_length));
nxt_go_request_set_remote_addr(go_req,
nxt_cgo_str_init(&remote_addr, &r->remote, r->remote_length));
diff --git a/src/java/README.JSR-340 b/src/java/README.JSR-340
new file mode 100644
index 00000000..0eb189a7
--- /dev/null
+++ b/src/java/README.JSR-340
@@ -0,0 +1,16 @@
+NOTICE:
+
+This version of Unit code is made available in support of the open source
+development process. This is an intermediate build made available for
+testing purposes only. This Unit code is untested and presumed incompatible
+with the JSR 340 Java Servlet 3.1 specification. You should not deploy or
+write to this code. You should instead deploy and write production
+applications on pre-built binaries that have been tested and certified
+to meet the JSR-340 compatibility requirements such as certified binaries
+published for the JSR-340 reference implementation available at
+https://javaee.github.io/glassfish/.
+
+Redistribution of any Intermediate Build must retain this notice.
+
+Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+Other names may be trademarks of their respective owners.
diff --git a/src/java/nginx/unit/Context.java b/src/java/nginx/unit/Context.java
new file mode 100644
index 00000000..643a336b
--- /dev/null
+++ b/src/java/nginx/unit/Context.java
@@ -0,0 +1,3502 @@
+package nginx.unit;
+
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ClassInfo;
+import io.github.classgraph.ClassInfoList;
+import io.github.classgraph.ScanResult;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+
+import java.lang.ClassLoader;
+import java.lang.ClassNotFoundException;
+import java.lang.IllegalArgumentException;
+import java.lang.IllegalStateException;
+import java.lang.reflect.Constructor;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.UUID;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarInputStream;
+import java.util.zip.ZipEntry;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.FilterRegistration.Dynamic;
+import javax.servlet.FilterRegistration;
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.Registration;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.annotation.HandlesTypes;
+import javax.servlet.annotation.WebInitParam;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.annotation.WebListener;
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.descriptor.JspPropertyGroupDescriptor;
+import javax.servlet.descriptor.TaglibDescriptor;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.eclipse.jetty.http.MimeTypes;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import org.apache.jasper.servlet.JspServlet;
+import org.apache.jasper.servlet.JasperInitializer;
+
+
+public class Context implements ServletContext, InitParams
+{
+ public final static int SERVLET_MAJOR_VERSION = 3;
+ public final static int SERVLET_MINOR_VERSION = 1;
+
+ private String context_path_ = "";
+ private String server_info_ = "unit";
+ private String app_version_ = "";
+ private MimeTypes mime_types_;
+ private boolean metadata_complete_ = false;
+ private boolean welcome_files_list_found_ = false;
+ private boolean ctx_initialized_ = false;
+
+ private ClassLoader loader_;
+ private File webapp_;
+ private File extracted_dir_;
+ private File temp_dir_;
+
+ private final Map<String, String> init_params_ = new HashMap<>();
+ private final Map<String, Object> attributes_ = new HashMap<>();
+
+ private final Map<String, URLPattern> parsed_patterns_ = new HashMap<>();
+
+ private final List<FilterReg> filters_ = new ArrayList<>();
+ private final Map<String, FilterReg> name2filter_ = new HashMap<>();
+ private final List<FilterMap> filter_maps_ = new ArrayList<>();
+
+ private final List<ServletReg> servlets_ = new ArrayList<>();
+ private final Map<String, ServletReg> name2servlet_ = new HashMap<>();
+ private final Map<String, ServletReg> pattern2servlet_ = new HashMap<>();
+ private final Map<String, ServletReg> exact2servlet_ = new HashMap<>();
+ private final List<PrefixPattern> prefix_patterns_ = new ArrayList<>();
+ private final Map<String, ServletReg> suffix2servlet_ = new HashMap<>();
+ private ServletReg default_servlet_;
+ private ServletReg system_default_servlet_;
+
+ private final List<String> welcome_files_ = new ArrayList<>();
+
+ private final Map<String, String> exception2location_ = new HashMap<>();
+ private final Map<Integer, String> error2location_ = new HashMap<>();
+
+ public static final Class<?>[] LISTENER_TYPES = new Class[] {
+ ServletContextListener.class,
+ ServletContextAttributeListener.class,
+ ServletRequestListener.class,
+ ServletRequestAttributeListener.class,
+ HttpSessionAttributeListener.class,
+ HttpSessionIdListener.class,
+ HttpSessionListener.class
+ };
+
+ private final List<String> pending_listener_classnames_ = new ArrayList<>();
+ private final Set<String> listener_classnames_ = new HashSet<>();
+
+ private final List<ServletContextListener> ctx_listeners_ = new ArrayList<>();
+ private final List<ServletContextListener> destroy_listeners_ = new ArrayList<>();
+ private final List<ServletContextAttributeListener> ctx_attr_listeners_ = new ArrayList<>();
+ private final List<ServletRequestListener> req_init_listeners_ = new ArrayList<>();
+ private final List<ServletRequestListener> req_destroy_listeners_ = new ArrayList<>();
+ private final List<ServletRequestAttributeListener> req_attr_listeners_ = new ArrayList<>();
+
+ private ServletRequestAttributeListener req_attr_proxy_ = null;
+
+ private final List<HttpSessionAttributeListener> sess_attr_listeners_ = new ArrayList<>();
+ private final List<HttpSessionIdListener> sess_id_listeners_ = new ArrayList<>();
+ private final List<HttpSessionListener> sess_listeners_ = new ArrayList<>();
+
+ private HttpSessionAttributeListener sess_attr_proxy_ = null;
+
+ private final SessionCookieConfig session_cookie_config_ = new UnitSessionCookieConfig();
+ private final Set<SessionTrackingMode> default_session_tracking_modes_ = new HashSet<>();
+ private Set<SessionTrackingMode> session_tracking_modes_ = default_session_tracking_modes_;
+ private int session_timeout_ = 60;
+
+ private final Map<String, Session> sessions_ = new HashMap<>();
+
+ private static final String WEB_INF = "WEB-INF/";
+ private static final String WEB_INF_CLASSES = WEB_INF + "classes/";
+ private static final String WEB_INF_LIB = WEB_INF + "lib/";
+
+ private class PrefixPattern implements Comparable<PrefixPattern>
+ {
+ public final String pattern;
+ public final ServletReg servlet;
+
+ public PrefixPattern(String p, ServletReg s)
+ {
+ pattern = p;
+ servlet = s;
+ }
+
+ public boolean match(String url)
+ {
+ return url.startsWith(pattern) && (
+ url.length() == pattern.length()
+ || url.charAt(pattern.length()) == '/');
+ }
+
+ @Override
+ public int compareTo(PrefixPattern p)
+ {
+ return p.pattern.length() - pattern.length();
+ }
+ }
+
+ private class StaticServlet extends HttpServlet
+ {
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ doGet(request, response);
+ }
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ String path = null;
+
+ if (request.getDispatcherType() == DispatcherType.INCLUDE) {
+ path = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+ }
+
+ if (path == null) {
+ path = request.getServletPath();
+ }
+
+ /*
+ 10.6 Web Application Archive File
+ ...
+ This directory [META-INF] must not be directly served as
+ content by the container in response to a Web client's request,
+ though its contents are visible to servlet code via the
+ getResource and getResourceAsStream calls on the
+ ServletContext. Also, any requests to access the resources in
+ META-INF directory must be returned with a SC_NOT_FOUND(404)
+ response.
+ */
+ if (request.getDispatcherType() == DispatcherType.REQUEST
+ && (path.equals("/WEB-INF") || path.startsWith("/WEB-INF/")
+ || path.equals("/META-INF") || path.startsWith("/META-INF/")))
+ {
+ response.sendError(response.SC_NOT_FOUND);
+ return;
+ }
+
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+
+ File f = new File(webapp_, path);
+ if (!f.exists()) {
+ if (request.getDispatcherType() == DispatcherType.INCLUDE) {
+ /*
+ 9.3 The Include Method
+ ...
+ If the default servlet is the target of a
+ RequestDispatch.include() and the requested resource
+ does not exist, then the default servlet MUST throw
+ FileNotFoundException.
+ */
+
+ throw new FileNotFoundException();
+ }
+
+ response.sendError(response.SC_NOT_FOUND);
+ return;
+ }
+
+ long ims = request.getDateHeader("If-Modified-Since");
+ long lm = f.lastModified();
+
+ if (lm < ims) {
+ response.sendError(response.SC_NOT_MODIFIED);
+ return;
+ }
+
+ response.setDateHeader("Last-Modified", f.lastModified());
+
+ if (f.isDirectory()) {
+ String url = request.getRequestURL().toString();
+ if (!url.endsWith("/")) {
+ response.setHeader("Location", url + "/");
+ response.sendError(response.SC_FOUND);
+ return;
+ }
+
+ String[] ls = f.list();
+
+ PrintWriter writer = response.getWriter();
+
+ for (String n : ls) {
+ writer.println("<a href=\"" + n + "\">" + n + "</a><br>");
+ }
+
+ writer.close();
+
+ } else {
+ response.setContentLengthLong(f.length());
+
+ InputStream is = new FileInputStream(f);
+ byte[] buffer = new byte[response.getBufferSize()];
+ ServletOutputStream os = response.getOutputStream();
+ while (true) {
+ int read = is.read(buffer);
+ if (read == -1) {
+ break;
+ }
+ os.write(buffer, 0, read);
+ }
+
+ os.close();
+ }
+ }
+ }
+
+ public static Context start(String webapp, URL[] classpaths)
+ throws Exception
+ {
+ Context ctx = new Context();
+
+ ctx.loadApp(webapp, classpaths);
+ ctx.initialized();
+
+ return ctx;
+ }
+
+ public Context()
+ {
+ default_session_tracking_modes_.add(SessionTrackingMode.COOKIE);
+
+ context_path_ = System.getProperty("nginx.unit.context.path", "").trim();
+
+ if (context_path_.endsWith("/")) {
+ context_path_ = context_path_.substring(0, context_path_.length() - 1);
+ }
+
+ if (!context_path_.isEmpty() && !context_path_.startsWith("/")) {
+ context_path_ = "/" + context_path_;
+ }
+
+ if (context_path_.isEmpty()) {
+ session_cookie_config_.setPath("/");
+ } else {
+ session_cookie_config_.setPath(context_path_);
+ }
+ }
+
+ public void loadApp(String webapp, URL[] classpaths)
+ throws Exception
+ {
+ File root = new File(webapp);
+ if (!root.exists()) {
+ throw new FileNotFoundException(
+ "Unable to determine code source archive from " + root);
+ }
+
+ ArrayList<URL> url_list = new ArrayList<>();
+
+ for (URL u : classpaths) {
+ url_list.add(u);
+ }
+
+ if (!root.isDirectory()) {
+ root = extractWar(root);
+ extracted_dir_ = root;
+ }
+
+ webapp_ = root;
+
+ Path tmpDir = Files.createTempDirectory("webapp");
+ temp_dir_ = tmpDir.toFile();
+ setAttribute(ServletContext.TEMPDIR, temp_dir_);
+
+ File web_inf_classes = new File(root, WEB_INF_CLASSES);
+ if (web_inf_classes.exists() && web_inf_classes.isDirectory()) {
+ url_list.add(new URL("file:" + root.getAbsolutePath() + "/" + WEB_INF_CLASSES));
+ }
+
+ File lib = new File(root, WEB_INF_LIB);
+ File[] libs = lib.listFiles();
+
+ if (libs != null) {
+ for (File l : libs) {
+ url_list.add(new URL("file:" + l.getAbsolutePath()));
+ }
+ }
+
+ URL[] urls = new URL[url_list.size()];
+
+ for (int i = 0; i < url_list.size(); i++) {
+ urls[i] = url_list.get(i);
+ trace("archives: " + urls[i]);
+ }
+
+ String custom_listener = System.getProperty("nginx.unit.context.listener", "").trim();
+ if (!custom_listener.isEmpty()) {
+ pending_listener_classnames_.add(custom_listener);
+ }
+
+ processWebXml(root);
+
+ loader_ = new AppClassLoader(urls,
+ Context.class.getClassLoader().getParent());
+
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(loader_);
+
+ try {
+ for (String listener_classname : pending_listener_classnames_) {
+ addListener(listener_classname);
+ }
+
+ ScanResult scan_res = null;
+
+ if (!metadata_complete_) {
+ ClassGraph classgraph = new ClassGraph()
+ //.verbose()
+ .overrideClassLoaders(loader_)
+ .ignoreParentClassLoaders()
+ .enableClassInfo()
+ .enableAnnotationInfo()
+ //.enableSystemPackages()
+ .whitelistModules("javax.*")
+ //.enableAllInfo()
+ ;
+
+ String verbose = System.getProperty("nginx.unit.context.classgraph.verbose", "").trim();
+
+ if (verbose.equals("true")) {
+ classgraph.verbose();
+ }
+
+ scan_res = classgraph.scan();
+
+ loadInitializers(scan_res);
+ }
+
+ if (!metadata_complete_) {
+ scanClasses(scan_res);
+ }
+
+ /*
+ 8.1.6 Other annotations / conventions
+ ...
+ By default all applications will have index.htm(l) and index.jsp
+ in the list of welcome-file-list. The descriptor may to be used
+ to override these default settings.
+ */
+ if (!welcome_files_list_found_) {
+ welcome_files_.add("index.htm");
+ welcome_files_.add("index.html");
+ welcome_files_.add("index.jsp");
+ }
+
+ ServletReg jsp_servlet = name2servlet_.get("jsp");
+ if (jsp_servlet == null) {
+ jsp_servlet = new ServletReg("jsp", JspServlet.class);
+ jsp_servlet.system_jsp_servlet_ = true;
+ servlets_.add(jsp_servlet);
+ name2servlet_.put("jsp", jsp_servlet);
+ }
+
+ if (jsp_servlet.getClassName() == null) {
+ jsp_servlet.setClass(JspServlet.class);
+ jsp_servlet.system_jsp_servlet_ = true;
+ }
+
+ if (jsp_servlet.patterns_.isEmpty()) {
+ parseURLPattern("*.jsp", jsp_servlet);
+ parseURLPattern("*.jspx", jsp_servlet);
+ }
+
+ ServletReg def_servlet = name2servlet_.get("default");
+ if (def_servlet == null) {
+ def_servlet = new ServletReg("default", new StaticServlet());
+ def_servlet.servlet_ = new StaticServlet();
+ servlets_.add(def_servlet);
+ name2servlet_.put("default", def_servlet);
+ }
+
+ if (def_servlet.getClassName() == null) {
+ def_servlet.setClass(StaticServlet.class);
+ def_servlet.servlet_ = new StaticServlet();
+ }
+
+ system_default_servlet_ = def_servlet;
+
+ for (PrefixPattern p : prefix_patterns_) {
+ /*
+ Optimization: add prefix patterns to exact2servlet_ map.
+ This should not affect matching result because full path
+ is the longest matched prefix.
+ */
+ if (!exact2servlet_.containsKey(p.pattern)) {
+ trace("adding prefix pattern " + p.pattern + " to exact patterns map");
+ exact2servlet_.put(p.pattern, p.servlet);
+ }
+ }
+
+ Collections.sort(prefix_patterns_);
+ } finally {
+ Thread.currentThread().setContextClassLoader(old);
+ }
+ }
+
+ private static class AppClassLoader extends URLClassLoader
+ {
+ static {
+ ClassLoader.registerAsParallelCapable();
+ }
+
+ private final static String[] system_prefix =
+ {
+ "java/", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
+ "javax/", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
+ "org/w3c/", // needed by javax.xml
+ "org/xml/", // needed by javax.xml
+ };
+
+ private ClassLoader system_loader;
+
+ public AppClassLoader(URL[] urls, ClassLoader parent)
+ {
+ super(urls, parent);
+
+ ClassLoader j = String.class.getClassLoader();
+ if (j == null) {
+ j = getSystemClassLoader();
+ while (j.getParent() != null) {
+ j = j.getParent();
+ }
+ }
+ system_loader = j;
+ }
+
+ private boolean isSystemPath(String path)
+ {
+ int i = Arrays.binarySearch(system_prefix, path);
+
+ if (i >= 0) {
+ return true;
+ }
+
+ i = -i - 1;
+
+ if (i > 0) {
+ return path.startsWith(system_prefix[i - 1]);
+ }
+
+ return false;
+ }
+
+ @Override
+ public URL getResource(String name)
+ {
+ URL res;
+
+ String s = "getResource: " + name;
+ trace(0, s, s.length());
+
+ /*
+ This is a required for compatibility with Tomcat which
+ stores all resources prefixed with '/' and application code
+ may try to get resource with leading '/' (like Jira). Jetty
+ also has such workaround in WebAppClassLoader.getResource().
+ */
+ if (name.startsWith("/")) {
+ name = name.substring(1);
+ }
+
+ if (isSystemPath(name)) {
+ return super.getResource(name);
+ }
+
+ res = system_loader.getResource(name);
+ if (res != null) {
+ return res;
+ }
+
+ res = findResource(name);
+ if (res != null) {
+ return res;
+ }
+
+ return super.getResource(name);
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ synchronized (this) {
+ Class<?> res = findLoadedClass(name);
+ if (res != null) {
+ return res;
+ }
+
+ try {
+ res = system_loader.loadClass(name);
+
+ if (resolve) {
+ resolveClass(res);
+ }
+
+ return res;
+ } catch (ClassNotFoundException e) {
+ }
+
+ String path = name.replace('.', '/').concat(".class");
+
+ if (isSystemPath(path)) {
+ return super.loadClass(name, resolve);
+ }
+
+ URL url = findResource(path);
+
+ if (url != null) {
+ res = super.findClass(name);
+
+ if (resolve) {
+ resolveClass(res);
+ }
+
+ return res;
+ }
+
+ return super.loadClass(name, resolve);
+ }
+
+ }
+ }
+
+ private File extractWar(File war) throws IOException
+ {
+ Path tmpDir = Files.createTempDirectory("webapp");
+
+ JarFile jf = new JarFile(war);
+
+ for (Enumeration<JarEntry> en = jf.entries(); en.hasMoreElements();) {
+ JarEntry e = en.nextElement();
+ long mod_time = e.getTime();
+ Path ep = tmpDir.resolve(e.getName());
+ Path p;
+ if (e.isDirectory()) {
+ p = ep;
+ } else {
+ p = ep.getParent();
+ }
+
+ if (!p.toFile().isDirectory()) {
+ Files.createDirectories(p);
+ }
+
+ if (!e.isDirectory()) {
+ Files.copy(jf.getInputStream(e), ep,
+ StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ if (mod_time > 0) {
+ ep.toFile().setLastModified(mod_time);
+ }
+ }
+
+ return tmpDir.toFile();
+ }
+
+ private class CtxFilterChain implements FilterChain
+ {
+ private int filter_index_ = 0;
+ private final ServletReg servlet_;
+ private final List<FilterReg> filters_;
+
+ CtxFilterChain(ServletReg servlet, String path, DispatcherType dtype)
+ {
+ servlet_ = servlet;
+
+ List<FilterReg> filters = new ArrayList<>();
+
+ for (FilterMap m : filter_maps_) {
+ if (filters.indexOf(m.filter_) != -1) {
+ continue;
+ }
+
+ if (!m.dtypes_.contains(dtype)) {
+ continue;
+ }
+
+ if (m.pattern_.match(path)) {
+ filters.add(m.filter_);
+
+ trace("add filter (matched): " + m.filter_.getName());
+ }
+ }
+
+ for (FilterMap m : servlet.filters_) {
+ if (filters.indexOf(m.filter_) != -1) {
+ continue;
+ }
+
+ if (!m.dtypes_.contains(dtype)) {
+ continue;
+ }
+
+ filters.add(m.filter_);
+
+ trace("add filter (servlet): " + m.filter_.getName());
+ }
+
+ filters_ = filters;
+ }
+
+ @Override
+ public void doFilter (ServletRequest request, ServletResponse response)
+ throws IOException, ServletException
+ {
+ if (filter_index_ < filters_.size()) {
+ filters_.get(filter_index_++).filter_.doFilter(request, response, this);
+
+ return;
+ }
+
+ servlet_.service(request, response);
+ }
+ }
+
+ private ServletReg findServlet(String path, DynamicPathRequest req)
+ {
+ /*
+ 12.1 Use of URL Paths
+ ...
+ 1. The container will try to find an exact match of the path of the
+ request to the path of the servlet. A successful match selects
+ the servlet.
+ */
+ ServletReg servlet = exact2servlet_.get(path);
+ if (servlet != null) {
+ trace("findServlet: '" + path + "' exact matched pattern");
+ req.setServletPath(path, null);
+ return servlet;
+ }
+
+ /*
+ 2. The container will recursively try to match the longest
+ path-prefix. This is done by stepping down the path tree a
+ directory at a time, using the '/' character as a path separator.
+ The longest match determines the servlet selected.
+ */
+ for (PrefixPattern p : prefix_patterns_) {
+ if (p.match(path)) {
+ trace("findServlet: '" + path + "' matched prefix pattern '" + p.pattern + "'");
+ if (p.pattern.length() == path.length()) {
+ log("findServlet: WARNING: it is expected '" + path + "' exactly matches " + p.pattern);
+ req.setServletPath(path, p.pattern, null);
+ } else {
+ req.setServletPath(path, p.pattern, path.substring(p.pattern.length()));
+ }
+ return p.servlet;
+ }
+ }
+
+ /*
+ 3. If the last segment in the URL path contains an extension
+ (e.g. .jsp), the servlet container will try to match a servlet
+ that handles requests for the extension. An extension is defined
+ as the part of the last segment after the last '.' character.
+ */
+ int suffix_start = path.lastIndexOf('.');
+ if (suffix_start != -1) {
+ String suffix = path.substring(suffix_start);
+ servlet = suffix2servlet_.get(suffix);
+ if (servlet != null) {
+ trace("findServlet: '" + path + "' matched suffix pattern");
+ req.setServletPath(path, null);
+ return servlet;
+ }
+ }
+
+ /*
+ 4. If neither of the previous three rules result in a servlet match,
+ the container will attempt to serve content appropriate for the
+ resource requested. If a "default" servlet is defined for the
+ application, it will be used. ...
+ */
+ if (default_servlet_ != null) {
+ trace("findServlet: '" + path + "' matched default servlet");
+ req.setServletPath(path, null);
+ return default_servlet_;
+ }
+
+ trace("findServlet: '" + path + "' no servlet found");
+
+ /*
+ 10.10 Welcome Files
+ ...
+ If a Web container receives a valid partial request, the Web
+ container must examine the welcome file list defined in the
+ deployment descriptor.
+ ...
+ */
+ if (path.endsWith("/")) {
+
+ /*
+ The Web server must append each welcome file in the order
+ specified in the deployment descriptor to the partial request
+ and check whether a static resource in the WAR is mapped to
+ that request URI.
+ */
+ for (String wf : welcome_files_) {
+ String wpath = path + wf;
+
+ File f = new File(webapp_, wpath.substring(1));
+ if (!f.exists()) {
+ continue;
+ }
+
+ trace("findServlet: '" + path + "' found static welcome "
+ + "file '" + wf + "'");
+
+ /*
+ Even if static file found, we should try to find matching
+ servlet for JSP serving etc.
+ */
+ servlet = findWelcomeServlet(wpath, true, req);
+ if (servlet != null) {
+ return servlet;
+ }
+
+ req.setServletPath(wpath, null);
+
+ return system_default_servlet_;
+ }
+
+ /*
+ If no match is found, the Web server MUST again append each
+ welcome file in the order specified in the deployment
+ descriptor to the partial request and check if a servlet is
+ mapped to that request URI. The Web container must send the
+ request to the first resource in the WAR that matches.
+ */
+ for (String wf : welcome_files_) {
+ String wpath = path + wf;
+
+ servlet = findWelcomeServlet(wpath, false, req);
+ if (servlet != null) {
+ return servlet;
+ }
+ }
+ }
+
+ trace("findServlet: '" + path + "' fallback to system default servlet");
+ req.setServletPath(path, null);
+
+ return system_default_servlet_;
+ }
+
+ private ServletReg findWelcomeServlet(String path, boolean exists,
+ DynamicPathRequest req)
+ {
+ ServletReg servlet = exact2servlet_.get(path);
+ if (servlet != null) {
+ trace("findWelcomeServlet: '" + path + "' exact matched pattern");
+ req.setServletPath(path, null);
+
+ return servlet;
+ }
+
+ int suffix_start = path.lastIndexOf('.');
+ if (suffix_start == -1) {
+ return null;
+ }
+
+ String suffix = path.substring(suffix_start);
+ servlet = suffix2servlet_.get(suffix);
+ if (servlet == null) {
+ return null;
+ }
+
+ trace("findWelcomeServlet: '" + path + "' matched suffix pattern");
+
+ /*
+ If we want to show the directory content when
+ index.jsp is absent, then we have to check file
+ presence here. Otherwise user will get 404.
+ */
+
+ if (servlet.system_jsp_servlet_ && !exists) {
+ trace("findWelcomeServlet: '" + path + "' not exists");
+ return null;
+ }
+
+ req.setServletPath(path, null);
+
+ return servlet;
+ }
+
+ public void service(Request req, Response resp)
+ throws ServletException, IOException
+ {
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(loader_);
+
+ ServletRequestEvent sre = null;
+
+ try {
+ if (!req_init_listeners_.isEmpty()) {
+ sre = new ServletRequestEvent(this, req);
+
+ for (ServletRequestListener l : req_init_listeners_) {
+ l.requestInitialized(sre);
+ }
+ }
+
+ URI uri = new URI(req.getRequestURI());
+ String path = uri.getPath();
+
+ if (!path.startsWith(context_path_)
+ || (path.length() > context_path_.length()
+ && path.charAt(context_path_.length()) != '/'))
+ {
+ trace("service: '" + path + "' not started with '" + context_path_ + "'");
+
+ resp.sendError(resp.SC_NOT_FOUND);
+ return;
+ }
+
+ if (path.equals(context_path_)) {
+ String url = req.getRequestURL().toString();
+ if (!url.endsWith("/")) {
+ resp.setHeader("Location", url + "/");
+ resp.sendError(resp.SC_FOUND);
+ return;
+ }
+ }
+
+ path = path.substring(context_path_.length());
+
+ ServletReg servlet = findServlet(path, req);
+
+ FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.REQUEST);
+
+ fc.doFilter(req, resp);
+
+ Object code = req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
+ if (code != null && code instanceof Integer) {
+ handleStatusCode((Integer) code, req, resp);
+ }
+ } catch (Throwable e) {
+ trace("service: caught " + e);
+
+ try {
+ if (!resp.isCommitted() && !exception2location_.isEmpty()) {
+ handleException(e, req, resp);
+ }
+
+ if (!resp.isCommitted()) {
+ resp.reset();
+ resp.setStatus(resp.SC_INTERNAL_SERVER_ERROR);
+ resp.setContentType("text/plain");
+
+ PrintWriter w = resp.getWriter();
+ w.println("Unhandled exception: " + e);
+ e.printStackTrace(w);
+
+ w.close();
+ }
+ } finally {
+ throw new ServletException(e);
+ }
+ } finally {
+ resp.flushBuffer();
+
+ try {
+ if (!req_destroy_listeners_.isEmpty()) {
+ for (ServletRequestListener l : req_destroy_listeners_) {
+ l.requestDestroyed(sre);
+ }
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(old);
+ }
+ }
+ }
+
+ private void handleException(Throwable e, Request req, Response resp)
+ throws ServletException, IOException
+ {
+ String location;
+
+ Class<?> cls = e.getClass();
+ while (cls != null && !cls.equals(Throwable.class)) {
+ location = exception2location_.get(cls.getName());
+
+ if (location != null) {
+ trace("Exception " + e + " matched. Error page location: " + location);
+
+ req.setAttribute_(RequestDispatcher.ERROR_EXCEPTION, e);
+ req.setAttribute_(RequestDispatcher.ERROR_EXCEPTION_TYPE, e.getClass());
+ req.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, req.getRequestURI());
+ req.setAttribute_(RequestDispatcher.ERROR_STATUS_CODE, resp.SC_INTERNAL_SERVER_ERROR);
+
+ handleError(location, req, resp);
+
+ return;
+ }
+
+ cls = cls.getSuperclass();
+ }
+
+ if (ServletException.class.isAssignableFrom(e.getClass())) {
+ ServletException se = (ServletException) e;
+
+ handleException(se.getRootCause(), req, resp);
+ }
+ }
+
+ private void handleStatusCode(int code, Request req, Response resp)
+ throws ServletException, IOException
+ {
+ String location;
+
+ location = error2location_.get(code);
+
+ if (location != null) {
+ trace("Status " + code + " matched. Error page location: " + location);
+
+ req.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, req.getRequestURI());
+
+ handleError(location, req, resp);
+ }
+ }
+
+ public void handleError(String location, Request req, Response resp)
+ throws ServletException, IOException
+ {
+ try {
+ log("handleError: " + location);
+
+ String filter_path = req.getFilterPath();
+ String servlet_path = req.getServletPath();
+ String path_info = req.getPathInfo();
+ String req_uri = req.getRequestURI();
+ DispatcherType dtype = req.getDispatcherType();
+
+ URI uri;
+
+ if (location.startsWith("/")) {
+ uri = new URI(context_path_ + location);
+ } else {
+ uri = new URI(req_uri).resolve(location);
+ }
+
+ req.setRequestURI(uri.getRawPath());
+ req.setDispatcherType(DispatcherType.ERROR);
+
+ String path = uri.getPath().substring(context_path_.length());
+
+ ServletReg servlet = findServlet(path, req);
+
+ FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.ERROR);
+
+ fc.doFilter(req, resp);
+
+ req.setServletPath(filter_path, servlet_path, path_info);
+ req.setRequestURI(req_uri);
+ req.setDispatcherType(dtype);
+ } catch (URISyntaxException e) {
+ throw new ServletException(e);
+ }
+ }
+
+ private void processWebXml(File root) throws Exception
+ {
+ if (root.isDirectory()) {
+ File web_xml = new File(root, "WEB-INF/web.xml");
+ if (web_xml.exists()) {
+ trace("start: web.xml file found");
+
+ InputStream is = new FileInputStream(web_xml);
+
+ processWebXml(is);
+
+ is.close();
+ }
+ } else {
+ JarFile jf = new JarFile(root);
+ ZipEntry ze = jf.getEntry("WEB-INF/web.xml");
+
+ if (ze == null) {
+ trace("start: web.xml entry NOT found");
+ } else {
+ trace("start: web.xml entry found");
+
+ processWebXml(jf.getInputStream(ze));
+ }
+
+ jf.close();
+ }
+ }
+
+ private void processWebXml(InputStream is)
+ throws ParserConfigurationException, SAXException, IOException
+ {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+
+ Document doc = builder.parse(is);
+
+ Element doc_elem = doc.getDocumentElement();
+ String doc_elem_name = doc_elem.getNodeName();
+ if (!doc_elem_name.equals("web-app")) {
+ throw new RuntimeException("Invalid web.xml: 'web-app' element expected, not '" + doc_elem_name + "'");
+ }
+
+ metadata_complete_ = doc_elem.getAttribute("metadata-complete").equals("true");
+ app_version_ = doc_elem.getAttribute("version");
+
+ NodeList welcome_file_lists = doc_elem.getElementsByTagName("welcome-file-list");
+
+ if (welcome_file_lists.getLength() > 0) {
+ welcome_files_list_found_ = true;
+ }
+
+ for (int i = 0; i < welcome_file_lists.getLength(); i++) {
+ Element list_el = (Element) welcome_file_lists.item(i);
+ NodeList files = list_el.getElementsByTagName("welcome-file");
+ for (int j = 0; j < files.getLength(); j++) {
+ Node node = files.item(j);
+ String wf = node.getTextContent().trim();
+
+ /*
+ 10.10 Welcome Files
+ ...
+ The welcome file list is an ordered list of partial URLs
+ with no trailing or leading /.
+ */
+
+ if (wf.startsWith("/") || wf.endsWith("/")) {
+ log("invalid welcome file: " + wf);
+ continue;
+ }
+
+ welcome_files_.add(wf);
+ }
+ }
+
+ NodeList context_params = doc_elem.getElementsByTagName("context-param");
+ for (int i = 0; i < context_params.getLength(); i++) {
+ processXmlInitParam(this, (Element) context_params.item(i));
+ }
+
+ NodeList filters = doc_elem.getElementsByTagName("filter");
+
+ for (int i = 0; i < filters.getLength(); i++) {
+ Element filter_el = (Element) filters.item(i);
+ NodeList names = filter_el.getElementsByTagName("filter-name");
+ if (names == null || names.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'filter-name' tag not found");
+ }
+
+ String filter_name = names.item(0).getTextContent().trim();
+ trace("filter-name=" + filter_name);
+
+ FilterReg reg = new FilterReg(filter_name);
+
+ NodeList child_nodes = filter_el.getChildNodes();
+ for(int j = 0; j < child_nodes.getLength(); j++) {
+ Node child_node = child_nodes.item(j);
+ String tag_name = child_node.getNodeName();
+
+ if (tag_name.equals("filter-class")) {
+ reg.setClassName(child_node.getTextContent().trim());
+ continue;
+ }
+
+ if (tag_name.equals("async-supported")) {
+ reg.setAsyncSupported(child_node.getTextContent().trim()
+ .equals("true"));
+ continue;
+ }
+
+ if (tag_name.equals("init-param")) {
+ processXmlInitParam(reg, (Element) child_node);
+ continue;
+ }
+ }
+
+ filters_.add(reg);
+ name2filter_.put(filter_name, reg);
+ }
+
+ NodeList filter_mappings = doc_elem.getElementsByTagName("filter-mapping");
+
+ for(int i = 0; i < filter_mappings.getLength(); i++) {
+ Element mapping_el = (Element) filter_mappings.item(i);
+ NodeList names = mapping_el.getElementsByTagName("filter-name");
+ if (names == null || names.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'filter-name' tag not found");
+ }
+
+ String filter_name = names.item(0).getTextContent().trim();
+ trace("filter-name=" + filter_name);
+
+ FilterReg reg = name2filter_.get(filter_name);
+ if (reg == null) {
+ throw new RuntimeException("Invalid web.xml: filter '" + filter_name + "' not found");
+ }
+
+ EnumSet<DispatcherType> dtypes = EnumSet.noneOf(DispatcherType.class);
+ NodeList dispatchers = mapping_el.getElementsByTagName("dispatcher");
+ for (int j = 0; j < dispatchers.getLength(); j++) {
+ Node child_node = dispatchers.item(j);
+ dtypes.add(DispatcherType.valueOf(child_node.getTextContent().trim()));
+ }
+
+ if (dtypes.isEmpty()) {
+ dtypes.add(DispatcherType.REQUEST);
+ }
+
+ boolean match_after = false;
+
+ NodeList child_nodes = mapping_el.getChildNodes();
+ for (int j = 0; j < child_nodes.getLength(); j++) {
+ Node child_node = child_nodes.item(j);
+ String tag_name = child_node.getNodeName();
+
+ if (tag_name.equals("url-pattern")) {
+ reg.addMappingForUrlPatterns(dtypes, match_after, child_node.getTextContent().trim());
+ continue;
+ }
+
+ if (tag_name.equals("servlet-name")) {
+ reg.addMappingForServletNames(dtypes, match_after, child_node.getTextContent().trim());
+ continue;
+ }
+ }
+ }
+
+ NodeList servlets = doc_elem.getElementsByTagName("servlet");
+
+ for (int i = 0; i < servlets.getLength(); i++) {
+ Element servlet_el = (Element) servlets.item(i);
+ NodeList names = servlet_el.getElementsByTagName("servlet-name");
+ if (names == null || names.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'servlet-name' tag not found");
+ }
+
+ String servlet_name = names.item(0).getTextContent().trim();
+ trace("servlet-name=" + servlet_name);
+
+ ServletReg reg = new ServletReg(servlet_name);
+
+ NodeList child_nodes = servlet_el.getChildNodes();
+ for(int j = 0; j < child_nodes.getLength(); j++) {
+ Node child_node = child_nodes.item(j);
+ String tag_name = child_node.getNodeName();
+
+ if (tag_name.equals("servlet-class")) {
+ reg.setClassName(child_node.getTextContent().trim());
+ continue;
+ }
+
+ if (tag_name.equals("async-supported")) {
+ reg.setAsyncSupported(child_node.getTextContent().trim()
+ .equals("true"));
+ continue;
+ }
+
+ if (tag_name.equals("init-param")) {
+ processXmlInitParam(reg, (Element) child_node);
+ continue;
+ }
+
+ if (tag_name.equals("load-on-startup")) {
+ reg.setLoadOnStartup(Integer.parseInt(child_node.getTextContent().trim()));
+ continue;
+ }
+ }
+
+ servlets_.add(reg);
+ name2servlet_.put(servlet_name, reg);
+ }
+
+ NodeList servlet_mappings = doc_elem.getElementsByTagName("servlet-mapping");
+
+ for(int i = 0; i < servlet_mappings.getLength(); i++) {
+ Element mapping_el = (Element) servlet_mappings.item(i);
+ NodeList names = mapping_el.getElementsByTagName("servlet-name");
+ if (names == null || names.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'servlet-name' tag not found");
+ }
+
+ String servlet_name = names.item(0).getTextContent().trim();
+ trace("servlet-name=" + servlet_name);
+
+ ServletReg reg = name2servlet_.get(servlet_name);
+ if (reg == null) {
+ throw new RuntimeException("Invalid web.xml: servlet '" + servlet_name + "' not found");
+ }
+
+ NodeList child_nodes = mapping_el.getElementsByTagName("url-pattern");
+ String patterns[] = new String[child_nodes.getLength()];
+ for(int j = 0; j < child_nodes.getLength(); j++) {
+ Node child_node = child_nodes.item(j);
+ patterns[j] = child_node.getTextContent().trim();
+ }
+
+ reg.addMapping(patterns);
+ }
+
+ NodeList listeners = doc_elem.getElementsByTagName("listener");
+
+ for (int i = 0; i < listeners.getLength(); i++) {
+ Element listener_el = (Element) listeners.item(i);
+ NodeList classes = listener_el.getElementsByTagName("listener-class");
+ if (classes == null || classes.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'listener-class' tag not found");
+ }
+
+ String class_name = classes.item(0).getTextContent().trim();
+ trace("listener-class=" + class_name);
+
+ pending_listener_classnames_.add(class_name);
+ }
+
+ NodeList error_pages = doc_elem.getElementsByTagName("error-page");
+
+ for (int i = 0; i < error_pages.getLength(); i++) {
+ Element error_page_el = (Element) error_pages.item(i);
+ NodeList locations = error_page_el.getElementsByTagName("location");
+ if (locations == null || locations.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'location' tag not found");
+ }
+
+ String location = locations.item(0).getTextContent().trim();
+
+ NodeList child_nodes = error_page_el.getChildNodes();
+ for(int j = 0; j < child_nodes.getLength(); j++) {
+ Node child_node = child_nodes.item(j);
+ String tag_name = child_node.getNodeName();
+
+ if (tag_name.equals("exception-type")) {
+ String ex = child_node.getTextContent().trim();
+
+ exception2location_.put(ex, location);
+ trace("error-page: exception " + ex + " -> " + location);
+ continue;
+ }
+
+ if (tag_name.equals("error-code")) {
+ Integer code = Integer.parseInt(child_node.getTextContent().trim());
+
+ error2location_.put(code, location);
+ trace("error-page: code " + code + " -> " + location);
+ continue;
+ }
+ }
+ }
+
+ NodeList session_config = doc_elem.getElementsByTagName("session-config");
+
+ for (int i = 0; i < session_config.getLength(); i++) {
+ Element session_config_el = (Element) session_config.item(i);
+ NodeList session_timeout = session_config_el.getElementsByTagName("session-timeout");
+ if (session_timeout != null) {
+ String timeout = session_timeout.item(0).getTextContent().trim();
+
+ trace("session_timeout: " + timeout);
+ session_timeout_ = Integer.parseInt(timeout);
+ break;
+ }
+ }
+
+ NodeList jsp_configs = doc_elem.getElementsByTagName("jsp-config");
+
+ for (int i = 0; i < jsp_configs.getLength(); i++) {
+ Element jsp_config_el = (Element) jsp_configs.item(i);
+
+ NodeList jsp_nodes = jsp_config_el.getChildNodes();
+
+ for(int j = 0; j < jsp_nodes.getLength(); j++) {
+ Node jsp_node = jsp_nodes.item(j);
+ String tag_name = jsp_node.getNodeName();
+
+ if (tag_name.equals("taglib")) {
+ NodeList tl_nodes = ((Element) jsp_node).getChildNodes();
+ Taglib tl = new Taglib(tl_nodes);
+
+ trace("add taglib");
+
+ taglibs_.add(tl);
+ continue;
+ }
+
+ if (tag_name.equals("jsp-property-group")) {
+ NodeList jpg_nodes = ((Element) jsp_node).getChildNodes();
+ JspPropertyGroup conf = new JspPropertyGroup(jpg_nodes);
+
+ trace("add prop group");
+
+ prop_groups_.add(conf);
+ continue;
+ }
+ }
+ }
+ }
+
+ private static int compareVersion(String ver1, String ver2)
+ {
+ String[] varr1 = ver1.split("\\.");
+ String[] varr2 = ver2.split("\\.");
+
+ int max_len = varr1.length > varr2.length ? varr1.length : varr2.length;
+ for (int i = 0; i < max_len; i++) {
+ int l = i < varr1.length ? Integer.parseInt(varr1[i]) : 0;
+ int r = i < varr2.length ? Integer.parseInt(varr2[i]) : 0;
+
+ int res = l - r;
+
+ if (res != 0) {
+ return res;
+ }
+ }
+
+ return 0;
+ }
+
+ private void processXmlInitParam(InitParams params, Element elem)
+ throws RuntimeException
+ {
+ NodeList n = elem.getElementsByTagName("param-name");
+ if (n == null || n.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'param-name' tag not found");
+ }
+
+ NodeList v = elem.getElementsByTagName("param-value");
+ if (v == null || v.getLength() != 1) {
+ throw new RuntimeException("Invalid web.xml: 'param-value' tag not found");
+ }
+ params.setInitParameter(n.item(0).getTextContent().trim(),
+ v.item(0).getTextContent().trim());
+ }
+
+ private void loadInitializers(ScanResult scan_res)
+ {
+ trace("load initializer(s)");
+
+ ServiceLoader<ServletContainerInitializer> initializers =
+ ServiceLoader.load(ServletContainerInitializer.class, loader_);
+
+ for (ServletContainerInitializer sci : initializers) {
+
+ trace("loadInitializers: initializer: " + sci.getClass().getName());
+
+ HandlesTypes ann = sci.getClass().getAnnotation(HandlesTypes.class);
+ if (ann == null) {
+ trace("loadInitializers: no HandlesTypes annotation");
+ continue;
+ }
+
+ Class<?>[] classes = ann.value();
+ if (classes == null) {
+ trace("loadInitializers: no handles classes");
+ continue;
+ }
+
+ Set<Class<?>> handles_classes = new HashSet<>();
+
+ for (Class<?> c : classes) {
+ trace("loadInitializers: find handles: " + c.getName());
+
+ ClassInfoList handles = c.isInterface()
+ ? scan_res.getClassesImplementing(c.getName())
+ : scan_res.getSubclasses(c.getName());
+
+ for (ClassInfo ci : handles) {
+ if (ci.isInterface()
+ || ci.isAnnotation()
+ || ci.isAbstract())
+ {
+ continue;
+ }
+
+ trace("loadInitializers: handles class: " + ci.getName());
+ handles_classes.add(ci.loadClass());
+ }
+ }
+
+ if (handles_classes.isEmpty()) {
+ trace("loadInitializers: no handles implementations");
+ continue;
+ }
+
+ try {
+ sci.onStartup(handles_classes, this);
+ metadata_complete_ = true;
+ } catch(Exception e) {
+ System.err.println("loadInitializers: exception caught: " + e.toString());
+ }
+ }
+ }
+
+ private void scanClasses(ScanResult scan_res)
+ throws ReflectiveOperationException
+ {
+ ClassInfoList filters = scan_res.getClassesWithAnnotation(WebFilter.class.getName());
+
+ for (ClassInfo ci : filters) {
+ if (ci.isInterface()
+ || ci.isAnnotation()
+ || ci.isAbstract()
+ || !ci.implementsInterface(Filter.class.getName()))
+ {
+ trace("scanClasses: ignoring Filter impl: " + ci.getName());
+ continue;
+ }
+
+ trace("scanClasses: found Filter class: " + ci.getName());
+
+ Class<?> cls = ci.loadClass();
+ if (!Filter.class.isAssignableFrom(cls)) {
+ trace("scanClasses: " + ci.getName() + " cannot be assigned to Filter");
+ continue;
+ }
+
+ WebFilter ann = cls.getAnnotation(WebFilter.class);
+
+ if (ann == null) {
+ trace("scanClasses: no WebFilter annotation for " + ci.getName());
+ continue;
+ }
+
+ String filter_name = ann.filterName();
+
+ if (filter_name.isEmpty()) {
+ filter_name = ci.getName();
+ }
+
+ FilterReg reg = name2filter_.get(filter_name);
+
+ if (reg == null) {
+ reg = new FilterReg(filter_name, cls);
+ filters_.add(reg);
+ name2filter_.put(filter_name, reg);
+ } else {
+ reg.setClass(cls);
+ }
+
+ EnumSet<DispatcherType> dtypes = EnumSet.noneOf(DispatcherType.class);
+ DispatcherType[] dispatchers = ann.dispatcherTypes();
+ for (DispatcherType d : dispatchers) {
+ dtypes.add(d);
+ }
+
+ if (dtypes.isEmpty()) {
+ dtypes.add(DispatcherType.REQUEST);
+ }
+
+ boolean match_after = false;
+
+ reg.addMappingForUrlPatterns(dtypes, match_after, ann.value());
+ reg.addMappingForUrlPatterns(dtypes, match_after, ann.urlPatterns());
+ reg.addMappingForServletNames(dtypes, match_after, ann.servletNames());
+
+ for (WebInitParam p : ann.initParams()) {
+ reg.setInitParameter(p.name(), p.value());
+ }
+
+ reg.setAsyncSupported(ann.asyncSupported());
+ }
+
+ ClassInfoList servlets = scan_res.getClassesWithAnnotation(WebServlet.class.getName());
+
+ for (ClassInfo ci : servlets) {
+ if (ci.isInterface()
+ || ci.isAnnotation()
+ || ci.isAbstract()
+ || !ci.extendsSuperclass(HttpServlet.class.getName()))
+ {
+ trace("scanClasses: ignoring HttpServlet subclass: " + ci.getName());
+ continue;
+ }
+
+ trace("scanClasses: found HttpServlet class: " + ci.getName());
+
+ Class<?> cls = ci.loadClass();
+ if (!HttpServlet.class.isAssignableFrom(cls)) {
+ trace("scanClasses: " + ci.getName() + " cannot be assigned to HttpFilter");
+ continue;
+ }
+
+ WebServlet ann = cls.getAnnotation(WebServlet.class);
+
+ if (ann == null) {
+ trace("scanClasses: no WebServlet annotation");
+ continue;
+ }
+
+ String servlet_name = ann.name();
+
+ if (servlet_name.isEmpty()) {
+ servlet_name = ci.getName();
+ }
+
+ ServletReg reg = name2servlet_.get(servlet_name);
+
+ if (reg == null) {
+ reg = new ServletReg(servlet_name, cls);
+ servlets_.add(reg);
+ name2servlet_.put(servlet_name, reg);
+ } else {
+ reg.setClass(cls);
+ }
+
+ reg.addMapping(ann.value());
+ reg.addMapping(ann.urlPatterns());
+
+ for (WebInitParam p : ann.initParams()) {
+ reg.setInitParameter(p.name(), p.value());
+ }
+
+ reg.setAsyncSupported(ann.asyncSupported());
+ }
+
+
+ ClassInfoList lstnrs = scan_res.getClassesWithAnnotation(WebListener.class.getName());
+
+ for (ClassInfo ci : lstnrs) {
+ if (ci.isInterface()
+ || ci.isAnnotation()
+ || ci.isAbstract())
+ {
+ trace("scanClasses: listener impl: " + ci.getName());
+ continue;
+ }
+
+ trace("scanClasses: listener class: " + ci.getName());
+
+ if (listener_classnames_.contains(ci.getName())) {
+ trace("scanClasses: " + ci.getName() + " already added as listener");
+ continue;
+ }
+
+ Class<?> cls = ci.loadClass();
+ Class<?> lclass = null;
+ for (Class<?> c : LISTENER_TYPES) {
+ if (c.isAssignableFrom(cls)) {
+ lclass = c;
+ break;
+ }
+ }
+
+ if (lclass == null) {
+ log("scanClasses: " + ci.getName() + " implements none of known listener interfaces");
+ continue;
+ }
+
+ WebListener ann = cls.getAnnotation(WebListener.class);
+
+ if (ann == null) {
+ log("scanClasses: no WebListener annotation");
+ continue;
+ }
+
+ Constructor<?> ctor = cls.getConstructor();
+ EventListener listener = (EventListener) ctor.newInstance();
+
+ addListener(listener);
+
+ listener_classnames_.add(ci.getName());
+ }
+ }
+
+ public void stop() throws IOException
+ {
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(loader_);
+
+ try {
+ for (ServletReg s : servlets_) {
+ s.destroy();
+ }
+
+ for (FilterReg f : filters_) {
+ f.destroy();
+ }
+
+ if (!destroy_listeners_.isEmpty()) {
+ ServletContextEvent event = new ServletContextEvent(this);
+ for (ServletContextListener listener : destroy_listeners_) {
+ listener.contextDestroyed(event);
+ }
+ }
+
+ if (extracted_dir_ != null) {
+ removeDir(extracted_dir_);
+ }
+
+ if (temp_dir_ != null) {
+ removeDir(temp_dir_);
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(old);
+ }
+ }
+
+ private void removeDir(File dir) throws IOException
+ {
+ Files.walkFileTree(dir.toPath(),
+ new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult postVisitDirectory(
+ Path dir, IOException exc) throws IOException {
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(
+ Path file, BasicFileAttributes attrs)
+ throws IOException {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ private class CtxInitParams implements InitParams
+ {
+ private final Map<String, String> init_params_ =
+ new HashMap<String, String>();
+
+ public boolean setInitParameter(String name, String value)
+ {
+ trace("CtxInitParams.setInitParameter " + name + " = " + value);
+
+ return init_params_.putIfAbsent(name, value) == null;
+ }
+
+ public String getInitParameter(String name)
+ {
+ trace("CtxInitParams.getInitParameter for " + name);
+
+ return init_params_.get(name);
+ }
+
+ public Set<String> setInitParameters(Map<String, String> initParameters)
+ {
+ // illegalStateIfContextStarted();
+ Set<String> clash = null;
+ for (Map.Entry<String, String> entry : initParameters.entrySet())
+ {
+ if (entry.getKey() == null) {
+ throw new IllegalArgumentException("init parameter name required");
+ }
+
+ if (entry.getValue() == null) {
+ throw new IllegalArgumentException("non-null value required for init parameter " + entry.getKey());
+ }
+
+ if (init_params_.get(entry.getKey()) != null)
+ {
+ if (clash == null)
+ clash = new HashSet<String>();
+ clash.add(entry.getKey());
+ }
+
+ trace("CtxInitParams.setInitParameters " + entry.getKey() + " = " + entry.getValue());
+ }
+
+ if (clash != null) {
+ return clash;
+ }
+
+ init_params_.putAll(initParameters);
+ return Collections.emptySet();
+ }
+
+ public Map<String, String> getInitParameters()
+ {
+ trace("CtxInitParams.getInitParameters");
+ return init_params_;
+ }
+
+ public Enumeration<String> getInitParameterNames()
+ {
+ return Collections.enumeration(init_params_.keySet());
+ }
+ }
+
+ private class NamedReg extends CtxInitParams
+ implements Registration
+ {
+ private final String name_;
+ private String class_name_;
+
+ public NamedReg(String name)
+ {
+ name_ = name;
+ }
+
+ public NamedReg(String name, String class_name)
+ {
+ name_ = name;
+ class_name_ = class_name;
+ }
+
+ @Override
+ public String getName()
+ {
+ return name_;
+ }
+
+ @Override
+ public String getClassName()
+ {
+ return class_name_;
+ }
+
+ public void setClassName(String class_name)
+ {
+ class_name_ = class_name;
+ }
+ }
+
+ private class ServletReg extends NamedReg
+ implements ServletRegistration.Dynamic, ServletConfig
+ {
+ private Class<?> servlet_class_;
+ private Servlet servlet_;
+ private String role_;
+ private boolean async_supported_ = false;
+ private final List<String> patterns_ = new ArrayList<>();
+ private int load_on_startup_ = -1;
+ private boolean initialized_ = false;
+ private final List<FilterMap> filters_ = new ArrayList<>();
+ private boolean system_jsp_servlet_ = false;
+
+ public ServletReg(String name, Class<?> servlet_class)
+ {
+ super(name, servlet_class.getName());
+ servlet_class_ = servlet_class;
+ }
+
+ public ServletReg(String name, Servlet servlet)
+ {
+ super(name, servlet.getClass().getName());
+ servlet_ = servlet;
+ }
+
+ public ServletReg(String name, String servlet_class_name)
+ {
+ super(name, servlet_class_name);
+ }
+
+ public ServletReg(String name)
+ {
+ super(name);
+ }
+
+ private void init() throws ServletException
+ {
+ if (initialized_) {
+ return;
+ }
+
+ trace("ServletReg.init(): " + getName());
+
+ if (system_jsp_servlet_) {
+ JasperInitializer ji = new JasperInitializer();
+
+ ji.onStartup(Collections.emptySet(), Context.this);
+ }
+
+ if (servlet_ == null) {
+ try {
+ if (servlet_class_ == null) {
+ servlet_class_ = loader_.loadClass(getClassName());
+ }
+
+ Constructor<?> ctor = servlet_class_.getConstructor();
+ servlet_ = (Servlet) ctor.newInstance();
+ } catch(Exception e) {
+ log("ServletReg.init() failed " + e);
+ throw new ServletException(e);
+ }
+ }
+
+ servlet_.init((ServletConfig) this);
+
+ initialized_ = true;
+ }
+
+ public void startup() throws ServletException
+ {
+ if (load_on_startup_ < 0) {
+ return;
+ }
+
+ init();
+ }
+
+ public void destroy()
+ {
+ if (initialized_) {
+ servlet_.destroy();
+ }
+ }
+
+ public void setClassName(String class_name) throws IllegalStateException
+ {
+ if (servlet_ != null
+ || servlet_class_ != null
+ || getClassName() != null)
+ {
+ throw new IllegalStateException("Class already initialized");
+ }
+
+ super.setClassName(class_name);
+ }
+
+ public void setClass(Class<?> servlet_class)
+ throws IllegalStateException
+ {
+ if (servlet_ != null
+ || servlet_class_ != null
+ || getClassName() != null)
+ {
+ throw new IllegalStateException("Class already initialized");
+ }
+
+ super.setClassName(servlet_class.getName());
+ servlet_class_ = servlet_class;
+ }
+
+ public void service(ServletRequest request, ServletResponse response)
+ throws ServletException, IOException
+ {
+ init();
+
+ servlet_.service(request, response);
+ }
+
+ public void addFilter(FilterMap fmap)
+ {
+ filters_.add(fmap);
+ }
+
+ @Override
+ public Set<String> addMapping(String... urlPatterns)
+ {
+ checkContextState();
+
+ Set<String> clash = null;
+ for (String pattern : urlPatterns) {
+ trace("ServletReg.addMapping: " + pattern);
+
+ if (pattern2servlet_.containsKey(pattern)) {
+ if (clash == null) {
+ clash = new HashSet<String>();
+ }
+ clash.add(pattern);
+ }
+ }
+
+ /* if there were any clashes amongst the urls, return them */
+ if (clash != null) {
+ return clash;
+ }
+
+ for (String pattern : urlPatterns) {
+ patterns_.add(pattern);
+ pattern2servlet_.put(pattern, this);
+ parseURLPattern(pattern, this);
+ }
+
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Collection<String> getMappings()
+ {
+ trace("ServletReg.getMappings");
+ return patterns_;
+ }
+
+ @Override
+ public String getRunAsRole()
+ {
+ return role_;
+ }
+
+ @Override
+ public void setLoadOnStartup(int loadOnStartup)
+ {
+ checkContextState();
+
+ trace("ServletReg.setLoadOnStartup: " + loadOnStartup);
+ load_on_startup_ = loadOnStartup;
+ }
+
+ @Override
+ public Set<String> setServletSecurity(ServletSecurityElement constraint)
+ {
+ log("ServletReg.setServletSecurity");
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void setMultipartConfig(
+ MultipartConfigElement multipartConfig)
+ {
+ log("ServletReg.setMultipartConfig");
+ }
+
+ @Override
+ public void setRunAsRole(String roleName)
+ {
+ log("ServletReg.setRunAsRole: " + roleName);
+ role_ = roleName;
+ }
+
+ @Override
+ public void setAsyncSupported(boolean isAsyncSupported)
+ {
+ log("ServletReg.setAsyncSupported: " + isAsyncSupported);
+ async_supported_ = isAsyncSupported;
+ }
+
+ @Override
+ public String getServletName()
+ {
+ return getName();
+ }
+
+ @Override
+ public ServletContext getServletContext()
+ {
+ return (ServletContext) Context.this;
+ }
+ }
+
+ public void checkContextState() throws IllegalStateException
+ {
+ if (ctx_initialized_) {
+ throw new IllegalStateException("Context already initialized");
+ }
+ }
+
+ public void parseURLPattern(String p, ServletReg servlet)
+ throws IllegalArgumentException
+ {
+ URLPattern pattern = parseURLPattern(p);
+
+ switch (pattern.type_) {
+ case PREFIX:
+ prefix_patterns_.add(new PrefixPattern(pattern.pattern_, servlet));
+ return;
+
+ case SUFFIX:
+ suffix2servlet_.put(pattern.pattern_, servlet);
+ return;
+
+ case EXACT:
+ exact2servlet_.put(pattern.pattern_, servlet);
+ return;
+
+ case DEFAULT:
+ default_servlet_ = servlet;
+ return;
+ }
+
+ /* TODO process other cases, throw IllegalArgumentException */
+ }
+
+ public URLPattern parseURLPattern(String p)
+ throws IllegalArgumentException
+ {
+ URLPattern pattern = parsed_patterns_.get(p);
+ if (pattern == null) {
+ pattern = new URLPattern(p);
+ parsed_patterns_.put(p, pattern);
+ }
+
+ return pattern;
+ }
+
+ private static enum URLPatternType {
+ PREFIX,
+ SUFFIX,
+ DEFAULT,
+ EXACT,
+ };
+
+ private class URLPattern
+ {
+ private final String pattern_;
+ private final URLPatternType type_;
+
+ public URLPattern(String p)
+ throws IllegalArgumentException
+ {
+ /*
+ 12.2 Specification of Mappings
+ ...
+ A string beginning with a '/' character and ending with a '/*'
+ suffix is used for path mapping.
+ */
+ if (p.startsWith("/") && p.endsWith("/*")) {
+ trace("URLPattern: '" + p + "' is a prefix pattern");
+ pattern_ = p.substring(0, p.length() - 2);
+ type_ = URLPatternType.PREFIX;
+ return;
+ }
+
+ /*
+ A string beginning with a '*.' prefix is used as an extension
+ mapping.
+ */
+ if (p.startsWith("*.")) {
+ trace("URLPattern: '" + p + "' is a suffix pattern");
+ pattern_ = p.substring(1, p.length());
+ type_ = URLPatternType.SUFFIX;
+ return;
+ }
+
+ /*
+ The empty string ("") is a special URL pattern that exactly maps to
+ the application's context root, i.e., requests of the form
+ http://host:port/<context- root>/. In this case the path info is '/'
+ and the servlet path and context path is empty string ("").
+ */
+ if (p.isEmpty()) {
+ trace("URLPattern: '" + p + "' is a root");
+ pattern_ = "/";
+ type_ = URLPatternType.EXACT;
+ return;
+ }
+
+ /*
+ A string containing only the '/' character indicates the "default"
+ servlet of the application. In this case the servlet path is the
+ request URI minus the context path and the path info is null.
+ */
+ if (p.equals("/")) {
+ trace("URLPattern: '" + p + "' is a default");
+ pattern_ = p;
+ type_ = URLPatternType.DEFAULT;
+ return;
+ }
+
+ /*
+ All other strings are used for exact matches only.
+ */
+ trace("URLPattern: '" + p + "' is an exact pattern");
+ pattern_ = p;
+ type_ = URLPatternType.EXACT;
+
+ /* TODO process other cases, throw IllegalArgumentException */
+ }
+
+ public boolean match(String url)
+ {
+ switch (type_) {
+ case PREFIX:
+ return url.startsWith(pattern_) && (
+ url.length() == pattern_.length()
+ || url.charAt(pattern_.length()) == '/');
+
+ case SUFFIX:
+ return url.endsWith(pattern_);
+
+ case EXACT:
+ return url.equals(pattern_);
+
+ case DEFAULT:
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ private class FilterReg extends NamedReg
+ implements FilterRegistration.Dynamic, FilterConfig
+ {
+ private Class<?> filter_class_;
+ private Filter filter_;
+ private boolean async_supported_ = false;
+ private boolean initialized_ = false;
+
+ public FilterReg(String name, Class<?> filter_class)
+ {
+ super(name, filter_class.getName());
+ filter_class_ = filter_class;
+ }
+
+ public FilterReg(String name, Filter filter)
+ {
+ super(name, filter.getClass().getName());
+ filter_ = filter;
+ }
+
+ public FilterReg(String name, String filter_class_name)
+ {
+ super(name, filter_class_name);
+ }
+
+ public FilterReg(String name)
+ {
+ super(name);
+ }
+
+ public void setClassName(String class_name) throws IllegalStateException
+ {
+ if (filter_ != null
+ || filter_class_ != null
+ || getClassName() != null)
+ {
+ throw new IllegalStateException("Class already initialized");
+ }
+
+ super.setClassName(class_name);
+ }
+
+ public void setClass(Class<?> filter_class) throws IllegalStateException
+ {
+ if (filter_ != null
+ || filter_class_ != null
+ || getClassName() != null)
+ {
+ throw new IllegalStateException("Class already initialized");
+ }
+
+ super.setClassName(filter_class.getName());
+ filter_class_ = filter_class;
+ }
+
+ public void init() throws ServletException
+ {
+ if (filter_ == null) {
+ try {
+ if (filter_class_ == null) {
+ filter_class_ = loader_.loadClass(getClassName());
+ }
+
+ Constructor<?> ctor = filter_class_.getConstructor();
+ filter_ = (Filter) ctor.newInstance();
+ } catch(Exception e) {
+ log("FilterReg.init() failed " + e);
+ throw new ServletException(e);
+ }
+ }
+
+ filter_.init((FilterConfig) this);
+
+ initialized_ = true;
+ }
+
+ public void destroy()
+ {
+ if (initialized_) {
+ filter_.destroy();
+ }
+ }
+
+ @Override
+ public void addMappingForServletNames(
+ EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
+ String... servletNames)
+ {
+ checkContextState();
+
+ for (String n : servletNames) {
+ trace("FilterReg.addMappingForServletNames: ... " + n);
+
+ ServletReg sreg = name2servlet_.get(n);
+ if (sreg == null) {
+ sreg = new ServletReg(n);
+ servlets_.add(sreg);
+ name2servlet_.put(n, sreg);
+ }
+
+ FilterMap map = new FilterMap(this, sreg, dispatcherTypes,
+ isMatchAfter);
+
+ sreg.addFilter(map);
+ }
+ }
+
+ @Override
+ public Collection<String> getServletNameMappings()
+ {
+ checkContextState();
+
+ log("FilterReg.getServletNameMappings");
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void addMappingForUrlPatterns(
+ EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
+ String... urlPatterns)
+ {
+ checkContextState();
+
+ for (String u : urlPatterns) {
+ trace("FilterReg.addMappingForUrlPatterns: ... " + u);
+
+ URLPattern p = parseURLPattern(u);
+ FilterMap map = new FilterMap(this, p, dispatcherTypes,
+ isMatchAfter);
+
+ filter_maps_.add(map);
+ }
+ }
+
+ @Override
+ public Collection<String> getUrlPatternMappings()
+ {
+ log("FilterReg.getUrlPatternMappings");
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void setAsyncSupported(boolean isAsyncSupported)
+ {
+ log("FilterReg.setAsyncSupported: " + isAsyncSupported);
+ async_supported_ = isAsyncSupported;
+ }
+
+ @Override
+ public String getFilterName()
+ {
+ return getName();
+ }
+
+ @Override
+ public ServletContext getServletContext()
+ {
+ return (ServletContext) Context.this;
+ }
+ }
+
+ private class FilterMap
+ {
+ private final FilterReg filter_;
+ private final ServletReg servlet_;
+ private final URLPattern pattern_;
+ private final EnumSet<DispatcherType> dtypes_;
+ private final boolean match_after_;
+
+ public FilterMap(FilterReg filter, ServletReg servlet,
+ EnumSet<DispatcherType> dtypes, boolean match_after)
+ {
+ filter_ = filter;
+ servlet_ = servlet;
+ pattern_ = null;
+ dtypes_ = dtypes;
+ match_after_ = match_after;
+ }
+
+ public FilterMap(FilterReg filter, URLPattern pattern,
+ EnumSet<DispatcherType> dtypes, boolean match_after)
+ {
+ filter_ = filter;
+ servlet_ = null;
+ pattern_ = pattern;
+ dtypes_ = dtypes;
+ match_after_ = match_after;
+ }
+ }
+
+ private void initialized()
+ {
+ if (!sess_attr_listeners_.isEmpty()) {
+ sess_attr_proxy_ = new SessionAttrProxy(sess_attr_listeners_);
+ }
+
+ if (!req_attr_listeners_.isEmpty()) {
+ req_attr_proxy_ = new RequestAttrProxy(req_attr_listeners_);
+ }
+
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(loader_);
+
+ try {
+ // Call context listeners
+ destroy_listeners_.clear();
+ if (!ctx_listeners_.isEmpty()) {
+ ServletContextEvent event = new ServletContextEvent(this);
+ for (ServletContextListener listener : ctx_listeners_)
+ {
+ try {
+ listener.contextInitialized(event);
+ } catch(AbstractMethodError e) {
+ log("initialized: AbstractMethodError exception caught: " + e);
+ }
+ destroy_listeners_.add(0, listener);
+ }
+ }
+
+ for (ServletReg sr : servlets_) {
+ try {
+ sr.startup();
+ } catch(ServletException e) {
+ log("initialized: exception caught: " + e);
+ }
+ }
+
+ for (FilterReg fr : filters_) {
+ try {
+ fr.init();
+ } catch(ServletException e) {
+ log("initialized: exception caught: " + e);
+ }
+ }
+
+ ctx_initialized_ = true;
+ } finally {
+ Thread.currentThread().setContextClassLoader(old);
+ }
+ }
+
+ @Override
+ public ServletContext getContext(String uripath)
+ {
+ trace("getContext for " + uripath);
+ return this;
+ }
+
+ @Override
+ public int getMajorVersion()
+ {
+ trace("getMajorVersion");
+ return SERVLET_MAJOR_VERSION;
+ }
+
+ @Override
+ public String getMimeType(String file)
+ {
+ log("getMimeType for " + file);
+ if (mime_types_ == null) {
+ mime_types_ = new MimeTypes();
+ }
+ return mime_types_.getMimeByExtension(file);
+ }
+
+ @Override
+ public int getMinorVersion()
+ {
+ trace("getMinorVersion");
+ return SERVLET_MINOR_VERSION;
+ }
+
+ private class URIRequestDispatcher implements RequestDispatcher
+ {
+ private final URI uri_;
+
+ public URIRequestDispatcher(URI uri)
+ {
+ uri_ = uri;
+ }
+
+ public URIRequestDispatcher(String uri)
+ throws URISyntaxException
+ {
+ uri_ = new URI(uri);
+ }
+
+ @Override
+ public void forward(ServletRequest request, ServletResponse response)
+ throws ServletException, IOException
+ {
+ /*
+ 9.4 The Forward Method
+ ...
+ If the response has been committed, an IllegalStateException
+ must be thrown.
+ */
+ if (response.isCommitted()) {
+ throw new IllegalStateException("Response already committed");
+ }
+
+ ForwardRequestWrapper req = new ForwardRequestWrapper(request);
+
+ try {
+ trace("URIRequestDispatcher.forward");
+
+ String path = uri_.getPath().substring(context_path_.length());
+
+ ServletReg servlet = findServlet(path, req);
+
+ req.setRequestURI(uri_.getRawPath());
+ req.setQueryString(uri_.getRawQuery());
+ req.setDispatcherType(DispatcherType.FORWARD);
+
+ /*
+ 9.4 The Forward Method
+ ...
+ If output data exists in the response buffer that has not
+ been committed, the content must be cleared before the
+ target servlet's service method is called.
+ */
+ response.resetBuffer();
+
+ FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.FORWARD);
+
+ fc.doFilter(request, response);
+
+ /*
+ 9.4 The Forward Method
+ ...
+ Before the forward method of the RequestDispatcher interface
+ returns without exception, the response content must be sent
+ and committed, and closed by the servlet container, unless
+ the request was put into the asynchronous mode. If an error
+ occurs in the target of the RequestDispatcher.forward() the
+ exception may be propagated back through all the calling
+ filters and servlets and eventually back to the container
+ */
+ if (!request.isAsyncStarted()) {
+ response.flushBuffer();
+ }
+
+ /*
+ 9.5 Error Handling
+
+ If the servlet that is the target of a request dispatcher
+ throws a runtime exception or a checked exception of type
+ ServletException or IOException, it should be propagated
+ to the calling servlet. All other exceptions should be
+ wrapped as ServletExceptions and the root cause of the
+ exception set to the original exception, as it should
+ not be propagated.
+ */
+ } catch (ServletException e) {
+ throw e;
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ServletException(e);
+ } finally {
+ req.close();
+
+ trace("URIRequestDispatcher.forward done");
+ }
+ }
+
+ @Override
+ public void include(ServletRequest request, ServletResponse response)
+ throws ServletException, IOException
+ {
+ IncludeRequestWrapper req = new IncludeRequestWrapper(request);
+
+ try {
+ trace("URIRequestDispatcher.include");
+
+ String path = uri_.getPath().substring(context_path_.length());
+
+ ServletReg servlet = findServlet(path, req);
+
+ req.setRequestURI(uri_.getRawPath());
+ req.setQueryString(uri_.getRawQuery());
+ req.setDispatcherType(DispatcherType.INCLUDE);
+
+ FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.INCLUDE);
+
+ fc.doFilter(request, new IncludeResponseWrapper(response));
+
+ } catch (ServletException e) {
+ throw e;
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ServletException(e);
+ } finally {
+ req.close();
+
+ trace("URIRequestDispatcher.include done");
+ }
+ }
+ }
+
+ private class ServletDispatcher implements RequestDispatcher
+ {
+ private final ServletReg servlet_;
+
+ public ServletDispatcher(ServletReg servlet)
+ {
+ servlet_ = servlet;
+ }
+
+ @Override
+ public void forward(ServletRequest request, ServletResponse response)
+ throws ServletException, IOException
+ {
+ /*
+ 9.4 The Forward Method
+ ...
+ If the response has been committed, an IllegalStateException
+ must be thrown.
+ */
+ if (response.isCommitted()) {
+ throw new IllegalStateException("Response already committed");
+ }
+
+ trace("ServletDispatcher.forward");
+
+ DispatcherType dtype = request.getDispatcherType();
+
+ Request req;
+ if (request instanceof Request) {
+ req = (Request) request;
+ } else {
+ req = (Request) request.getAttribute(Request.BARE);
+ }
+
+ try {
+ req.setDispatcherType(DispatcherType.FORWARD);
+
+ /*
+ 9.4 The Forward Method
+ ...
+ If output data exists in the response buffer that has not
+ been committed, the content must be cleared before the
+ target servlet's service method is called.
+ */
+ response.resetBuffer();
+
+ servlet_.service(request, response);
+
+ /*
+ 9.4 The Forward Method
+ ...
+ Before the forward method of the RequestDispatcher interface
+ returns without exception, the response content must be sent
+ and committed, and closed by the servlet container, unless
+ the request was put into the asynchronous mode. If an error
+ occurs in the target of the RequestDispatcher.forward() the
+ exception may be propagated back through all the calling
+ filters and servlets and eventually back to the container
+ */
+ if (!request.isAsyncStarted()) {
+ response.flushBuffer();
+ }
+
+ /*
+ 9.5 Error Handling
+
+ If the servlet that is the target of a request dispatcher
+ throws a runtime exception or a checked exception of type
+ ServletException or IOException, it should be propagated
+ to the calling servlet. All other exceptions should be
+ wrapped as ServletExceptions and the root cause of the
+ exception set to the original exception, as it should
+ not be propagated.
+ */
+ } catch (ServletException e) {
+ throw e;
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ServletException(e);
+ } finally {
+ req.setDispatcherType(dtype);
+
+ trace("ServletDispatcher.forward done");
+ }
+ }
+
+ @Override
+ public void include(ServletRequest request, ServletResponse response)
+ throws ServletException, IOException
+ {
+ trace("ServletDispatcher.include");
+
+ DispatcherType dtype = request.getDispatcherType();
+
+ Request req;
+ if (request instanceof Request) {
+ req = (Request) request;
+ } else {
+ req = (Request) request.getAttribute(Request.BARE);
+ }
+
+ try {
+ req.setDispatcherType(DispatcherType.INCLUDE);
+
+ servlet_.service(request, new IncludeResponseWrapper(response));
+
+ } catch (ServletException e) {
+ throw e;
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ServletException(e);
+ } finally {
+ req.setDispatcherType(dtype);
+
+ trace("ServletDispatcher.include done");
+ }
+ }
+ }
+
+ @Override
+ public RequestDispatcher getNamedDispatcher(String name)
+ {
+ trace("getNamedDispatcher for " + name);
+
+ ServletReg servlet = name2servlet_.get(name);
+ if (servlet != null) {
+ return new ServletDispatcher(servlet);
+ }
+
+ return null;
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String uriInContext)
+ {
+ trace("getRequestDispatcher for " + uriInContext);
+ try {
+ return new URIRequestDispatcher(context_path_ + uriInContext);
+ } catch (URISyntaxException e) {
+ log("getRequestDispatcher: failed to create dispatcher: " + e);
+ }
+
+ return null;
+ }
+
+ public RequestDispatcher getRequestDispatcher(URI uri)
+ {
+ trace("getRequestDispatcher for " + uri.getRawPath());
+ return new URIRequestDispatcher(uri);
+ }
+
+ @Override
+ public String getRealPath(String path)
+ {
+ trace("getRealPath for " + path);
+
+ File f = new File(webapp_, path.substring(1));
+
+ return f.getAbsolutePath();
+ }
+
+ @Override
+ public URL getResource(String path) throws MalformedURLException
+ {
+ trace("getResource for " + path);
+
+ File f = new File(webapp_, path.substring(1));
+
+ if (f.exists()) {
+ return new URL("file:" + f.getAbsolutePath());
+ }
+
+ return null;
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String path)
+ {
+ trace("getResourceAsStream for " + path);
+
+ try {
+ File f = new File(webapp_, path.substring(1));
+
+ return new FileInputStream(f);
+ } catch (FileNotFoundException e) {
+ log("getResourceAsStream: failed " + e);
+
+ return null;
+ }
+ }
+
+ @Override
+ public Set<String> getResourcePaths(String path)
+ {
+ trace("getResourcePaths for " + path);
+
+ File dir = new File(webapp_, path.substring(1));
+ File[] list = dir.listFiles();
+
+ if (list == null) {
+ return null;
+ }
+
+ Set<String> res = new HashSet<>();
+ Path root = webapp_.toPath();
+
+ for (File f : list) {
+ String r = "/" + root.relativize(f.toPath());
+ if (f.isDirectory()) {
+ r += "/";
+ }
+
+ trace("getResourcePaths: " + r);
+
+ res.add(r);
+ }
+
+ return res;
+ }
+
+ @Override
+ public String getServerInfo()
+ {
+ trace("getServerInfo: " + server_info_);
+ return server_info_;
+ }
+
+ @Override
+ @Deprecated
+ public Servlet getServlet(String name) throws ServletException
+ {
+ log("getServlet for " + name);
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ @Deprecated
+ public Enumeration<String> getServletNames()
+ {
+ log("getServletNames");
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ @Deprecated
+ public Enumeration<Servlet> getServlets()
+ {
+ log("getServlets");
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+ @Override
+ @Deprecated
+ public void log(Exception exception, String msg)
+ {
+ log(msg, exception);
+ }
+
+ @Override
+ public void log(String msg)
+ {
+ msg = "Context." + msg;
+ log(0, msg, msg.length());
+ }
+
+ @Override
+ public void log(String message, Throwable throwable)
+ {
+ log(message);
+ }
+
+ private static native void log(long ctx_ptr, String msg, int msg_len);
+
+
+ public static void trace(String msg)
+ {
+ msg = "Context." + msg;
+ trace(0, msg, msg.length());
+ }
+
+ private static native void trace(long ctx_ptr, String msg, int msg_len);
+
+ @Override
+ public String getInitParameter(String name)
+ {
+ trace("getInitParameter for " + name);
+ return init_params_.get(name);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Enumeration<String> getInitParameterNames()
+ {
+ trace("getInitParameterNames");
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+ @Override
+ public String getServletContextName()
+ {
+ log("getServletContextName");
+ return "No Context";
+ }
+
+ @Override
+ public String getContextPath()
+ {
+ trace("getContextPath");
+ return context_path_;
+ }
+
+ @Override
+ public boolean setInitParameter(String name, String value)
+ {
+ trace("setInitParameter " + name + " = " + value);
+ return init_params_.putIfAbsent(name, value) == null;
+ }
+
+ @Override
+ public Object getAttribute(String name)
+ {
+ trace("getAttribute " + name);
+
+ return attributes_.get(name);
+ }
+
+ @Override
+ public Enumeration<String> getAttributeNames()
+ {
+ trace("getAttributeNames");
+
+ Set<String> names = attributes_.keySet();
+ return Collections.enumeration(names);
+ }
+
+ @Override
+ public void setAttribute(String name, Object object)
+ {
+ trace("setAttribute " + name);
+
+ Object prev = attributes_.put(name, object);
+
+ if (ctx_attr_listeners_.isEmpty()) {
+ return;
+ }
+
+ ServletContextAttributeEvent scae = new ServletContextAttributeEvent(
+ this, name, prev == null ? object : prev);
+
+ for (ServletContextAttributeListener l : ctx_attr_listeners_) {
+ if (prev == null) {
+ l.attributeAdded(scae);
+ } else {
+ l.attributeReplaced(scae);
+ }
+ }
+ }
+
+ @Override
+ public void removeAttribute(String name)
+ {
+ trace("removeAttribute " + name);
+
+ Object value = attributes_.remove(name);
+
+ if (ctx_attr_listeners_.isEmpty()) {
+ return;
+ }
+
+ ServletContextAttributeEvent scae = new ServletContextAttributeEvent(
+ this, name, value);
+
+ for (ServletContextAttributeListener l : ctx_attr_listeners_) {
+ l.attributeRemoved(scae);
+ }
+ }
+
+ @Override
+ public FilterRegistration.Dynamic addFilter(String name,
+ Class<? extends Filter> filterClass)
+ {
+ log("addFilter<C> " + name + ", " + filterClass.getName());
+
+ checkContextState();
+
+ FilterReg reg = new FilterReg(name, filterClass);
+ filters_.add(reg);
+ name2filter_.put(name, reg);
+ return reg;
+ }
+
+ @Override
+ public FilterRegistration.Dynamic addFilter(String name, Filter filter)
+ {
+ log("addFilter<F> " + name);
+
+ checkContextState();
+
+ FilterReg reg = new FilterReg(name, filter);
+ filters_.add(reg);
+ name2filter_.put(name, reg);
+ return reg;
+ }
+
+ @Override
+ public FilterRegistration.Dynamic addFilter(String name, String className)
+ {
+ log("addFilter<N> " + name + ", " + className);
+
+ checkContextState();
+
+ FilterReg reg = new FilterReg(name, className);
+ filters_.add(reg);
+ name2filter_.put(name, reg);
+ return reg;
+ }
+
+ @Override
+ public ServletRegistration.Dynamic addServlet(String name,
+ Class<? extends Servlet> servletClass)
+ {
+ log("addServlet<C> " + name + ", " + servletClass.getName());
+
+ checkContextState();
+
+ ServletReg reg = null;
+ try {
+ reg = new ServletReg(name, servletClass);
+ servlets_.add(reg);
+ name2servlet_.put(name, reg);
+ } catch(Exception e) {
+ System.err.println("addServlet: exception caught: " + e.toString());
+ }
+
+ return reg;
+ }
+
+ @Override
+ public ServletRegistration.Dynamic addServlet(String name, Servlet servlet)
+ {
+ log("addServlet<S> " + name);
+
+ checkContextState();
+
+ ServletReg reg = null;
+ try {
+ reg = new ServletReg(name, servlet);
+ servlets_.add(reg);
+ name2servlet_.put(name, reg);
+ } catch(Exception e) {
+ System.err.println("addServlet: exception caught: " + e.toString());
+ }
+
+ return reg;
+ }
+
+ @Override
+ public ServletRegistration.Dynamic addServlet(String name, String className)
+ {
+ log("addServlet<N> " + name + ", " + className);
+
+ checkContextState();
+
+ ServletReg reg = null;
+ try {
+ reg = new ServletReg(name, className);
+ servlets_.add(reg);
+ name2servlet_.put(name, reg);
+ } catch(Exception e) {
+ System.err.println("addServlet: exception caught: " + e.toString());
+ }
+
+ return reg;
+ }
+
+ @Override
+ public ServletRegistration.Dynamic addJspFile(String jspName, String jspFile)
+ {
+ log("addJspFile: " + jspName + " " + jspFile);
+
+ return null;
+ }
+
+ @Override
+ public <T extends Filter> T createFilter(Class<T> c) throws ServletException
+ {
+ log("createFilter<C> " + c.getName());
+
+ checkContextState();
+
+ try {
+ Constructor<T> ctor = c.getConstructor();
+ T filter = ctor.newInstance();
+ return filter;
+ } catch (Exception e) {
+ log("createFilter() failed " + e);
+
+ throw new ServletException(e);
+ }
+ }
+
+ @Override
+ public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
+ {
+ log("createServlet<C> " + c.getName());
+
+ checkContextState();
+
+ try {
+ Constructor<T> ctor = c.getConstructor();
+ T servlet = ctor.newInstance();
+ return servlet;
+ } catch (Exception e) {
+ log("createServlet() failed " + e);
+
+ throw new ServletException(e);
+ }
+ }
+
+ @Override
+ public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+ {
+ log("getDefaultSessionTrackingModes");
+
+ return default_session_tracking_modes_;
+ }
+
+ @Override
+ public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+ {
+ log("getEffectiveSessionTrackingModes");
+
+ return session_tracking_modes_;
+ }
+
+ public boolean isSessionIdValid(String id)
+ {
+ synchronized (sessions_) {
+ return sessions_.containsKey(id);
+ }
+ }
+
+ public Session getSession(String id)
+ {
+ synchronized (sessions_) {
+ Session s = sessions_.get(id);
+
+ if (s != null) {
+ s.accessed();
+
+ if (s.checkTimeOut()) {
+ s.invalidate();
+ return null;
+ }
+ }
+
+ return s;
+ }
+ }
+
+ public Session createSession()
+ {
+ Session session = new Session(this, generateSessionId(),
+ sess_attr_proxy_, session_timeout_ * 60);
+
+ if (!sess_listeners_.isEmpty())
+ {
+ HttpSessionEvent event = new HttpSessionEvent(session);
+
+ for (HttpSessionListener l : sess_listeners_)
+ {
+ l.sessionCreated(event);
+ }
+ }
+
+ synchronized (sessions_) {
+ sessions_.put(session.getId(), session);
+
+ return session;
+ }
+ }
+
+ public void invalidateSession(Session session)
+ {
+ synchronized (sessions_) {
+ sessions_.remove(session.getId());
+ }
+
+ if (!sess_listeners_.isEmpty())
+ {
+ HttpSessionEvent event = new HttpSessionEvent(session);
+
+ for (int i = sess_listeners_.size() - 1; i >= 0; i--)
+ {
+ sess_listeners_.get(i).sessionDestroyed(event);
+ }
+ }
+ }
+
+ public void changeSessionId(Session session)
+ {
+ String old_id;
+
+ synchronized (sessions_) {
+ old_id = session.getId();
+ sessions_.remove(old_id);
+
+ session.setId(generateSessionId());
+
+ sessions_.put(session.getId(), session);
+ }
+
+ if (!sess_id_listeners_.isEmpty())
+ {
+ HttpSessionEvent event = new HttpSessionEvent(session);
+ for (HttpSessionIdListener l : sess_id_listeners_)
+ {
+ l.sessionIdChanged(event, old_id);
+ }
+ }
+ }
+
+ private String generateSessionId()
+ {
+ return UUID.randomUUID().toString();
+ }
+
+ @Override
+ public FilterRegistration getFilterRegistration(String filterName)
+ {
+ log("getFilterRegistration " + filterName);
+ return name2filter_.get(filterName);
+ }
+
+ @Override
+ public Map<String, ? extends FilterRegistration> getFilterRegistrations()
+ {
+ log("getFilterRegistrations");
+ return name2filter_;
+ }
+
+ @Override
+ public ServletRegistration getServletRegistration(String servletName)
+ {
+ log("getServletRegistration " + servletName);
+ return name2servlet_.get(servletName);
+ }
+
+ @Override
+ public Map<String, ? extends ServletRegistration> getServletRegistrations()
+ {
+ log("getServletRegistrations");
+ return name2servlet_;
+ }
+
+ @Override
+ public SessionCookieConfig getSessionCookieConfig()
+ {
+ log("getSessionCookieConfig");
+
+ return session_cookie_config_;
+ }
+
+ @Override
+ public void setSessionTrackingModes(Set<SessionTrackingMode> modes)
+ {
+ log("setSessionTrackingModes");
+
+ session_tracking_modes_ = modes;
+ }
+
+ @Override
+ public void addListener(String className)
+ {
+ trace("addListener<N> " + className);
+
+ checkContextState();
+
+ if (listener_classnames_.contains(className)) {
+ log("addListener<N> " + className + " already added as listener");
+ return;
+ }
+
+ try {
+ Class<?> cls = loader_.loadClass(className);
+
+ Constructor<?> ctor = cls.getConstructor();
+ EventListener listener = (EventListener) ctor.newInstance();
+
+ addListener(listener);
+
+ listener_classnames_.add(className);
+ } catch (Exception e) {
+ log("addListener<N>: exception caught: " + e.toString());
+ }
+ }
+
+ @Override
+ public <T extends EventListener> void addListener(T t)
+ {
+ trace("addListener<T> " + t.getClass().getName());
+
+ checkContextState();
+
+ for (int i = 0; i < LISTENER_TYPES.length; i++) {
+ Class<?> c = LISTENER_TYPES[i];
+ if (c.isAssignableFrom(t.getClass())) {
+ trace("addListener<T>: assignable to " + c.getName());
+ }
+ }
+
+ if (t instanceof ServletContextListener) {
+ ctx_listeners_.add((ServletContextListener) t);
+ }
+
+ if (t instanceof ServletContextAttributeListener) {
+ ctx_attr_listeners_.add((ServletContextAttributeListener) t);
+ }
+
+ if (t instanceof ServletRequestListener) {
+ req_init_listeners_.add((ServletRequestListener) t);
+ req_destroy_listeners_.add(0, (ServletRequestListener) t);
+ }
+
+ if (t instanceof ServletRequestAttributeListener) {
+ req_attr_listeners_.add((ServletRequestAttributeListener) t);
+ }
+
+ if (t instanceof HttpSessionAttributeListener) {
+ sess_attr_listeners_.add((HttpSessionAttributeListener) t);
+ }
+
+ if (t instanceof HttpSessionIdListener) {
+ sess_id_listeners_.add((HttpSessionIdListener) t);
+ }
+
+ if (t instanceof HttpSessionListener) {
+ sess_listeners_.add((HttpSessionListener) t);
+ }
+ }
+
+ @Override
+ public void addListener(Class<? extends EventListener> listenerClass)
+ {
+ String className = listenerClass.getName();
+ trace("addListener<C> " + className);
+
+ checkContextState();
+
+ if (listener_classnames_.contains(className)) {
+ log("addListener<C> " + className + " already added as listener");
+ return;
+ }
+
+ try {
+ Constructor<?> ctor = listenerClass.getConstructor();
+ EventListener listener = (EventListener) ctor.newInstance();
+
+ addListener(listener);
+
+ listener_classnames_.add(className);
+ } catch (Exception e) {
+ log("addListener<C>: exception caught: " + e.toString());
+ }
+ }
+
+ @Override
+ public <T extends EventListener> T createListener(Class<T> clazz)
+ throws ServletException
+ {
+ trace("createListener<C> " + clazz.getName());
+
+ checkContextState();
+
+ try
+ {
+ return clazz.getDeclaredConstructor().newInstance();
+ }
+ catch (Exception e)
+ {
+ throw new ServletException(e);
+ }
+ }
+
+ @Override
+ public ClassLoader getClassLoader()
+ {
+ trace("getClassLoader");
+ return loader_;
+ }
+
+ @Override
+ public int getEffectiveMajorVersion()
+ {
+ log("getEffectiveMajorVersion");
+ return SERVLET_MAJOR_VERSION;
+ }
+
+ @Override
+ public int getEffectiveMinorVersion()
+ {
+ log("getEffectiveMinorVersion");
+ return SERVLET_MINOR_VERSION;
+ }
+
+ private final List<TaglibDescriptor> taglibs_ = new ArrayList<>();
+ private final List<JspPropertyGroupDescriptor> prop_groups_ = new ArrayList<>();
+
+ private class JspConfig implements JspConfigDescriptor
+ {
+ @Override
+ public Collection<TaglibDescriptor> getTaglibs()
+ {
+ trace("getTaglibs");
+ return taglibs_;
+ }
+
+ @Override
+ public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups()
+ {
+ trace("getJspPropertyGroups");
+ return prop_groups_;
+ }
+ }
+
+ private final JspConfig jsp_config_ = new JspConfig();
+
+ @Override
+ public JspConfigDescriptor getJspConfigDescriptor()
+ {
+ trace("getJspConfigDescriptor");
+
+ return jsp_config_;
+ }
+
+ @Override
+ public void declareRoles(String... roleNames)
+ {
+ log("declareRoles");
+ //LOG.warn(__unimplmented);
+ }
+
+ @Override
+ public String getVirtualServerName()
+ {
+ log("getVirtualServerName");
+ return null;
+ }
+
+ @Override
+ public int getSessionTimeout()
+ {
+ trace("getSessionTimeout");
+
+ return session_timeout_;
+ }
+
+ @Override
+ public void setSessionTimeout(int sessionTimeout)
+ {
+ trace("setSessionTimeout: " + sessionTimeout);
+
+ session_timeout_ = sessionTimeout;
+ }
+
+ @Override
+ public String getRequestCharacterEncoding()
+ {
+ log("getRequestCharacterEncoding");
+
+ return null;
+ }
+
+ @Override
+ public void setRequestCharacterEncoding(String encoding)
+ {
+ log("setRequestCharacterEncoding: " + encoding);
+ }
+
+ @Override
+ public String getResponseCharacterEncoding()
+ {
+ log("getResponseCharacterEncoding");
+
+ return null;
+ }
+
+ @Override
+ public void setResponseCharacterEncoding(String encoding)
+ {
+ log("setResponseCharacterEncoding: " + encoding);
+ }
+
+ public ServletRequestAttributeListener getRequestAttributeListener()
+ {
+ return req_attr_proxy_;
+ }
+}
diff --git a/src/java/nginx/unit/DynamicDispatcherRequest.java b/src/java/nginx/unit/DynamicDispatcherRequest.java
new file mode 100644
index 00000000..af0747eb
--- /dev/null
+++ b/src/java/nginx/unit/DynamicDispatcherRequest.java
@@ -0,0 +1,8 @@
+package nginx.unit;
+
+import javax.servlet.DispatcherType;
+
+public interface DynamicDispatcherRequest
+{
+ public void setDispatcherType(DispatcherType type);
+}
diff --git a/src/java/nginx/unit/DynamicPathRequest.java b/src/java/nginx/unit/DynamicPathRequest.java
new file mode 100644
index 00000000..efc1bcd1
--- /dev/null
+++ b/src/java/nginx/unit/DynamicPathRequest.java
@@ -0,0 +1,15 @@
+package nginx.unit;
+
+public interface DynamicPathRequest
+ extends DynamicDispatcherRequest
+{
+ public void setServletPath(String servlet_path, String path_info);
+
+ public void setServletPath(String filter_path, String servlet_path, String path_info);
+
+ public void setRequestURI(String uri);
+
+ public void setQueryString(String query);
+
+ public String getFilterPath();
+}
diff --git a/src/java/nginx/unit/ForwardRequestWrapper.java b/src/java/nginx/unit/ForwardRequestWrapper.java
new file mode 100644
index 00000000..f88b6aef
--- /dev/null
+++ b/src/java/nginx/unit/ForwardRequestWrapper.java
@@ -0,0 +1,150 @@
+package nginx.unit;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.UrlEncoded;
+
+public class ForwardRequestWrapper implements DynamicPathRequest
+{
+ private final Request request_;
+
+ private final boolean keep_attrs;
+
+ private final String orig_filter_path;
+ private final String orig_servlet_path;
+ private final String orig_path_info;
+ private final String orig_uri;
+ private final String orig_context_path;
+ private final String orig_query;
+
+ private final DispatcherType orig_dtype;
+
+ private MultiMap<String> orig_parameters;
+
+ public ForwardRequestWrapper(ServletRequest request)
+ {
+ if (request instanceof Request) {
+ request_ = (Request) request;
+ } else {
+ request_ = (Request) request.getAttribute(Request.BARE);
+ }
+
+ keep_attrs = request_.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI) != null;
+
+ orig_dtype = request_.getDispatcherType();
+
+ orig_filter_path = request_.getFilterPath();
+ orig_servlet_path = request_.getServletPath();
+ orig_path_info = request_.getPathInfo();
+ orig_uri = request_.getRequestURI();
+ orig_context_path = request_.getContextPath();
+ orig_query = request_.getQueryString();
+ }
+
+ @Override
+ public void setDispatcherType(DispatcherType type)
+ {
+ request_.setDispatcherType(type);
+
+ /*
+ 9.4.2 Forwarded Request Parameters
+ ...
+ Note that these attributes must always reflect the information in
+ the original request even under the situation that multiple
+ forwards and subsequent includes are called.
+ */
+
+ if (keep_attrs) {
+ return;
+ }
+
+ /*
+ 9.4.2 Forwarded Request Parameters
+ ...
+ The values of these attributes must be equal to the return values
+ of the HttpServletRequest methods getRequestURI, getContextPath,
+ getServletPath, getPathInfo, getQueryString respectively, invoked
+ on the request object passed to the first servlet object in the
+ call chain that received the request from the client.
+ */
+
+ request_.setAttribute_(RequestDispatcher.FORWARD_SERVLET_PATH, orig_servlet_path);
+ request_.setAttribute_(RequestDispatcher.FORWARD_PATH_INFO, orig_path_info);
+ request_.setAttribute_(RequestDispatcher.FORWARD_REQUEST_URI, orig_uri);
+ request_.setAttribute_(RequestDispatcher.FORWARD_CONTEXT_PATH, orig_context_path);
+ request_.setAttribute_(RequestDispatcher.FORWARD_QUERY_STRING, orig_query);
+ }
+
+ @Override
+ public void setServletPath(String servlet_path, String path_info)
+ {
+ request_.setServletPath(servlet_path, path_info);
+ }
+
+ @Override
+ public void setServletPath(String filter_path, String servlet_path, String path_info)
+ {
+ request_.setServletPath(filter_path, servlet_path, path_info);
+ }
+
+ @Override
+ public void setRequestURI(String uri)
+ {
+ request_.setRequestURI(uri);
+ }
+
+ @Override
+ public void setQueryString(String query)
+ {
+ if (query != null) {
+ orig_parameters = request_.getParameters();
+
+ MultiMap<String> parameters = new MultiMap<>();
+ UrlEncoded.decodeUtf8To(query, parameters);
+
+ for (Map.Entry<String, List<String>> e: orig_parameters.entrySet()) {
+ parameters.addValues(e.getKey(), e.getValue());
+ }
+
+ request_.setParameters(parameters);
+
+ request_.setQueryString(query);
+ }
+ }
+
+ @Override
+ public String getFilterPath()
+ {
+ return request_.getFilterPath();
+ }
+
+ public void close()
+ {
+ request_.setDispatcherType(orig_dtype);
+
+ request_.setRequestURI(orig_uri);
+ request_.setServletPath(orig_filter_path, orig_servlet_path, orig_path_info);
+ request_.setQueryString(orig_query);
+
+ if (orig_parameters != null) {
+ request_.setParameters(orig_parameters);
+ }
+
+ if (keep_attrs) {
+ return;
+ }
+
+ request_.setAttribute_(RequestDispatcher.FORWARD_SERVLET_PATH, null);
+ request_.setAttribute_(RequestDispatcher.FORWARD_PATH_INFO, null);
+ request_.setAttribute_(RequestDispatcher.FORWARD_REQUEST_URI, null);
+ request_.setAttribute_(RequestDispatcher.FORWARD_CONTEXT_PATH, null);
+ request_.setAttribute_(RequestDispatcher.FORWARD_QUERY_STRING, null);
+ }
+}
diff --git a/src/java/nginx/unit/HeaderNamesEnumeration.java b/src/java/nginx/unit/HeaderNamesEnumeration.java
new file mode 100644
index 00000000..d81b8778
--- /dev/null
+++ b/src/java/nginx/unit/HeaderNamesEnumeration.java
@@ -0,0 +1,42 @@
+package nginx.unit;
+
+import java.lang.String;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+public class HeaderNamesEnumeration implements Enumeration<String> {
+
+ private long headers_ptr;
+ private long size;
+ private long pos = 0;
+
+ public HeaderNamesEnumeration(long _headers_ptr, long _size) {
+ headers_ptr = _headers_ptr;
+ size = _size;
+ }
+
+ @Override
+ public boolean hasMoreElements()
+ {
+ if (pos >= size) {
+ return false;
+ }
+
+ pos = nextElementPos(headers_ptr, size, pos);
+ return pos < size;
+ }
+
+ static private native long nextElementPos(long headers_ptr, long size, long pos);
+
+ @Override
+ public String nextElement()
+ {
+ if (pos >= size) {
+ throw new NoSuchElementException();
+ }
+
+ return nextElement(headers_ptr, size, pos++);
+ }
+
+ static private native String nextElement(long headers_ptr, long size, long pos);
+}
diff --git a/src/java/nginx/unit/HeadersEnumeration.java b/src/java/nginx/unit/HeadersEnumeration.java
new file mode 100644
index 00000000..31b5ae24
--- /dev/null
+++ b/src/java/nginx/unit/HeadersEnumeration.java
@@ -0,0 +1,40 @@
+package nginx.unit;
+
+import java.lang.String;
+import java.util.Enumeration;
+
+public class HeadersEnumeration implements Enumeration<String> {
+
+ private long headers_ptr;
+ private long size;
+ private long initial_pos;
+ private long pos;
+
+ public HeadersEnumeration(long _headers_ptr, long _size, long _initial_pos) {
+ headers_ptr = _headers_ptr;
+ size = _size;
+ initial_pos = _initial_pos;
+ pos = _initial_pos;
+ }
+
+ @Override
+ public boolean hasMoreElements()
+ {
+ if (pos >= size) {
+ return false;
+ }
+
+ pos = nextElementPos(headers_ptr, size, initial_pos, pos);
+ return pos < size;
+ }
+
+ static private native long nextElementPos(long headers_ptr, long size, long initial_pos, long pos);
+
+ @Override
+ public String nextElement()
+ {
+ return nextElement(headers_ptr, size, initial_pos, pos++);
+ }
+
+ static private native String nextElement(long headers_ptr, long size, long initial_pos, long pos);
+}
diff --git a/src/java/nginx/unit/IncludeRequestWrapper.java b/src/java/nginx/unit/IncludeRequestWrapper.java
new file mode 100644
index 00000000..67a51b24
--- /dev/null
+++ b/src/java/nginx/unit/IncludeRequestWrapper.java
@@ -0,0 +1,88 @@
+package nginx.unit;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletRequest;
+
+public class IncludeRequestWrapper implements DynamicPathRequest
+{
+ private final Request request_;
+
+ private final Object orig_servlet_path_attr;
+ private final Object orig_path_info_attr;
+ private final Object orig_uri_attr;
+ private final Object orig_context_path_attr;
+ private final Object orig_query_string_attr;
+
+ private final DispatcherType orig_dtype;
+
+ private String filter_path_;
+
+ public IncludeRequestWrapper(ServletRequest request)
+ {
+ if (request instanceof Request) {
+ request_ = (Request) request;
+ } else {
+ request_ = (Request) request.getAttribute(Request.BARE);
+ }
+
+ orig_servlet_path_attr = request_.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+ orig_path_info_attr = request_.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
+ orig_uri_attr = request_.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI);
+ orig_context_path_attr = request_.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH);
+ orig_query_string_attr = request_.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING);
+
+ orig_dtype = request_.getDispatcherType();
+
+ request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, request_.getContextPath());
+ }
+
+ @Override
+ public void setDispatcherType(DispatcherType type)
+ {
+ request_.setDispatcherType(type);
+ }
+
+ @Override
+ public void setServletPath(String servlet_path, String path_info)
+ {
+ setServletPath(servlet_path, servlet_path, path_info);
+ }
+
+ @Override
+ public void setServletPath(String filter_path, String servlet_path, String path_info)
+ {
+ request_.setAttribute_(RequestDispatcher.INCLUDE_SERVLET_PATH, servlet_path);
+ request_.setAttribute_(RequestDispatcher.INCLUDE_PATH_INFO, path_info);
+ filter_path_ = filter_path;
+ }
+
+ @Override
+ public void setRequestURI(String uri)
+ {
+ request_.setAttribute_(RequestDispatcher.INCLUDE_REQUEST_URI, uri);
+ }
+
+ @Override
+ public void setQueryString(String query)
+ {
+ request_.setAttribute_(RequestDispatcher.INCLUDE_QUERY_STRING, query);
+ }
+
+ @Override
+ public String getFilterPath()
+ {
+ return filter_path_;
+ }
+
+ public void close()
+ {
+ request_.setDispatcherType(orig_dtype);
+
+ request_.setAttribute_(RequestDispatcher.INCLUDE_SERVLET_PATH, orig_servlet_path_attr);
+ request_.setAttribute_(RequestDispatcher.INCLUDE_PATH_INFO, orig_path_info_attr);
+ request_.setAttribute_(RequestDispatcher.INCLUDE_REQUEST_URI, orig_uri_attr);
+ request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, orig_context_path_attr);
+ request_.setAttribute_(RequestDispatcher.INCLUDE_QUERY_STRING, orig_query_string_attr);
+ }
+}
diff --git a/src/java/nginx/unit/IncludeResponseWrapper.java b/src/java/nginx/unit/IncludeResponseWrapper.java
new file mode 100644
index 00000000..4537114a
--- /dev/null
+++ b/src/java/nginx/unit/IncludeResponseWrapper.java
@@ -0,0 +1,117 @@
+package nginx.unit;
+
+import java.io.IOException;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+public class IncludeResponseWrapper extends HttpServletResponseWrapper {
+ private static final Charset UTF_8 = StandardCharsets.UTF_8;
+
+ public IncludeResponseWrapper(ServletResponse response)
+ {
+ super((HttpServletResponse) response);
+ }
+
+ @Override
+ public void addCookie(Cookie cookie)
+ {
+ trace("addCookie: " + cookie.getName() + "=" + cookie.getValue());
+ }
+
+ @Override
+ public void addDateHeader(String name, long date)
+ {
+ trace("addDateHeader: " + name + ": " + date);
+ }
+
+ @Override
+ public void addHeader(String name, String value)
+ {
+ trace("addHeader: " + name + ": " + value);
+ }
+
+ @Override
+ public void addIntHeader(String name, int value)
+ {
+ trace("addIntHeader: " + name + ": " + value);
+ }
+
+ @Override
+ public void sendRedirect(String location) throws IOException
+ {
+ trace("sendRedirect: " + location);
+ }
+
+ @Override
+ public void setDateHeader(String name, long date)
+ {
+ trace("setDateHeader: " + name + ": " + date);
+ }
+
+ @Override
+ public void setHeader(String name, String value)
+ {
+ trace("setHeader: " + name + ": " + value);
+ }
+
+ @Override
+ public void setIntHeader(String name, int value)
+ {
+ trace("setIntHeader: " + name + ": " + value);
+ }
+
+ @Override
+ public void setStatus(int sc)
+ {
+ trace("setStatus: " + sc);
+ }
+
+ @Override
+ @Deprecated
+ public void setStatus(int sc, String sm)
+ {
+ trace("setStatus: " + sc + "; " + sm);
+ }
+
+ @Override
+ public void reset()
+ {
+ trace("reset");
+ }
+
+ @Override
+ public void setCharacterEncoding(String charset)
+ {
+ trace("setCharacterEncoding " + charset);
+ }
+
+ @Override
+ public void setContentLength(int len)
+ {
+ trace("setContentLength: " + len);
+ }
+
+ @Override
+ public void setContentLengthLong(long len)
+ {
+ trace("setContentLengthLong: " + len);
+ }
+
+ @Override
+ public void setContentType(String type)
+ {
+ trace("setContentType: " + type);
+ }
+
+ private void trace(String msg)
+ {
+ msg = "IncludeResponse." + msg;
+ Response.trace(0, msg.getBytes(UTF_8));
+ }
+}
diff --git a/src/java/nginx/unit/InitParams.java b/src/java/nginx/unit/InitParams.java
new file mode 100644
index 00000000..2f5dcbf9
--- /dev/null
+++ b/src/java/nginx/unit/InitParams.java
@@ -0,0 +1,7 @@
+package nginx.unit;
+
+public interface InitParams {
+ public boolean setInitParameter(String name, String value);
+
+ public String getInitParameter(String name);
+}
diff --git a/src/java/nginx/unit/InputStream.java b/src/java/nginx/unit/InputStream.java
new file mode 100644
index 00000000..6fe72ace
--- /dev/null
+++ b/src/java/nginx/unit/InputStream.java
@@ -0,0 +1,90 @@
+package nginx.unit;
+
+import java.io.IOException;
+
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+
+public class InputStream extends ServletInputStream {
+
+ private long req_info_ptr;
+
+ public InputStream(long ptr)
+ {
+ req_info_ptr = ptr;
+ }
+
+ @Override
+ public int readLine(byte[] b, int off, int len) throws IOException {
+
+ if (len <= 0) {
+ return 0;
+ }
+ return readLine(req_info_ptr, b, off, len);
+ }
+
+ private static native int readLine(long req_info_ptr, byte[] b, int off, int len);
+
+
+ @Override
+ public boolean isFinished()
+ {
+ return isFinished(req_info_ptr);
+ }
+
+ private static native boolean isFinished(long req_info_ptr);
+
+
+ @Override
+ public boolean isReady()
+ {
+ return true;
+ }
+
+
+ @Override
+ public void setReadListener(ReadListener listener)
+ {
+ }
+
+
+ @Override
+ public int read() throws IOException
+ {
+ return read(req_info_ptr);
+ }
+
+ private static native int read(long req_info_ptr);
+
+
+ @Override
+ public int read(byte b[], int off, int len) throws IOException {
+ if (b == null) {
+ throw new NullPointerException();
+ } else if (off < 0 || len < 0 || len > b.length - off) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return 0;
+ }
+
+ return read(req_info_ptr, b, off, len);
+ }
+
+ private static native int read(long req_info_ptr, byte b[], int off, int len);
+
+
+ @Override
+ public long skip(long n) throws IOException {
+ return skip(req_info_ptr, n);
+ }
+
+ private static native long skip(long req_info_ptr, long n);
+
+
+ @Override
+ public int available() throws IOException {
+ return available(req_info_ptr);
+ }
+
+ private static native int available(long req_info_ptr);
+}
diff --git a/src/java/nginx/unit/JspPropertyGroup.java b/src/java/nginx/unit/JspPropertyGroup.java
new file mode 100644
index 00000000..2df27718
--- /dev/null
+++ b/src/java/nginx/unit/JspPropertyGroup.java
@@ -0,0 +1,169 @@
+package nginx.unit;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.servlet.descriptor.JspPropertyGroupDescriptor;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class JspPropertyGroup implements JspPropertyGroupDescriptor
+{
+ private final List<String> url_patterns_ = new ArrayList<>();
+ private String el_ignored_ = null;
+ private String page_encoding_ = null;
+ private String scripting_invalid_ = null;
+ private String is_xml_ = null;
+ private final List<String> include_preludes_ = new ArrayList<>();
+ private final List<String> include_codas_ = new ArrayList<>();
+
+ private String deffered_syntax_allowed_as_literal_ = null;
+ private String trim_directive_whitespaces_ = null;
+ private String default_content_type_ = null;
+ private String buffer_ = null;
+ private String error_on_undeclared_namespace_ = null;
+
+ public JspPropertyGroup(NodeList nodes)
+ {
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Node node = nodes.item(i);
+ String tag_name = node.getNodeName();
+
+ if (tag_name.equals("url-pattern")) {
+ url_patterns_.add(node.getTextContent().trim());
+ continue;
+ }
+
+ if (tag_name.equals("el-ignored")) {
+ el_ignored_ = node.getTextContent().trim();
+ continue;
+ }
+
+ if (tag_name.equals("page-encoding")) {
+ page_encoding_ = node.getTextContent().trim();
+ continue;
+ }
+
+ if (tag_name.equals("scripting-invalid")) {
+ scripting_invalid_ = node.getTextContent().trim();
+ continue;
+ }
+
+ if (tag_name.equals("is-xml")) {
+ is_xml_ = node.getTextContent().trim();
+ continue;
+ }
+
+ if (tag_name.equals("include-prelude")) {
+ include_preludes_.add(node.getTextContent().trim());
+ continue;
+ }
+
+ if (tag_name.equals("include-coda")) {
+ include_codas_.add(node.getTextContent().trim());
+ continue;
+ }
+
+ if (tag_name.equals("deferred-syntax-allowed-as-literal")) {
+ deffered_syntax_allowed_as_literal_ = node.getTextContent().trim();
+ continue;
+ }
+
+ if (tag_name.equals("trim-directive-whitespaces")) {
+ trim_directive_whitespaces_ = node.getTextContent().trim();
+ continue;
+ }
+
+ if (tag_name.equals("default-content-type")) {
+ default_content_type_ = node.getTextContent().trim();
+ continue;
+ }
+
+ if (tag_name.equals("buffer")) {
+ buffer_ = node.getTextContent().trim();
+ continue;
+ }
+
+ if (tag_name.equals("error-on-undeclared-namespace")) {
+ error_on_undeclared_namespace_ = node.getTextContent().trim();
+ continue;
+ }
+ }
+
+ }
+
+ @Override
+ public Collection<String> getUrlPatterns()
+ {
+ return new ArrayList<>(url_patterns_);
+ }
+
+ @Override
+ public String getElIgnored()
+ {
+ return el_ignored_;
+ }
+
+ @Override
+ public String getPageEncoding()
+ {
+ return page_encoding_;
+ }
+
+ @Override
+ public String getScriptingInvalid()
+ {
+ return scripting_invalid_;
+ }
+
+ @Override
+ public String getIsXml()
+ {
+ return is_xml_;
+ }
+
+ @Override
+ public Collection<String> getIncludePreludes()
+ {
+ return new ArrayList<>(include_preludes_);
+ }
+
+ @Override
+ public Collection<String> getIncludeCodas()
+ {
+ return new ArrayList<>(include_codas_);
+ }
+
+ @Override
+ public String getDeferredSyntaxAllowedAsLiteral()
+ {
+ return deffered_syntax_allowed_as_literal_;
+ }
+
+ @Override
+ public String getTrimDirectiveWhitespaces()
+ {
+ return trim_directive_whitespaces_;
+ }
+
+ @Override
+ public String getDefaultContentType()
+ {
+ return default_content_type_;
+ }
+
+ @Override
+ public String getBuffer()
+ {
+ return buffer_;
+ }
+
+ @Override
+ public String getErrorOnUndeclaredNamespace()
+ {
+ return error_on_undeclared_namespace_;
+ }
+}
+
diff --git a/src/java/nginx/unit/OutputStream.java b/src/java/nginx/unit/OutputStream.java
new file mode 100644
index 00000000..68af3423
--- /dev/null
+++ b/src/java/nginx/unit/OutputStream.java
@@ -0,0 +1,68 @@
+package nginx.unit;
+
+import java.io.IOException;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.WriteListener;
+
+public class OutputStream extends ServletOutputStream {
+
+ private long req_info_ptr;
+
+ public OutputStream(long ptr) {
+ req_info_ptr = ptr;
+ }
+
+ @Override
+ public void write(int b) throws IOException
+ {
+ write(req_info_ptr, b);
+ }
+
+ private static native void write(long req_info_ptr, int b);
+
+
+ @Override
+ public void write(byte b[], int off, int len) throws IOException
+ {
+ if (b == null) {
+ throw new NullPointerException();
+ } else if ((off < 0) || (off > b.length) || (len < 0) ||
+ ((off + len) > b.length) || ((off + len) < 0)) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return;
+ }
+
+ write(req_info_ptr, b, off, len);
+ }
+
+ private static native void write(long req_info_ptr, byte b[], int off, int len);
+
+ @Override
+ public void flush()
+ {
+ flush(req_info_ptr);
+ }
+
+ private static native void flush(long req_info_ptr);
+
+ @Override
+ public void close()
+ {
+ close(req_info_ptr);
+ }
+
+ private static native void close(long req_info_ptr);
+
+ @Override
+ public boolean isReady()
+ {
+ return true;
+ }
+
+ @Override
+ public void setWriteListener(WriteListener listener)
+ {
+ }
+}
diff --git a/src/java/nginx/unit/Request.java b/src/java/nginx/unit/Request.java
new file mode 100644
index 00000000..dc73c656
--- /dev/null
+++ b/src/java/nginx/unit/Request.java
@@ -0,0 +1,1123 @@
+package nginx.unit;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import java.lang.IllegalArgumentException;
+import java.lang.IllegalStateException;
+import java.lang.Object;
+import java.lang.String;
+import java.lang.StringBuffer;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import java.security.Principal;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletResponse;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.StringUtil;
+
+import org.eclipse.jetty.server.CookieCutter;
+import org.eclipse.jetty.http.MimeTypes;
+
+public class Request implements HttpServletRequest, DynamicPathRequest
+{
+ private final Context context;
+ private final long req_info_ptr;
+ private final long req_ptr;
+
+ protected String authType = null;
+
+ protected boolean cookiesParsed = false;
+
+ protected CookieCutter cookies = null;
+
+ private final Map<String, Object> attributes = new HashMap<>();
+
+ private MultiMap<String> parameters = null;
+
+ private final String context_path;
+ private String filter_path = null;
+ private String servlet_path = null;
+ private String path_info = null;
+ private String request_uri = null;
+ private String query_string = null;
+ private boolean query_string_valid = false;
+
+ private DispatcherType dispatcher_type = DispatcherType.REQUEST;
+
+ private String characterEncoding = null;
+
+ /**
+ * The only date format permitted when generating HTTP headers.
+ */
+ public static final String RFC1123_DATE =
+ "EEE, dd MMM yyyy HH:mm:ss zzz";
+
+ private static final SimpleDateFormat formats[] = {
+ new SimpleDateFormat(RFC1123_DATE, Locale.US),
+ new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
+ new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
+ };
+
+ private InputStream inputStream = null;
+ private BufferedReader reader = null;
+
+ private boolean request_session_id_parsed = false;
+ private String request_session_id = null;
+ private boolean request_session_id_from_cookie = false;
+ private boolean request_session_id_from_url = false;
+ private Session session = null;
+
+ private final ServletRequestAttributeListener attr_listener;
+
+ public static final String BARE = "nginx.unit.request.bare";
+
+ public Request(Context ctx, long req_info, long req) {
+ context = ctx;
+ req_info_ptr = req_info;
+ req_ptr = req;
+
+ attr_listener = context.getRequestAttributeListener();
+ context_path = context.getContextPath();
+ }
+
+ @Override
+ public boolean authenticate(HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ log("authenticate");
+
+ if (response.isCommitted()) {
+ throw new IllegalStateException();
+ }
+
+ return false;
+ }
+
+ @Override
+ public String getAuthType()
+ {
+ log("getAuthType");
+
+ return authType;
+ }
+
+ @Override
+ public String getContextPath()
+ {
+ trace("getContextPath: " + context_path);
+
+ return context_path;
+ }
+
+ @Override
+ public Cookie[] getCookies()
+ {
+ trace("getCookies");
+
+ if (!cookiesParsed) {
+ parseCookies();
+ }
+
+ //Javadoc for Request.getCookies() stipulates null for no cookies
+ if (cookies == null || cookies.getCookies().length == 0) {
+ return null;
+ }
+
+ return cookies.getCookies();
+ }
+
+ protected void parseCookies()
+ {
+ cookiesParsed = true;
+
+ cookies = new CookieCutter();
+
+ Enumeration<String> cookie_headers = getHeaders("Cookie");
+
+ while (cookie_headers.hasMoreElements()) {
+ cookies.addCookieField(cookie_headers.nextElement());
+ }
+ }
+
+ @Override
+ public long getDateHeader(String name)
+ {
+ trace("getDateHeader: " + name);
+
+ String value = getHeader(name);
+ if (value == null) {
+ return -1L;
+ }
+
+ long res = parseDate(value);
+ if (res == -1L) {
+ throw new IllegalArgumentException(value);
+ }
+
+ return res;
+ }
+
+ protected long parseDate(String value)
+ {
+ Date date = null;
+ for (int i = 0; (date == null) && (i < formats.length); i++) {
+ try {
+ date = formats[i].parse(value);
+ } catch (ParseException e) {
+ // Ignore
+ }
+ }
+ if (date == null) {
+ return -1L;
+ }
+ return date.getTime();
+ }
+
+ @Override
+ public String getHeader(String name)
+ {
+ String res = getHeader(req_ptr, name, name.length());
+
+ trace("getHeader: " + name + " = '" + res + "'");
+
+ return res;
+ }
+
+ private static native String getHeader(long req_ptr, String name, int name_len);
+
+
+ @Override
+ public Enumeration<String> getHeaderNames()
+ {
+ trace("getHeaderNames");
+
+ return getHeaderNames(req_ptr);
+ }
+
+ private static native Enumeration<String> getHeaderNames(long req_ptr);
+
+
+ @Override
+ public Enumeration<String> getHeaders(String name)
+ {
+ trace("getHeaders: " + name);
+
+ return getHeaders(req_ptr, name, name.length());
+ }
+
+ private static native Enumeration<String> getHeaders(long req_ptr, String name, int name_len);
+
+
+ @Override
+ public int getIntHeader(String name)
+ {
+ trace("getIntHeader: " + name);
+
+ return getIntHeader(req_ptr, name, name.length());
+ }
+
+ private static native int getIntHeader(long req_ptr, String name, int name_len);
+
+
+ @Override
+ public String getMethod()
+ {
+ trace("getMethod");
+
+ return getMethod(req_ptr);
+ }
+
+ private static native String getMethod(long req_ptr);
+
+
+ @Override
+ public Part getPart(String name) throws IOException, ServletException
+ {
+ log("getPart: " + name);
+
+ return null;
+ }
+
+ @Override
+ public Collection<Part> getParts() throws IOException, ServletException
+ {
+ log("getParts");
+
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String getPathInfo()
+ {
+ trace("getPathInfo: " + path_info);
+
+ return path_info;
+ }
+
+ @Override
+ public String getPathTranslated()
+ {
+ trace("getPathTranslated");
+
+ if (path_info == null) {
+ return null;
+ }
+
+ return context.getRealPath(path_info);
+ }
+
+ @Override
+ public String getQueryString()
+ {
+ if (!query_string_valid) {
+ query_string = getQueryString(req_ptr);
+ query_string_valid = true;
+ }
+
+ trace("getQueryString: " + query_string);
+
+ return query_string;
+ }
+
+ private static native String getQueryString(long req_ptr);
+
+ @Override
+ public void setQueryString(String query)
+ {
+ trace("setQueryString: " + query);
+
+ query_string = query;
+ query_string_valid = true;
+ }
+
+ @Override
+ public String getRemoteUser()
+ {
+ log("getRemoteUser");
+
+ /* TODO */
+ return null;
+ }
+
+ @Override
+ public String getRequestedSessionId()
+ {
+ trace("getRequestedSessionId");
+
+ if (!request_session_id_parsed) {
+ parseRequestSessionId();
+ }
+
+ return request_session_id;
+ }
+
+ private void parseRequestSessionId()
+ {
+ request_session_id_parsed = true;
+
+ Cookie[] cookies = getCookies();
+ if (cookies == null) {
+ return;
+ }
+
+ if (context.getEffectiveSessionTrackingModes().contains(
+ SessionTrackingMode.COOKIE))
+ {
+ final String name = context.getSessionCookieConfig().getName();
+
+ for (Cookie c : cookies) {
+ if (c.getName().equals(name)) {
+ request_session_id = c.getValue();
+ request_session_id_from_cookie = true;
+
+ return;
+ }
+ }
+ }
+ }
+
+ @Override
+ public String getRequestURI()
+ {
+ if (request_uri == null) {
+ request_uri = getRequestURI(req_ptr);
+ }
+
+ trace("getRequestURI: " + request_uri);
+
+ return request_uri;
+ }
+
+ private static native String getRequestURI(long req_ptr);
+
+
+ @Override
+ public void setRequestURI(String uri)
+ {
+ trace("setRequestURI: " + uri);
+
+ request_uri = uri;
+ }
+
+
+ @Override
+ public StringBuffer getRequestURL()
+ {
+ String host = getHeader("Host");
+ String uri = getRequestURI();
+ StringBuffer res = new StringBuffer("http://" + host + uri);
+
+ trace("getRequestURL: " + res);
+
+ return res;
+ }
+
+ @Override
+ public String getServletPath()
+ {
+ trace("getServletPath: " + servlet_path);
+
+ return servlet_path;
+ }
+
+ @Override
+ public void setServletPath(String servlet_path, String path_info)
+ {
+ trace("setServletPath: " + servlet_path);
+
+ this.filter_path = servlet_path;
+ this.servlet_path = servlet_path;
+ this.path_info = path_info;
+ }
+
+ @Override
+ public void setServletPath(String filter_path, String servlet_path, String path_info)
+ {
+ trace("setServletPath: " + filter_path + ", " + servlet_path);
+
+ this.filter_path = filter_path;
+ this.servlet_path = servlet_path;
+ this.path_info = path_info;
+ }
+
+ @Override
+ public String getFilterPath()
+ {
+ return filter_path;
+ }
+
+ @Override
+ public HttpSession getSession()
+ {
+ return getSession(true);
+ }
+
+ @Override
+ public HttpSession getSession(boolean create)
+ {
+ if (session != null) {
+ if (context.isSessionIdValid(session.getId())) {
+ trace("getSession(" + create + "): " + session.getId());
+
+ return session;
+ }
+
+ session = null;
+ }
+
+ if (!request_session_id_parsed) {
+ parseRequestSessionId();
+
+ session = context.getSession(request_session_id);
+ }
+
+ if (session != null || !create) {
+ trace("getSession(" + create + "): " + (session != null ? session.getId() : "null"));
+
+ return session;
+ }
+
+ session = context.createSession();
+
+ if (context.getEffectiveSessionTrackingModes().contains(
+ SessionTrackingMode.COOKIE))
+ {
+ setSessionIdCookie();
+ }
+
+ trace("getSession(" + create + "): " + session.getId());
+
+ return session;
+ }
+
+ private void setSessionIdCookie()
+ {
+ SessionCookieConfig config = context.getSessionCookieConfig();
+
+ Cookie c = new Cookie(config.getName(), session.getId());
+
+ c.setComment(config.getComment());
+ if (!StringUtil.isBlank(config.getDomain())) {
+ c.setDomain(config.getDomain());
+ }
+
+ c.setHttpOnly(config.isHttpOnly());
+ if (!StringUtil.isBlank(config.getPath())) {
+ c.setPath(config.getPath());
+ }
+
+ c.setMaxAge(config.getMaxAge());
+
+ getResponse(req_info_ptr).addSessionIdCookie(c);
+ }
+
+ @Override
+ public Principal getUserPrincipal()
+ {
+ log("getUserPrincipal");
+
+ return null;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromCookie()
+ {
+ trace("isRequestedSessionIdFromCookie");
+
+ if (!request_session_id_parsed) {
+ parseRequestSessionId();
+ }
+
+ return request_session_id_from_cookie;
+ }
+
+ @Override
+ @Deprecated
+ public boolean isRequestedSessionIdFromUrl()
+ {
+ trace("isRequestedSessionIdFromUrl");
+
+ if (!request_session_id_parsed) {
+ parseRequestSessionId();
+ }
+
+ return request_session_id_from_url;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromURL()
+ {
+ trace("isRequestedSessionIdFromURL");
+
+ if (!request_session_id_parsed) {
+ parseRequestSessionId();
+ }
+
+ return request_session_id_from_url;
+ }
+
+ @Override
+ public boolean isRequestedSessionIdValid()
+ {
+ trace("isRequestedSessionIdValid");
+
+ if (!request_session_id_parsed) {
+ parseRequestSessionId();
+ }
+
+ return context.isSessionIdValid(request_session_id);
+ }
+
+ @Override
+ public boolean isUserInRole(String role)
+ {
+ log("isUserInRole: " + role);
+
+ return false;
+ }
+
+ @Override
+ public void login(String username, String password) throws ServletException
+ {
+ log("login: " + username + "," + password);
+ }
+
+ @Override
+ public void logout() throws ServletException
+ {
+ log("logout");
+ }
+
+
+ @Override
+ public AsyncContext getAsyncContext()
+ {
+ log("getAsyncContext");
+
+ return null;
+ }
+
+ @Override
+ public Object getAttribute(String name)
+ {
+ if (BARE.equals(name)) {
+ return this;
+ }
+
+ Object o = attributes.get(name);
+
+ trace("getAttribute: " + name + " = " + o);
+
+ return o;
+ }
+
+ @Override
+ public Enumeration<String> getAttributeNames()
+ {
+ trace("getAttributeNames");
+
+ Set<String> names = attributes.keySet();
+ return Collections.enumeration(names);
+ }
+
+ @Override
+ public String getCharacterEncoding()
+ {
+ trace("getCharacterEncoding");
+
+ if (characterEncoding != null) {
+ return characterEncoding;
+ }
+
+ getContentType();
+
+ return characterEncoding;
+ }
+
+ @Override
+ public int getContentLength()
+ {
+ trace("getContentLength");
+
+ return (int) getContentLength(req_ptr);
+ }
+
+ private static native long getContentLength(long req_ptr);
+
+ @Override
+ public long getContentLengthLong()
+ {
+ trace("getContentLengthLong");
+
+ return getContentLength(req_ptr);
+ }
+
+ @Override
+ public String getContentType()
+ {
+ trace("getContentType");
+
+ String content_type = getContentType(req_ptr);
+
+ if (characterEncoding == null && content_type != null) {
+ MimeTypes.Type mime = MimeTypes.CACHE.get(content_type);
+ String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(content_type) : mime.getCharset().toString();
+ if (charset != null) {
+ characterEncoding = charset;
+ }
+ }
+
+ return content_type;
+ }
+
+ private static native String getContentType(long req_ptr);
+
+
+ @Override
+ public DispatcherType getDispatcherType()
+ {
+ trace("getDispatcherType: " + dispatcher_type);
+
+ return dispatcher_type;
+ }
+
+ @Override
+ public void setDispatcherType(DispatcherType type)
+ {
+ trace("setDispatcherType: " + type);
+
+ dispatcher_type = type;
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException
+ {
+ trace("getInputStream");
+
+ if (reader != null) {
+ throw new IllegalStateException("getInputStream: getReader() already used");
+ }
+
+ if (inputStream == null) {
+ inputStream = new InputStream(req_info_ptr);
+ }
+
+ return inputStream;
+ }
+
+ @Override
+ public String getLocalAddr()
+ {
+ trace("getLocalAddr");
+
+ return getLocalAddr(req_ptr);
+ }
+
+ private static native String getLocalAddr(long req_ptr);
+
+
+ @Override
+ public Locale getLocale()
+ {
+ log("getLocale");
+
+ return Locale.getDefault();
+ }
+
+ @Override
+ public Enumeration<Locale> getLocales()
+ {
+ log("getLocales");
+
+ return Collections.emptyEnumeration();
+ }
+
+ @Override
+ public String getLocalName()
+ {
+ trace("getLocalName");
+
+ return getLocalName(req_ptr);
+ }
+
+ private static native String getLocalName(long req_ptr);
+
+
+ @Override
+ public int getLocalPort()
+ {
+ trace("getLocalPort");
+
+ return getLocalPort(req_ptr);
+ }
+
+ private static native int getLocalPort(long req_ptr);
+
+
+ public MultiMap<String> getParameters()
+ {
+ if (parameters != null) {
+ return parameters;
+ }
+
+ parameters = new MultiMap<>();
+
+ String query = getQueryString();
+
+ if (query != null) {
+ UrlEncoded.decodeUtf8To(query, parameters);
+ }
+
+ if (getContentLength() > 0 &&
+ getMethod().equals("POST") &&
+ getContentType().startsWith("application/x-www-form-urlencoded"))
+ {
+ try {
+ UrlEncoded.decodeUtf8To(new InputStream(req_info_ptr),
+ parameters, getContentLength(), -1);
+ } catch (IOException e) {
+ log("Unhandled IOException: " + e);
+ }
+ }
+
+ return parameters;
+ }
+
+ public void setParameters(MultiMap<String> p)
+ {
+ parameters = p;
+ }
+
+ @Override
+ public String getParameter(String name)
+ {
+ trace("getParameter: " + name);
+
+ return getParameters().getValue(name, 0);
+ }
+
+ @Override
+ public Map<String,String[]> getParameterMap()
+ {
+ trace("getParameterMap");
+
+ return Collections.unmodifiableMap(getParameters().toStringArrayMap());
+ }
+
+ @Override
+ public Enumeration<String> getParameterNames()
+ {
+ trace("getParameterNames");
+
+ return Collections.enumeration(getParameters().keySet());
+ }
+
+ @Override
+ public String[] getParameterValues(String name)
+ {
+ trace("getParameterValues: " + name);
+
+ List<String> vals = getParameters().getValues(name);
+ if (vals == null)
+ return null;
+ return vals.toArray(new String[vals.size()]);
+ }
+
+ @Override
+ public String getProtocol()
+ {
+ trace("getProtocol");
+
+ return getProtocol(req_ptr);
+ }
+
+ private static native String getProtocol(long req_ptr);
+
+ @Override
+ public BufferedReader getReader() throws IOException
+ {
+ trace("getReader");
+
+ if (inputStream != null) {
+ throw new IllegalStateException("getReader: getInputStream() already used");
+ }
+
+ if (reader == null) {
+ reader = new BufferedReader(new InputStreamReader(new InputStream(req_info_ptr)));
+ }
+
+ return reader;
+ }
+
+ @Override
+ @Deprecated
+ public String getRealPath(String path)
+ {
+ trace("getRealPath: " + path);
+
+ return context.getRealPath(path);
+ }
+
+ @Override
+ public String getRemoteAddr()
+ {
+ String res = getRemoteAddr(req_ptr);
+
+ trace("getRemoteAddr: " + res);
+
+ return res;
+ }
+
+ private static native String getRemoteAddr(long req_ptr);
+
+
+ @Override
+ public String getRemoteHost()
+ {
+ String res = getRemoteHost(req_ptr);
+
+ trace("getRemoteHost: " + res);
+
+ return res;
+ }
+
+ private static native String getRemoteHost(long req_ptr);
+
+
+ @Override
+ public int getRemotePort()
+ {
+ int res = getRemotePort(req_ptr);
+
+ trace("getRemotePort: " + res);
+
+ return res;
+ }
+
+ private static native int getRemotePort(long req_ptr);
+
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path)
+ {
+ trace("getRequestDispatcher: " + path);
+
+ if (path.startsWith("/")) {
+ return context.getRequestDispatcher(path);
+ }
+
+ try {
+ URI uri = new URI(getRequestURI());
+ uri = uri.resolve(path);
+
+ return context.getRequestDispatcher(uri);
+ } catch (URISyntaxException e) {
+ log("getRequestDispatcher: failed to create dispatcher: " + e);
+ }
+
+ return null;
+ }
+
+
+ @Override
+ public String getScheme()
+ {
+ log("getScheme");
+
+ return getScheme(req_ptr);
+ }
+
+ private static native String getScheme(long req_ptr);
+
+
+ @Override
+ public String getServerName()
+ {
+ String res = getServerName(req_ptr);
+
+ trace("getServerName: " + res);
+
+ return res;
+ }
+
+ private static native String getServerName(long req_ptr);
+
+
+ @Override
+ public int getServerPort()
+ {
+ int res = getServerPort(req_ptr);
+
+ trace("getServerPort: " + res);
+
+ return res;
+ }
+
+ private static native int getServerPort(long req_ptr);
+
+ @Override
+ public ServletContext getServletContext()
+ {
+ trace("getServletContext");
+
+ return context;
+ }
+
+ @Override
+ public boolean isAsyncStarted()
+ {
+ log("isAsyncStarted");
+
+ return false;
+ }
+
+ @Override
+ public boolean isAsyncSupported()
+ {
+ log("isAsyncSupported");
+
+ return false;
+ }
+
+ @Override
+ public boolean isSecure()
+ {
+ log("isSecure");
+
+ return false;
+ }
+
+ @Override
+ public void removeAttribute(String name)
+ {
+ trace("removeAttribute: " + name);
+
+ Object prev = attributes.remove(name);
+
+ if (attr_listener == null || prev == null) {
+ return;
+ }
+
+ attr_listener.attributeRemoved(
+ new ServletRequestAttributeEvent(context, this, name, prev));
+ }
+
+ @Override
+ public void setAttribute(String name, Object o)
+ {
+ trace("setAttribute: " + name + ", " + o);
+
+ Object prev;
+
+ if (o != null) {
+ prev = attributes.put(name, o);
+ } else {
+ prev = attributes.remove(name);
+ }
+
+ if (attr_listener == null) {
+ return;
+ }
+
+ if (prev == null) {
+ if (o == null) {
+ return;
+ }
+
+ attr_listener.attributeAdded(new ServletRequestAttributeEvent(
+ context, this, name, o));
+ } else {
+ if (o != null) {
+ attr_listener.attributeReplaced(
+ new ServletRequestAttributeEvent(context, this, name, prev));
+ } else {
+ attr_listener.attributeRemoved(
+ new ServletRequestAttributeEvent(context, this, name, prev));
+ }
+ }
+ }
+
+ public void setAttribute_(String name, Object o)
+ {
+ trace("setAttribute_: " + name + ", " + o);
+
+ if (o != null) {
+ attributes.put(name, o);
+ } else {
+ attributes.remove(name);
+ }
+ }
+
+ @Override
+ public void setCharacterEncoding(String env) throws UnsupportedEncodingException
+ {
+ trace("setCharacterEncoding: " + env);
+
+ characterEncoding = env;
+ }
+
+ @Override
+ public AsyncContext startAsync() throws IllegalStateException
+ {
+ log("startAsync");
+
+ return null;
+ }
+
+ @Override
+ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException
+ {
+ log("startAsync(Req, resp)");
+
+ return null;
+ }
+
+ @Override
+ public <T extends HttpUpgradeHandler> T upgrade(
+ Class<T> httpUpgradeHandlerClass) throws java.io.IOException, ServletException
+ {
+ log("upgrade: " + httpUpgradeHandlerClass.getName());
+
+ return null;
+ }
+
+ @Override
+ public String changeSessionId()
+ {
+ trace("changeSessionId");
+
+ getSession(false);
+
+ if (session == null) {
+ return null;
+ }
+
+ context.changeSessionId(session);
+
+ if (context.getEffectiveSessionTrackingModes().contains(
+ SessionTrackingMode.COOKIE))
+ {
+ setSessionIdCookie();
+ }
+
+ return session.getId();
+ }
+
+ private void log(String msg)
+ {
+ msg = "Request." + msg;
+ log(req_info_ptr, msg, msg.length());
+ }
+
+ public static native void log(long req_info_ptr, String msg, int msg_len);
+
+
+ private void trace(String msg)
+ {
+ msg = "Request." + msg;
+ trace(req_info_ptr, msg, msg.length());
+ }
+
+ public static native void trace(long req_info_ptr, String msg, int msg_len);
+
+ private static native Response getResponse(long req_info_ptr);
+}
+
diff --git a/src/java/nginx/unit/RequestAttrProxy.java b/src/java/nginx/unit/RequestAttrProxy.java
new file mode 100644
index 00000000..daedd01a
--- /dev/null
+++ b/src/java/nginx/unit/RequestAttrProxy.java
@@ -0,0 +1,40 @@
+package nginx.unit;
+
+import java.util.List;
+
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+
+public class RequestAttrProxy implements ServletRequestAttributeListener
+{
+ private final List<ServletRequestAttributeListener> listeners_;
+
+ public RequestAttrProxy(List<ServletRequestAttributeListener> listeners)
+ {
+ listeners_ = listeners;
+ }
+
+ @Override
+ public void attributeAdded(ServletRequestAttributeEvent srae)
+ {
+ for (ServletRequestAttributeListener l : listeners_) {
+ l.attributeAdded(srae);
+ }
+ }
+
+ @Override
+ public void attributeReplaced(ServletRequestAttributeEvent srae)
+ {
+ for (ServletRequestAttributeListener l : listeners_) {
+ l.attributeReplaced(srae);
+ }
+ }
+
+ @Override
+ public void attributeRemoved(ServletRequestAttributeEvent srae)
+ {
+ for (ServletRequestAttributeListener l : listeners_) {
+ l.attributeRemoved(srae);
+ }
+ }
+}
diff --git a/src/java/nginx/unit/Response.java b/src/java/nginx/unit/Response.java
new file mode 100644
index 00000000..099d7f15
--- /dev/null
+++ b/src/java/nginx/unit/Response.java
@@ -0,0 +1,817 @@
+package nginx.unit;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import java.lang.IllegalArgumentException;
+import java.lang.String;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import java.text.SimpleDateFormat;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.Vector;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.util.StringUtil;
+
+public class Response implements HttpServletResponse {
+
+ private long req_info_ptr;
+
+ private static final String defaultCharacterEncoding = "iso-8859-1";
+ private String characterEncoding = defaultCharacterEncoding;
+ private String contentType = null;
+ private String contentTypeHeader = null;
+
+ private static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
+ private static final Charset UTF_8 = StandardCharsets.UTF_8;
+
+ private static final String CONTENT_TYPE = "Content-Type";
+ private static final byte[] SET_COOKIE_BYTES = "Set-Cookie".getBytes(ISO_8859_1);
+ private static final byte[] EXPIRES_BYTES = "Expires".getBytes(ISO_8859_1);
+
+ /**
+ * The only date format permitted when generating HTTP headers.
+ */
+ public static final String RFC1123_DATE =
+ "EEE, dd MMM yyyy HH:mm:ss zzz";
+
+ private static final SimpleDateFormat format =
+ new SimpleDateFormat(RFC1123_DATE, Locale.US);
+
+ private static final String ZERO_DATE_STRING = dateToString(0);
+ private static final byte[] ZERO_DATE_BYTES = ZERO_DATE_STRING.getBytes(ISO_8859_1);
+
+ /**
+ * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie
+ * will be set as HTTP ONLY.
+ */
+ public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
+
+ private OutputStream outputStream = null;
+
+ private PrintWriter writer = null;
+
+
+ public Response(long ptr) {
+ req_info_ptr = ptr;
+ }
+
+ /**
+ * Format a set cookie value by RFC6265
+ *
+ * @param name the name
+ * @param value the value
+ * @param domain the domain
+ * @param path the path
+ * @param maxAge the maximum age
+ * @param isSecure true if secure cookie
+ * @param isHttpOnly true if for http only
+ */
+ public void addSetRFC6265Cookie(
+ final String name,
+ final String value,
+ final String domain,
+ final String path,
+ final long maxAge,
+ final boolean isSecure,
+ final boolean isHttpOnly)
+ {
+ // Check arguments
+ if (name == null || name.length() == 0) {
+ throw new IllegalArgumentException("Bad cookie name");
+ }
+
+ // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
+ // Per RFC6265, Cookie.name follows RFC2616 Section 2.2 token rules
+ //Syntax.requireValidRFC2616Token(name, "RFC6265 Cookie name");
+ // Ensure that Per RFC6265, Cookie.value follows syntax rules
+ //Syntax.requireValidRFC6265CookieValue(value);
+
+ // Format value and params
+ StringBuilder buf = new StringBuilder();
+ buf.append(name).append('=').append(value == null ? "" : value);
+
+ // Append path
+ if (path != null && path.length() > 0) {
+ buf.append(";Path=").append(path);
+ }
+
+ // Append domain
+ if (domain != null && domain.length() > 0) {
+ buf.append(";Domain=").append(domain);
+ }
+
+ // Handle max-age and/or expires
+ if (maxAge >= 0) {
+ // Always use expires
+ // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
+ buf.append(";Expires=");
+ if (maxAge == 0)
+ buf.append(ZERO_DATE_STRING);
+ else
+ buf.append(dateToString(System.currentTimeMillis() + 1000L * maxAge));
+
+ buf.append(";Max-Age=");
+ buf.append(maxAge);
+ }
+
+ // add the other fields
+ if (isSecure)
+ buf.append(";Secure");
+ if (isHttpOnly)
+ buf.append(";HttpOnly");
+
+ // add the set cookie
+ addHeader(req_info_ptr, SET_COOKIE_BYTES,
+ buf.toString().getBytes(ISO_8859_1));
+
+ // Expire responses with set-cookie headers so they do not get cached.
+ setHeader(req_info_ptr, EXPIRES_BYTES, ZERO_DATE_BYTES);
+ }
+
+ @Override
+ public void addCookie(Cookie cookie)
+ {
+ trace("addCookie: " + cookie.getName() + "=" + cookie.getValue());
+
+ if (StringUtil.isBlank(cookie.getName())) {
+ throw new IllegalArgumentException("Cookie.name cannot be blank/null");
+ }
+
+ if (isCommitted()) {
+ return;
+ }
+
+ addCookie_(cookie);
+ }
+
+ private void addCookie_(Cookie cookie)
+ {
+ String comment = cookie.getComment();
+ boolean httpOnly = false;
+
+ if (comment != null && comment.contains(HTTP_ONLY_COMMENT)) {
+ httpOnly = true;
+ }
+
+ addSetRFC6265Cookie(cookie.getName(),
+ cookie.getValue(),
+ cookie.getDomain(),
+ cookie.getPath(),
+ cookie.getMaxAge(),
+ cookie.getSecure(),
+ httpOnly || cookie.isHttpOnly());
+ }
+
+ public void addSessionIdCookie(Cookie cookie)
+ {
+ trace("addSessionIdCookie: " + cookie.getName() + "=" + cookie.getValue());
+
+ if (isCommitted()) {
+ /*
+ 9.3 The Include Method
+
+ ... any call to HttpServletRequest.getSession() or
+ HttpServletRequest.getSession(boolean) that would require
+ adding a Cookie response header must throw an
+ IllegalStateException if the response has been committed.
+ */
+ throw new IllegalStateException("Response already sent");
+ }
+
+ addCookie_(cookie);
+ }
+
+ @Override
+ public void addDateHeader(String name, long date)
+ {
+ trace("addDateHeader: " + name + ": " + date);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ String value = dateToString(date);
+
+ addHeader(req_info_ptr, name.getBytes(ISO_8859_1),
+ value.getBytes(ISO_8859_1));
+ }
+
+ private static String dateToString(long date)
+ {
+ Date dateValue = new Date(date);
+ format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return format.format(dateValue);
+ }
+
+
+ @Override
+ public void addHeader(String name, String value)
+ {
+ trace("addHeader: " + name + ": " + value);
+
+ if (value == null) {
+ return;
+ }
+
+ if (isCommitted()) {
+ return;
+ }
+
+ if (CONTENT_TYPE.equalsIgnoreCase(name)) {
+ setContentType(value);
+ return;
+ }
+
+ addHeader(req_info_ptr, name.getBytes(ISO_8859_1),
+ value.getBytes(ISO_8859_1));
+ }
+
+ private static native void addHeader(long req_info_ptr, byte[] name, byte[] value);
+
+
+ @Override
+ public void addIntHeader(String name, int value)
+ {
+ trace("addIntHeader: " + name + ": " + value);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ addIntHeader(req_info_ptr, name.getBytes(ISO_8859_1), value);
+ }
+
+ private static native void addIntHeader(long req_info_ptr, byte[] name, int value);
+
+
+ @Override
+ public boolean containsHeader(String name)
+ {
+ trace("containsHeader: " + name);
+
+ return containsHeader(req_info_ptr, name.getBytes(ISO_8859_1));
+ }
+
+ private static native boolean containsHeader(long req_info_ptr, byte[] name);
+
+
+ @Override
+ @Deprecated
+ public String encodeRedirectUrl(String url)
+ {
+ return encodeRedirectURL(url);
+ }
+
+ @Override
+ public String encodeRedirectURL(String url)
+ {
+ log("encodeRedirectURL: " + url);
+
+ return url;
+ }
+
+ @Override
+ @Deprecated
+ public String encodeUrl(String url)
+ {
+ return encodeURL(url);
+ }
+
+ @Override
+ public String encodeURL(String url)
+ {
+ log("encodeURL: " + url);
+
+ return url;
+ }
+
+ @Override
+ public String getHeader(String name)
+ {
+ trace("getHeader: " + name);
+
+ return getHeader(req_info_ptr, name.getBytes(ISO_8859_1));
+ }
+
+ private static native String getHeader(long req_info_ptr, byte[] name);
+
+
+ @Override
+ public Collection<String> getHeaderNames()
+ {
+ trace("getHeaderNames");
+
+ Enumeration<String> e = getHeaderNames(req_info_ptr);
+ if (e == null) {
+ return Collections.emptyList();
+ }
+
+ return Collections.list(e);
+ }
+
+ private static native Enumeration<String> getHeaderNames(long req_info_ptr);
+
+
+ @Override
+ public Collection<String> getHeaders(String name)
+ {
+ trace("getHeaders: " + name);
+
+ Enumeration<String> e = getHeaders(req_info_ptr, name.getBytes(ISO_8859_1));
+ if (e == null) {
+ return Collections.emptyList();
+ }
+
+ return Collections.list(e);
+ }
+
+ private static native Enumeration<String> getHeaders(long req_info_ptr, byte[] name);
+
+
+ @Override
+ public int getStatus()
+ {
+ trace("getStatus");
+
+ return getStatus(req_info_ptr);
+ }
+
+ private static native int getStatus(long req_info_ptr);
+
+
+ @Override
+ public void sendError(int sc) throws IOException
+ {
+ sendError(sc, null);
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException
+ {
+ trace("sendError: " + sc + ", " + msg);
+
+ if (isCommitted()) {
+ throw new IllegalStateException("Response already sent");
+ }
+
+ setStatus(sc);
+
+ Request request = getRequest(req_info_ptr);
+
+ // If we are allowed to have a body, then produce the error page.
+ if (sc != SC_NO_CONTENT && sc != SC_NOT_MODIFIED &&
+ sc != SC_PARTIAL_CONTENT && sc >= SC_OK)
+ {
+ request.setAttribute_(RequestDispatcher.ERROR_STATUS_CODE, sc);
+ request.setAttribute_(RequestDispatcher.ERROR_MESSAGE, msg);
+ request.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI,
+ request.getRequestURI());
+/*
+ request.setAttribute_(RequestDispatcher.ERROR_SERVLET_NAME,
+ request.getServletName());
+*/
+ }
+
+/*
+ Avoid commit and give chance for error handlers.
+
+ if (!request.isAsyncStarted()) {
+ commit();
+ }
+*/
+ }
+
+ private static native Request getRequest(long req_info_ptr);
+
+ private void commit()
+ {
+ if (writer != null) {
+ writer.close();
+
+ } else if (outputStream != null) {
+ outputStream.close();
+
+ } else {
+ commit(req_info_ptr);
+ }
+ }
+
+ private static native void commit(long req_info_ptr);
+
+
+ @Override
+ public void sendRedirect(String location) throws IOException
+ {
+ trace("sendRedirect: " + location);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ try {
+ URI uri = new URI(location);
+
+ if (!uri.isAbsolute()) {
+ URI req_uri = new URI(getRequest(req_info_ptr).getRequestURL().toString());
+ uri = req_uri.resolve(uri);
+
+ location = uri.toString();
+ }
+ } catch (URISyntaxException e) {
+ log("sendRedirect: failed to send redirect: " + e);
+ return;
+ }
+
+ sendRedirect(req_info_ptr, location.getBytes(ISO_8859_1));
+ }
+
+ private static native void sendRedirect(long req_info_ptr, byte[] location);
+
+
+ @Override
+ public void setDateHeader(String name, long date)
+ {
+ trace("setDateHeader: " + name + ": " + date);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ String value = dateToString(date);
+
+ setHeader(req_info_ptr, name.getBytes(ISO_8859_1),
+ value.getBytes(ISO_8859_1));
+ }
+
+
+ @Override
+ public void setHeader(String name, String value)
+ {
+ trace("setHeader: " + name + ": " + value);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ if (CONTENT_TYPE.equalsIgnoreCase(name)) {
+ setContentType(value);
+ return;
+ }
+
+ /*
+ * When value is null container behaviour is undefined.
+ * - Tomcat ignores setHeader call;
+ * - Jetty & Resin acts as removeHeader;
+ */
+ if (value == null) {
+ removeHeader(req_info_ptr, name.getBytes(ISO_8859_1));
+ return;
+ }
+
+ setHeader(req_info_ptr, name.getBytes(ISO_8859_1),
+ value.getBytes(ISO_8859_1));
+ }
+
+ private static native void setHeader(long req_info_ptr, byte[] name, byte[] value);
+
+ private static native void removeHeader(long req_info_ptr, byte[] name);
+
+ @Override
+ public void setIntHeader(String name, int value)
+ {
+ trace("setIntHeader: " + name + ": " + value);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ setIntHeader(req_info_ptr, name.getBytes(ISO_8859_1), value);
+ }
+
+ private static native void setIntHeader(long req_info_ptr, byte[] name, int value);
+
+
+ @Override
+ public void setStatus(int sc)
+ {
+ trace("setStatus: " + sc);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ setStatus(req_info_ptr, sc);
+ }
+
+ private static native void setStatus(long req_info_ptr, int sc);
+
+
+ @Override
+ @Deprecated
+ public void setStatus(int sc, String sm)
+ {
+ trace("setStatus: " + sc + "; " + sm);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ setStatus(req_info_ptr, sc);
+ }
+
+
+ @Override
+ public void flushBuffer() throws IOException
+ {
+ trace("flushBuffer");
+
+ if (writer != null) {
+ writer.flush();
+ }
+
+ if (outputStream != null) {
+ outputStream.flush();
+ }
+ }
+
+ @Override
+ public int getBufferSize()
+ {
+ trace("getBufferSize");
+
+ return getBufferSize(req_info_ptr);
+ }
+
+ public static native int getBufferSize(long req_info_ptr);
+
+
+ @Override
+ public String getCharacterEncoding()
+ {
+ trace("getCharacterEncoding");
+
+ return characterEncoding;
+ }
+
+ @Override
+ public String getContentType()
+ {
+ /* In JIRA decorator get content type called after commit. */
+
+ String res = contentTypeHeader;
+
+ trace("getContentType: " + res);
+
+ return res;
+ }
+
+ private static native String getContentType(long req_info_ptr);
+
+ @Override
+ public Locale getLocale()
+ {
+ log("getLocale");
+
+ return null;
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException
+ {
+ trace("getOutputStream");
+
+ if (writer != null) {
+ throw new IllegalStateException("Writer already created");
+ }
+
+ if (outputStream == null) {
+ outputStream = new OutputStream(req_info_ptr);
+ }
+
+ return outputStream;
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException
+ {
+ trace("getWriter ( characterEncoding = '" + characterEncoding + "' )");
+
+ if (outputStream != null) {
+ throw new IllegalStateException("OutputStream already created");
+ }
+
+ if (writer == null) {
+ ServletOutputStream stream = new OutputStream(req_info_ptr);
+
+ writer = new PrintWriter(
+ new OutputStreamWriter(stream, Charset.forName(characterEncoding)),
+ false);
+ }
+
+ return writer;
+ }
+
+ @Override
+ public boolean isCommitted()
+ {
+ trace("isCommitted");
+
+ return isCommitted(req_info_ptr);
+ }
+
+ public static native boolean isCommitted(long req_info_ptr);
+
+ @Override
+ public void reset()
+ {
+ trace("reset");
+
+ if (isCommitted()) {
+ return;
+ }
+
+ reset(req_info_ptr);
+
+ writer = null;
+ outputStream = null;
+ }
+
+ public static native void reset(long req_info_ptr);
+
+ @Override
+ public void resetBuffer()
+ {
+ trace("resetBuffer");
+
+ resetBuffer(req_info_ptr);
+
+ writer = null;
+ outputStream = null;
+ }
+
+ public static native void resetBuffer(long req_info_ptr);
+
+ @Override
+ public void setBufferSize(int size)
+ {
+ trace("setBufferSize: " + size);
+
+ setBufferSize(req_info_ptr, size);
+ }
+
+ public static native void setBufferSize(long req_info_ptr, int size);
+
+ @Override
+ public void setCharacterEncoding(String charset)
+ {
+ trace("setCharacterEncoding " + charset);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ if (charset == null) {
+ if (writer != null
+ && !characterEncoding.equalsIgnoreCase(defaultCharacterEncoding))
+ {
+ /* TODO throw */
+ return;
+ }
+
+ characterEncoding = defaultCharacterEncoding;
+ } else {
+ if (writer != null
+ && !characterEncoding.equalsIgnoreCase(charset))
+ {
+ /* TODO throw */
+ return;
+ }
+
+ characterEncoding = charset;
+ }
+
+ if (contentType != null) {
+ String type = contentType + ";charset=" + characterEncoding;
+
+ contentTypeHeader = type;
+
+ setContentType(req_info_ptr, type.getBytes(ISO_8859_1));
+ }
+ }
+
+
+ @Override
+ public void setContentLength(int len)
+ {
+ trace("setContentLength: " + len);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ setContentLength(req_info_ptr, len);
+ }
+
+ @Override
+ public void setContentLengthLong(long len)
+ {
+ trace("setContentLengthLong: " + len);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ setContentLength(req_info_ptr, len);
+ }
+
+ private static native void setContentLength(long req_info_ptr, long len);
+
+
+ @Override
+ public void setContentType(String type)
+ {
+ trace("setContentType: " + type);
+
+ if (isCommitted()) {
+ return;
+ }
+
+ if (type == null) {
+ removeContentType(req_info_ptr);
+ contentType = null;
+ contentTypeHeader = null;
+ return;
+ }
+
+ String charset = MimeTypes.getCharsetFromContentType(type);
+ String ctype = MimeTypes.getContentTypeWithoutCharset(type);
+
+ if (writer != null
+ && charset != null
+ && !characterEncoding.equalsIgnoreCase(charset))
+ {
+ /* To late to change character encoding */
+ charset = characterEncoding;
+ type = ctype + ";charset=" + characterEncoding;
+ }
+
+ if (charset == null) {
+ type = type + ";charset=" + characterEncoding;
+ } else {
+ characterEncoding = charset;
+ }
+
+ contentType = ctype;
+ contentTypeHeader = type;
+
+ setContentType(req_info_ptr, type.getBytes(ISO_8859_1));
+ }
+
+ private static native void setContentType(long req_info_ptr, byte[] type);
+
+ private static native void removeContentType(long req_info_ptr);
+
+
+ @Override
+ public void setLocale(Locale loc)
+ {
+ log("setLocale: " + loc);
+ }
+
+ private void log(String msg)
+ {
+ msg = "Response." + msg;
+ log(req_info_ptr, msg.getBytes(UTF_8));
+ }
+
+ public static native void log(long req_info_ptr, byte[] msg);
+
+
+ private void trace(String msg)
+ {
+ msg = "Response." + msg;
+ trace(req_info_ptr, msg.getBytes(UTF_8));
+ }
+
+ public static native void trace(long req_info_ptr, byte[] msg);
+}
diff --git a/src/java/nginx/unit/Session.java b/src/java/nginx/unit/Session.java
new file mode 100644
index 00000000..6be74709
--- /dev/null
+++ b/src/java/nginx/unit/Session.java
@@ -0,0 +1,251 @@
+package nginx.unit;
+
+import java.io.Serializable;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Andrey Kazankov
+ */
+public class Session implements HttpSession, Serializable
+{
+ private final Map<String, Object> attributes = new HashMap<>();
+ private final long creation_time = new Date().getTime();
+ private long last_access_time = creation_time;
+ private long access_time = creation_time;
+ private int max_inactive_interval;
+ private String id;
+ private final Context context;
+ private boolean is_new = true;
+ private final HttpSessionAttributeListener attr_listener;
+
+ public Session(Context context, String id,
+ HttpSessionAttributeListener al, int max_inactive_interval)
+ {
+ this.id = id;
+ this.context = context;
+ attr_listener = al;
+ this.max_inactive_interval = max_inactive_interval;
+ }
+
+ public void setId(String id)
+ {
+ this.id = id;
+ }
+
+ @Override
+ public long getCreationTime()
+ {
+ return creation_time;
+ }
+
+ @Override
+ public String getId()
+ {
+ return id;
+ }
+
+ @Override
+ public long getLastAccessedTime()
+ {
+ return last_access_time;
+ }
+
+ @Override
+ public ServletContext getServletContext()
+ {
+ return context;
+ }
+
+ @Override
+ public void setMaxInactiveInterval(int i)
+ {
+ max_inactive_interval = i;
+ }
+
+ @Override
+ public int getMaxInactiveInterval()
+ {
+ return max_inactive_interval;
+ }
+
+ @Deprecated
+ @Override
+ public javax.servlet.http.HttpSessionContext getSessionContext()
+ {
+ return null;
+ }
+
+ @Override
+ public Object getAttribute(String s)
+ {
+ synchronized (attributes) {
+ return attributes.get(s);
+ }
+ }
+
+ @Deprecated
+ @Override
+ public Object getValue(String s)
+ {
+ return getAttribute(s);
+ }
+
+ @Override
+ public Enumeration<String> getAttributeNames()
+ {
+ synchronized (attributes) {
+ return Collections.enumeration(attributes.keySet());
+ }
+ }
+
+ @Deprecated
+ @Override
+ public String[] getValueNames()
+ {
+ synchronized (attributes) {
+ return attributes.keySet().toArray(new String[attributes.keySet().size()]);
+ }
+ }
+
+ @Override
+ public void setAttribute(String s, Object o)
+ {
+ Object old;
+
+ if (o != null && o instanceof HttpSessionBindingListener) {
+ HttpSessionBindingListener l = (HttpSessionBindingListener) o;
+ HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s);
+
+ l.valueBound(e);
+ }
+
+ synchronized (attributes) {
+ if (o != null) {
+ old = attributes.put(s, o);
+ } else {
+ old = attributes.remove(s);
+ }
+ }
+
+ if (old != null && old instanceof HttpSessionBindingListener) {
+ HttpSessionBindingListener l = (HttpSessionBindingListener) old;
+ HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s);
+
+ l.valueUnbound(e);
+ }
+
+ if (attr_listener == null) {
+ return;
+ }
+
+ if (o == null) {
+ if (old != null) {
+ HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, old);
+ attr_listener.attributeRemoved(e);
+ }
+
+ return;
+ }
+
+ if (old != null) {
+ HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, old);
+ attr_listener.attributeReplaced(e);
+ } else {
+ HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, o);
+ attr_listener.attributeAdded(e);
+ }
+ }
+
+ @Deprecated
+ @Override
+ public void putValue(String s, Object o)
+ {
+ setAttribute(s,o);
+ }
+
+ @Override
+ public void removeAttribute(String s)
+ {
+ Object o;
+
+ synchronized (attributes) {
+ o = attributes.remove(s);
+ }
+
+ if (o != null && o instanceof HttpSessionBindingListener) {
+ HttpSessionBindingListener l = (HttpSessionBindingListener) o;
+ HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s);
+
+ l.valueUnbound(e);
+ }
+
+ if (attr_listener == null || o == null) {
+ return;
+ }
+
+ HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, o);
+ attr_listener.attributeRemoved(e);
+ }
+
+ @Deprecated
+ @Override
+ public void removeValue(String s)
+ {
+ removeAttribute(s);
+ }
+
+ @Override
+ public void invalidate()
+ {
+ context.invalidateSession(this);
+
+ unboundAttributes();
+ }
+
+ private void unboundAttributes()
+ {
+ for (Map.Entry<String, Object> a : attributes.entrySet()) {
+ Object o = a.getValue();
+ if (o != null && o instanceof HttpSessionBindingListener) {
+ HttpSessionBindingListener l = (HttpSessionBindingListener) o;
+ HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, a.getKey());
+
+ l.valueUnbound(e);
+ }
+ }
+
+ attributes.clear();
+ }
+
+ @Override
+ public boolean isNew()
+ {
+ return is_new;
+ }
+
+ public void accessed() {
+ synchronized (this) {
+ is_new = false;
+
+ last_access_time = access_time;
+ access_time = new Date().getTime();
+ }
+ }
+
+ public boolean checkTimeOut()
+ {
+ return (max_inactive_interval > 0) &&
+ (access_time - last_access_time > max_inactive_interval * 1000);
+ }
+} \ No newline at end of file
diff --git a/src/java/nginx/unit/SessionAttrProxy.java b/src/java/nginx/unit/SessionAttrProxy.java
new file mode 100644
index 00000000..bddeede9
--- /dev/null
+++ b/src/java/nginx/unit/SessionAttrProxy.java
@@ -0,0 +1,40 @@
+package nginx.unit;
+
+import java.util.List;
+
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+
+public class SessionAttrProxy implements HttpSessionAttributeListener
+{
+ private final List<HttpSessionAttributeListener> listeners_;
+
+ public SessionAttrProxy(List<HttpSessionAttributeListener> listeners)
+ {
+ listeners_ = listeners;
+ }
+
+ @Override
+ public void attributeAdded(HttpSessionBindingEvent event)
+ {
+ for (HttpSessionAttributeListener l : listeners_) {
+ l.attributeAdded(event);
+ }
+ }
+
+ @Override
+ public void attributeRemoved(HttpSessionBindingEvent event)
+ {
+ for (HttpSessionAttributeListener l : listeners_) {
+ l.attributeRemoved(event);
+ }
+ }
+
+ @Override
+ public void attributeReplaced(HttpSessionBindingEvent event)
+ {
+ for (HttpSessionAttributeListener l : listeners_) {
+ l.attributeReplaced(event);
+ }
+ }
+}
diff --git a/src/java/nginx/unit/Taglib.java b/src/java/nginx/unit/Taglib.java
new file mode 100644
index 00000000..f72033ba
--- /dev/null
+++ b/src/java/nginx/unit/Taglib.java
@@ -0,0 +1,44 @@
+package nginx.unit;
+
+import javax.servlet.descriptor.TaglibDescriptor;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class Taglib implements TaglibDescriptor
+{
+ private String uri_ = null;
+ private String location_ = null;
+
+ public Taglib(NodeList nodes)
+ {
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Node node = nodes.item(i);
+ String tag_name = node.getNodeName();
+
+ if (tag_name.equals("taglib-uri")) {
+ uri_ = node.getTextContent().trim();
+ continue;
+ }
+
+ if (tag_name.equals("taglib-location")) {
+ location_ = node.getTextContent().trim();
+ continue;
+ }
+ }
+
+ }
+
+ @Override
+ public String getTaglibURI()
+ {
+ return uri_;
+ }
+
+ @Override
+ public String getTaglibLocation()
+ {
+ return location_;
+ }
+}
+
diff --git a/src/java/nginx/unit/UnitSessionCookieConfig.java b/src/java/nginx/unit/UnitSessionCookieConfig.java
new file mode 100644
index 00000000..e1b2ae04
--- /dev/null
+++ b/src/java/nginx/unit/UnitSessionCookieConfig.java
@@ -0,0 +1,110 @@
+package nginx.unit;
+
+import javax.servlet.SessionCookieConfig;
+
+/*
+
+ <session-config>
+ <session-timeout>60</session-timeout>
+ <cookie-config></cookie-config>
+ <tracking-mode></tracking-mode>
+ </session-config>
+
+
+ */
+public class UnitSessionCookieConfig implements SessionCookieConfig {
+
+ private static final String default_name = "JSESSIONID";
+
+ private String name = default_name;
+ private String domain;
+ private String path;
+ private String comment;
+ private boolean httpOnly = true;
+ private boolean secure = false;
+ private int maxAge = -1;
+
+ @Override
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+ @Override
+ public String getName()
+ {
+ return name;
+ }
+
+ @Override
+ public void setDomain(String domain)
+ {
+ this.domain = domain;
+ }
+
+ @Override
+ public String getDomain()
+ {
+ return domain;
+ }
+
+ @Override
+ public void setPath(String path)
+ {
+ this.path = path;
+ }
+
+ @Override
+ public String getPath()
+ {
+ return path;
+ }
+
+ @Override
+ public void setComment(String comment)
+ {
+ this.comment = comment;
+ }
+
+ @Override
+ public String getComment()
+ {
+ return comment;
+ }
+
+ @Override
+ public void setHttpOnly(boolean httpOnly)
+ {
+ this.httpOnly = httpOnly;
+ }
+
+ @Override
+ public boolean isHttpOnly()
+ {
+ return httpOnly;
+ }
+
+ @Override
+ public void setSecure(boolean secure)
+ {
+ this.secure = secure;
+ }
+
+ @Override
+ public boolean isSecure()
+ {
+ return secure;
+ }
+
+ @Override
+ public void setMaxAge(int maxAge)
+ {
+ this.maxAge = maxAge;
+ }
+
+ @Override
+ public int getMaxAge()
+ {
+ return maxAge;
+ }
+}
diff --git a/src/java/nxt_jni.c b/src/java/nxt_jni.c
new file mode 100644
index 00000000..02ec1e37
--- /dev/null
+++ b/src/java/nxt_jni.c
@@ -0,0 +1,175 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+
+#include <jni.h>
+#include <nxt_unit.h>
+#include <nxt_unit_field.h>
+
+#include "nxt_jni.h"
+
+
+static jclass nxt_java_NoSuchElementException_class;
+static jclass nxt_java_IOException_class;
+static jclass nxt_java_IllegalStateException_class;
+static jclass nxt_java_File_class;
+static jmethodID nxt_java_File_ctor;
+
+static inline char nxt_java_lowcase(char c);
+
+
+int
+nxt_java_jni_init(JNIEnv *env)
+{
+ jclass cls;
+
+ cls = (*env)->FindClass(env, "java/util/NoSuchElementException");
+ if (cls == NULL) {
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_NoSuchElementException_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+
+
+ cls = (*env)->FindClass(env, "java/io/IOException");
+ if (cls == NULL) {
+ (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class);
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_IOException_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+
+
+ cls = (*env)->FindClass(env, "java/lang/IllegalStateException");
+ if (cls == NULL) {
+ (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class);
+ (*env)->DeleteGlobalRef(env, nxt_java_IOException_class);
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_IllegalStateException_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+
+
+ cls = (*env)->FindClass(env, "java/io/File");
+ if (cls == NULL) {
+ (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class);
+ (*env)->DeleteGlobalRef(env, nxt_java_IOException_class);
+ (*env)->DeleteGlobalRef(env, nxt_java_IllegalStateException_class);
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_File_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+
+
+ nxt_java_File_ctor = (*env)->GetMethodID(env, nxt_java_File_class, "<init>",
+ "(Ljava/lang/String;)V");
+ if (nxt_java_File_ctor == NULL) {
+ (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class);
+ (*env)->DeleteGlobalRef(env, nxt_java_IOException_class);
+ (*env)->DeleteGlobalRef(env, nxt_java_IllegalStateException_class);
+ (*env)->DeleteGlobalRef(env, nxt_java_File_class);
+ return NXT_UNIT_ERROR;
+ }
+
+ return NXT_UNIT_OK;
+}
+
+
+void
+nxt_java_throw_NoSuchElementException(JNIEnv *env, const char *msg)
+{
+ (*env)->ThrowNew(env, nxt_java_NoSuchElementException_class, msg);
+}
+
+
+void
+nxt_java_throw_IOException(JNIEnv *env, const char *msg)
+{
+ (*env)->ThrowNew(env, nxt_java_IOException_class, msg);
+}
+
+
+void
+nxt_java_throw_IllegalStateException(JNIEnv *env, const char *msg)
+{
+ (*env)->ThrowNew(env, nxt_java_IllegalStateException_class, msg);
+}
+
+
+nxt_unit_field_t *
+nxt_java_findHeader(nxt_unit_field_t *f, nxt_unit_field_t *end,
+ const char *name, uint8_t name_len)
+{
+ const char *field_name;
+
+ for (/* void */ ; f < end; f++) {
+ if (f->skip != 0 || f->name_length != name_len) {
+ continue;
+ }
+
+ field_name = nxt_unit_sptr_get(&f->name);
+
+ if (nxt_java_strcaseeq(name, field_name, name_len)) {
+ return f;
+ }
+ }
+
+ return NULL;
+}
+
+
+int
+nxt_java_strcaseeq(const char *str1, const char *str2, int len)
+{
+ char c1, c2;
+ const char *end1;
+
+ end1 = str1 + len;
+
+ while (str1 < end1) {
+ c1 = nxt_java_lowcase(*str1++);
+ c2 = nxt_java_lowcase(*str2++);
+
+ if (c1 != c2) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+
+static inline char
+nxt_java_lowcase(char c)
+{
+ return (c >= 'A' && c <= 'Z') ? c | 0x20 : c;
+}
+
+
+jstring
+nxt_java_newString(JNIEnv *env, char *str, uint32_t len)
+{
+ char tmp;
+ jstring res;
+
+ tmp = str[len];
+
+ if (tmp != '\0') {
+ str[len] = '\0';
+ }
+
+ res = (*env)->NewStringUTF(env, str);
+
+ if (tmp != '\0') {
+ str[len] = tmp;
+ }
+
+ return res;
+}
diff --git a/src/java/nxt_jni.h b/src/java/nxt_jni.h
new file mode 100644
index 00000000..62acb752
--- /dev/null
+++ b/src/java/nxt_jni.h
@@ -0,0 +1,55 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_JAVA_JNI_H_INCLUDED_
+#define _NXT_JAVA_JNI_H_INCLUDED_
+
+
+#include <jni.h>
+#include <nxt_unit_typedefs.h>
+
+
+int nxt_java_jni_init(JNIEnv *env);
+
+void nxt_java_throw_NoSuchElementException(JNIEnv *env, const char *msg);
+
+void nxt_java_throw_IOException(JNIEnv *env, const char *msg);
+
+void nxt_java_throw_IllegalStateException(JNIEnv *env, const char *msg);
+
+nxt_unit_field_t *nxt_java_findHeader(nxt_unit_field_t *f, nxt_unit_field_t *e,
+ const char *name, uint8_t name_len);
+
+int nxt_java_strcaseeq(const char *str1, const char *str2, int len);
+
+jstring nxt_java_newString(JNIEnv *env, char *str, uint32_t len);
+
+
+typedef struct {
+ uint32_t header_size;
+ uint32_t buf_size;
+
+ jobject jreq;
+ jobject jresp;
+
+ nxt_unit_buf_t *first;
+ nxt_unit_buf_t *buf;
+
+} nxt_java_request_data_t;
+
+
+static inline jlong
+nxt_ptr2jlong(void *ptr)
+{
+ return (jlong) (intptr_t) ptr;
+}
+
+static inline void *
+nxt_jlong2ptr(jlong l)
+{
+ return (void *) (intptr_t) l;
+}
+
+#endif /* _NXT_JAVA_JNI_H_INCLUDED_ */
diff --git a/src/java/nxt_jni_Context.c b/src/java/nxt_jni_Context.c
new file mode 100644
index 00000000..8f7adddf
--- /dev/null
+++ b/src/java/nxt_jni_Context.c
@@ -0,0 +1,164 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+
+#include <nxt_unit.h>
+#include <jni.h>
+
+#include "nxt_jni.h"
+#include "nxt_jni_Context.h"
+#include "nxt_jni_URLClassLoader.h"
+
+
+static jclass nxt_java_Context_class;
+static jmethodID nxt_java_Context_start;
+static jmethodID nxt_java_Context_service;
+static jmethodID nxt_java_Context_stop;
+
+static void JNICALL nxt_java_Context_log(JNIEnv *env, jclass cls,
+ jlong ctx_ptr, jstring msg, jint msg_len);
+static void JNICALL nxt_java_Context_trace(JNIEnv *env, jclass cls,
+ jlong ctx_ptr, jstring msg, jint msg_len);
+
+
+int
+nxt_java_initContext(JNIEnv *env, jobject cl)
+{
+ int res;
+ jclass cls;
+
+ cls = nxt_java_loadClass(env, cl, "nginx.unit.Context");
+ if (cls == NULL) {
+ nxt_unit_warn(NULL, "nginx.unit.Context not found");
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_Context_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+ cls = nxt_java_Context_class;
+
+ nxt_java_Context_start = (*env)->GetStaticMethodID(env, cls, "start",
+ "(Ljava/lang/String;[Ljava/net/URL;)Lnginx/unit/Context;");
+ if (nxt_java_Context_start == NULL) {
+ nxt_unit_warn(NULL, "nginx.unit.Context.start() not found");
+ goto failed;
+ }
+
+ nxt_java_Context_service = (*env)->GetMethodID(env, cls, "service",
+ "(Lnginx/unit/Request;Lnginx/unit/Response;)V");
+ if (nxt_java_Context_service == NULL) {
+ nxt_unit_warn(NULL, "nginx.unit.Context.service() not found");
+ goto failed;
+ }
+
+ nxt_java_Context_stop = (*env)->GetMethodID(env, cls, "stop", "()V");
+ if (nxt_java_Context_service == NULL) {
+ nxt_unit_warn(NULL, "nginx.unit.Context.stop() not found");
+ goto failed;
+ }
+
+ JNINativeMethod context_methods[] = {
+ { (char *) "log",
+ (char *) "(JLjava/lang/String;I)V",
+ nxt_java_Context_log },
+
+ { (char *) "trace",
+ (char *) "(JLjava/lang/String;I)V",
+ nxt_java_Context_trace },
+
+ };
+
+ res = (*env)->RegisterNatives(env, nxt_java_Context_class,
+ context_methods,
+ sizeof(context_methods)
+ / sizeof(context_methods[0]));
+
+ nxt_unit_debug(NULL, "registered Context methods: %d", res);
+
+ if (res != 0) {
+ nxt_unit_warn(NULL, "registering natives for Context failed");
+ goto failed;
+ }
+
+ return NXT_UNIT_OK;
+
+failed:
+
+ (*env)->DeleteGlobalRef(env, cls);
+ return NXT_UNIT_ERROR;
+}
+
+
+jobject
+nxt_java_startContext(JNIEnv *env, const char *webapp, jobject classpaths)
+{
+ jstring webapp_str;
+
+ webapp_str = (*env)->NewStringUTF(env, webapp);
+ if (webapp_str == NULL) {
+ return NULL;
+ }
+
+ return (*env)->CallStaticObjectMethod(env, nxt_java_Context_class,
+ nxt_java_Context_start, webapp_str,
+ classpaths);
+}
+
+
+void
+nxt_java_service(JNIEnv *env, jobject ctx, jobject jreq, jobject jresp)
+{
+ (*env)->CallVoidMethod(env, ctx, nxt_java_Context_service, jreq, jresp);
+}
+
+
+void
+nxt_java_stopContext(JNIEnv *env, jobject ctx)
+{
+ (*env)->CallVoidMethod(env, ctx, nxt_java_Context_stop);
+}
+
+
+static void JNICALL
+nxt_java_Context_log(JNIEnv *env, jclass cls, jlong ctx_ptr, jstring msg,
+ jint msg_len)
+{
+ const char *msg_str;
+ nxt_unit_ctx_t *ctx;
+
+ ctx = nxt_jlong2ptr(ctx_ptr);
+
+ msg_str = (*env)->GetStringUTFChars(env, msg, NULL);
+ if (msg_str == NULL) {
+ return;
+ }
+
+ nxt_unit_log(ctx, NXT_UNIT_LOG_INFO, "%.*s", msg_len, msg_str);
+
+ (*env)->ReleaseStringUTFChars(env, msg, msg_str);
+}
+
+
+static void JNICALL
+nxt_java_Context_trace(JNIEnv *env, jclass cls, jlong ctx_ptr, jstring msg,
+ jint msg_len)
+{
+#if (NXT_DEBUG)
+ const char *msg_str;
+ nxt_unit_ctx_t *ctx;
+
+ ctx = nxt_jlong2ptr(ctx_ptr);
+
+ msg_str = (*env)->GetStringUTFChars(env, msg, NULL);
+ if (msg_str == NULL) {
+ return;
+ }
+
+ nxt_unit_debug(ctx, "%.*s", msg_len, msg_str);
+
+ (*env)->ReleaseStringUTFChars(env, msg, msg_str);
+#endif
+}
diff --git a/src/java/nxt_jni_Context.h b/src/java/nxt_jni_Context.h
new file mode 100644
index 00000000..976c36cf
--- /dev/null
+++ b/src/java/nxt_jni_Context.h
@@ -0,0 +1,23 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_JAVA_CONTEXT_H_INCLUDED_
+#define _NXT_JAVA_CONTEXT_H_INCLUDED_
+
+
+#include <jni.h>
+
+
+int nxt_java_initContext(JNIEnv *env, jobject cl);
+
+jobject nxt_java_startContext(JNIEnv *env, const char *webapp,
+ jobject classpaths);
+
+void nxt_java_service(JNIEnv *env, jobject ctx, jobject jreq, jobject jresp);
+
+void nxt_java_stopContext(JNIEnv *env, jobject ctx);
+
+#endif /* _NXT_JAVA_CONTEXT_H_INCLUDED_ */
+
diff --git a/src/java/nxt_jni_HeaderNamesEnumeration.c b/src/java/nxt_jni_HeaderNamesEnumeration.c
new file mode 100644
index 00000000..eea0c387
--- /dev/null
+++ b/src/java/nxt_jni_HeaderNamesEnumeration.c
@@ -0,0 +1,153 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+
+#include <nxt_unit.h>
+#include <nxt_unit_request.h>
+#include <jni.h>
+#include <stdio.h>
+
+#include "nxt_jni.h"
+#include "nxt_jni_URLClassLoader.h"
+#include "nxt_jni_HeaderNamesEnumeration.h"
+
+
+static jlong JNICALL nxt_java_HeaderNamesEnumeration_nextElementPos(JNIEnv *env,
+ jclass cls, jlong headers_ptr, jlong size, jlong pos);
+static jstring JNICALL nxt_java_HeaderNamesEnumeration_nextElement(JNIEnv *env,
+ jclass cls, jlong headers_ptr, jlong size, jlong pos);
+
+
+static jclass nxt_java_HeaderNamesEnumeration_class;
+static jmethodID nxt_java_HeaderNamesEnumeration_ctor;
+
+
+int
+nxt_java_initHeaderNamesEnumeration(JNIEnv *env, jobject cl)
+{
+ int res;
+ jclass cls;
+
+ cls = nxt_java_loadClass(env, cl, "nginx.unit.HeaderNamesEnumeration");
+ if (cls == NULL) {
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_HeaderNamesEnumeration_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+ cls = nxt_java_HeaderNamesEnumeration_class;
+
+ nxt_java_HeaderNamesEnumeration_ctor = (*env)->GetMethodID(env, cls,
+ "<init>", "(JJ)V");
+ if (nxt_java_HeaderNamesEnumeration_ctor == NULL) {
+ (*env)->DeleteGlobalRef(env, cls);
+ return NXT_UNIT_ERROR;
+ }
+
+ JNINativeMethod hnenum_methods[] = {
+ { (char *) "nextElementPos",
+ (char *) "(JJJ)J",
+ nxt_java_HeaderNamesEnumeration_nextElementPos },
+
+ { (char *) "nextElement",
+ (char *) "(JJJ)Ljava/lang/String;",
+ nxt_java_HeaderNamesEnumeration_nextElement },
+ };
+
+ res = (*env)->RegisterNatives(env, nxt_java_HeaderNamesEnumeration_class,
+ hnenum_methods,
+ sizeof(hnenum_methods)
+ / sizeof(hnenum_methods[0]));
+
+ nxt_unit_debug(NULL, "registered HeaderNamesEnumeration methods: %d", res);
+
+ if (res != 0) {
+ (*env)->DeleteGlobalRef(env, cls);
+ return NXT_UNIT_ERROR;
+ }
+
+ return NXT_UNIT_OK;
+}
+
+
+jobject
+nxt_java_newHeaderNamesEnumeration(JNIEnv *env, nxt_unit_field_t *f,
+ uint32_t fields_count)
+{
+ return (*env)->NewObject(env,
+ nxt_java_HeaderNamesEnumeration_class,
+ nxt_java_HeaderNamesEnumeration_ctor, nxt_ptr2jlong(f),
+ (jlong) fields_count);
+}
+
+
+static jlong JNICALL
+nxt_java_HeaderNamesEnumeration_nextElementPos(JNIEnv *env, jclass cls,
+ jlong headers_ptr, jlong size, jlong pos)
+{
+ nxt_unit_field_t *f;
+
+ f = nxt_jlong2ptr(headers_ptr);
+
+ if (pos >= size) {
+ return size;
+ }
+
+ if (pos > 0) {
+ while (pos < size
+ && f[pos].hash == f[pos - 1].hash
+ && f[pos].name_length == f[pos - 1].name_length)
+ {
+ pos++;
+ }
+ }
+
+ return pos;
+}
+
+
+static jstring JNICALL
+nxt_java_HeaderNamesEnumeration_nextElement(JNIEnv *env, jclass cls,
+ jlong headers_ptr, jlong size, jlong pos)
+{
+ char *name, tmp;
+ jstring res;
+ nxt_unit_field_t *f;
+
+ f = nxt_jlong2ptr(headers_ptr);
+
+ if (pos > 0) {
+ while (pos < size
+ && f[pos].hash == f[pos - 1].hash
+ && f[pos].name_length == f[pos - 1].name_length)
+ {
+ pos++;
+ }
+ }
+
+ if (pos >= size) {
+ nxt_java_throw_NoSuchElementException(env, "pos >= size");
+
+ return NULL;
+ }
+
+ f += pos;
+
+ name = nxt_unit_sptr_get(&f->name);
+ tmp = name[f->name_length];
+
+ if (tmp != '\0') {
+ name[f->name_length] = '\0';
+ }
+
+ res = (*env)->NewStringUTF(env, name);
+
+ if (tmp != '\0') {
+ name[f->name_length] = tmp;
+ }
+
+ return res;
+}
diff --git a/src/java/nxt_jni_HeaderNamesEnumeration.h b/src/java/nxt_jni_HeaderNamesEnumeration.h
new file mode 100644
index 00000000..90df8f54
--- /dev/null
+++ b/src/java/nxt_jni_HeaderNamesEnumeration.h
@@ -0,0 +1,19 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_JAVA_HEADERNAMESENUMERATION_H_INCLUDED_
+#define _NXT_JAVA_HEADERNAMESENUMERATION_H_INCLUDED_
+
+
+#include <jni.h>
+#include <nxt_unit_typedefs.h>
+
+
+int nxt_java_initHeaderNamesEnumeration(JNIEnv *env, jobject cl);
+
+jobject nxt_java_newHeaderNamesEnumeration(JNIEnv *env, nxt_unit_field_t *f,
+ uint32_t fields_count);
+
+#endif /* _NXT_JAVA_HEADERNAMESENUMERATION_H_INCLUDED_ */
diff --git a/src/java/nxt_jni_HeadersEnumeration.c b/src/java/nxt_jni_HeadersEnumeration.c
new file mode 100644
index 00000000..aaea710d
--- /dev/null
+++ b/src/java/nxt_jni_HeadersEnumeration.c
@@ -0,0 +1,148 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+
+#include <nxt_unit.h>
+#include <nxt_unit_request.h>
+#include <jni.h>
+#include <stdio.h>
+
+#include "nxt_jni.h"
+#include "nxt_jni_URLClassLoader.h"
+#include "nxt_jni_HeadersEnumeration.h"
+
+
+static jclass nxt_java_HeadersEnumeration_class;
+static jmethodID nxt_java_HeadersEnumeration_ctor;
+
+
+static jlong JNICALL nxt_java_HeadersEnumeration_nextElementPos(JNIEnv *env,
+ jclass cls, jlong headers_ptr, jlong size, jlong ipos, jlong pos);
+
+static jstring JNICALL nxt_java_HeadersEnumeration_nextElement(JNIEnv *env,
+ jclass cls, jlong headers_ptr, jlong size, jlong ipos, jlong pos);
+
+
+int
+nxt_java_initHeadersEnumeration(JNIEnv *env, jobject cl)
+{
+ int res;
+ jclass cls;
+
+ cls = nxt_java_loadClass(env, cl, "nginx.unit.HeadersEnumeration");
+ if (cls == NULL) {
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_HeadersEnumeration_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+ cls = nxt_java_HeadersEnumeration_class;
+
+ nxt_java_HeadersEnumeration_ctor = (*env)->GetMethodID(env, cls,
+ "<init>", "(JJJ)V");
+ if (nxt_java_HeadersEnumeration_ctor == NULL) {
+ (*env)->DeleteGlobalRef(env, cls);
+ return NXT_UNIT_ERROR;
+ }
+
+ JNINativeMethod methods[] = {
+ { (char *) "nextElementPos",
+ (char *) "(JJJJ)J",
+ nxt_java_HeadersEnumeration_nextElementPos },
+
+ { (char *) "nextElement",
+ (char *) "(JJJJ)Ljava/lang/String;",
+ nxt_java_HeadersEnumeration_nextElement },
+ };
+
+ res = (*env)->RegisterNatives(env, nxt_java_HeadersEnumeration_class,
+ methods,
+ sizeof(methods) / sizeof(methods[0]));
+
+ nxt_unit_debug(NULL, "registered HeadersEnumeration methods: %d", res);
+
+ if (res != 0) {
+ (*env)->DeleteGlobalRef(env, cls);
+ return NXT_UNIT_ERROR;
+ }
+
+ return NXT_UNIT_OK;
+}
+
+
+jobject
+nxt_java_newHeadersEnumeration(JNIEnv *env, nxt_unit_field_t *f,
+ uint32_t fields_count, uint32_t pos)
+{
+ return (*env)->NewObject(env,
+ nxt_java_HeadersEnumeration_class,
+ nxt_java_HeadersEnumeration_ctor, nxt_ptr2jlong(f),
+ (jlong) fields_count, (jlong) pos);
+}
+
+
+static jlong JNICALL
+nxt_java_HeadersEnumeration_nextElementPos(JNIEnv *env, jclass cls,
+ jlong headers_ptr, jlong size, jlong ipos, jlong pos)
+{
+ nxt_unit_field_t *f, *init_field;
+
+ f = nxt_jlong2ptr(headers_ptr);
+
+ init_field = f + ipos;
+
+ if (pos >= size) {
+ return size;
+ }
+
+ f += pos;
+
+ if (f->hash != init_field->hash
+ || f->name_length != init_field->name_length)
+ {
+ return size;
+ }
+
+ if (!nxt_java_strcaseeq(nxt_unit_sptr_get(&f->name),
+ nxt_unit_sptr_get(&init_field->name),
+ init_field->name_length))
+ {
+ return size;
+ }
+
+ return pos;
+}
+
+
+static jstring JNICALL
+nxt_java_HeadersEnumeration_nextElement(JNIEnv *env, jclass cls,
+ jlong headers_ptr, jlong size, jlong ipos, jlong pos)
+{
+ nxt_unit_field_t *f, *init_field;
+
+ f = nxt_jlong2ptr(headers_ptr);
+
+ init_field = f + ipos;
+
+ if (pos >= size) {
+ nxt_java_throw_IOException(env, "pos >= size");
+
+ return NULL;
+ }
+
+ f += pos;
+
+ if (f->hash != init_field->hash
+ || f->name_length != init_field->name_length)
+ {
+ nxt_java_throw_IOException(env, "f->hash != hash");
+
+ return NULL;
+ }
+
+ return nxt_java_newString(env, nxt_unit_sptr_get(&f->value),
+ f->value_length);
+}
diff --git a/src/java/nxt_jni_HeadersEnumeration.h b/src/java/nxt_jni_HeadersEnumeration.h
new file mode 100644
index 00000000..10f9393c
--- /dev/null
+++ b/src/java/nxt_jni_HeadersEnumeration.h
@@ -0,0 +1,19 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_JAVA_HEADERSENUMERATION_H_INCLUDED_
+#define _NXT_JAVA_HEADERSENUMERATION_H_INCLUDED_
+
+
+#include <jni.h>
+#include <nxt_unit_typedefs.h>
+
+
+int nxt_java_initHeadersEnumeration(JNIEnv *env, jobject cl);
+
+jobject nxt_java_newHeadersEnumeration(JNIEnv *env, nxt_unit_field_t *f,
+ uint32_t fields_count, uint32_t pos);
+
+#endif /* _NXT_JAVA_HEADERSENUMERATION_H_INCLUDED_ */
diff --git a/src/java/nxt_jni_InputStream.c b/src/java/nxt_jni_InputStream.c
new file mode 100644
index 00000000..b96ff742
--- /dev/null
+++ b/src/java/nxt_jni_InputStream.c
@@ -0,0 +1,230 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+
+#include <jni.h>
+#include <nxt_unit.h>
+#include <string.h>
+
+#include "nxt_jni.h"
+#include "nxt_jni_InputStream.h"
+#include "nxt_jni_URLClassLoader.h"
+
+
+static jint JNICALL nxt_java_InputStream_readLine(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray b, jint off, jint len);
+static jboolean JNICALL nxt_java_InputStream_isFinished(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+static jint JNICALL nxt_java_InputStream_readByte(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+static jint JNICALL nxt_java_InputStream_read(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray b, jint off, jint len);
+static jlong JNICALL nxt_java_InputStream_skip(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jlong n);
+static jint JNICALL nxt_java_InputStream_available(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+
+
+static jclass nxt_java_InputStream_class;
+
+
+int
+nxt_java_initInputStream(JNIEnv *env, jobject cl)
+{
+ int res;
+ jclass cls;
+
+ cls = nxt_java_loadClass(env, cl, "nginx.unit.InputStream");
+ if (cls == NULL) {
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_InputStream_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+
+ JNINativeMethod is_methods[] = {
+ { (char *) "readLine",
+ (char *) "(J[BII)I",
+ nxt_java_InputStream_readLine },
+
+ { (char *) "isFinished",
+ (char *) "(J)Z",
+ nxt_java_InputStream_isFinished },
+
+ { (char *) "read",
+ (char *) "(J)I",
+ nxt_java_InputStream_readByte },
+
+ { (char *) "read",
+ (char *) "(J[BII)I",
+ nxt_java_InputStream_read },
+
+ { (char *) "skip",
+ (char *) "(JJ)J",
+ nxt_java_InputStream_skip },
+
+ { (char *) "available",
+ (char *) "(J)I",
+ nxt_java_InputStream_available },
+ };
+
+ res = (*env)->RegisterNatives(env, nxt_java_InputStream_class,
+ is_methods,
+ sizeof(is_methods) / sizeof(is_methods[0]));
+
+ nxt_unit_debug(NULL, "registered InputStream methods: %d", res);
+
+ if (res != 0) {
+ (*env)->DeleteGlobalRef(env, cls);
+ return NXT_UNIT_ERROR;
+ }
+
+ return NXT_UNIT_OK;
+}
+
+
+static jint JNICALL
+nxt_java_InputStream_readLine(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray out, jint off, jint len)
+{
+ char *p;
+ jint size, b_size;
+ uint8_t *data;
+ ssize_t res;
+ nxt_unit_buf_t *b;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ size = 0;
+
+ for (b = req->content_buf; b; b = nxt_unit_buf_next(b)) {
+ b_size = b->end - b->free;
+ p = memchr(b->free, '\n', b_size);
+
+ if (p != NULL) {
+ p++;
+ size += p - b->free;
+ break;
+ }
+
+ size += b_size;
+
+ if (size >= len) {
+ break;
+ }
+ }
+
+ len = len < size ? len : size;
+
+ data = (*env)->GetPrimitiveArrayCritical(env, out, NULL);
+
+ res = nxt_unit_request_read(req, data + off, len);
+
+ nxt_unit_req_debug(req, "readLine '%.*s'", res, (char *) data + off);
+
+ (*env)->ReleasePrimitiveArrayCritical(env, out, data, 0);
+
+ return res > 0 ? res : -1;
+}
+
+
+static jboolean JNICALL
+nxt_java_InputStream_isFinished(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ return req->content_length == 0;
+}
+
+
+static jint JNICALL
+nxt_java_InputStream_readByte(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ uint8_t b;
+ ssize_t size;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ size = nxt_unit_request_read(req, &b, 1);
+
+ return size == 1 ? b : -1;
+}
+
+
+static jint JNICALL
+nxt_java_InputStream_read(JNIEnv *env, jclass cls, jlong req_info_ptr,
+ jarray b, jint off, jint len)
+{
+ uint8_t *data;
+ ssize_t res;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ data = (*env)->GetPrimitiveArrayCritical(env, b, NULL);
+
+ res = nxt_unit_request_read(req, data + off, len);
+
+ nxt_unit_req_debug(req, "read '%.*s'", res, (char *) data + off);
+
+ (*env)->ReleasePrimitiveArrayCritical(env, b, data, 0);
+
+ return res > 0 ? res : -1;
+}
+
+
+static jlong JNICALL
+nxt_java_InputStream_skip(JNIEnv *env, jclass cls, jlong req_info_ptr, jlong n)
+{
+ size_t rest, b_size;
+ nxt_unit_buf_t *buf;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ rest = n;
+
+ buf = req->content_buf;
+
+ while (buf != NULL) {
+ b_size = buf->end - buf->free;
+ b_size = rest < b_size ? rest : b_size;
+
+ buf->free += b_size;
+ rest -= b_size;
+
+ if (rest == 0) {
+ if (buf->end == buf->free) {
+ buf = nxt_unit_buf_next(buf);
+ }
+
+ break;
+ }
+
+ buf = nxt_unit_buf_next(buf);
+ }
+
+ n = n < (jlong) req->content_length ? n : (jlong) req->content_length;
+
+ req->content_length -= n;
+
+ return n;
+}
+
+
+static jint JNICALL
+nxt_java_InputStream_available(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ return req->content_length;
+}
diff --git a/src/java/nxt_jni_InputStream.h b/src/java/nxt_jni_InputStream.h
new file mode 100644
index 00000000..b20b262a
--- /dev/null
+++ b/src/java/nxt_jni_InputStream.h
@@ -0,0 +1,15 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_JAVA_INPUTSTREAM_H_INCLUDED_
+#define _NXT_JAVA_INPUTSTREAM_H_INCLUDED_
+
+
+#include <jni.h>
+
+
+int nxt_java_initInputStream(JNIEnv *env, jobject cl);
+
+#endif /* _NXT_JAVA_INPUTSTREAM_H_INCLUDED_ */
diff --git a/src/java/nxt_jni_OutputStream.c b/src/java/nxt_jni_OutputStream.c
new file mode 100644
index 00000000..170b33ba
--- /dev/null
+++ b/src/java/nxt_jni_OutputStream.c
@@ -0,0 +1,236 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+
+#include <jni.h>
+#include <nxt_unit.h>
+
+#include "nxt_jni.h"
+#include "nxt_jni_OutputStream.h"
+#include "nxt_jni_URLClassLoader.h"
+
+
+static void JNICALL nxt_java_OutputStream_writeByte(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jint b);
+static nxt_unit_buf_t *nxt_java_OutputStream_req_buf(JNIEnv *env,
+ nxt_unit_request_info_t *req);
+static void JNICALL nxt_java_OutputStream_write(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray b, jint off, jint len);
+static void JNICALL nxt_java_OutputStream_flush(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+static void JNICALL nxt_java_OutputStream_close(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+
+
+static jclass nxt_java_OutputStream_class;
+
+
+int
+nxt_java_initOutputStream(JNIEnv *env, jobject cl)
+{
+ int res;
+ jclass cls;
+
+ cls = nxt_java_loadClass(env, cl, "nginx.unit.OutputStream");
+ if (cls == NULL) {
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_OutputStream_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+
+ cls = nxt_java_OutputStream_class;
+
+ JNINativeMethod os_methods[] = {
+ { (char *) "write",
+ (char *) "(JI)V",
+ nxt_java_OutputStream_writeByte },
+
+ { (char *) "write",
+ (char *) "(J[BII)V",
+ nxt_java_OutputStream_write },
+
+ { (char *) "flush",
+ (char *) "(J)V",
+ nxt_java_OutputStream_flush },
+
+ { (char *) "close",
+ (char *) "(J)V",
+ nxt_java_OutputStream_close },
+ };
+
+ res = (*env)->RegisterNatives(env, nxt_java_OutputStream_class,
+ os_methods,
+ sizeof(os_methods) / sizeof(os_methods[0]));
+
+ nxt_unit_debug(NULL, "registered OutputStream methods: %d", res);
+
+ if (res != 0) {
+ (*env)->DeleteGlobalRef(env, cls);
+ return NXT_UNIT_ERROR;
+ }
+
+ return NXT_UNIT_OK;
+}
+
+
+static void JNICALL
+nxt_java_OutputStream_writeByte(JNIEnv *env, jclass cls, jlong req_info_ptr,
+ jint b)
+{
+ nxt_unit_buf_t *buf;
+ nxt_unit_request_info_t *req;
+ nxt_java_request_data_t *data;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+ data = req->data;
+
+ buf = nxt_java_OutputStream_req_buf(env, req);
+ if (buf == NULL) {
+ return;
+ }
+
+ *buf->free++ = b;
+
+ if ((uint32_t) (buf->free - buf->start) >= data->buf_size) {
+ nxt_java_OutputStream_flush_buf(env, req);
+ }
+}
+
+
+int
+nxt_java_OutputStream_flush_buf(JNIEnv *env, nxt_unit_request_info_t *req)
+{
+ int rc;
+ nxt_java_request_data_t *data;
+
+ data = req->data;
+
+ if (!nxt_unit_response_is_init(req)) {
+ rc = nxt_unit_response_init(req, 200, 0, 0);
+ if (rc != NXT_UNIT_OK) {
+ nxt_java_throw_IOException(env, "Failed to allocate response");
+
+ return rc;
+ }
+ }
+
+ if (!nxt_unit_response_is_sent(req)) {
+ rc = nxt_unit_response_send(req);
+ if (rc != NXT_UNIT_OK) {
+ nxt_java_throw_IOException(env, "Failed to send response headers");
+
+ return rc;
+ }
+ }
+
+ if (data->buf != NULL) {
+ rc = nxt_unit_buf_send(data->buf);
+ if (rc != NXT_UNIT_OK) {
+ nxt_java_throw_IOException(env, "Failed to send buffer");
+
+ } else {
+ data->buf = NULL;
+ }
+
+ } else {
+ rc = NXT_UNIT_OK;
+ }
+
+ return rc;
+}
+
+
+static nxt_unit_buf_t *
+nxt_java_OutputStream_req_buf(JNIEnv *env, nxt_unit_request_info_t *req)
+{
+ uint32_t size;
+ nxt_unit_buf_t *buf;
+ nxt_java_request_data_t *data;
+
+ data = req->data;
+ buf = data->buf;
+
+ if (buf == NULL || buf->free >= buf->end) {
+ size = data->buf_size == 0 ? nxt_unit_buf_min() : data->buf_size;
+
+ buf = nxt_unit_response_buf_alloc(req, size);
+ if (buf == NULL) {
+ nxt_java_throw_IOException(env, "Failed to allocate buffer");
+
+ return NULL;
+ }
+
+ data->buf = buf;
+ }
+
+ return buf;
+}
+
+
+static void JNICALL
+nxt_java_OutputStream_write(JNIEnv *env, jclass cls, jlong req_info_ptr,
+ jarray b, jint off, jint len)
+{
+ int rc;
+ jint copy;
+ uint8_t *ptr;
+ nxt_unit_buf_t *buf;
+ nxt_unit_request_info_t *req;
+ nxt_java_request_data_t *data;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+ data = req->data;
+
+ ptr = (*env)->GetPrimitiveArrayCritical(env, b, NULL);
+
+ while (len > 0) {
+ buf = nxt_java_OutputStream_req_buf(env, req);
+ if (buf == NULL) {
+ return;
+ }
+
+ copy = buf->end - buf->free;
+ copy = copy < len ? copy : len;
+
+ memcpy(buf->free, ptr + off, copy);
+ buf->free += copy;
+
+ len -= copy;
+ off += copy;
+
+ if ((uint32_t) (buf->free - buf->start) >= data->buf_size) {
+ rc = nxt_java_OutputStream_flush_buf(env, req);
+ if (rc != NXT_UNIT_OK) {
+ break;
+ }
+ }
+ }
+
+ (*env)->ReleasePrimitiveArrayCritical(env, b, ptr, 0);
+}
+
+
+static void JNICALL
+nxt_java_OutputStream_flush(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_unit_request_info_t *req;
+ nxt_java_request_data_t *data;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+ data = req->data;
+
+ if (data->buf != NULL && data->buf->free > data->buf->start) {
+ nxt_java_OutputStream_flush_buf(env, req);
+ }
+}
+
+
+static void JNICALL
+nxt_java_OutputStream_close(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_java_OutputStream_flush_buf(env, nxt_jlong2ptr(req_info_ptr));
+}
diff --git a/src/java/nxt_jni_OutputStream.h b/src/java/nxt_jni_OutputStream.h
new file mode 100644
index 00000000..0c3c9989
--- /dev/null
+++ b/src/java/nxt_jni_OutputStream.h
@@ -0,0 +1,17 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_JAVA_OUTPUTSTREAM_H_INCLUDED_
+#define _NXT_JAVA_OUTPUTSTREAM_H_INCLUDED_
+
+
+#include <jni.h>
+
+
+int nxt_java_initOutputStream(JNIEnv *env, jobject cl);
+
+int nxt_java_OutputStream_flush_buf(JNIEnv *env, nxt_unit_request_info_t *req);
+
+#endif /* _NXT_JAVA_OUTPUTSTREAM_H_INCLUDED_ */
diff --git a/src/java/nxt_jni_Request.c b/src/java/nxt_jni_Request.c
new file mode 100644
index 00000000..6fb9cb44
--- /dev/null
+++ b/src/java/nxt_jni_Request.c
@@ -0,0 +1,658 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+
+#include <nxt_unit.h>
+#include <nxt_unit_request.h>
+#include <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "nxt_jni.h"
+#include "nxt_jni_Request.h"
+#include "nxt_jni_URLClassLoader.h"
+#include "nxt_jni_HeadersEnumeration.h"
+#include "nxt_jni_HeaderNamesEnumeration.h"
+
+
+static jstring JNICALL nxt_java_Request_getHeader(JNIEnv *env, jclass cls,
+ jlong req_ptr, jstring name, jint name_len);
+static jobject JNICALL nxt_java_Request_getHeaderNames(JNIEnv *env, jclass cls,
+ jlong req_ptr);
+static jobject JNICALL nxt_java_Request_getHeaders(JNIEnv *env, jclass cls,
+ jlong req_ptr, jstring name, jint name_len);
+static jint JNICALL nxt_java_Request_getIntHeader(JNIEnv *env, jclass cls,
+ jlong req_ptr, jstring name, jint name_len);
+static jstring JNICALL nxt_java_Request_getMethod(JNIEnv *env, jclass cls,
+ jlong req_ptr);
+static jstring JNICALL nxt_java_Request_getQueryString(JNIEnv *env, jclass cls,
+ jlong req_ptr);
+static jstring JNICALL nxt_java_Request_getRequestURI(JNIEnv *env, jclass cls,
+ jlong req_ptr);
+static jlong JNICALL nxt_java_Request_getContentLength(JNIEnv *env, jclass cls,
+ jlong req_ptr);
+static jstring JNICALL nxt_java_Request_getContentType(JNIEnv *env, jclass cls,
+ jlong req_ptr);
+static jstring JNICALL nxt_java_Request_getLocalAddr(JNIEnv *env, jclass cls,
+ jlong req_ptr);
+static jstring JNICALL nxt_java_Request_getLocalName(JNIEnv *env, jclass cls,
+ jlong req_ptr);
+static jint JNICALL nxt_java_Request_getLocalPort(JNIEnv *env, jclass cls,
+ jlong req_ptr);
+static jstring JNICALL nxt_java_Request_getProtocol(JNIEnv *env, jclass cls,
+ jlong req_ptr);
+static jstring JNICALL nxt_java_Request_getRemoteAddr(JNIEnv *env, jclass cls,
+ jlong req_ptr);
+static jstring JNICALL nxt_java_Request_getRemoteHost(JNIEnv *env, jclass cls,
+ jlong req_ptr);
+static jint JNICALL 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);
+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 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,
+ jlong req_info_ptr, jstring msg, jint msg_len);
+static jobject JNICALL nxt_java_Request_getResponse(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+
+
+static jclass nxt_java_Request_class;
+static jmethodID nxt_java_Request_ctor;
+
+
+int
+nxt_java_initRequest(JNIEnv *env, jobject cl)
+{
+ int res;
+ jclass cls;
+
+ cls = nxt_java_loadClass(env, cl, "nginx.unit.Request");
+ if (cls == NULL) {
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_Request_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+ cls = nxt_java_Request_class;
+
+ nxt_java_Request_ctor = (*env)->GetMethodID(env, cls, "<init>", "(Lnginx/unit/Context;JJ)V");
+ if (nxt_java_Request_ctor == NULL) {
+ (*env)->DeleteGlobalRef(env, cls);
+ return NXT_UNIT_ERROR;
+ }
+
+ JNINativeMethod request_methods[] = {
+ { (char *) "getHeader",
+ (char *) "(JLjava/lang/String;I)Ljava/lang/String;",
+ nxt_java_Request_getHeader },
+
+ { (char *) "getHeaderNames",
+ (char *) "(J)Ljava/util/Enumeration;",
+ nxt_java_Request_getHeaderNames },
+
+ { (char *) "getHeaders",
+ (char *) "(JLjava/lang/String;I)Ljava/util/Enumeration;",
+ nxt_java_Request_getHeaders },
+
+ { (char *) "getIntHeader",
+ (char *) "(JLjava/lang/String;I)I",
+ nxt_java_Request_getIntHeader },
+
+ { (char *) "getMethod",
+ (char *) "(J)Ljava/lang/String;",
+ nxt_java_Request_getMethod },
+
+ { (char *) "getQueryString",
+ (char *) "(J)Ljava/lang/String;",
+ nxt_java_Request_getQueryString },
+
+ { (char *) "getRequestURI",
+ (char *) "(J)Ljava/lang/String;",
+ nxt_java_Request_getRequestURI },
+
+ { (char *) "getContentLength",
+ (char *) "(J)J",
+ nxt_java_Request_getContentLength },
+
+ { (char *) "getContentType",
+ (char *) "(J)Ljava/lang/String;",
+ nxt_java_Request_getContentType },
+
+ { (char *) "getLocalAddr",
+ (char *) "(J)Ljava/lang/String;",
+ nxt_java_Request_getLocalAddr },
+
+ { (char *) "getLocalName",
+ (char *) "(J)Ljava/lang/String;",
+ nxt_java_Request_getLocalName },
+
+ { (char *) "getLocalPort",
+ (char *) "(J)I",
+ nxt_java_Request_getLocalPort },
+
+ { (char *) "getProtocol",
+ (char *) "(J)Ljava/lang/String;",
+ nxt_java_Request_getProtocol },
+
+ { (char *) "getRemoteAddr",
+ (char *) "(J)Ljava/lang/String;",
+ nxt_java_Request_getRemoteAddr },
+
+ { (char *) "getRemoteHost",
+ (char *) "(J)Ljava/lang/String;",
+ nxt_java_Request_getRemoteHost },
+
+ { (char *) "getRemotePort",
+ (char *) "(J)I",
+ nxt_java_Request_getRemotePort },
+
+ { (char *) "getScheme",
+ (char *) "(J)Ljava/lang/String;",
+ nxt_java_Request_getScheme },
+
+ { (char *) "getServerName",
+ (char *) "(J)Ljava/lang/String;",
+ nxt_java_Request_getServerName },
+
+ { (char *) "getServerPort",
+ (char *) "(J)I",
+ nxt_java_Request_getServerPort },
+
+ { (char *) "log",
+ (char *) "(JLjava/lang/String;I)V",
+ nxt_java_Request_log },
+
+ { (char *) "trace",
+ (char *) "(JLjava/lang/String;I)V",
+ nxt_java_Request_trace },
+
+ { (char *) "getResponse",
+ (char *) "(J)Lnginx/unit/Response;",
+ nxt_java_Request_getResponse },
+
+ };
+
+ res = (*env)->RegisterNatives(env, nxt_java_Request_class,
+ request_methods,
+ sizeof(request_methods) / sizeof(request_methods[0]));
+
+ nxt_unit_debug(NULL, "registered Request methods: %d", res);
+
+ if (res != 0) {
+ nxt_unit_warn(NULL, "registering natives for Request failed");
+ goto failed;
+ }
+
+ res = nxt_java_initHeadersEnumeration(env, cl);
+ if (res != NXT_UNIT_OK) {
+ goto failed;
+ }
+
+ res = nxt_java_initHeaderNamesEnumeration(env, cl);
+ if (res != NXT_UNIT_OK) {
+ goto failed;
+ }
+
+ return NXT_UNIT_OK;
+
+failed:
+
+ (*env)->DeleteGlobalRef(env, cls);
+ return NXT_UNIT_ERROR;
+}
+
+
+jobject
+nxt_java_newRequest(JNIEnv *env, jobject ctx, nxt_unit_request_info_t *req)
+{
+ return (*env)->NewObject(env, nxt_java_Request_class,
+ nxt_java_Request_ctor, ctx, nxt_ptr2jlong(req),
+ nxt_ptr2jlong(req->request));
+}
+
+
+static jstring JNICALL
+nxt_java_Request_getHeader(JNIEnv *env, jclass cls, jlong req_ptr,
+ jstring name, jint name_len)
+{
+ const char *name_str;
+ nxt_unit_field_t *f;
+ nxt_unit_request_t *r;
+
+ name_str = (*env)->GetStringUTFChars(env, name, NULL);
+ if (name_str == NULL) {
+ return NULL;
+ }
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ f = nxt_java_findHeader(r->fields, r->fields + r->fields_count,
+ name_str, name_len);
+
+ (*env)->ReleaseStringUTFChars(env, name, name_str);
+
+ if (f == NULL) {
+ return NULL;
+ }
+
+ return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&f->value));
+}
+
+
+static jobject JNICALL
+nxt_java_Request_getHeaderNames(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ return nxt_java_newHeaderNamesEnumeration(env, r->fields, r->fields_count);
+}
+
+
+static jobject JNICALL
+nxt_java_Request_getHeaders(JNIEnv *env, jclass cls, jlong req_ptr,
+ jstring name, jint name_len)
+{
+ const char *name_str;
+ nxt_unit_field_t *f;
+ nxt_unit_request_t *r;
+
+ name_str = (*env)->GetStringUTFChars(env, name, NULL);
+ if (name_str == NULL) {
+ return NULL;
+ }
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ f = nxt_java_findHeader(r->fields, r->fields + r->fields_count,
+ name_str, name_len);
+
+ (*env)->ReleaseStringUTFChars(env, name, name_str);
+
+ if (f == NULL) {
+ f = r->fields + r->fields_count;
+ }
+
+ return nxt_java_newHeadersEnumeration(env, r->fields, r->fields_count,
+ f - r->fields);
+}
+
+
+static jint JNICALL
+nxt_java_Request_getIntHeader(JNIEnv *env, jclass cls, jlong req_ptr,
+ jstring name, jint name_len)
+{
+ jint res;
+ char *value, *end;
+ const char *name_str;
+ nxt_unit_field_t *f;
+ nxt_unit_request_t *r;
+
+ res = -1;
+
+ name_str = (*env)->GetStringUTFChars(env, name, NULL);
+ if (name_str == NULL) {
+ return res;
+ }
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ f = nxt_java_findHeader(r->fields, r->fields + r->fields_count,
+ name_str, name_len);
+
+ (*env)->ReleaseStringUTFChars(env, name, name_str);
+
+ if (f == NULL) {
+ return res;
+ }
+
+ value = nxt_unit_sptr_get(&f->value);
+ end = value + f->value_length;
+
+ res = strtol(value, &end, 10);
+
+ if (end < value + f->value_length) {
+ // TODO throw NumberFormatException.forInputString(value)
+ }
+
+ return res;
+}
+
+
+static jstring JNICALL
+nxt_java_Request_getMethod(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&r->method));
+}
+
+
+static jstring JNICALL
+nxt_java_Request_getQueryString(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ char *query;
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ if (r->query.offset != 0) {
+ query = nxt_unit_sptr_get(&r->query);
+ return (*env)->NewStringUTF(env, query);
+ }
+
+ return NULL;
+}
+
+
+static jstring JNICALL
+nxt_java_Request_getRequestURI(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ char *target, *query;
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ target = nxt_unit_sptr_get(&r->target);
+
+ if (r->query.offset != 0) {
+ query = nxt_unit_sptr_get(&r->query);
+ return nxt_java_newString(env, target, query - target - 1);
+ }
+
+ return (*env)->NewStringUTF(env, target);
+}
+
+
+static jlong JNICALL
+nxt_java_Request_getContentLength(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ return r->content_length;
+}
+
+
+static jstring JNICALL
+nxt_java_Request_getContentType(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ nxt_unit_field_t *f;
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ if (r->content_type_field != NXT_UNIT_NONE_FIELD) {
+ f = r->fields + r->content_type_field;
+
+ return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&f->value));
+ }
+
+ return NULL;
+}
+
+
+static jstring JNICALL
+nxt_java_Request_getLocalAddr(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ return nxt_java_newString(env, nxt_unit_sptr_get(&r->local),
+ r->local_length);
+}
+
+
+static jstring JNICALL
+nxt_java_Request_getLocalName(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ char *local, *colon;
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ local = nxt_unit_sptr_get(&r->local);
+ colon = memchr(local, ':', r->local_length);
+
+ if (colon == NULL) {
+ colon = local + r->local_length;
+ }
+
+ return nxt_java_newString(env, local, colon - local);
+}
+
+
+static jint JNICALL
+nxt_java_Request_getLocalPort(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ jint res;
+ char *local, *colon, tmp;
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ local = nxt_unit_sptr_get(&r->local);
+ colon = memchr(local, ':', r->local_length);
+
+ if (colon == NULL) {
+ return 80;
+ }
+
+ tmp = local[r->local_length];
+
+ local[r->local_length] = '\0';
+
+ res = strtol(colon + 1, NULL, 10);
+
+ local[r->local_length] = tmp;
+
+ return res;
+}
+
+
+static jstring JNICALL
+nxt_java_Request_getProtocol(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&r->version));
+}
+
+
+static jstring JNICALL
+nxt_java_Request_getRemoteAddr(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ return nxt_java_newString(env, nxt_unit_sptr_get(&r->remote),
+ r->remote_length);
+}
+
+
+static jstring JNICALL
+nxt_java_Request_getRemoteHost(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ char *remote, *colon;
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ remote = nxt_unit_sptr_get(&r->remote);
+ colon = memchr(remote, ':', r->remote_length);
+
+ if (colon == NULL) {
+ colon = remote + r->remote_length;
+ }
+
+ return nxt_java_newString(env, remote, colon - remote);
+}
+
+
+static jint JNICALL
+nxt_java_Request_getRemotePort(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ jint res;
+ char *remote, *colon, tmp;
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ remote = nxt_unit_sptr_get(&r->remote);
+ colon = memchr(remote, ':', r->remote_length);
+
+ if (colon == NULL) {
+ return 80;
+ }
+
+ tmp = remote[r->remote_length];
+
+ remote[r->remote_length] = '\0';
+
+ res = strtol(colon + 1, NULL, 10);
+
+ remote[r->remote_length] = tmp;
+
+ return res;
+}
+
+
+static jstring JNICALL
+nxt_java_Request_getScheme(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ return (*env)->NewStringUTF(env, "http");
+}
+
+
+static jstring JNICALL
+nxt_java_Request_getServerName(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ char *host, *colon;
+ nxt_unit_field_t *f;
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ f = nxt_java_findHeader(r->fields, r->fields + r->fields_count,
+ "Host", 4);
+ if (f != NULL) {
+ host = nxt_unit_sptr_get(&f->value);
+
+ colon = memchr(host, ':', f->value_length);
+
+ if (colon == NULL) {
+ colon = host + f->value_length;
+ }
+
+ return nxt_java_newString(env, host, colon - host);
+ }
+
+ return nxt_java_Request_getLocalName(env, cls, req_ptr);
+}
+
+
+static jint JNICALL
+nxt_java_Request_getServerPort(JNIEnv *env, jclass cls, jlong req_ptr)
+{
+ jint res;
+ char *host, *colon, tmp;
+ nxt_unit_field_t *f;
+ nxt_unit_request_t *r;
+
+ r = nxt_jlong2ptr(req_ptr);
+
+ f = nxt_java_findHeader(r->fields, r->fields + r->fields_count,
+ "Host", 4);
+ if (f != NULL) {
+ host = nxt_unit_sptr_get(&f->value);
+
+ colon = memchr(host, ':', f->value_length);
+
+ if (colon == NULL) {
+ return 80;
+ }
+
+ tmp = host[f->value_length];
+
+ host[f->value_length] = '\0';
+
+ res = strtol(colon + 1, NULL, 10);
+
+ host[f->value_length] = tmp;
+
+ return res;
+ }
+
+ return nxt_java_Request_getLocalPort(env, cls, req_ptr);
+}
+
+
+static void JNICALL
+nxt_java_Request_log(JNIEnv *env, jclass cls, jlong req_info_ptr, jstring msg,
+ jint msg_len)
+{
+ const char *msg_str;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ msg_str = (*env)->GetStringUTFChars(env, msg, NULL);
+ if (msg_str == NULL) {
+ return;
+ }
+
+ nxt_unit_req_log(req, NXT_UNIT_LOG_INFO, "%.*s", msg_len, msg_str);
+
+ (*env)->ReleaseStringUTFChars(env, msg, msg_str);
+}
+
+
+static void JNICALL
+nxt_java_Request_trace(JNIEnv *env, jclass cls, jlong req_info_ptr, jstring msg,
+ jint msg_len)
+{
+#if (NXT_DEBUG)
+ const char *msg_str;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ msg_str = (*env)->GetStringUTFChars(env, msg, NULL);
+ if (msg_str == NULL) {
+ return;
+ }
+
+ nxt_unit_req_debug(req, "%.*s", msg_len, msg_str);
+
+ (*env)->ReleaseStringUTFChars(env, msg, msg_str);
+#endif
+}
+
+
+static jobject JNICALL
+nxt_java_Request_getResponse(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_unit_request_info_t *req;
+ nxt_java_request_data_t *data;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+ data = req->data;
+
+ return data->jresp;
+}
diff --git a/src/java/nxt_jni_Request.h b/src/java/nxt_jni_Request.h
new file mode 100644
index 00000000..1c9c1428
--- /dev/null
+++ b/src/java/nxt_jni_Request.h
@@ -0,0 +1,18 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_JAVA_REQUEST_H_INCLUDED_
+#define _NXT_JAVA_REQUEST_H_INCLUDED_
+
+
+#include <jni.h>
+#include <nxt_unit_typedefs.h>
+
+
+int nxt_java_initRequest(JNIEnv *env, jobject cl);
+
+jobject nxt_java_newRequest(JNIEnv *env, jobject ctx, nxt_unit_request_info_t *req);
+
+#endif /* _NXT_JAVA_REQUEST_H_INCLUDED_ */
diff --git a/src/java/nxt_jni_Response.c b/src/java/nxt_jni_Response.c
new file mode 100644
index 00000000..2ccfd854
--- /dev/null
+++ b/src/java/nxt_jni_Response.c
@@ -0,0 +1,1105 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+
+#include <nxt_unit.h>
+#include <nxt_unit_response.h>
+#include <jni.h>
+#include <stdio.h>
+
+#include "nxt_jni.h"
+#include "nxt_jni_Response.h"
+#include "nxt_jni_HeadersEnumeration.h"
+#include "nxt_jni_HeaderNamesEnumeration.h"
+#include "nxt_jni_OutputStream.h"
+#include "nxt_jni_URLClassLoader.h"
+
+
+static jclass nxt_java_Response_class;
+static jmethodID nxt_java_Response_ctor;
+
+
+static void JNICALL nxt_java_Response_addHeader(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray name, jarray value);
+
+static nxt_unit_request_info_t *nxt_java_get_response_info(
+ jlong req_info_ptr, uint32_t extra_fields, uint32_t extra_data);
+
+static void JNICALL nxt_java_Response_addIntHeader(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray name, jint value);
+
+static void nxt_java_add_int_header(nxt_unit_request_info_t *req,
+ const char *name, uint8_t name_len, int value);
+
+static jboolean JNICALL nxt_java_Response_containsHeader(JNIEnv *env,
+ jclass cls, jlong req_info_ptr, jarray name);
+
+static jstring JNICALL nxt_java_Response_getHeader(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray name);
+
+static jobject JNICALL nxt_java_Response_getHeaderNames(JNIEnv *env,
+ jclass cls, jlong req_info_ptr);
+
+static jobject JNICALL nxt_java_Response_getHeaders(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray name);
+
+static jint JNICALL nxt_java_Response_getStatus(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+
+static jobject JNICALL nxt_java_Response_getRequest(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+
+static void JNICALL nxt_java_Response_commit(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+
+static void JNICALL nxt_java_Response_sendRedirect(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray loc);
+
+static int nxt_java_response_set_header(jlong req_info_ptr,
+ const char *name, jint name_len, const char *value, jint value_len);
+
+static void JNICALL nxt_java_Response_setHeader(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray name, jarray value);
+
+static void JNICALL nxt_java_Response_removeHeader(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray name);
+
+static int nxt_java_response_remove_header(jlong req_info_ptr,
+ const char *name, jint name_len);
+
+static void JNICALL nxt_java_Response_setIntHeader(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray name, jint value);
+
+static void JNICALL nxt_java_Response_setStatus(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jint sc);
+
+static jstring JNICALL nxt_java_Response_getContentType(JNIEnv *env,
+ jclass cls, jlong req_info_ptr);
+
+static jboolean JNICALL nxt_java_Response_isCommitted(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+
+static void JNICALL nxt_java_Response_reset(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+
+static void JNICALL nxt_java_Response_resetBuffer(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+
+static void JNICALL nxt_java_Response_setBufferSize(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jint size);
+
+static jint JNICALL nxt_java_Response_getBufferSize(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+
+static void JNICALL nxt_java_Response_setContentLength(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jlong len);
+
+static void JNICALL nxt_java_Response_setContentType(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray type);
+
+static void JNICALL nxt_java_Response_removeContentType(JNIEnv *env, jclass cls,
+ jlong req_info_ptr);
+
+static void JNICALL nxt_java_Response_log(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray msg);
+
+static void JNICALL nxt_java_Response_trace(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray msg);
+
+int
+nxt_java_initResponse(JNIEnv *env, jobject cl)
+{
+ int res;
+ jclass cls;
+
+ cls = nxt_java_loadClass(env, cl, "nginx.unit.Response");
+ if (cls == NULL) {
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_Response_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+ cls = nxt_java_Response_class;
+
+ nxt_java_Response_ctor = (*env)->GetMethodID(env, cls, "<init>", "(J)V");
+ if (nxt_java_Response_ctor == NULL) {
+ (*env)->DeleteGlobalRef(env, cls);
+ return NXT_UNIT_ERROR;
+ }
+
+ JNINativeMethod resp_methods[] = {
+ { (char *) "addHeader",
+ (char *) "(J[B[B)V",
+ nxt_java_Response_addHeader },
+
+ { (char *) "addIntHeader",
+ (char *) "(J[BI)V",
+ nxt_java_Response_addIntHeader },
+
+ { (char *) "containsHeader",
+ (char *) "(J[B)Z",
+ nxt_java_Response_containsHeader },
+
+ { (char *) "getHeader",
+ (char *) "(J[B)Ljava/lang/String;",
+ nxt_java_Response_getHeader },
+
+ { (char *) "getHeaderNames",
+ (char *) "(J)Ljava/util/Enumeration;",
+ nxt_java_Response_getHeaderNames },
+
+ { (char *) "getHeaders",
+ (char *) "(J[B)Ljava/util/Enumeration;",
+ nxt_java_Response_getHeaders },
+
+ { (char *) "getStatus",
+ (char *) "(J)I",
+ nxt_java_Response_getStatus },
+
+ { (char *) "getRequest",
+ (char *) "(J)Lnginx/unit/Request;",
+ nxt_java_Response_getRequest },
+
+ { (char *) "commit",
+ (char *) "(J)V",
+ nxt_java_Response_commit },
+
+ { (char *) "sendRedirect",
+ (char *) "(J[B)V",
+ nxt_java_Response_sendRedirect },
+
+ { (char *) "setHeader",
+ (char *) "(J[B[B)V",
+ nxt_java_Response_setHeader },
+
+ { (char *) "removeHeader",
+ (char *) "(J[B)V",
+ nxt_java_Response_removeHeader },
+
+ { (char *) "setIntHeader",
+ (char *) "(J[BI)V",
+ nxt_java_Response_setIntHeader },
+
+ { (char *) "setStatus",
+ (char *) "(JI)V",
+ nxt_java_Response_setStatus },
+
+ { (char *) "getContentType",
+ (char *) "(J)Ljava/lang/String;",
+ nxt_java_Response_getContentType },
+
+ { (char *) "isCommitted",
+ (char *) "(J)Z",
+ nxt_java_Response_isCommitted },
+
+ { (char *) "reset",
+ (char *) "(J)V",
+ nxt_java_Response_reset },
+
+ { (char *) "resetBuffer",
+ (char *) "(J)V",
+ nxt_java_Response_resetBuffer },
+
+ { (char *) "setBufferSize",
+ (char *) "(JI)V",
+ nxt_java_Response_setBufferSize },
+
+ { (char *) "getBufferSize",
+ (char *) "(J)I",
+ nxt_java_Response_getBufferSize },
+
+ { (char *) "setContentLength",
+ (char *) "(JJ)V",
+ nxt_java_Response_setContentLength },
+
+ { (char *) "setContentType",
+ (char *) "(J[B)V",
+ nxt_java_Response_setContentType },
+
+ { (char *) "removeContentType",
+ (char *) "(J)V",
+ nxt_java_Response_removeContentType },
+
+ { (char *) "log",
+ (char *) "(J[B)V",
+ nxt_java_Response_log },
+
+ { (char *) "trace",
+ (char *) "(J[B)V",
+ nxt_java_Response_trace },
+
+ };
+
+ res = (*env)->RegisterNatives(env, nxt_java_Response_class,
+ resp_methods,
+ sizeof(resp_methods)
+ / sizeof(resp_methods[0]));
+
+ nxt_unit_debug(NULL, "registered Response methods: %d", res);
+
+ if (res != 0) {
+ (*env)->DeleteGlobalRef(env, cls);
+ return NXT_UNIT_ERROR;
+ }
+
+ return NXT_UNIT_OK;
+}
+
+
+jobject
+nxt_java_newResponse(JNIEnv *env, nxt_unit_request_info_t *req)
+{
+ return (*env)->NewObject(env, nxt_java_Response_class,
+ nxt_java_Response_ctor, nxt_ptr2jlong(req));
+}
+
+
+static void JNICALL
+nxt_java_Response_addHeader(JNIEnv *env, jclass cls, jlong req_info_ptr,
+ jarray name, jarray value)
+{
+ int rc;
+ char *name_str, *value_str;
+ jsize name_len, value_len;
+ nxt_unit_request_info_t *req;
+
+ name_len = (*env)->GetArrayLength(env, name);
+ value_len = (*env)->GetArrayLength(env, value);
+
+ req = nxt_java_get_response_info(req_info_ptr, 1, name_len + value_len + 2);
+ if (req == NULL) {
+ return;
+ }
+
+ name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL);
+ if (name_str == NULL) {
+ nxt_unit_req_warn(req, "addHeader: failed to get name content");
+ return;
+ }
+
+ value_str = (*env)->GetPrimitiveArrayCritical(env, value, NULL);
+ if (value_str == NULL) {
+ (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0);
+ nxt_unit_req_warn(req, "addHeader: failed to get value content");
+
+ return;
+ }
+
+ rc = nxt_unit_response_add_field(req, name_str, name_len,
+ value_str, value_len);
+ if (rc != NXT_UNIT_OK) {
+ // throw
+ }
+
+ (*env)->ReleasePrimitiveArrayCritical(env, value, value_str, 0);
+ (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0);
+}
+
+
+static nxt_unit_request_info_t *
+nxt_java_get_response_info(jlong req_info_ptr, uint32_t extra_fields,
+ uint32_t extra_data)
+{
+ int rc;
+ char *p;
+ uint32_t max_size;
+ nxt_unit_buf_t *buf;
+ nxt_unit_request_info_t *req;
+ nxt_java_request_data_t *data;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ if (nxt_unit_response_is_sent(req)) {
+ return NULL;
+ }
+
+ data = req->data;
+
+ if (!nxt_unit_response_is_init(req)) {
+ max_size = nxt_unit_buf_max();
+ max_size = max_size < data->header_size ? max_size : data->header_size;
+
+ rc = nxt_unit_response_init(req, 200, 16, max_size);
+ if (rc != NXT_UNIT_OK) {
+ return NULL;
+ }
+ }
+
+ buf = req->response_buf;
+
+ if (extra_fields > req->response_max_fields
+ - req->response->fields_count
+ || extra_data > (uint32_t) (buf->end - buf->free))
+ {
+ p = buf->start + req->response_max_fields * sizeof(nxt_unit_field_t);
+
+ max_size = 2 * (buf->end - p);
+ if (max_size > nxt_unit_buf_max()) {
+ nxt_unit_req_warn(req, "required max_size is too big: %"PRIu32,
+ max_size);
+ return NULL;
+ }
+
+ rc = nxt_unit_response_realloc(req, 2 * req->response_max_fields,
+ max_size);
+ if (rc != NXT_UNIT_OK) {
+ nxt_unit_req_warn(req, "reallocation failed: %"PRIu32", %"PRIu32,
+ 2 * req->response_max_fields, max_size);
+ return NULL;
+ }
+ }
+
+ return req;
+}
+
+
+static void JNICALL
+nxt_java_Response_addIntHeader(JNIEnv *env, jclass cls, jlong req_info_ptr,
+ jarray name, jint value)
+{
+ char *name_str;
+ jsize name_len;
+ nxt_unit_request_info_t *req;
+
+ name_len = (*env)->GetArrayLength(env, name);
+
+ req = nxt_java_get_response_info(req_info_ptr, 1, name_len + 40);
+ if (req == NULL) {
+ return;
+ }
+
+ name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL);
+ if (name_str == NULL) {
+ nxt_unit_req_warn(req, "addIntHeader: failed to get name content");
+ return;
+ }
+
+ nxt_java_add_int_header(req, name_str, name_len, value);
+
+ (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0);
+}
+
+
+static void
+nxt_java_add_int_header(nxt_unit_request_info_t *req, const char *name,
+ uint8_t name_len, int value)
+{
+ char *p;
+ nxt_unit_field_t *f;
+ nxt_unit_response_t *resp;
+
+ resp = req->response;
+
+ f = resp->fields + resp->fields_count;
+ p = req->response_buf->free;
+
+ f->hash = nxt_unit_field_hash(name, name_len);
+ f->skip = 0;
+ f->name_length = name_len;
+
+ nxt_unit_sptr_set(&f->name, p);
+ memcpy(p, name, name_len);
+ p += name_len;
+
+ nxt_unit_sptr_set(&f->value, p);
+ f->value_length = snprintf(p, 40, "%d", (int) value);
+ p += f->value_length + 1;
+
+ resp->fields_count++;
+ req->response_buf->free = p;
+
+}
+
+
+static jboolean JNICALL
+nxt_java_Response_containsHeader(JNIEnv *env,
+ jclass cls, jlong req_info_ptr, jarray name)
+{
+ jboolean res;
+ char *name_str;
+ jsize name_len;
+ nxt_unit_response_t *resp;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ if (!nxt_unit_response_is_init(req)) {
+ nxt_unit_req_debug(req, "containsHeader: response is not initialized");
+ return 0;
+ }
+
+ if (nxt_unit_response_is_sent(req)) {
+ nxt_unit_req_debug(req, "containsHeader: response already sent");
+ return 0;
+ }
+
+ name_len = (*env)->GetArrayLength(env, name);
+
+ name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL);
+ if (name_str == NULL) {
+ nxt_unit_req_warn(req, "containsHeader: failed to get name content");
+ return 0;
+ }
+
+ resp = req->response;
+
+ res = nxt_java_findHeader(resp->fields,
+ resp->fields + resp->fields_count,
+ name_str, name_len) != NULL;
+
+ (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0);
+
+ return res;
+}
+
+
+static jstring JNICALL
+nxt_java_Response_getHeader(JNIEnv *env, jclass cls, jlong req_info_ptr,
+ jarray name)
+{
+ char *name_str;
+ jsize name_len;
+ nxt_unit_field_t *f;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ if (!nxt_unit_response_is_init(req)) {
+ nxt_unit_req_debug(req, "getHeader: response is not initialized");
+ return NULL;
+ }
+
+ if (nxt_unit_response_is_sent(req)) {
+ nxt_unit_req_debug(req, "getHeader: response already sent");
+ return NULL;
+ }
+
+ name_len = (*env)->GetArrayLength(env, name);
+
+ name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL);
+ if (name_str == NULL) {
+ nxt_unit_req_warn(req, "getHeader: failed to get name content");
+ return NULL;
+ }
+
+ f = nxt_java_findHeader(req->response->fields,
+ req->response->fields + req->response->fields_count,
+ name_str, name_len);
+
+ (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0);
+
+ if (f == NULL) {
+ return NULL;
+ }
+
+ return nxt_java_newString(env, nxt_unit_sptr_get(&f->value),
+ f->value_length);
+}
+
+
+static jobject JNICALL
+nxt_java_Response_getHeaderNames(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ if (!nxt_unit_response_is_init(req)) {
+ nxt_unit_req_debug(req, "getHeaderNames: response is not initialized");
+ return NULL;
+ }
+
+ if (nxt_unit_response_is_sent(req)) {
+ nxt_unit_req_debug(req, "getHeaderNames: response already sent");
+ return NULL;
+ }
+
+ return nxt_java_newHeaderNamesEnumeration(env, req->response->fields,
+ req->response->fields_count);
+}
+
+
+static jobject JNICALL
+nxt_java_Response_getHeaders(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray name)
+{
+ char *name_str;
+ jsize name_len;
+ nxt_unit_field_t *f;
+ nxt_unit_response_t *resp;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ if (!nxt_unit_response_is_init(req)) {
+ nxt_unit_req_debug(req, "getHeaders: response is not initialized");
+ return NULL;
+ }
+
+ if (nxt_unit_response_is_sent(req)) {
+ nxt_unit_req_debug(req, "getHeaders: response already sent");
+ return NULL;
+ }
+
+ resp = req->response;
+
+ name_len = (*env)->GetArrayLength(env, name);
+
+ name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL);
+ if (name_str == NULL) {
+ nxt_unit_req_warn(req, "getHeaders: failed to get name content");
+ return NULL;
+ }
+
+ f = nxt_java_findHeader(resp->fields, resp->fields + resp->fields_count,
+ name_str, name_len);
+
+ (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0);
+
+ if (f == NULL) {
+ f = resp->fields + resp->fields_count;
+ }
+
+ return nxt_java_newHeadersEnumeration(env, resp->fields, resp->fields_count,
+ f - resp->fields);
+}
+
+
+static jint JNICALL
+nxt_java_Response_getStatus(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ if (!nxt_unit_response_is_init(req)) {
+ nxt_unit_req_debug(req, "getStatus: response is not initialized");
+ return 200;
+ }
+
+ if (nxt_unit_response_is_sent(req)) {
+ nxt_unit_req_debug(req, "getStatus: response already sent");
+ return 200;
+ }
+
+ return req->response->status;
+}
+
+
+static jobject JNICALL
+nxt_java_Response_getRequest(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_unit_request_info_t *req;
+ nxt_java_request_data_t *data;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+ data = req->data;
+
+ return data->jreq;
+}
+
+
+static void JNICALL
+nxt_java_Response_commit(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ nxt_java_OutputStream_flush_buf(env, req);
+}
+
+
+static void JNICALL
+nxt_java_Response_sendRedirect(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray loc)
+{
+ int rc;
+ char *loc_str;
+ jsize loc_len;
+ nxt_unit_request_info_t *req;
+
+ static const char location[] = "Location";
+ static const uint32_t location_len = sizeof(location) - 1;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ if (nxt_unit_response_is_sent(req)) {
+ nxt_java_throw_IllegalStateException(env, "Response already sent");
+
+ return;
+ }
+
+ loc_len = (*env)->GetArrayLength(env, loc);
+
+ req = nxt_java_get_response_info(req_info_ptr, 1,
+ location_len + loc_len + 2);
+ if (req == NULL) {
+ return;
+ }
+
+ loc_str = (*env)->GetPrimitiveArrayCritical(env, loc, NULL);
+ if (loc_str == NULL) {
+ nxt_unit_req_warn(req, "sendRedirect: failed to get loc content");
+ return;
+ }
+
+ req->response->status = 302;
+
+ rc = nxt_java_response_set_header(req_info_ptr, location, location_len,
+ loc_str, loc_len);
+ if (rc != NXT_UNIT_OK) {
+ // throw
+ }
+
+ (*env)->ReleasePrimitiveArrayCritical(env, loc, loc_str, 0);
+
+ nxt_unit_response_send(req);
+}
+
+
+static int
+nxt_java_response_set_header(jlong req_info_ptr,
+ const char *name, jint name_len, const char *value, jint value_len)
+{
+ int add_field;
+ char *dst;
+ nxt_unit_field_t *f, *e;
+ nxt_unit_response_t *resp;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_java_get_response_info(req_info_ptr, 0, 0);
+ if (req == NULL) {
+ return NXT_UNIT_ERROR;
+ }
+
+ resp = req->response;
+
+ f = resp->fields;
+ e = f + resp->fields_count;
+
+ add_field = 1;
+
+ for ( ;; ) {
+ f = nxt_java_findHeader(f, e, name, name_len);
+ if (f == NULL) {
+ break;
+ }
+
+ if (add_field && f->value_length >= (uint32_t) value_len) {
+ dst = nxt_unit_sptr_get(&f->value);
+ memcpy(dst, value, value_len);
+ dst[value_len] = '\0';
+ f->value_length = value_len;
+
+ add_field = 0;
+ f->skip = 0;
+
+ } else {
+ f->skip = 1;
+ }
+
+ ++f;
+ }
+
+ if (!add_field) {
+ return NXT_UNIT_OK;
+ }
+
+ req = nxt_java_get_response_info(req_info_ptr, 1, name_len + value_len + 2);
+ if (req == NULL) {
+ return NXT_UNIT_ERROR;
+ }
+
+ return nxt_unit_response_add_field(req, name, name_len, value, value_len);
+}
+
+
+static void JNICALL
+nxt_java_Response_setHeader(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray name, jarray value)
+{
+ int rc;
+ char *name_str, *value_str;
+ jsize name_len, value_len;
+ nxt_unit_request_info_t *req;
+
+ name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL);
+ if (name_str == NULL) {
+ req = nxt_jlong2ptr(req_info_ptr);
+ nxt_unit_req_warn(req, "setHeader: failed to get name content");
+ return;
+ }
+
+ value_str = (*env)->GetPrimitiveArrayCritical(env, value, NULL);
+ if (value_str == NULL) {
+ (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0);
+
+ req = nxt_jlong2ptr(req_info_ptr);
+ nxt_unit_req_warn(req, "setHeader: failed to get value content");
+
+ return;
+ }
+
+ name_len = (*env)->GetArrayLength(env, name);
+ value_len = (*env)->GetArrayLength(env, value);
+
+ rc = nxt_java_response_set_header(req_info_ptr, name_str, name_len,
+ value_str, value_len);
+ if (rc != NXT_UNIT_OK) {
+ // throw
+ }
+
+ (*env)->ReleasePrimitiveArrayCritical(env, value, value_str, 0);
+ (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0);
+}
+
+
+static void JNICALL
+nxt_java_Response_removeHeader(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray name)
+{
+ int rc;
+ char *name_str;
+ jsize name_len;
+ nxt_unit_request_info_t *req;
+
+ name_len = (*env)->GetArrayLength(env, name);
+
+ name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL);
+ if (name_str == NULL) {
+ req = nxt_jlong2ptr(req_info_ptr);
+ nxt_unit_req_warn(req, "setHeader: failed to get name content");
+ return;
+ }
+
+ rc = nxt_java_response_remove_header(req_info_ptr, name_str, name_len);
+ if (rc != NXT_UNIT_OK) {
+ // throw
+ }
+
+ (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0);
+}
+
+
+static int
+nxt_java_response_remove_header(jlong req_info_ptr,
+ const char *name, jint name_len)
+{
+ nxt_unit_field_t *f, *e;
+ nxt_unit_response_t *resp;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_java_get_response_info(req_info_ptr, 0, 0);
+ if (req == NULL) {
+ return NXT_UNIT_ERROR;
+ }
+
+ resp = req->response;
+
+ f = resp->fields;
+ e = f + resp->fields_count;
+
+ for ( ;; ) {
+ f = nxt_java_findHeader(f, e, name, name_len);
+ if (f == NULL) {
+ break;
+ }
+
+ f->skip = 1;
+
+ ++f;
+ }
+
+ return NXT_UNIT_OK;
+}
+
+
+static void JNICALL
+nxt_java_Response_setIntHeader(JNIEnv *env, jclass cls,
+ jlong req_info_ptr, jarray name, jint value)
+{
+ int value_len, rc;
+ char value_str[40];
+ char *name_str;
+ jsize name_len;
+
+ value_len = snprintf(value_str, sizeof(value_str), "%d", (int) value);
+
+ name_len = (*env)->GetArrayLength(env, name);
+
+ name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL);
+ if (name_str == NULL) {
+ nxt_unit_req_warn(nxt_jlong2ptr(req_info_ptr),
+ "setIntHeader: failed to get name content");
+ return;
+ }
+
+ rc = nxt_java_response_set_header(req_info_ptr, name_str, name_len,
+ value_str, value_len);
+ if (rc != NXT_UNIT_OK) {
+ // throw
+ }
+
+ (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0);
+}
+
+
+static void JNICALL
+nxt_java_Response_setStatus(JNIEnv *env, jclass cls, jlong req_info_ptr,
+ jint sc)
+{
+ nxt_unit_request_info_t *req;
+
+ req = nxt_java_get_response_info(req_info_ptr, 0, 0);
+ if (req == NULL) {
+ return;
+ }
+
+ req->response->status = sc;
+}
+
+
+static jstring JNICALL
+nxt_java_Response_getContentType(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_unit_field_t *f;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ if (!nxt_unit_response_is_init(req)) {
+ nxt_unit_req_debug(req, "getContentType: response is not initialized");
+ return NULL;
+ }
+
+ if (nxt_unit_response_is_sent(req)) {
+ nxt_unit_req_debug(req, "getContentType: response already sent");
+ return NULL;
+ }
+
+ f = nxt_java_findHeader(req->response->fields,
+ req->response->fields + req->response->fields_count,
+ "Content-Type", sizeof("Content-Type") - 1);
+
+ if (f == NULL) {
+ return NULL;
+ }
+
+ return nxt_java_newString(env, nxt_unit_sptr_get(&f->value),
+ f->value_length);
+}
+
+
+static jboolean JNICALL
+nxt_java_Response_isCommitted(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ if (nxt_unit_response_is_sent(req)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static void JNICALL
+nxt_java_Response_reset(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_unit_buf_t *buf;
+ nxt_unit_request_info_t *req;
+ nxt_java_request_data_t *data;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+
+ if (nxt_unit_response_is_sent(req)) {
+ nxt_java_throw_IllegalStateException(env, "Response already sent");
+
+ return;
+ }
+
+ data = req->data;
+
+ if (data->buf != NULL && data->buf->free > data->buf->start) {
+ data->buf->free = data->buf->start;
+ }
+
+ if (nxt_unit_response_is_init(req)) {
+ req->response->status = 200;
+ req->response->fields_count = 0;
+
+ buf = req->response_buf;
+
+ buf->free = buf->start + req->response_max_fields
+ * sizeof(nxt_unit_field_t);
+ }
+}
+
+
+static void JNICALL
+nxt_java_Response_resetBuffer(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_unit_request_info_t *req;
+ nxt_java_request_data_t *data;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+ data = req->data;
+
+ if (data->buf != NULL && data->buf->free > data->buf->start) {
+ data->buf->free = data->buf->start;
+ }
+}
+
+
+static void JNICALL
+nxt_java_Response_setBufferSize(JNIEnv *env, jclass cls, jlong req_info_ptr,
+ jint size)
+{
+ nxt_unit_request_info_t *req;
+ nxt_java_request_data_t *data;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+ data = req->data;
+
+ if (data->buf_size == (uint32_t) size) {
+ return;
+ }
+
+ if (data->buf != NULL && data->buf->free > data->buf->start) {
+ nxt_java_throw_IllegalStateException(env, "Buffer is not empty");
+
+ return;
+ }
+
+ data->buf_size = size;
+
+ if (data->buf_size > nxt_unit_buf_max()) {
+ data->buf_size = nxt_unit_buf_max();
+ }
+
+ if (data->buf != NULL
+ && (uint32_t) (data->buf->end - data->buf->start) < data->buf_size)
+ {
+ nxt_unit_buf_free(data->buf);
+
+ data->buf = NULL;
+ }
+}
+
+
+static jint JNICALL
+nxt_java_Response_getBufferSize(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_unit_request_info_t *req;
+ nxt_java_request_data_t *data;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+ data = req->data;
+
+ return data->buf_size;
+}
+
+
+static void JNICALL
+nxt_java_Response_setContentLength(JNIEnv *env, jclass cls, jlong req_info_ptr,
+ jlong len)
+{
+ nxt_unit_request_info_t *req;
+
+ req = nxt_java_get_response_info(req_info_ptr, 0, 0);
+ if (req == NULL) {
+ return;
+ }
+
+ req->response->content_length = len;
+}
+
+
+static void JNICALL
+nxt_java_Response_setContentType(JNIEnv *env, jclass cls, jlong req_info_ptr,
+ jarray type)
+{
+ int rc;
+ char *type_str;
+ jsize type_len;
+
+ static const char content_type[] = "Content-Type";
+ static const uint32_t content_type_len = sizeof(content_type) - 1;
+
+ type_len = (*env)->GetArrayLength(env, type);
+
+ type_str = (*env)->GetPrimitiveArrayCritical(env, type, NULL);
+ if (type_str == NULL) {
+ return;
+ }
+
+ rc = nxt_java_response_set_header(req_info_ptr,
+ content_type, content_type_len,
+ type_str, type_len);
+ if (rc != NXT_UNIT_OK) {
+ // throw
+ }
+
+ (*env)->ReleasePrimitiveArrayCritical(env, type, type_str, 0);
+}
+
+
+static void JNICALL
+nxt_java_Response_removeContentType(JNIEnv *env, jclass cls, jlong req_info_ptr)
+{
+ nxt_java_response_remove_header(req_info_ptr, "Content-Type",
+ sizeof("Content-Type") - 1);
+}
+
+
+static void JNICALL
+nxt_java_Response_log(JNIEnv *env, jclass cls, jlong req_info_ptr, jarray msg)
+{
+ char *msg_str;
+ jsize msg_len;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+ msg_len = (*env)->GetArrayLength(env, msg);
+
+ msg_str = (*env)->GetPrimitiveArrayCritical(env, msg, NULL);
+ if (msg_str == NULL) {
+ nxt_unit_req_warn(req, "log: failed to get msg content");
+ return;
+ }
+
+ nxt_unit_req_log(req, NXT_UNIT_LOG_INFO, "%.*s", msg_len, msg_str);
+
+ (*env)->ReleasePrimitiveArrayCritical(env, msg, msg_str, 0);
+}
+
+
+static void JNICALL
+nxt_java_Response_trace(JNIEnv *env, jclass cls, jlong req_info_ptr, jarray msg)
+{
+#if (NXT_DEBUG)
+ char *msg_str;
+ jsize msg_len;
+ nxt_unit_request_info_t *req;
+
+ req = nxt_jlong2ptr(req_info_ptr);
+ msg_len = (*env)->GetArrayLength(env, msg);
+
+ msg_str = (*env)->GetPrimitiveArrayCritical(env, msg, NULL);
+ if (msg_str == NULL) {
+ nxt_unit_req_warn(req, "trace: failed to get msg content");
+ return;
+ }
+
+ nxt_unit_req_debug(req, "%.*s", msg_len, msg_str);
+
+ (*env)->ReleasePrimitiveArrayCritical(env, msg, msg_str, 0);
+#endif
+}
+
diff --git a/src/java/nxt_jni_Response.h b/src/java/nxt_jni_Response.h
new file mode 100644
index 00000000..d10dba58
--- /dev/null
+++ b/src/java/nxt_jni_Response.h
@@ -0,0 +1,18 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_JAVA_RESPONSE_H_INCLUDED_
+#define _NXT_JAVA_RESPONSE_H_INCLUDED_
+
+
+#include <jni.h>
+#include <nxt_unit_typedefs.h>
+
+
+int nxt_java_initResponse(JNIEnv *env, jobject cl);
+
+jobject nxt_java_newResponse(JNIEnv *env, nxt_unit_request_info_t *req);
+
+#endif /* _NXT_JAVA_RESPONSE_H_INCLUDED_ */
diff --git a/src/java/nxt_jni_Thread.c b/src/java/nxt_jni_Thread.c
new file mode 100644
index 00000000..43dd90bd
--- /dev/null
+++ b/src/java/nxt_jni_Thread.c
@@ -0,0 +1,94 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_unit.h>
+#include <jni.h>
+
+#include "nxt_jni_Thread.h"
+
+
+static jclass nxt_java_Thread_class;
+static jmethodID nxt_java_Thread_currentThread;
+static jmethodID nxt_java_Thread_getContextClassLoader;
+static jmethodID nxt_java_Thread_setContextClassLoader;
+
+
+int
+nxt_java_initThread(JNIEnv *env)
+{
+ jclass cls;
+
+ cls = (*env)->FindClass(env, "java/lang/Thread");
+ if (cls == NULL) {
+ nxt_unit_warn(NULL, "java.lang.Thread not found");
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_Thread_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+ cls = nxt_java_Thread_class;
+
+ nxt_java_Thread_currentThread = (*env)->GetStaticMethodID(env, cls,
+ "currentThread", "()Ljava/lang/Thread;");
+ if (nxt_java_Thread_currentThread == NULL) {
+ nxt_unit_warn(NULL, "java.lang.Thread.currentThread() not found");
+ goto failed;
+ }
+
+ nxt_java_Thread_getContextClassLoader = (*env)->GetMethodID(env, cls,
+ "getContextClassLoader", "()Ljava/lang/ClassLoader;");
+ if (nxt_java_Thread_getContextClassLoader == NULL) {
+ nxt_unit_warn(NULL, "java.lang.Thread.getContextClassLoader() "
+ "not found");
+ goto failed;
+ }
+
+ nxt_java_Thread_setContextClassLoader = (*env)->GetMethodID(env, cls,
+ "setContextClassLoader", "(Ljava/lang/ClassLoader;)V");
+ if (nxt_java_Thread_setContextClassLoader == NULL) {
+ nxt_unit_warn(NULL, "java.lang.Thread.setContextClassLoader() "
+ "not found");
+ goto failed;
+ }
+
+ return NXT_UNIT_OK;
+
+failed:
+
+ (*env)->DeleteGlobalRef(env, cls);
+ return NXT_UNIT_ERROR;
+}
+
+void
+nxt_java_setContextClassLoader(JNIEnv *env, jobject cl)
+{
+ jobject thread;
+
+ thread = (*env)->CallStaticObjectMethod(env, nxt_java_Thread_class,
+ nxt_java_Thread_currentThread);
+
+ if (thread == NULL) {
+ return;
+ }
+
+ (*env)->CallVoidMethod(env, thread, nxt_java_Thread_setContextClassLoader,
+ cl);
+}
+
+jobject
+nxt_java_getContextClassLoader(JNIEnv *env)
+{
+ jobject thread;
+
+ thread = (*env)->CallStaticObjectMethod(env, nxt_java_Thread_class,
+ nxt_java_Thread_currentThread);
+
+ if (thread == NULL) {
+ return NULL;
+ }
+
+ return (*env)->CallObjectMethod(env, thread,
+ nxt_java_Thread_getContextClassLoader);
+}
diff --git a/src/java/nxt_jni_Thread.h b/src/java/nxt_jni_Thread.h
new file mode 100644
index 00000000..4d0b650e
--- /dev/null
+++ b/src/java/nxt_jni_Thread.h
@@ -0,0 +1,20 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_JAVA_THREAD_H_INCLUDED_
+#define _NXT_JAVA_THREAD_H_INCLUDED_
+
+
+#include <jni.h>
+
+
+int nxt_java_initThread(JNIEnv *env);
+
+void nxt_java_setContextClassLoader(JNIEnv *env, jobject cl);
+
+jobject nxt_java_getContextClassLoader(JNIEnv *env);
+
+#endif /* _NXT_JAVA_THREAD_H_INCLUDED_ */
+
diff --git a/src/java/nxt_jni_URLClassLoader.c b/src/java/nxt_jni_URLClassLoader.c
new file mode 100644
index 00000000..bf3ab0c3
--- /dev/null
+++ b/src/java/nxt_jni_URLClassLoader.c
@@ -0,0 +1,187 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_unit.h>
+#include <jni.h>
+
+#include "nxt_jni_URLClassLoader.h"
+
+
+static jclass nxt_java_URLClassLoader_class;
+static jmethodID nxt_java_URLClassLoader_ctor;
+static jmethodID nxt_java_URLClassLoader_parent_ctor;
+static jmethodID nxt_java_URLClassLoader_loadClass;
+static jmethodID nxt_java_URLClassLoader_addURL;
+
+static jclass nxt_java_URL_class;
+static jmethodID nxt_java_URL_ctor;
+
+
+int
+nxt_java_initURLClassLoader(JNIEnv *env)
+{
+ jclass cls;
+
+ cls = (*env)->FindClass(env, "java/net/URLClassLoader");
+ if (cls == NULL) {
+ nxt_unit_warn(NULL, "java.net.URLClassLoader not found");
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_URLClassLoader_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+ cls = nxt_java_URLClassLoader_class;
+
+ nxt_java_URLClassLoader_ctor = (*env)->GetMethodID(env, cls,
+ "<init>", "([Ljava/net/URL;)V");
+ if (nxt_java_URLClassLoader_ctor == NULL) {
+ nxt_unit_warn(NULL, "java.net.URLClassLoader constructor not found");
+ goto failed;
+ }
+
+ nxt_java_URLClassLoader_parent_ctor = (*env)->GetMethodID(env, cls,
+ "<init>", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V");
+ if (nxt_java_URLClassLoader_ctor == NULL) {
+ nxt_unit_warn(NULL, "java.net.URLClassLoader constructor not found");
+ goto failed;
+ }
+
+ nxt_java_URLClassLoader_loadClass = (*env)->GetMethodID(env, cls,
+ "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+ if (nxt_java_URLClassLoader_loadClass == NULL) {
+ nxt_unit_warn(NULL, "java.net.URLClassLoader.loadClass not found");
+ goto failed;
+ }
+
+ nxt_java_URLClassLoader_addURL = (*env)->GetMethodID(env, cls,
+ "addURL", "(Ljava/net/URL;)V");
+ if (nxt_java_URLClassLoader_addURL == NULL) {
+ nxt_unit_warn(NULL, "java.net.URLClassLoader.addURL not found");
+ goto failed;
+ }
+
+ cls = (*env)->FindClass(env, "java/net/URL");
+ if (cls == NULL) {
+ nxt_unit_warn(NULL, "java.net.URL not found");
+ return NXT_UNIT_ERROR;
+ }
+
+ nxt_java_URL_class = (*env)->NewGlobalRef(env, cls);
+ (*env)->DeleteLocalRef(env, cls);
+ cls = nxt_java_URL_class;
+
+ nxt_java_URL_ctor = (*env)->GetMethodID(env, cls,
+ "<init>", "(Ljava/lang/String;)V");
+ if (nxt_java_URL_ctor == NULL) {
+ nxt_unit_warn(NULL, "java.net.URL constructor not found");
+ goto failed;
+ }
+
+ return NXT_UNIT_OK;
+
+failed:
+
+ (*env)->DeleteGlobalRef(env, cls);
+ return NXT_UNIT_ERROR;
+}
+
+
+jobject
+nxt_java_newURLClassLoader(JNIEnv *env, int url_count, char **urls)
+{
+ jobjectArray jurls;
+
+ jurls = nxt_java_newURLs(env, url_count, urls);
+ if (jurls == NULL) {
+ return NULL;
+ }
+
+ return (*env)->NewObject(env, nxt_java_URLClassLoader_class,
+ nxt_java_URLClassLoader_ctor, jurls);
+}
+
+
+jobject
+nxt_java_newURLClassLoader_parent(JNIEnv *env, int url_count, char **urls,
+ jobject parent)
+{
+ jobjectArray jurls;
+
+ jurls = nxt_java_newURLs(env, url_count, urls);
+ if (jurls == NULL) {
+ return NULL;
+ }
+
+ return (*env)->NewObject(env, nxt_java_URLClassLoader_class,
+ nxt_java_URLClassLoader_parent_ctor, jurls,
+ parent);
+}
+
+
+jobjectArray
+nxt_java_newURLs(JNIEnv *env, int url_count, char **urls)
+{
+ int i;
+ jstring surl;
+ jobject jurl;
+ jobjectArray jurls;
+
+ jurls = (*env)->NewObjectArray(env, url_count, nxt_java_URL_class, NULL);
+ if (jurls == NULL) {
+ return NULL;
+ }
+
+ for (i = 0; i < url_count; i++) {
+ surl = (*env)->NewStringUTF(env, urls[i]);
+ if (surl == NULL) {
+ return NULL;
+ }
+
+ jurl = (*env)->NewObject(env, nxt_java_URL_class, nxt_java_URL_ctor,
+ surl);
+ if (jurl == NULL) {
+ return NULL;
+ }
+
+ (*env)->SetObjectArrayElement(env, jurls, i, jurl);
+ }
+
+ return jurls;
+}
+
+
+jclass
+nxt_java_loadClass(JNIEnv *env, jobject cl, const char *name)
+{
+ jstring jname;
+
+ jname = (*env)->NewStringUTF(env, name);
+ if (jname == NULL) {
+ return NULL;
+ }
+
+ return (*env)->CallObjectMethod(env, cl, nxt_java_URLClassLoader_loadClass,
+ jname);
+}
+
+
+void
+nxt_java_addURL(JNIEnv *env, jobject cl, const char *url)
+{
+ jstring surl;
+ jobject jurl;
+
+ surl = (*env)->NewStringUTF(env, url);
+ if (surl == NULL) {
+ return;
+ }
+
+ jurl = (*env)->NewObject(env, nxt_java_URL_class, nxt_java_URL_ctor, surl);
+ if (jurl == NULL) {
+ return;
+ }
+
+ (*env)->CallVoidMethod(env, cl, nxt_java_URLClassLoader_addURL, jurl);
+}
diff --git a/src/java/nxt_jni_URLClassLoader.h b/src/java/nxt_jni_URLClassLoader.h
new file mode 100644
index 00000000..4cf2c0ec
--- /dev/null
+++ b/src/java/nxt_jni_URLClassLoader.h
@@ -0,0 +1,27 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_JAVA_URLCLASSLOADER_H_INCLUDED_
+#define _NXT_JAVA_URLCLASSLOADER_H_INCLUDED_
+
+
+#include <jni.h>
+
+
+int nxt_java_initURLClassLoader(JNIEnv *env);
+
+jobject nxt_java_newURLClassLoader(JNIEnv *env, int url_count, char **urls);
+
+jobject nxt_java_newURLClassLoader_parent(JNIEnv *env, int url_count,
+ char **urls, jobject parent);
+
+jobjectArray nxt_java_newURLs(JNIEnv *env, int url_count, char **urls);
+
+jclass nxt_java_loadClass(JNIEnv *env, jobject cl, const char *name);
+
+void nxt_java_addURL(JNIEnv *env, jobject cl, const char *url);
+
+#endif /* _NXT_JAVA_URLCLASSLOADER_H_INCLUDED_ */
+
diff --git a/src/nodejs/unit-http/unit.h b/src/nodejs/unit-http/unit.h
index 8baeb967..db85e85c 100644
--- a/src/nodejs/unit-http/unit.h
+++ b/src/nodejs/unit-http/unit.h
@@ -15,7 +15,7 @@ extern "C" {
#include "version.h"
#include <nxt_unit.h>
-#if NXT_UNIT_VERNUM != NXT_NODE_VERNUM
+#if NXT_VERNUM != NXT_NODE_VERNUM
#error "libunit version mismatch."
#endif
diff --git a/src/nxt_application.c b/src/nxt_application.c
index acdebe04..a2827b75 100644
--- a/src/nxt_application.c
+++ b/src/nxt_application.c
@@ -331,6 +331,17 @@ nxt_app_start(nxt_task_t *task, void *data)
nxt_app = nxt_app_module_load(task, lang->file);
}
+ if (nxt_app->pre_init != NULL) {
+ ret = nxt_app->pre_init(task, data);
+
+ if (nxt_slow_path(ret != NXT_OK)) {
+ nxt_debug(task, "application pre_init failed");
+
+ } else {
+ nxt_debug(task, "application pre_init done");
+ }
+ }
+
if (app_conf->working_directory != NULL
&& app_conf->working_directory[0] != 0)
{
@@ -521,6 +532,9 @@ nxt_app_parse_type(u_char *p, size_t length)
} else if (nxt_str_eq(&str, "ruby", 4)) {
return NXT_APP_RUBY;
+
+ } else if (nxt_str_eq(&str, "java", 4)) {
+ return NXT_APP_JAVA;
}
return NXT_APP_UNKNOWN;
diff --git a/src/nxt_application.h b/src/nxt_application.h
index 10f5a922..781f05e0 100644
--- a/src/nxt_application.h
+++ b/src/nxt_application.h
@@ -20,6 +20,7 @@ typedef enum {
NXT_APP_PHP,
NXT_APP_PERL,
NXT_APP_RUBY,
+ NXT_APP_JAVA,
NXT_APP_UNKNOWN,
} nxt_app_type_t;
@@ -70,6 +71,14 @@ typedef struct {
} nxt_ruby_app_conf_t;
+typedef struct {
+ nxt_conf_value_t *classpath;
+ char *webapp;
+ nxt_conf_value_t *options;
+ char *unit_jars;
+} nxt_java_app_conf_t;
+
+
struct nxt_common_app_conf_s {
nxt_str_t name;
nxt_str_t type;
@@ -85,6 +94,7 @@ struct nxt_common_app_conf_s {
nxt_php_app_conf_t php;
nxt_perl_app_conf_t perl;
nxt_ruby_app_conf_t ruby;
+ nxt_java_app_conf_t java;
} u;
};
@@ -95,13 +105,13 @@ typedef struct {
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;
- nxt_str_t host;
off_t parsed_content_length;
nxt_bool_t done;
@@ -152,6 +162,8 @@ struct nxt_app_module_s {
nxt_str_t type;
const char *version;
+ nxt_int_t (*pre_init)(nxt_task_t *task,
+ nxt_common_app_conf_t *conf);
nxt_int_t (*init)(nxt_task_t *task,
nxt_common_app_conf_t *conf);
};
diff --git a/src/nxt_conf.c b/src/nxt_conf.c
index 2255e12f..4c6d8839 100644
--- a/src/nxt_conf.c
+++ b/src/nxt_conf.c
@@ -361,6 +361,13 @@ nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp,
nxt_uint_t
+nxt_conf_array_elements_count(nxt_conf_value_t *value)
+{
+ return value->u.array->count;
+}
+
+
+nxt_uint_t
nxt_conf_type(nxt_conf_value_t *value)
{
switch (value->type) {
@@ -713,6 +720,22 @@ nxt_conf_get_array_element(nxt_conf_value_t *value, uint32_t index)
}
+void
+nxt_conf_array_qsort(nxt_conf_value_t *value,
+ int (*compare)(const void *, const void *))
+{
+ nxt_conf_array_t *array;
+
+ if (value->type != NXT_CONF_VALUE_ARRAY) {
+ return;
+ }
+
+ array = value->u.array;
+
+ nxt_qsort(array->elements, array->count, sizeof(nxt_conf_value_t), compare);
+}
+
+
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)
diff --git a/src/nxt_conf.h b/src/nxt_conf.h
index 48521917..20ff3b1e 100644
--- a/src/nxt_conf.h
+++ b/src/nxt_conf.h
@@ -125,6 +125,9 @@ void nxt_conf_set_element(nxt_conf_value_t *array, nxt_uint_t index,
nxt_conf_value_t *value);
nxt_int_t nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp,
nxt_uint_t index, nxt_str_t *value);
+NXT_EXPORT nxt_uint_t nxt_conf_array_elements_count(nxt_conf_value_t *value);
+void nxt_conf_array_qsort(nxt_conf_value_t *value,
+ int (*compare)(const void *, const void *));
#endif /* _NXT_CONF_INCLUDED_ */
diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c
index b1e30955..5653b9eb 100644
--- a/src/nxt_conf_validation.c
+++ b/src/nxt_conf_validation.c
@@ -54,6 +54,18 @@ static nxt_int_t nxt_conf_vldt_listener(nxt_conf_validation_t *vldt,
static nxt_int_t nxt_conf_vldt_certificate(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
#endif
+static nxt_int_t nxt_conf_vldt_pass(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value, void *data);
+static nxt_int_t nxt_conf_vldt_routes(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value, void *data);
+static nxt_int_t nxt_conf_vldt_routes_member(nxt_conf_validation_t *vldt,
+ nxt_str_t *name, nxt_conf_value_t *value);
+static nxt_int_t nxt_conf_vldt_route(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value);
+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_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,
@@ -76,6 +88,10 @@ static nxt_int_t nxt_conf_vldt_argument(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value);
static nxt_int_t nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt,
nxt_str_t *name, nxt_conf_value_t *value);
+static nxt_int_t nxt_conf_vldt_java_classpath(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value);
+static nxt_int_t nxt_conf_vldt_java_option(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value);
static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = {
@@ -129,6 +145,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_root_members[] = {
&nxt_conf_vldt_object_iterator,
(void *) &nxt_conf_vldt_listener },
+ { nxt_string("routes"),
+ NXT_CONF_VLDT_ARRAY | NXT_CONF_VLDT_OBJECT,
+ &nxt_conf_vldt_routes,
+ NULL },
+
{ nxt_string("applications"),
NXT_CONF_VLDT_OBJECT,
&nxt_conf_vldt_object_iterator,
@@ -158,6 +179,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[] = {
static nxt_conf_vldt_object_t nxt_conf_vldt_listener_members[] = {
+ { nxt_string("pass"),
+ NXT_CONF_VLDT_STRING,
+ &nxt_conf_vldt_pass,
+ NULL },
+
{ nxt_string("application"),
NXT_CONF_VLDT_STRING,
&nxt_conf_vldt_app_name,
@@ -176,6 +202,51 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_listener_members[] = {
};
+static nxt_conf_vldt_object_t nxt_conf_vldt_match_members[] = {
+ { nxt_string("method"),
+ NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY,
+ &nxt_conf_vldt_match_patterns,
+ NULL },
+
+ { nxt_string("host"),
+ NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY,
+ &nxt_conf_vldt_match_patterns,
+ NULL },
+
+ { nxt_string("uri"),
+ NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY,
+ &nxt_conf_vldt_match_patterns,
+ NULL },
+
+ NXT_CONF_VLDT_END
+};
+
+
+static nxt_conf_vldt_object_t nxt_conf_vldt_action_members[] = {
+ { nxt_string("pass"),
+ NXT_CONF_VLDT_STRING,
+ &nxt_conf_vldt_pass,
+ NULL },
+
+ NXT_CONF_VLDT_END
+};
+
+
+static nxt_conf_vldt_object_t nxt_conf_vldt_route_members[] = {
+ { nxt_string("match"),
+ NXT_CONF_VLDT_OBJECT,
+ &nxt_conf_vldt_object,
+ (void *) &nxt_conf_vldt_match_members },
+
+ { nxt_string("action"),
+ NXT_CONF_VLDT_OBJECT,
+ &nxt_conf_vldt_object,
+ (void *) &nxt_conf_vldt_action_members },
+
+ NXT_CONF_VLDT_END
+};
+
+
static nxt_conf_vldt_object_t nxt_conf_vldt_app_limits_members[] = {
{ nxt_string("timeout"),
NXT_CONF_VLDT_INTEGER,
@@ -356,6 +427,31 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_ruby_members[] = {
};
+static nxt_conf_vldt_object_t nxt_conf_vldt_java_members[] = {
+ { nxt_string("classpath"),
+ NXT_CONF_VLDT_ARRAY,
+ &nxt_conf_vldt_array_iterator,
+ (void *) &nxt_conf_vldt_java_classpath},
+
+ { nxt_string("webapp"),
+ NXT_CONF_VLDT_STRING,
+ NULL,
+ NULL },
+
+ { nxt_string("options"),
+ NXT_CONF_VLDT_ARRAY,
+ &nxt_conf_vldt_array_iterator,
+ (void *) &nxt_conf_vldt_java_option},
+
+ { nxt_string("unit_jars"),
+ NXT_CONF_VLDT_STRING,
+ NULL,
+ NULL },
+
+ NXT_CONF_VLDT_NEXT(&nxt_conf_vldt_common_members)
+};
+
+
nxt_int_t
nxt_conf_validate(nxt_conf_validation_t *vldt)
{
@@ -495,6 +591,187 @@ nxt_conf_vldt_listener(nxt_conf_validation_t *vldt, nxt_str_t *name,
}
+static nxt_int_t
+nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
+ void *data)
+{
+ u_char *p;
+ nxt_str_t pass, first, second;
+
+ nxt_conf_get_string(value, &pass);
+
+ p = nxt_memchr(pass.start, '/', pass.length);
+
+ if (p != NULL) {
+ first.length = p - pass.start;
+ first.start = pass.start;
+
+ if (pass.length - first.length == 1) {
+ goto error;
+ }
+
+ second.length = pass.length - first.length - 1;
+ second.start = p + 1;
+
+ } else {
+ first = pass;
+ second.length = 0;
+ }
+
+ if (nxt_str_eq(&first, "applications", 12)) {
+
+ if (second.length == 0) {
+ goto error;
+ }
+
+ value = nxt_conf_get_object_member(vldt->conf, &first, NULL);
+
+ if (nxt_slow_path(value == NULL)) {
+ goto error;
+ }
+
+ value = nxt_conf_get_object_member(value, &second, NULL);
+
+ if (nxt_slow_path(value == NULL)) {
+ goto error;
+ }
+
+ return NXT_OK;
+ }
+
+ if (nxt_str_eq(&first, "routes", 6)) {
+ value = nxt_conf_get_object_member(vldt->conf, &first, NULL);
+
+ if (nxt_slow_path(value == NULL)) {
+ goto error;
+ }
+
+ if (second.length == 0) {
+ if (nxt_conf_type(value) != NXT_CONF_ARRAY) {
+ goto error;
+ }
+
+ return NXT_OK;
+ }
+
+ if (nxt_conf_type(value) != NXT_CONF_OBJECT) {
+ goto error;
+ }
+
+ value = nxt_conf_get_object_member(value, &second, NULL);
+
+ if (nxt_slow_path(value == NULL)) {
+ goto error;
+ }
+
+ return NXT_OK;
+ }
+
+error:
+
+ return nxt_conf_vldt_error(vldt, "Request \"pass\" points to invalid "
+ "location \"%V\".", &pass);
+}
+
+
+static nxt_int_t
+nxt_conf_vldt_routes(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_route);
+ }
+
+ /* NXT_CONF_OBJECT */
+
+ return nxt_conf_vldt_object_iterator(vldt, value,
+ &nxt_conf_vldt_routes_member);
+}
+
+
+static nxt_int_t
+nxt_conf_vldt_routes_member(nxt_conf_validation_t *vldt, nxt_str_t *name,
+ nxt_conf_value_t *value)
+{
+ nxt_int_t ret;
+
+ ret = nxt_conf_vldt_type(vldt, name, value, NXT_CONF_VLDT_ARRAY);
+
+ if (ret != NXT_OK) {
+ return ret;
+ }
+
+ return nxt_conf_vldt_array_iterator(vldt, value, &nxt_conf_vldt_route);
+}
+
+
+static nxt_int_t
+nxt_conf_vldt_route(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 \"routes\" array must contain "
+ "only object values.");
+ }
+
+ return nxt_conf_vldt_object(vldt, value, nxt_conf_vldt_route_members);
+}
+
+
+static nxt_int_t
+nxt_conf_vldt_match_patterns(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_pattern);
+ }
+
+ /* NXT_CONF_STRING */
+
+ return nxt_conf_vldt_match_pattern(vldt, value);
+}
+
+
+static nxt_int_t
+nxt_conf_vldt_match_pattern(nxt_conf_validation_t *vldt,
+ nxt_conf_value_t *value)
+{
+ u_char ch;
+ nxt_str_t pattern;
+ nxt_uint_t i, first, last;
+
+ if (nxt_conf_type(value) != NXT_CONF_STRING) {
+ return nxt_conf_vldt_error(vldt,
+ "The \"match\" patterns must be strings.");
+ }
+
+ nxt_conf_get_string(value, &pattern);
+
+ if (pattern.length == 0) {
+ return NXT_OK;
+ }
+
+ first = (pattern.start[0] == '!');
+ last = pattern.length - 1;
+
+ 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.");
+ }
+ }
+
+ return NXT_OK;
+}
+
+
#if (NXT_TLS)
static nxt_int_t
@@ -570,6 +847,7 @@ nxt_conf_vldt_app(nxt_conf_validation_t *vldt, nxt_str_t *name,
nxt_conf_vldt_php_members,
nxt_conf_vldt_perl_members,
nxt_conf_vldt_ruby_members,
+ nxt_conf_vldt_java_members,
};
ret = nxt_conf_vldt_type(vldt, name, value, NXT_CONF_VLDT_OBJECT);
@@ -968,3 +1246,44 @@ nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name,
return NXT_OK;
}
+
+
+static nxt_int_t
+nxt_conf_vldt_java_classpath(nxt_conf_validation_t *vldt, nxt_conf_value_t *value)
+{
+ nxt_str_t str;
+
+ if (nxt_conf_type(value) != NXT_CONF_STRING) {
+ return nxt_conf_vldt_error(vldt, "The \"classpath\" array "
+ "must contain only string values.");
+ }
+
+ nxt_conf_get_string(value, &str);
+
+ if (nxt_memchr(str.start, '\0', str.length) != NULL) {
+ return nxt_conf_vldt_error(vldt, "The \"classpath\" array must not "
+ "contain strings with null character.");
+ }
+
+ return NXT_OK;
+}
+
+static nxt_int_t
+nxt_conf_vldt_java_option(nxt_conf_validation_t *vldt, nxt_conf_value_t *value)
+{
+ nxt_str_t str;
+
+ if (nxt_conf_type(value) != NXT_CONF_STRING) {
+ return nxt_conf_vldt_error(vldt, "The \"options\" array "
+ "must contain only string values.");
+ }
+
+ nxt_conf_get_string(value, &str);
+
+ if (nxt_memchr(str.start, '\0', str.length) != NULL) {
+ return nxt_conf_vldt_error(vldt, "The \"options\" array must not "
+ "contain strings with null character.");
+ }
+
+ return NXT_OK;
+}
diff --git a/src/nxt_conn.h b/src/nxt_conn.h
index d8b48694..7284808b 100644
--- a/src/nxt_conn.h
+++ b/src/nxt_conn.h
@@ -157,7 +157,8 @@ struct nxt_conn_s {
nxt_sockaddr_t *local;
const char *action;
- uint8_t blocked; /* 1 bit */
+ uint8_t block_read; /* 1 bit */
+ uint8_t block_write; /* 1 bit */
uint8_t delayed; /* 1 bit */
#define NXT_CONN_SENDFILE_OFF 0
diff --git a/src/nxt_conn_read.c b/src/nxt_conn_read.c
index 8228326b..83969b31 100644
--- a/src/nxt_conn_read.c
+++ b/src/nxt_conn_read.c
@@ -45,10 +45,11 @@ nxt_conn_io_read(nxt_task_t *task, void *obj, void *data)
c = obj;
- nxt_debug(task, "conn read fd:%d rdy:%d cl:%d",
- c->socket.fd, c->socket.read_ready, c->socket.closed);
+ nxt_debug(task, "conn read fd:%d rdy:%d cl:%d er:%d bl:%d",
+ c->socket.fd, c->socket.read_ready, c->socket.closed,
+ c->socket.error, c->block_read);
- if (c->socket.error != 0) {
+ if (c->socket.error != 0 || c->block_read) {
return;
}
diff --git a/src/nxt_conn_write.c b/src/nxt_conn_write.c
index 80d6f5cf..298d8f75 100644
--- a/src/nxt_conn_write.c
+++ b/src/nxt_conn_write.c
@@ -22,9 +22,10 @@ nxt_conn_io_write(nxt_task_t *task, void *obj, void *data)
c = obj;
- nxt_debug(task, "conn write fd:%d", c->socket.fd);
+ nxt_debug(task, "conn write fd:%d er:%d bl:%d",
+ c->socket.fd, c->socket.error, c->block_write);
- if (c->socket.error != 0) {
+ if (c->socket.error != 0 || c->block_write) {
goto error;
}
diff --git a/src/nxt_event_conn_job_sendfile.c b/src/nxt_event_conn_job_sendfile.c
index 2ca6e421..0f6f9353 100644
--- a/src/nxt_event_conn_job_sendfile.c
+++ b/src/nxt_event_conn_job_sendfile.c
@@ -80,7 +80,8 @@ nxt_event_conn_job_sendfile_start(nxt_task_t *task, void *obj, void *data)
c->write = NULL;
jbs->ready_handler = nxt_event_conn_job_sendfile_return;
- c->blocked = 1;
+ c->block_read = 1;
+ c->block_write = 1;
nxt_job_start(task, &jbs->job, nxt_event_conn_job_sendfile_handler);
return;
@@ -170,7 +171,8 @@ nxt_event_conn_job_sendfile_return(nxt_task_t *task, void *obj, void *data)
jbs = obj;
c = data;
- c->blocked = 0;
+ c->block_read = 0;
+ c->block_write = 0;
sent = jbs->sent;
c->sent += sent;
diff --git a/src/nxt_external.c b/src/nxt_external.c
index c7aacffc..89fe08c8 100644
--- a/src/nxt_external.c
+++ b/src/nxt_external.c
@@ -18,6 +18,7 @@ nxt_app_module_t nxt_external_module = {
NULL,
nxt_string("external"),
"*",
+ NULL,
nxt_external_init,
};
diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c
index 2194e56f..07e3c7bc 100644
--- a/src/nxt_h1proto.c
+++ b/src/nxt_h1proto.c
@@ -526,9 +526,7 @@ nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj, void *data)
return;
}
- /* ret == NXT_ERROR */
- status = NXT_HTTP_BAD_REQUEST;
-
+ status = ret;
goto error;
case NXT_AGAIN:
@@ -565,6 +563,8 @@ nxt_h1p_conn_request_header_parse(nxt_task_t *task, void *obj, void *data)
error:
+ h1p->keepalive = 0;
+
nxt_http_request_error(task, r, status);
}
@@ -1220,6 +1220,7 @@ nxt_h1p_conn_request_timeout(nxt_task_t *task, void *obj, void *data)
nxt_debug(task, "h1p conn request timeout");
c = nxt_read_timer_conn(timer);
+ c->block_read = 1;
/*
* Disable SO_LINGER off during socket closing
* to send "408 Request Timeout" error response.
@@ -1250,6 +1251,7 @@ nxt_h1p_conn_request_send_timeout(nxt_task_t *task, void *obj, void *data)
nxt_debug(task, "h1p conn request send timeout");
c = nxt_write_timer_conn(timer);
+ c->block_write = 1;
h1p = c->socket.data;
nxt_h1p_request_error(task, h1p, h1p->request);
@@ -1464,6 +1466,7 @@ nxt_h1p_idle_timeout(nxt_task_t *task, void *obj, void *data)
nxt_debug(task, "h1p idle timeout");
c = nxt_read_timer_conn(timer);
+ c->block_read = 1;
nxt_h1p_idle_response(task, c);
}
@@ -1559,6 +1562,7 @@ nxt_h1p_idle_response_timeout(nxt_task_t *task, void *obj, void *data)
nxt_debug(task, "h1p idle timeout response timeout");
c = nxt_read_timer_conn(timer);
+ c->block_write = 1;
nxt_h1p_shutdown(task, c);
}
diff --git a/src/nxt_http.h b/src/nxt_http.h
index b2111f90..23c406d3 100644
--- a/src/nxt_http.h
+++ b/src/nxt_http.h
@@ -21,6 +21,7 @@ typedef enum {
NXT_HTTP_NOT_MODIFIED = 304,
NXT_HTTP_BAD_REQUEST = 400,
+ NXT_HTTP_NOT_FOUND = 404,
NXT_HTTP_REQUEST_TIMEOUT = 408,
NXT_HTTP_LENGTH_REQUIRED = 411,
NXT_HTTP_PAYLOAD_TOO_LARGE = 413,
@@ -112,6 +113,7 @@ struct nxt_http_request_s {
nxt_buf_t *out;
const nxt_http_request_state_t *state;
+ nxt_str_t host;
nxt_str_t target;
nxt_str_t version;
nxt_str_t *method;
@@ -119,7 +121,6 @@ struct nxt_http_request_s {
nxt_str_t *args;
nxt_list_t *fields;
- nxt_http_field_t *host;
nxt_http_field_t *content_type;
nxt_http_field_t *content_length;
nxt_http_field_t *cookie;
@@ -136,6 +137,7 @@ struct nxt_http_request_s {
nxt_http_status_t status:16;
+ uint8_t pass_count; /* 8 bits */
uint8_t protocol; /* 2 bits */
uint8_t logged; /* 1 bit */
uint8_t header_sent; /* 1 bit */
@@ -143,6 +145,22 @@ struct nxt_http_request_s {
};
+typedef struct nxt_http_route_s nxt_http_route_t;
+
+
+struct nxt_http_pass_s {
+ nxt_http_pass_t *(*handler)(nxt_task_t *task,
+ nxt_http_request_t *r,
+ nxt_http_pass_t *pass);
+ union {
+ nxt_http_route_t *route;
+ nxt_app_t *application;
+ } u;
+
+ nxt_str_t name;
+};
+
+
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,
@@ -176,7 +194,6 @@ nxt_buf_t *nxt_http_buf_mem(nxt_task_t *task, nxt_http_request_t *r,
size_t size);
nxt_buf_t *nxt_http_buf_last(nxt_http_request_t *r);
void nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data);
-void nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data);
nxt_int_t nxt_http_request_host(void *ctx, nxt_http_field_t *field,
uintptr_t data);
@@ -185,6 +202,18 @@ nxt_int_t nxt_http_request_field(void *ctx, nxt_http_field_t *field,
nxt_int_t nxt_http_request_content_length(void *ctx, nxt_http_field_t *field,
uintptr_t data);
+nxt_http_routes_t *nxt_http_routes_create(nxt_task_t *task,
+ nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *routes_conf);
+nxt_http_pass_t *nxt_http_pass_create(nxt_task_t *task,
+ nxt_router_temp_conf_t *tmcf, nxt_str_t *name);
+void nxt_http_routes_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf);
+nxt_http_pass_t *nxt_http_pass_application(nxt_task_t *task,
+ nxt_router_temp_conf_t *tmcf, nxt_str_t *name);
+void nxt_http_routes_cleanup(nxt_task_t *task, nxt_http_routes_t *routes);
+void nxt_http_pass_cleanup(nxt_task_t *task, nxt_http_pass_t *pass);
+
+nxt_http_pass_t *nxt_http_request_application(nxt_task_t *task,
+ nxt_http_request_t *r, nxt_http_pass_t *pass);
extern nxt_time_string_t nxt_http_date_cache;
diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c
index c8adb499..724b0808 100644
--- a/src/nxt_http_request.c
+++ b/src/nxt_http_request.c
@@ -8,11 +8,14 @@
#include <nxt_http.h>
+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_app_request(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_mem_buf_completion(nxt_task_t *task, void *obj,
void *data);
static void nxt_http_request_done(nxt_task_t *task, void *obj, void *data);
+static void nxt_http_request_close_handler(nxt_task_t *task, void *obj,
+ void *data);
static u_char *nxt_http_date(u_char *buf, nxt_realtime_t *now, struct tm *tm,
size_t size, const char *format);
@@ -50,13 +53,117 @@ nxt_http_init(nxt_task_t *task, nxt_runtime_t *rt)
nxt_int_t
nxt_http_request_host(void *ctx, nxt_http_field_t *field, uintptr_t data)
{
+ nxt_int_t ret;
+ nxt_str_t host;
nxt_http_request_t *r;
r = ctx;
- /* TODO: validate host. */
+ if (nxt_slow_path(r->host.start != NULL)) {
+ return NXT_HTTP_BAD_REQUEST;
+ }
+
+ host.length = field->value_length;
+ host.start = field->value;
+
+ ret = nxt_http_validate_host(&host, r->mem_pool);
+
+ if (nxt_fast_path(ret == NXT_OK)) {
+ r->host = host;
+ }
+
+ return ret;
+}
+
+
+static nxt_int_t
+nxt_http_validate_host(nxt_str_t *host, nxt_mp_t *mp)
+{
+ u_char *h, ch;
+ size_t i, dot_pos, host_length;
+ nxt_bool_t lowcase;
+
+ enum {
+ sw_usual,
+ sw_literal,
+ sw_rest
+ } state;
+
+ dot_pos = host->length;
+ host_length = host->length;
+
+ h = host->start;
+
+ lowcase = 0;
+ state = sw_usual;
+
+ for (i = 0; i < host->length; i++) {
+ ch = h[i];
+
+ if (ch > ']') {
+ /* Short path. */
+ continue;
+ }
+
+ switch (ch) {
+
+ case '.':
+ if (dot_pos == i - 1) {
+ return NXT_HTTP_BAD_REQUEST;
+ }
- r->host = field;
+ dot_pos = i;
+ break;
+
+ case ':':
+ if (state == sw_usual) {
+ host_length = i;
+ state = sw_rest;
+ }
+
+ break;
+
+ case '[':
+ if (i == 0) {
+ state = sw_literal;
+ }
+
+ break;
+
+ case ']':
+ if (state == sw_literal) {
+ host_length = i + 1;
+ state = sw_rest;
+ }
+
+ break;
+
+ case '/':
+ return NXT_HTTP_BAD_REQUEST;
+
+ default:
+ if (ch >= 'A' && ch <= 'Z') {
+ lowcase = 1;
+ }
+
+ break;
+ }
+ }
+
+ if (dot_pos == host_length - 1) {
+ host_length--;
+ }
+
+ host->length = host_length;
+
+ if (lowcase) {
+ host->start = nxt_mp_nget(mp, host_length);
+ if (nxt_slow_path(host->start == NULL)) {
+ return NXT_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ nxt_memcpy_lowcase(host->start, h, host_length);
+ }
return NXT_OK;
}
@@ -83,16 +190,19 @@ nxt_http_request_content_length(void *ctx, nxt_http_field_t *field,
nxt_http_request_t *r;
r = ctx;
- r->content_length = field;
- n = nxt_off_t_parse(field->value, field->value_length);
+ if (nxt_fast_path(r->content_length == NULL)) {
+ r->content_length = field;
+
+ n = nxt_off_t_parse(field->value, field->value_length);
- if (nxt_fast_path(n >= 0)) {
- r->content_length_n = n;
- return NXT_OK;
+ if (nxt_fast_path(n >= 0)) {
+ r->content_length_n = n;
+ return NXT_OK;
+ }
}
- return NXT_ERROR;
+ return NXT_HTTP_BAD_REQUEST;
}
@@ -168,25 +278,60 @@ nxt_http_request_start(nxt_task_t *task, void *obj, void *data)
static const nxt_http_request_state_t nxt_http_request_body_state
nxt_aligned(64) =
{
- .ready_handler = nxt_http_app_request,
+ .ready_handler = nxt_http_request_pass,
.error_handler = nxt_http_request_close_handler,
};
static void
-nxt_http_app_request(nxt_task_t *task, void *obj, void *data)
+nxt_http_request_pass(nxt_task_t *task, void *obj, void *data)
+{
+ nxt_http_pass_t *pass;
+ nxt_http_request_t *r;
+
+ r = obj;
+
+ pass = r->conf->socket_conf->pass;
+
+ if (nxt_slow_path(pass == NULL)) {
+ goto fail;
+ }
+
+ for ( ;; ) {
+ nxt_debug(task, "http request route: %V", &pass->name);
+
+ pass = pass->handler(task, r, pass);
+ if (pass == NULL) {
+ break;
+ }
+
+ if (nxt_slow_path(r->pass_count++ == 255)) {
+ goto fail;
+ }
+ }
+
+ return;
+
+fail:
+
+ nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
+}
+
+
+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_http_request_t *r;
nxt_app_parse_ctx_t *ar;
- r = obj;
+ 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;
+ return NULL;
}
ar->request = r;
@@ -222,6 +367,13 @@ nxt_http_app_request(nxt_task_t *task, void *obj, void *data)
ar->r.header.method = *r->method;
}
+ if (r->host.length != 0) {
+ ar->r.header.server_name = r->host;
+
+ } else {
+ nxt_str_set(&ar->r.header.server_name, "localhost");
+ }
+
ar->r.header.target = r->target;
if (r->path != NULL) {
@@ -232,11 +384,6 @@ nxt_http_app_request(nxt_task_t *task, void *obj, void *data)
ar->r.header.query = *r->args;
}
- if (r->host != NULL) {
- ar->r.header.host.length = r->host->value_length;
- ar->r.header.host.start = r->host->value;
- }
-
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;
@@ -263,10 +410,12 @@ nxt_http_app_request(nxt_task_t *task, void *obj, void *data)
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;
+ return NULL;
}
- nxt_router_process_http_request(task, ar);
+ nxt_router_process_http_request(task, ar, pass->u.application);
+
+ return NULL;
}
@@ -446,7 +595,7 @@ nxt_http_request_error_handler(nxt_task_t *task, void *obj, void *data)
}
-void
+static void
nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data)
{
nxt_http_proto_t proto;
diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c
new file mode 100644
index 00000000..133c39ab
--- /dev/null
+++ b/src/nxt_http_route.c
@@ -0,0 +1,849 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_router.h>
+#include <nxt_http.h>
+
+
+typedef enum {
+ NXT_HTTP_ROUTE_STRING = 0,
+ NXT_HTTP_ROUTE_STRING_PTR,
+ NXT_HTTP_ROUTE_FIELD,
+ NXT_HTTP_ROUTE_HEADER,
+ NXT_HTTP_ROUTE_ARGUMENT,
+ NXT_HTTP_ROUTE_COOKIE,
+} nxt_http_route_object_t;
+
+
+typedef enum {
+ NXT_HTTP_ROUTE_PATTERN_EXACT = 0,
+ NXT_HTTP_ROUTE_PATTERN_BEGIN,
+ NXT_HTTP_ROUTE_PATTERN_END,
+ NXT_HTTP_ROUTE_PATTERN_SUBSTRING,
+} nxt_http_route_pattern_type_t;
+
+
+typedef enum {
+ NXT_HTTP_ROUTE_PATTERN_NOCASE = 0,
+ NXT_HTTP_ROUTE_PATTERN_LOWCASE,
+ NXT_HTTP_ROUTE_PATTERN_UPCASE,
+} nxt_http_route_pattern_case_t;
+
+
+typedef struct {
+ nxt_conf_value_t *host;
+ nxt_conf_value_t *uri;
+ nxt_conf_value_t *method;
+} nxt_http_route_match_conf_t;
+
+
+typedef struct {
+ nxt_str_t test;
+ uint32_t min_length;
+
+ nxt_http_route_pattern_type_t type:8;
+ uint8_t case_sensitive; /* 1 bit */
+ uint8_t negative; /* 1 bit */
+ uint8_t any; /* 1 bit */
+} nxt_http_route_pattern_t;
+
+
+typedef struct {
+ uintptr_t offset;
+ uint32_t items;
+ nxt_http_route_object_t object:8;
+ 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_match_t;
+
+
+struct nxt_http_route_s {
+ nxt_str_t name;
+ uint32_t items;
+ nxt_http_route_match_t *match[0];
+};
+
+
+struct nxt_http_routes_s {
+ uint32_t items;
+ nxt_http_route_t *route[0];
+};
+
+
+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_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);
+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 void nxt_http_route_resolve(nxt_task_t *task,
+ nxt_router_temp_conf_t *tmcf, nxt_http_route_t *route);
+static void nxt_http_pass_resolve(nxt_task_t *task,
+ nxt_router_temp_conf_t *tmcf, nxt_http_pass_t *pass);
+static nxt_http_route_t *nxt_http_route_find(nxt_http_routes_t *routes,
+ nxt_str_t *name);
+static void nxt_http_route_cleanup(nxt_task_t *task, nxt_http_route_t *routes);
+
+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,
+ nxt_http_route_rule_t *rule);
+static nxt_bool_t nxt_http_route_pattern(nxt_http_request_t *r,
+ nxt_http_route_pattern_t *pattern, u_char *start, size_t length);
+
+
+nxt_http_routes_t *
+nxt_http_routes_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
+ nxt_conf_value_t *routes_conf)
+{
+ size_t size;
+ uint32_t i, n, next;
+ nxt_mp_t *mp;
+ nxt_str_t name, *string;
+ nxt_bool_t object;
+ nxt_conf_value_t *route_conf;
+ nxt_http_route_t *route;
+ nxt_http_routes_t *routes;
+
+ object = (nxt_conf_type(routes_conf) == NXT_CONF_OBJECT);
+ n = object ? nxt_conf_object_members_count(routes_conf) : 1;
+ size = sizeof(nxt_http_routes_t) + n * sizeof(nxt_http_route_t *);
+
+ mp = tmcf->router_conf->mem_pool;
+
+ routes = nxt_mp_alloc(mp, size);
+ if (nxt_slow_path(routes == NULL)) {
+ return NULL;
+ }
+
+ routes->items = n;
+
+ if (object) {
+ next = 0;
+
+ for (i = 0; i < n; i++) {
+ route_conf = nxt_conf_next_object_member(routes_conf, &name, &next);
+
+ route = nxt_http_route_create(task, tmcf, route_conf);
+ if (nxt_slow_path(route == NULL)) {
+ return NULL;
+ }
+
+ routes->route[i] = route;
+
+ string = nxt_str_dup(mp, &route->name, &name);
+ if (nxt_slow_path(string == NULL)) {
+ return NULL;
+ }
+ }
+
+ } else {
+ route = nxt_http_route_create(task, tmcf, routes_conf);
+ if (nxt_slow_path(route == NULL)) {
+ return NULL;
+ }
+
+ routes->route[0] = route;
+
+ route->name.length = 0;
+ route->name.start = NULL;
+ }
+
+ return routes;
+}
+
+
+static nxt_conf_map_t nxt_http_route_match_conf[] = {
+ {
+ nxt_string("host"),
+ NXT_CONF_MAP_PTR,
+ offsetof(nxt_http_route_match_conf_t, host),
+ },
+
+ {
+ nxt_string("uri"),
+ NXT_CONF_MAP_PTR,
+ offsetof(nxt_http_route_match_conf_t, uri),
+ },
+
+ {
+ nxt_string("method"),
+ NXT_CONF_MAP_PTR,
+ offsetof(nxt_http_route_match_conf_t, method),
+ },
+};
+
+
+static nxt_http_route_t *
+nxt_http_route_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
+ nxt_conf_value_t *cv)
+{
+ size_t size;
+ uint32_t i, n;
+ nxt_conf_value_t *value;
+ nxt_http_route_t *route;
+ nxt_http_route_match_t *match, **m;
+
+ n = nxt_conf_array_elements_count(cv);
+ size = sizeof(nxt_http_route_t) + n * sizeof(nxt_http_route_match_t *);
+
+ route = nxt_mp_alloc(tmcf->router_conf->mem_pool, size);
+ if (nxt_slow_path(route == NULL)) {
+ return NULL;
+ }
+
+ route->items = n;
+ m = &route->match[0];
+
+ for (i = 0; i < n; i++) {
+ value = nxt_conf_get_array_element(cv, i);
+
+ match = nxt_http_route_match_create(task, tmcf, value);
+ if (match == NULL) {
+ return NULL;
+ }
+
+ *m++ = match;
+ }
+
+ return route;
+}
+
+
+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)
+{
+ size_t size;
+ uint32_t n;
+ 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_match_t *match;
+ nxt_http_route_match_conf_t mtcf;
+
+ static nxt_str_t pass_path = nxt_string("/action/pass");
+ static nxt_str_t match_path = nxt_string("/match");
+
+ pass_conf = nxt_conf_get_path(cv, &pass_path);
+ if (nxt_slow_path(pass_conf == NULL)) {
+ return NULL;
+ }
+
+ nxt_conf_get_string(pass_conf, &pass);
+
+ match_conf = nxt_conf_get_path(cv, &match_path);
+
+ n = (match_conf != NULL) ? nxt_conf_object_members_count(match_conf) : 0;
+ size = sizeof(nxt_http_route_match_t) + n * sizeof(nxt_http_route_rule_t *);
+
+ match = nxt_mp_alloc(tmcf->router_conf->mem_pool, size);
+ if (nxt_slow_path(match == NULL)) {
+ return NULL;
+ }
+
+ match->pass.u.route = NULL;
+ match->pass.handler = NULL;
+ match->items = n;
+
+ string = nxt_str_dup(tmcf->router_conf->mem_pool, &match->pass.name, &pass);
+ if (nxt_slow_path(string == NULL)) {
+ return NULL;
+ }
+
+ if (n == 0) {
+ return match;
+ }
+
+ nxt_memzero(&mtcf, sizeof(mtcf));
+
+ ret = nxt_conf_map_object(tmcf->mem_pool,
+ match_conf, nxt_http_route_match_conf,
+ nxt_nitems(nxt_http_route_match_conf), &mtcf);
+ if (ret != NXT_OK) {
+ return NULL;
+ }
+
+ p = &match->rule[0];
+
+ if (mtcf.host != NULL) {
+ rule = nxt_http_route_rule_create(task, tmcf, mtcf.host, 1,
+ NXT_HTTP_ROUTE_PATTERN_LOWCASE);
+ if (rule == NULL) {
+ return NULL;
+ }
+
+ rule->offset = offsetof(nxt_http_request_t, host);
+ rule->object = NXT_HTTP_ROUTE_STRING;
+ *p++ = rule;
+ }
+
+ if (mtcf.uri != NULL) {
+ rule = nxt_http_route_rule_create(task, tmcf, mtcf.uri, 1,
+ NXT_HTTP_ROUTE_PATTERN_NOCASE);
+ if (rule == NULL) {
+ return NULL;
+ }
+
+ rule->offset = offsetof(nxt_http_request_t, path);
+ rule->object = NXT_HTTP_ROUTE_STRING_PTR;
+ *p++ = rule;
+ }
+
+ if (mtcf.method != NULL) {
+ rule = nxt_http_route_rule_create(task, tmcf, mtcf.method, 1,
+ NXT_HTTP_ROUTE_PATTERN_UPCASE);
+ if (rule == NULL) {
+ return NULL;
+ }
+
+ rule->offset = offsetof(nxt_http_request_t, method);
+ rule->object = NXT_HTTP_ROUTE_STRING_PTR;
+ *p++ = rule;
+ }
+
+ return match;
+}
+
+
+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)
+{
+ 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);
+
+ rule = nxt_mp_alloc(mp, size);
+ if (nxt_slow_path(rule == NULL)) {
+ return NULL;
+ }
+
+ rule->items = n;
+
+ pattern = &rule->pattern[0];
+
+ if (string) {
+ pattern[0].case_sensitive = case_sensitive;
+ ret = nxt_http_route_pattern_create(task, mp, cv, &pattern[0],
+ pattern_case);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return NULL;
+ }
+
+ return rule;
+ }
+
+ nxt_conf_array_qsort(cv, nxt_http_pattern_compare);
+
+ for (i = 0; i < n; i++) {
+ pattern[i].case_sensitive = case_sensitive;
+ value = nxt_conf_get_array_element(cv, i);
+
+ ret = nxt_http_route_pattern_create(task, mp, value, &pattern[i],
+ pattern_case);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return NULL;
+ }
+ }
+
+ return rule;
+}
+
+
+static int
+nxt_http_pattern_compare(const void *one, const void *two)
+{
+ nxt_str_t test;
+ nxt_bool_t negative1, negative2;
+ nxt_conf_value_t *value;
+
+ value = (nxt_conf_value_t *) one;
+ nxt_conf_get_string(value, &test);
+ negative1 = (test.length != 0 && test.start[0] == '!');
+
+ value = (nxt_conf_value_t *) two;
+ nxt_conf_get_string(value, &test);
+ negative2 = (test.length != 0 && test.start[0] == '!');
+
+ return (negative2 - negative1);
+}
+
+
+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)
+{
+ u_char *start;
+ nxt_str_t test;
+ nxt_http_route_pattern_type_t type;
+
+ type = NXT_HTTP_ROUTE_PATTERN_EXACT;
+
+ nxt_conf_get_string(cv, &test);
+
+ pattern->negative = 0;
+ pattern->any = 1;
+
+ if (test.length != 0) {
+
+ if (test.start[0] == '!') {
+ test.start++;
+ test.length--;
+
+ pattern->negative = 1;
+ pattern->any = 0;
+ }
+
+ if (test.length != 0) {
+
+ if (test.start[0] == '*') {
+ test.start++;
+ test.length--;
+
+ if (test.length != 0) {
+ if (test.start[test.length - 1] == '*') {
+ test.length--;
+ type = NXT_HTTP_ROUTE_PATTERN_SUBSTRING;
+
+ } else {
+ type = NXT_HTTP_ROUTE_PATTERN_END;
+ }
+
+ } else {
+ type = NXT_HTTP_ROUTE_PATTERN_BEGIN;
+ }
+
+ } else if (test.start[test.length - 1] == '*') {
+ test.length--;
+ type = NXT_HTTP_ROUTE_PATTERN_BEGIN;
+ }
+ }
+ }
+
+ pattern->type = type;
+ pattern->min_length = test.length;
+ pattern->test.length = test.length;
+
+ start = nxt_mp_nget(mp, test.length);
+ if (nxt_slow_path(start == NULL)) {
+ return NXT_ERROR;
+ }
+
+ pattern->test.start = start;
+
+ switch (pattern_case) {
+
+ case NXT_HTTP_ROUTE_PATTERN_UPCASE:
+ nxt_memcpy_upcase(start, test.start, test.length);
+ break;
+
+ case NXT_HTTP_ROUTE_PATTERN_LOWCASE:
+ nxt_memcpy_lowcase(start, test.start, test.length);
+ break;
+
+ case NXT_HTTP_ROUTE_PATTERN_NOCASE:
+ nxt_memcpy(start, test.start, test.length);
+ break;
+ }
+
+ return NXT_OK;
+}
+
+
+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_routes_t *routes;
+
+ routes = tmcf->router_conf->routes;
+ if (routes != NULL) {
+ items = routes->items;
+ route = &routes->route[0];
+
+ while (items != 0) {
+ nxt_http_route_resolve(task, tmcf, *route);
+
+ route++;
+ items--;
+ }
+ }
+}
+
+
+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;
+
+ items = route->items;
+ match = &route->match[0];
+
+ while (items != 0) {
+ nxt_http_pass_resolve(task, tmcf, &(*match)->pass);
+
+ match++;
+ items--;
+ }
+}
+
+
+static void
+nxt_http_pass_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
+ nxt_http_pass_t *pass)
+{
+ nxt_str_t name;
+
+ name = pass->name;
+
+ if (nxt_str_start(&name, "applications/", 13)) {
+ name.length -= 13;
+ name.start += 13;
+
+ pass->u.application = nxt_router_listener_application(tmcf, &name);
+ nxt_router_app_use(task, pass->u.application, 1);
+
+ pass->handler = nxt_http_request_application;
+
+ } else if (nxt_str_start(&name, "routes", 6)) {
+
+ if (name.length == 6) {
+ name.length = 0;
+ name.start = NULL;
+
+ } else if (name.start[6] == '/') {
+ name.length -= 7;
+ name.start += 7;
+ }
+
+ pass->u.route = nxt_http_route_find(tmcf->router_conf->routes, &name);
+
+ pass->handler = nxt_http_route_pass;
+ }
+}
+
+
+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;
+
+ items = routes->items;
+ route = &routes->route[0];
+
+ do {
+ if (nxt_strstr_eq(&(*route)->name, name)) {
+ return *route;
+ }
+
+ route++;
+ items--;
+
+ } while (items != 0);
+
+ return NULL;
+}
+
+
+nxt_http_pass_t *
+nxt_http_pass_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
+ nxt_str_t *name)
+{
+ nxt_http_pass_t *pass;
+
+ pass = nxt_mp_alloc(tmcf->router_conf->mem_pool, sizeof(nxt_http_pass_t));
+ if (nxt_slow_path(pass == NULL)) {
+ return NULL;
+ }
+
+ pass->name = *name;
+
+ nxt_http_pass_resolve(task, tmcf, pass);
+
+ return pass;
+}
+
+
+/* COMPATIBILITY: listener application. */
+
+nxt_http_pass_t *
+nxt_http_pass_application(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
+ nxt_str_t *name)
+{
+ nxt_http_pass_t *pass;
+
+ pass = nxt_mp_alloc(tmcf->router_conf->mem_pool, sizeof(nxt_http_pass_t));
+ if (nxt_slow_path(pass == NULL)) {
+ return NULL;
+ }
+
+ pass->name = *name;
+
+ pass->u.application = nxt_router_listener_application(tmcf, name);
+ nxt_router_app_use(task, pass->u.application, 1);
+
+ pass->handler = nxt_http_request_application;
+
+ return pass;
+}
+
+
+void
+nxt_http_routes_cleanup(nxt_task_t *task, nxt_http_routes_t *routes)
+{
+ nxt_uint_t items;
+ nxt_http_route_t **route;
+
+ if (routes != NULL) {
+ items = routes->items;
+ route = &routes->route[0];
+
+ do {
+ nxt_http_route_cleanup(task, *route);
+
+ route++;
+ items--;
+
+ } while (items != 0);
+ }
+}
+
+
+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;
+
+ items = route->items;
+ match = &route->match[0];
+
+ do {
+ nxt_http_pass_cleanup(task, &(*match)->pass);
+
+ match++;
+ items--;
+
+ } while (items != 0);
+}
+
+
+void
+nxt_http_pass_cleanup(nxt_task_t *task, nxt_http_pass_t *pass)
+{
+ if (pass->handler == nxt_http_request_application) {
+ nxt_router_app_use(task, pass->u.application, -1);
+ }
+}
+
+
+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;
+
+ route = start->u.route;
+ items = route->items;
+ match = &route->match[0];
+
+ while (items != 0) {
+ pass = nxt_http_route_match(r, *match);
+ if (pass != NULL) {
+ return pass;
+ }
+
+ match++;
+ items--;
+ }
+
+ nxt_http_request_error(task, r, NXT_HTTP_NOT_FOUND);
+
+ return NULL;
+}
+
+
+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;
+
+ rule = &match->rule[0];
+ items = match->items;
+
+ while (items != 0) {
+ if (!nxt_http_route_rule(r, *rule)) {
+ return NULL;
+ }
+
+ rule++;
+ items--;
+ }
+
+ return &match->pass;
+}
+
+
+static nxt_bool_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;
+
+ p = nxt_pointer_to(r, rule->offset);
+
+ if (rule->object == NXT_HTTP_ROUTE_STRING) {
+ s = p;
+ length = s->length;
+ start = s->start;
+
+ } else {
+ pp = p;
+ p = *pp;
+
+ if (p == NULL) {
+ return 0;
+ }
+
+ switch (rule->object) {
+
+ case NXT_HTTP_ROUTE_STRING_PTR:
+ s = p;
+ length = s->length;
+ start = s->start;
+ break;
+
+ case NXT_HTTP_ROUTE_FIELD:
+ f = p;
+ length = f->value_length;
+ start = f->value;
+ break;
+
+ case NXT_HTTP_ROUTE_HEADER:
+ return 0;
+
+ case NXT_HTTP_ROUTE_ARGUMENT:
+ return 0;
+
+ case NXT_HTTP_ROUTE_COOKIE:
+ return 0;
+
+ default:
+ nxt_unreachable();
+ return 0;
+ }
+ }
+
+ items = rule->items;
+ pattern = &rule->pattern[0];
+
+ do {
+ ret = nxt_http_route_pattern(r, pattern, start, length);
+
+ ret ^= pattern->negative;
+
+ if (pattern->any == ret) {
+ return ret;
+ }
+
+ pattern++;
+ items--;
+
+ } while (items != 0);
+
+ return ret;
+}
+
+
+static nxt_bool_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;
+
+ if (length < pattern->min_length) {
+ return 0;
+ }
+
+ test = &pattern->test;
+
+ switch (pattern->type) {
+
+ case NXT_HTTP_ROUTE_PATTERN_EXACT:
+ if (length != test->length) {
+ return 0;
+ }
+
+ break;
+
+ case NXT_HTTP_ROUTE_PATTERN_BEGIN:
+ break;
+
+ case NXT_HTTP_ROUTE_PATTERN_END:
+ start += length - test->length;
+ break;
+
+ case NXT_HTTP_ROUTE_PATTERN_SUBSTRING:
+ if (pattern->case_sensitive) {
+ return (nxt_memstrn(start, start + length,
+ (char *) test->start, test->length)
+ != NULL);
+ }
+
+ return (nxt_memcasestrn(start, start + length,
+ (char *) test->start, test->length)
+ != NULL);
+ }
+
+ if (pattern->case_sensitive) {
+ return (nxt_memcmp(start, test->start, test->length) == 0);
+ }
+
+ return (nxt_memcasecmp(start, test->start, test->length) == 0);
+}
diff --git a/src/nxt_java.c b/src/nxt_java.c
new file mode 100644
index 00000000..bf4931ab
--- /dev/null
+++ b/src/nxt_java.c
@@ -0,0 +1,462 @@
+
+/*
+ * Copyright (C) NGINX, Inc.
+ */
+
+
+#include <jni.h>
+
+#include <nxt_main.h>
+#include <nxt_runtime.h>
+#include <nxt_router.h>
+#include <nxt_unit.h>
+#include <nxt_unit_field.h>
+#include <nxt_unit_request.h>
+#include <nxt_unit_response.h>
+
+#include <java/nxt_jni.h>
+
+#include "java/nxt_jni_Thread.h"
+#include "java/nxt_jni_Context.h"
+#include "java/nxt_jni_Request.h"
+#include "java/nxt_jni_Response.h"
+#include "java/nxt_jni_InputStream.h"
+#include "java/nxt_jni_OutputStream.h"
+#include "java/nxt_jni_URLClassLoader.h"
+
+#include "nxt_jars.h"
+
+static nxt_int_t nxt_java_pre_init(nxt_task_t *task,
+ nxt_common_app_conf_t *conf);
+static nxt_int_t nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf);
+static void nxt_java_request_handler(nxt_unit_request_info_t *req);
+
+static uint32_t compat[] = {
+ NXT_VERNUM, NXT_DEBUG,
+};
+
+char *nxt_java_modules;
+
+
+#define NXT_STRING(x) _NXT_STRING(x)
+#define _NXT_STRING(x) #x
+
+NXT_EXPORT nxt_app_module_t nxt_app_module = {
+ sizeof(compat),
+ compat,
+ nxt_string("java"),
+ NXT_STRING(NXT_JAVA_VERSION),
+ nxt_java_pre_init,
+ nxt_java_init,
+};
+
+typedef struct {
+ JNIEnv *env;
+ jobject ctx;
+} nxt_java_data_t;
+
+
+static nxt_int_t
+nxt_java_pre_init(nxt_task_t *task, nxt_common_app_conf_t *conf)
+{
+ const char *unit_jars;
+
+ unit_jars = conf->u.java.unit_jars;
+ if (unit_jars == NULL) {
+ unit_jars = NXT_JARS;
+ }
+
+ nxt_java_modules = realpath(unit_jars, NULL);
+ if (nxt_java_modules == NULL) {
+ nxt_alert(task, "realpath(%s) failed: %E", NXT_JARS, nxt_errno);
+ return NXT_ERROR;
+ }
+
+ return NXT_OK;
+}
+
+
+static char **
+nxt_java_module_jars(const char *jars[], int jar_count)
+{
+ char **res, *jurl;
+ nxt_int_t modules_len, jlen, i;
+ const char **jar;
+
+ res = nxt_malloc(jar_count * sizeof(char*));
+ if (res == NULL) {
+ return NULL;
+ }
+
+ modules_len = nxt_strlen(nxt_java_modules);
+
+ for (i = 0, jar = jars; *jar != NULL; jar++) {
+ jlen = nxt_length("file:") + modules_len + nxt_length("/")
+ + nxt_strlen(*jar) + 1;
+ jurl = nxt_malloc(jlen);
+ if (jurl == NULL) {
+ return NULL;
+ }
+
+ res[i++] = jurl;
+
+ jurl = nxt_cpymem(jurl, "file:", nxt_length("file:"));
+ jurl = nxt_cpymem(jurl, nxt_java_modules, modules_len);
+ *jurl++ = '/';
+ jurl = nxt_cpymem(jurl, *jar, nxt_strlen(*jar));
+ *jurl++ = '\0';
+ }
+
+ return res;
+}
+
+
+static nxt_int_t
+nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf)
+{
+ jint rc;
+ char *opt, *real_path;
+ char **classpath_arr, **unit_jars, **system_jars;
+ JavaVM *jvm;
+ JNIEnv *env;
+ jobject cl, classpath;
+ nxt_str_t str;
+ nxt_int_t opt_len, real_path_len;
+ nxt_uint_t i, unit_jars_count, classpath_count, system_jars_count;
+ JavaVMOption *jvm_opt;
+ JavaVMInitArgs jvm_args;
+ nxt_unit_ctx_t *ctx;
+ nxt_unit_init_t java_init;
+ nxt_java_data_t data;
+ nxt_conf_value_t *value;
+ nxt_java_app_conf_t *c;
+
+ //setenv("ASAN_OPTIONS", "handle_segv=0", 1);
+
+ jvm_args.version = JNI_VERSION_1_6;
+ jvm_args.nOptions = 0;
+ jvm_args.ignoreUnrecognized = 0;
+
+ c = &conf->u.java;
+
+ if (c->options != NULL) {
+ jvm_args.nOptions += nxt_conf_array_elements_count(c->options);
+ }
+
+ jvm_opt = nxt_malloc(jvm_args.nOptions * sizeof(JavaVMOption));
+ if (jvm_opt == NULL) {
+ nxt_alert(task, "failed to allocate jvm_opt");
+ return NXT_ERROR;
+ }
+
+ jvm_args.options = jvm_opt;
+
+ unit_jars_count = nxt_nitems(nxt_java_unit_jars) - 1;
+
+ unit_jars = nxt_java_module_jars(nxt_java_unit_jars, unit_jars_count);
+ if (unit_jars == NULL) {
+ nxt_alert(task, "failed to allocate buffer for unit_jars array");
+
+ return NXT_ERROR;
+ }
+
+ system_jars_count = nxt_nitems(nxt_java_system_jars) - 1;
+
+ system_jars = nxt_java_module_jars(nxt_java_system_jars, system_jars_count);
+ if (system_jars == NULL) {
+ nxt_alert(task, "failed to allocate buffer for system_jars array");
+
+ return NXT_ERROR;
+ }
+
+ if (c->options != NULL) {
+
+ for (i = 0; /* void */ ; i++) {
+ value = nxt_conf_get_array_element(c->options, i);
+ if (value == NULL) {
+ break;
+ }
+
+ nxt_conf_get_string(value, &str);
+
+ opt = nxt_malloc(str.length + 1);
+ if (opt == NULL) {
+ nxt_alert(task, "failed to allocate jvm_opt");
+ return NXT_ERROR;
+ }
+
+ memcpy(opt, str.start, str.length);
+ opt[str.length] = '\0';
+
+ jvm_opt[i].optionString = opt;
+ }
+ }
+
+ if (c->classpath != NULL) {
+ classpath_count = nxt_conf_array_elements_count(c->classpath);
+ classpath_arr = nxt_malloc(classpath_count * sizeof(char *));
+
+ for (i = 0; /* void */ ; i++) {
+ value = nxt_conf_get_array_element(c->classpath, i);
+ if (value == NULL) {
+ break;
+ }
+
+ nxt_conf_get_string(value, &str);
+
+ opt_len = str.length + 1;
+
+ char *sc = memchr(str.start, ':', str.length);
+ if (sc == NULL && str.start[0] == '/') {
+ opt_len += nxt_length("file:");
+ }
+
+ opt = nxt_malloc(opt_len);
+ if (opt == NULL) {
+ nxt_alert(task, "failed to allocate classpath");
+ return NXT_ERROR;
+ }
+
+ if (sc == NULL && str.start[0] != '/') {
+ nxt_memcpy(opt, str.start, str.length);
+ opt[str.length] = '\0';
+
+ real_path = realpath(opt, NULL);
+ if (real_path == NULL) {
+ nxt_alert(task, "realpath(%s) failed: %E", opt, nxt_errno);
+ return NXT_ERROR;
+ }
+
+ real_path_len = nxt_strlen(real_path);
+
+ free(opt);
+
+ opt_len = nxt_length("file:") + real_path_len + 1;
+
+ opt = nxt_malloc(opt_len);
+ if (opt == NULL) {
+ nxt_alert(task, "failed to allocate classpath");
+ return NXT_ERROR;
+ }
+
+ } else {
+ real_path = (char *) str.start; /* I love this cast! */
+ real_path_len = str.length;
+ }
+
+ classpath_arr[i] = opt;
+
+ if (sc == NULL) {
+ opt = nxt_cpymem(opt, "file:", nxt_length("file:"));
+ }
+
+ opt = nxt_cpymem(opt, real_path, real_path_len);
+ *opt = '\0';
+ }
+
+ } else {
+ classpath_count = 0;
+ classpath_arr = NULL;
+ }
+
+ rc = JNI_CreateJavaVM(&jvm, (void **) &env, &jvm_args);
+ if (rc != JNI_OK) {
+ nxt_alert(task, "failed to create Java VM: %d", (int) rc);
+ return NXT_ERROR;
+ }
+
+ rc = nxt_java_initThread(env);
+ if (rc != NXT_UNIT_OK) {
+ nxt_alert(task, "nxt_java_initThread() failed");
+ goto env_failed;
+ }
+
+ rc = nxt_java_initURLClassLoader(env);
+ if (rc != NXT_UNIT_OK) {
+ nxt_alert(task, "nxt_java_initURLClassLoader() failed");
+ goto env_failed;
+ }
+
+ cl = nxt_java_newURLClassLoader(env, system_jars_count, system_jars);
+ if (cl == NULL) {
+ nxt_alert(task, "nxt_java_newURLClassLoader failed");
+ goto env_failed;
+ }
+
+ nxt_java_setContextClassLoader(env, cl);
+
+ cl = nxt_java_newURLClassLoader_parent(env, unit_jars_count, unit_jars, cl);
+ if (cl == NULL) {
+ nxt_alert(task, "nxt_java_newURLClassLoader_parent failed");
+ goto env_failed;
+ }
+
+ nxt_java_setContextClassLoader(env, cl);
+
+ rc = nxt_java_initContext(env, cl);
+ if (rc != NXT_UNIT_OK) {
+ nxt_alert(task, "nxt_java_initContext() failed");
+ goto env_failed;
+ }
+
+ rc = nxt_java_initRequest(env, cl);
+ if (rc != NXT_UNIT_OK) {
+ nxt_alert(task, "nxt_java_initRequest() failed");
+ goto env_failed;
+ }
+
+ rc = nxt_java_initResponse(env, cl);
+ if (rc != NXT_UNIT_OK) {
+ nxt_alert(task, "nxt_java_initResponse() failed");
+ goto env_failed;
+ }
+
+ rc = nxt_java_initInputStream(env, cl);
+ if (rc != NXT_UNIT_OK) {
+ nxt_alert(task, "nxt_java_initInputStream() failed");
+ goto env_failed;
+ }
+
+ rc = nxt_java_initOutputStream(env, cl);
+ if (rc != NXT_UNIT_OK) {
+ nxt_alert(task, "nxt_java_initOutputStream() failed");
+ goto env_failed;
+ }
+
+ nxt_java_jni_init(env);
+ if (rc != NXT_UNIT_OK) {
+ nxt_alert(task, "nxt_java_jni_init() failed");
+ goto env_failed;
+ }
+
+ classpath = nxt_java_newURLs(env, classpath_count, classpath_arr);
+ if (classpath == NULL) {
+ nxt_alert(task, "nxt_java_newURLs failed");
+ goto env_failed;
+ }
+
+ data.env = env;
+ data.ctx = nxt_java_startContext(env, c->webapp, classpath);
+
+ if ((*env)->ExceptionCheck(env)) {
+ nxt_alert(task, "Unhandled exception in application start");
+ (*env)->ExceptionDescribe(env);
+ return NXT_ERROR;
+ }
+
+ nxt_unit_default_init(task, &java_init);
+
+ java_init.callbacks.request_handler = nxt_java_request_handler;
+ java_init.request_data_size = sizeof(nxt_java_request_data_t);
+ java_init.data = &data;
+
+ ctx = nxt_unit_init(&java_init);
+ if (nxt_slow_path(ctx == NULL)) {
+ nxt_alert(task, "nxt_unit_init() failed");
+ return NXT_ERROR;
+ }
+
+ rc = nxt_unit_run(ctx);
+ if (nxt_slow_path(rc != NXT_UNIT_OK)) {
+ /* TODO report error */
+ }
+
+ nxt_unit_done(ctx);
+
+ nxt_java_stopContext(env, data.ctx);
+
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionDescribe(env);
+ }
+
+ (*jvm)->DestroyJavaVM(jvm);
+
+ exit(0);
+
+ return NXT_OK;
+
+env_failed:
+
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionDescribe(env);
+ }
+
+ return NXT_ERROR;
+}
+
+
+static void
+nxt_java_request_handler(nxt_unit_request_info_t *req)
+{
+ JNIEnv *env;
+ jobject jreq, jresp;
+ nxt_java_data_t *java_data;
+ nxt_java_request_data_t *data;
+
+ java_data = req->unit->data;
+ env = java_data->env;
+ data = req->data;
+
+ jreq = nxt_java_newRequest(env, java_data->ctx, req);
+ if (jreq == NULL) {
+ nxt_unit_req_alert(req, "failed to create Request instance");
+
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionDescribe(env);
+ (*env)->ExceptionClear(env);
+ }
+
+ nxt_unit_request_done(req, NXT_UNIT_ERROR);
+ return;
+ }
+
+ jresp = nxt_java_newResponse(env, req);
+ if (jresp == NULL) {
+ nxt_unit_req_alert(req, "failed to create Response instance");
+
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionDescribe(env);
+ (*env)->ExceptionClear(env);
+ }
+
+ (*env)->DeleteLocalRef(env, jreq);
+
+ nxt_unit_request_done(req, NXT_UNIT_ERROR);
+ return;
+ }
+
+ data->header_size = 10 * 1024;
+ data->buf_size = 32 * 1024; /* from Jetty */
+ data->jreq = jreq;
+ data->jresp = jresp;
+ data->buf = NULL;
+
+ nxt_unit_request_group_dup_fields(req);
+
+ nxt_java_service(env, java_data->ctx, jreq, jresp);
+
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionDescribe(env);
+ (*env)->ExceptionClear(env);
+ }
+
+ if (!nxt_unit_response_is_init(req)) {
+ nxt_unit_response_init(req, 200, 0, 0);
+ }
+
+ if (!nxt_unit_response_is_sent(req)) {
+ nxt_unit_response_send(req);
+ }
+
+ if (data->buf != NULL) {
+ nxt_unit_buf_send(data->buf);
+
+ data->buf = NULL;
+ }
+
+ (*env)->DeleteLocalRef(env, jresp);
+ (*env)->DeleteLocalRef(env, jreq);
+
+ nxt_unit_request_done(req, NXT_UNIT_OK);
+}
+
diff --git a/src/nxt_main.h b/src/nxt_main.h
index 760384ea..23c55002 100644
--- a/src/nxt_main.h
+++ b/src/nxt_main.h
@@ -9,10 +9,7 @@
#include <nxt_auto_config.h>
-
-
-#define NXT_VERSION "1.7.1"
-#define NXT_VERNUM 10701
+#include <nxt_version.h>
#define NXT_SERVER "Unit/" NXT_VERSION
diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c
index 819ed44c..f756bff7 100644
--- a/src/nxt_main_process.c
+++ b/src/nxt_main_process.c
@@ -52,6 +52,8 @@ static void nxt_main_process_sigusr1_handler(nxt_task_t *task, void *obj,
void *data);
static void nxt_main_process_sigchld_handler(nxt_task_t *task, void *obj,
void *data);
+static void nxt_main_process_signal_handler(nxt_task_t *task, void *obj,
+ void *data);
static void nxt_main_cleanup_worker_process(nxt_task_t *task, nxt_pid_t pid);
static void nxt_main_stop_worker_processes(nxt_task_t *task, nxt_runtime_t *rt);
static void nxt_main_port_socket_handler(nxt_task_t *task,
@@ -68,6 +70,7 @@ static void nxt_main_port_access_log_handler(nxt_task_t *task,
const nxt_sig_event_t nxt_main_process_signals[] = {
+ nxt_event_signal(SIGHUP, nxt_main_process_signal_handler),
nxt_event_signal(SIGINT, nxt_main_process_sigterm_handler),
nxt_event_signal(SIGQUIT, nxt_main_process_sigquit_handler),
nxt_event_signal(SIGTERM, nxt_main_process_sigterm_handler),
@@ -216,12 +219,38 @@ static nxt_conf_map_t nxt_ruby_app_conf[] = {
};
+static nxt_conf_map_t nxt_java_app_conf[] = {
+ {
+ nxt_string("classpath"),
+ NXT_CONF_MAP_PTR,
+ offsetof(nxt_common_app_conf_t, u.java.classpath),
+ },
+ {
+ nxt_string("webapp"),
+ NXT_CONF_MAP_CSTRZ,
+ offsetof(nxt_common_app_conf_t, u.java.webapp),
+ },
+ {
+ nxt_string("options"),
+ NXT_CONF_MAP_PTR,
+ offsetof(nxt_common_app_conf_t, u.java.options),
+ },
+ {
+ nxt_string("unit_jars"),
+ NXT_CONF_MAP_CSTRZ,
+ offsetof(nxt_common_app_conf_t, u.java.unit_jars),
+ },
+
+};
+
+
static nxt_conf_app_map_t nxt_app_maps[] = {
{ nxt_nitems(nxt_external_app_conf), nxt_external_app_conf },
{ nxt_nitems(nxt_python_app_conf), nxt_python_app_conf },
{ nxt_nitems(nxt_php_app_conf), nxt_php_app_conf },
{ nxt_nitems(nxt_perl_app_conf), nxt_perl_app_conf },
{ nxt_nitems(nxt_ruby_app_conf), nxt_ruby_app_conf },
+ { nxt_nitems(nxt_java_app_conf), nxt_java_app_conf },
};
@@ -889,6 +918,14 @@ nxt_main_process_sigchld_handler(nxt_task_t *task, void *obj, void *data)
static void
+nxt_main_process_signal_handler(nxt_task_t *task, void *obj, void *data)
+{
+ nxt_trace(task, "signal signo:%d (%s) recevied, ignored",
+ (int) (uintptr_t) obj, data);
+}
+
+
+static void
nxt_main_cleanup_worker_process(nxt_task_t *task, nxt_pid_t pid)
{
nxt_buf_t *buf;
@@ -1116,24 +1153,23 @@ nxt_main_listening_socket(nxt_sockaddr_t *sa, nxt_listening_socket_t *ls)
break;
}
- goto next;
- }
-
+ } else
#endif
+ {
+ switch (err) {
- switch (err) {
-
- case EACCES:
- ls->error = NXT_SOCKET_ERROR_PORT;
- break;
+ case EACCES:
+ ls->error = NXT_SOCKET_ERROR_PORT;
+ break;
- case EADDRINUSE:
- ls->error = NXT_SOCKET_ERROR_INUSE;
- break;
+ case EADDRINUSE:
+ ls->error = NXT_SOCKET_ERROR_INUSE;
+ break;
- case EADDRNOTAVAIL:
- ls->error = NXT_SOCKET_ERROR_NOADDR;
- break;
+ case EADDRNOTAVAIL:
+ ls->error = NXT_SOCKET_ERROR_NOADDR;
+ break;
+ }
}
ls->end = nxt_sprintf(ls->start, ls->end, "bind(\\\"%*s\\\") failed %E",
@@ -1143,8 +1179,6 @@ nxt_main_listening_socket(nxt_sockaddr_t *sa, nxt_listening_socket_t *ls)
#if (NXT_HAVE_UNIX_DOMAIN)
-next:
-
if (sa->u.sockaddr.sa_family == AF_UNIX) {
char *filename;
mode_t access;
diff --git a/src/nxt_openssl.c b/src/nxt_openssl.c
index 99dd2077..c01f92c8 100644
--- a/src/nxt_openssl.c
+++ b/src/nxt_openssl.c
@@ -855,12 +855,11 @@ nxt_openssl_conn_test_error(nxt_task_t *task, nxt_conn_t *c, int ret,
switch (tls->ssl_error) {
case SSL_ERROR_WANT_READ:
+ c->socket.read_ready = 0;
if (io != NXT_OPENSSL_READ) {
nxt_fd_event_block_write(task->thread->engine, &c->socket);
- c->socket.read_ready = 0;
-
if (nxt_fd_event_is_disabled(c->socket.read)) {
nxt_fd_event_enable_read(task->thread->engine, &c->socket);
}
@@ -869,12 +868,11 @@ nxt_openssl_conn_test_error(nxt_task_t *task, nxt_conn_t *c, int ret,
return NXT_AGAIN;
case SSL_ERROR_WANT_WRITE:
+ c->socket.write_ready = 0;
if (io != NXT_OPENSSL_WRITE) {
nxt_fd_event_block_read(task->thread->engine, &c->socket);
- c->socket.write_ready = 0;
-
if (nxt_fd_event_is_disabled(c->socket.write)) {
nxt_fd_event_enable_write(task->thread->engine, &c->socket);
}
diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c
index 8c25f82a..80321a85 100644
--- a/src/nxt_php_sapi.c
+++ b/src/nxt_php_sapi.c
@@ -62,7 +62,7 @@ static void nxt_php_set_sptr(nxt_unit_request_info_t *req, const char *name,
nxt_inline void nxt_php_set_str(nxt_unit_request_info_t *req, const char *name,
nxt_str_t *s, zval *track_vars_array TSRMLS_DC);
static void nxt_php_set_cstr(nxt_unit_request_info_t *req, const char *name,
- char *str, uint32_t len, zval *track_vars_array TSRMLS_DC);
+ const char *str, uint32_t len, zval *track_vars_array TSRMLS_DC);
static void nxt_php_register_variables(zval *track_vars_array TSRMLS_DC);
#ifdef NXT_HAVE_PHP_LOG_MESSAGE_WITH_SYSLOG_TYPE
static void nxt_php_log_message(char *message, int syslog_type_int);
@@ -164,6 +164,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = {
compat,
nxt_string("php"),
PHP_VERSION,
+ NULL,
nxt_php_init,
};
@@ -846,8 +847,6 @@ nxt_php_read_cookies(TSRMLS_D)
static void
nxt_php_register_variables(zval *track_vars_array TSRMLS_DC)
{
- char *host_start, *port_start;
- uint32_t host_length, port_length;
const char *name;
nxt_unit_field_t *f, *f_end;
nxt_php_run_ctx_t *ctx;
@@ -918,16 +917,18 @@ nxt_php_register_variables(zval *track_vars_array TSRMLS_DC)
track_vars_array TSRMLS_CC);
nxt_php_set_sptr(req, "REQUEST_URI", &r->target, r->target_length,
track_vars_array TSRMLS_CC);
- if (r->query.offset) {
- nxt_php_set_sptr(req, "QUERY_STRING", &r->query, r->query_length,
- track_vars_array TSRMLS_CC);
- }
+ nxt_php_set_sptr(req, "QUERY_STRING", &r->query, r->query_length,
+ track_vars_array TSRMLS_CC);
nxt_php_set_sptr(req, "REMOTE_ADDR", &r->remote, r->remote_length,
track_vars_array TSRMLS_CC);
nxt_php_set_sptr(req, "SERVER_ADDR", &r->local, r->local_length,
track_vars_array TSRMLS_CC);
+ nxt_php_set_sptr(req, "SERVER_NAME", &r->server_name, r->server_name_length,
+ track_vars_array TSRMLS_CC);
+ nxt_php_set_cstr(req, "SERVER_PORT", "80", 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);
@@ -949,25 +950,6 @@ nxt_php_register_variables(zval *track_vars_array TSRMLS_DC)
nxt_php_set_sptr(req, "CONTENT_TYPE", &f->value, f->value_length,
track_vars_array TSRMLS_CC);
}
-
- if (r->host_field != NXT_UNIT_NONE_FIELD) {
- f = r->fields + r->host_field;
-
- host_start = nxt_unit_sptr_get(&f->value);
- host_length = f->value_length;
-
- } else {
- host_start = NULL;
- host_length = 0;
- }
-
- nxt_unit_split_host(host_start, host_length, &host_start, &host_length,
- &port_start, &port_length);
-
- nxt_php_set_cstr(req, "SERVER_NAME", host_start, host_length,
- track_vars_array TSRMLS_CC);
- nxt_php_set_cstr(req, "SERVER_PORT", port_start, port_length,
- track_vars_array TSRMLS_CC);
}
@@ -997,7 +979,7 @@ nxt_php_set_str(nxt_unit_request_info_t *req, const char *name,
static void
nxt_php_set_cstr(nxt_unit_request_info_t *req, const char *name,
- char *cstr, uint32_t len, zval *track_vars_array TSRMLS_DC)
+ const char *cstr, uint32_t len, zval *track_vars_array TSRMLS_DC)
{
if (nxt_slow_path(cstr == NULL)) {
return;
@@ -1005,7 +987,7 @@ nxt_php_set_cstr(nxt_unit_request_info_t *req, const char *name,
nxt_unit_req_debug(req, "php: register %s='%.*s'", name, (int) len, cstr);
- php_register_variable_safe((char *) name, cstr, len,
+ php_register_variable_safe((char *) name, (char *) cstr, len,
track_vars_array TSRMLS_CC);
}
diff --git a/src/nxt_port_rpc.c b/src/nxt_port_rpc.c
index 401707f0..77e8af45 100644
--- a/src/nxt_port_rpc.c
+++ b/src/nxt_port_rpc.c
@@ -489,7 +489,7 @@ nxt_port_rpc_close(nxt_task_t *task, nxt_port_t *port)
msg.port_msg.nf = 0;
msg.port_msg.mf = 0;
msg.port_msg.tracking = 0;
- msg.size = sizeof(msg.port_msg);
+ msg.size = 0;
msg.cancelled = 0;
msg.u.data = NULL;
diff --git a/src/nxt_port_socket.c b/src/nxt_port_socket.c
index a545013f..01fe2dab 100644
--- a/src/nxt_port_socket.c
+++ b/src/nxt_port_socket.c
@@ -179,6 +179,7 @@ nxt_port_msg_create(nxt_task_t *task, nxt_port_send_msg_t *m)
msg->link.prev = NULL;
msg->buf = m->buf;
+ msg->share = m->share;
msg->fd = m->fd;
msg->close_fd = m->close_fd;
msg->port_msg = m->port_msg;
@@ -799,6 +800,14 @@ nxt_port_read_msg_process(nxt_task_t *task, nxt_port_t *port,
msg->buf = fmsg->buf;
msg->fd = fmsg->fd;
+
+ /*
+ * To disable instant completion or buffer re-usage,
+ * handler should reset 'msg.buf'.
+ */
+ if (!msg->port_msg.mmap && msg->buf == b) {
+ nxt_port_buf_free(port, b);
+ }
}
}
@@ -895,7 +904,7 @@ nxt_port_buf_alloc(nxt_port_t *port)
static void
nxt_port_buf_free(nxt_port_t *port, nxt_buf_t *b)
{
- b->next = port->free_bufs;
+ nxt_buf_chain_add(&b, port->free_bufs);
port->free_bufs = b;
}
diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c
index bd3a2cb2..6478f38c 100644
--- a/src/nxt_python_wsgi.c
+++ b/src/nxt_python_wsgi.c
@@ -72,7 +72,7 @@ static PyObject *nxt_python_get_environ(nxt_python_run_ctx_t *ctx);
static int nxt_python_add_sptr(nxt_python_run_ctx_t *ctx, const char *name,
nxt_unit_sptr_t *sptr, uint32_t size);
static int nxt_python_add_str(nxt_python_run_ctx_t *ctx, const char *name,
- char *str, uint32_t size);
+ const char *str, uint32_t size);
static PyObject *nxt_py_start_resp(PyObject *self, PyObject *args);
static int nxt_python_response_add_field(nxt_python_run_ctx_t *ctx,
@@ -105,6 +105,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = {
compat,
nxt_string("python"),
PY_VERSION,
+ NULL,
nxt_python_init,
};
@@ -690,8 +691,8 @@ static PyObject *
nxt_python_get_environ(nxt_python_run_ctx_t *ctx)
{
int rc;
- char *name, *host_start, *port_start;
- uint32_t i, host_length, port_length;
+ char *name;
+ uint32_t i;
PyObject *environ;
nxt_unit_field_t *f;
nxt_unit_request_t *r;
@@ -719,11 +720,7 @@ nxt_python_get_environ(nxt_python_run_ctx_t *ctx)
RC(nxt_python_add_sptr(ctx, "REQUEST_METHOD", &r->method,
r->method_length));
RC(nxt_python_add_sptr(ctx, "REQUEST_URI", &r->target, r->target_length));
-
- if (r->query.offset) {
- RC(nxt_python_add_sptr(ctx, "QUERY_STRING", &r->query,
- r->query_length));
- }
+ RC(nxt_python_add_sptr(ctx, "QUERY_STRING", &r->query, r->query_length));
RC(nxt_python_add_sptr(ctx, "PATH_INFO", &r->path, r->path_length));
RC(nxt_python_add_sptr(ctx, "REMOTE_ADDR", &r->remote, r->remote_length));
@@ -732,6 +729,10 @@ nxt_python_get_environ(nxt_python_run_ctx_t *ctx)
RC(nxt_python_add_sptr(ctx, "SERVER_PROTOCOL", &r->version,
r->version_length));
+ RC(nxt_python_add_sptr(ctx, "SERVER_NAME", &r->server_name,
+ r->server_name_length));
+ RC(nxt_python_add_str(ctx, "SERVER_PORT", "80", 2));
+
for (i = 0; i < r->fields_count; i++) {
f = r->fields + i;
name = nxt_unit_sptr_get(&f->name);
@@ -753,23 +754,6 @@ nxt_python_get_environ(nxt_python_run_ctx_t *ctx)
f->value_length));
}
- if (r->host_field != NXT_UNIT_NONE_FIELD) {
- f = r->fields + r->host_field;
-
- host_start = nxt_unit_sptr_get(&f->value);
- host_length = f->value_length;
-
- } else {
- host_start = NULL;
- host_length = 0;
- }
-
- nxt_unit_split_host(host_start, host_length, &host_start, &host_length,
- &port_start, &port_length);
-
- RC(nxt_python_add_str(ctx, "SERVER_NAME", host_start, host_length));
- RC(nxt_python_add_str(ctx, "SERVER_PORT", port_start, port_length));
-
#undef RC
return environ;
@@ -818,7 +802,7 @@ nxt_python_add_sptr(nxt_python_run_ctx_t *ctx, const char *name,
static int
nxt_python_add_str(nxt_python_run_ctx_t *ctx, const char *name,
- char *str, uint32_t size)
+ const char *str, uint32_t size)
{
PyObject *value;
diff --git a/src/nxt_router.c b/src/nxt_router.c
index 7ecbca81..e46e8f82 100644
--- a/src/nxt_router.c
+++ b/src/nxt_router.c
@@ -31,7 +31,8 @@ typedef struct {
typedef struct {
- nxt_str_t application;
+ nxt_str_t pass;
+ nxt_str_t application;
} nxt_router_listener_conf_t;
@@ -163,8 +164,6 @@ static void nxt_router_conf_send(nxt_task_t *task,
static nxt_int_t nxt_router_conf_create(nxt_task_t *task,
nxt_router_temp_conf_t *tmcf, u_char *start, u_char *end);
static nxt_app_t *nxt_router_app_find(nxt_queue_t *queue, nxt_str_t *name);
-static nxt_app_t *nxt_router_listener_application(nxt_router_temp_conf_t *tmcf,
- nxt_str_t *name);
static void nxt_router_listen_socket_rpc_create(nxt_task_t *task,
nxt_router_temp_conf_t *tmcf, nxt_socket_conf_t *skcf);
static void nxt_router_listen_socket_ready(nxt_task_t *task,
@@ -294,6 +293,7 @@ static const nxt_str_t *nxt_app_msg_prefix[] = {
&http_prefix,
&http_prefix,
&http_prefix,
+ &empty_prefix,
};
@@ -1174,11 +1174,14 @@ nxt_router_conf_error(nxt_task_t *task, nxt_router_temp_conf_t *tmcf)
nxt_queue_add(&new_socket_confs, &tmcf->pending);
nxt_queue_add(&new_socket_confs, &tmcf->creating);
+ rtcf = tmcf->router_conf;
+
+ nxt_http_routes_cleanup(task, rtcf->routes);
+
nxt_queue_each(skcf, &new_socket_confs, nxt_socket_conf_t, link) {
- if (skcf->application != NULL) {
- nxt_router_app_use(task, skcf->application, -1);
- skcf->application = NULL;
+ if (skcf->pass != NULL) {
+ nxt_http_pass_cleanup(task, skcf->pass);
}
} nxt_queue_loop;
@@ -1189,7 +1192,6 @@ nxt_router_conf_error(nxt_task_t *task, nxt_router_temp_conf_t *tmcf)
} nxt_queue_loop;
- rtcf = tmcf->router_conf;
router = rtcf->router;
nxt_queue_add(&router->sockets, &tmcf->keeping);
@@ -1299,8 +1301,14 @@ static nxt_conf_map_t nxt_router_app_processes_conf[] = {
static nxt_conf_map_t nxt_router_listener_conf[] = {
{
+ nxt_string("pass"),
+ NXT_CONF_MAP_STR_COPY,
+ offsetof(nxt_router_listener_conf_t, pass),
+ },
+
+ {
nxt_string("application"),
- NXT_CONF_MAP_STR,
+ NXT_CONF_MAP_STR_COPY,
offsetof(nxt_router_listener_conf_t, application),
},
};
@@ -1379,7 +1387,9 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
nxt_conf_value_t *conf, *http, *value;
nxt_conf_value_t *applications, *application;
nxt_conf_value_t *listeners, *listener;
+ nxt_conf_value_t *routes_conf;
nxt_socket_conf_t *skcf;
+ nxt_http_routes_t *routes;
nxt_event_engine_t *engine;
nxt_app_lang_module_t *lang;
nxt_router_app_conf_t apcf;
@@ -1392,6 +1402,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
static nxt_str_t http_path = nxt_string("/settings/http");
static nxt_str_t applications_path = nxt_string("/applications");
static nxt_str_t listeners_path = nxt_string("/listeners");
+ static nxt_str_t routes_path = nxt_string("/routes");
static nxt_str_t access_log_path = nxt_string("/access_log");
#if (NXT_TLS)
static nxt_str_t certificate_path = nxt_string("/tls/certificate");
@@ -1589,6 +1600,15 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
app_joint->free_app_work.obj = app_joint;
}
+ routes_conf = nxt_conf_get_path(conf, &routes_path);
+ if (nxt_fast_path(routes_conf != NULL)) {
+ routes = nxt_http_routes_create(task, tmcf, routes_conf);
+ if (nxt_slow_path(routes == NULL)) {
+ return NXT_ERROR;
+ }
+ tmcf->router_conf->routes = routes;
+ }
+
http = nxt_conf_get_path(conf, &http_path);
#if 0
if (http == NULL) {
@@ -1671,10 +1691,13 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
skcf->router_conf = tmcf->router_conf;
skcf->router_conf->count++;
- if (lscf.application.length > 0) {
- skcf->application = nxt_router_listener_application(tmcf,
- &lscf.application);
- nxt_router_app_use(task, skcf->application, 1);
+ if (lscf.pass.length != 0) {
+ skcf->pass = nxt_http_pass_create(task, tmcf, &lscf.pass);
+
+ /* COMPATIBILITY: listener application. */
+ } else if (lscf.application.length > 0) {
+ skcf->pass = nxt_http_pass_application(task, tmcf,
+ &lscf.application);
}
}
@@ -1712,6 +1735,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
tmcf->router_conf->access_log = access_log;
}
+ nxt_http_routes_resolve(task, tmcf);
+
nxt_queue_add(&tmcf->deleting, &router->sockets);
nxt_queue_init(&router->sockets);
@@ -1752,7 +1777,7 @@ nxt_router_app_find(nxt_queue_t *queue, nxt_str_t *name)
}
-static nxt_app_t *
+nxt_app_t *
nxt_router_listener_application(nxt_router_temp_conf_t *tmcf, nxt_str_t *name)
{
nxt_app_t *app;
@@ -1982,13 +2007,18 @@ static void
nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg,
void *data)
{
+ nxt_socket_rpc_t *rpc;
+ nxt_router_temp_conf_t *tmcf;
+
+ rpc = data;
+ tmcf = rpc->temp_conf;
+
+#if 0
u_char *p;
size_t size;
uint8_t error;
nxt_buf_t *in, *out;
nxt_sockaddr_t *sa;
- nxt_socket_rpc_t *rpc;
- nxt_router_temp_conf_t *tmcf;
static nxt_str_t socket_errors[] = {
nxt_string("ListenerSystem"),
@@ -2000,9 +2030,7 @@ nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg,
nxt_string("ListenerPath"),
};
- rpc = data;
sa = rpc->socket_conf->listen->sockaddr;
- tmcf = rpc->temp_conf;
in = nxt_buf_chk_make_plain(tmcf->mem_pool, msg->buf, msg->size);
@@ -2030,6 +2058,7 @@ nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg,
&socket_errors[error], in->mem.free - p, p);
nxt_debug(task, "%*s", out->mem.free - out->mem.pos, out->mem.pos);
+#endif
nxt_router_conf_error(task, tmcf);
}
@@ -2881,7 +2910,6 @@ nxt_router_listen_event_release(nxt_task_t *task, nxt_listen_event_t *lev,
void
nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint)
{
- nxt_app_t *app;
nxt_socket_conf_t *skcf;
nxt_router_conf_t *rtcf;
nxt_thread_spinlock_t *lock;
@@ -2900,7 +2928,6 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint)
* be already destroyed by another thread.
*/
skcf = joint->socket_conf;
- app = skcf->application;
rtcf = skcf->router_conf;
lock = &rtcf->router->lock;
@@ -2910,8 +2937,8 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint)
rtcf, rtcf->count);
if (--skcf->count != 0) {
+ skcf = NULL;
rtcf = NULL;
- app = NULL;
} else {
nxt_queue_remove(&skcf->link);
@@ -2923,8 +2950,16 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint)
nxt_thread_spin_unlock(lock);
- if (app != NULL) {
- nxt_router_app_use(task, app, -1);
+ if (skcf != NULL) {
+ if (skcf->pass != NULL) {
+ nxt_http_pass_cleanup(task, skcf->pass);
+ }
+
+#if (NXT_TLS)
+ if (skcf->tls != NULL) {
+ task->thread->runtime->tls->server_free(task, skcf->tls);
+ }
+#endif
}
/* TODO remove engine->port */
@@ -2933,11 +2968,7 @@ nxt_router_conf_release(nxt_task_t *task, nxt_socket_conf_joint_t *joint)
if (rtcf != NULL) {
nxt_debug(task, "old router conf is destroyed");
-#if (NXT_TLS)
- if (skcf->tls != NULL) {
- task->thread->runtime->tls->server_free(task, skcf->tls);
- }
-#endif
+ nxt_http_routes_cleanup(task, rtcf->routes);
nxt_router_access_log_release(task, lock, rtcf->access_log);
@@ -3363,10 +3394,6 @@ nxt_router_response_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg,
dump_size = 300;
}
- nxt_debug(task, "%srouter app data (%uz): %*s",
- msg->port_msg.last ? "last " : "", msg->size, dump_size,
- b->mem.pos);
-
if (msg->size == 0) {
b = NULL;
}
@@ -3500,7 +3527,7 @@ static const nxt_http_request_state_t nxt_http_request_send_state
nxt_aligned(64) =
{
.ready_handler = nxt_http_request_send_body,
- .error_handler = nxt_http_request_close_handler,
+ .error_handler = nxt_http_request_error_handler,
};
@@ -4425,10 +4452,10 @@ 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_app_parse_ctx_t *ar,
+ nxt_app_t *app)
{
nxt_int_t res;
- nxt_app_t *app;
nxt_port_t *port;
nxt_event_engine_t *engine;
nxt_http_request_t *r;
@@ -4436,13 +4463,6 @@ nxt_router_process_http_request(nxt_task_t *task, nxt_app_parse_ctx_t *ar)
nxt_req_conn_link_t *rc;
r = ar->request;
- app = r->conf->socket_conf->application;
-
- if (app == NULL) {
- nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
- return;
- }
-
engine = task->thread->engine;
rc = nxt_port_rpc_register_handler_ex(task, engine->port,
@@ -4639,12 +4659,13 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r,
h = &r->header;
req_size = sizeof(nxt_unit_request_t)
- + h->method.length + 1
- + h->version.length + 1
- + r->remote.length + 1
- + r->local.length + 1
- + h->target.length + 1
- + (h->path.start != h->target.start ? h->path.length + 1 : 0);
+ + 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);
fields_count = 0;
@@ -4699,6 +4720,11 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r,
p = nxt_cpymem(p, r->local.start, r->local.length);
*p++ = '\0';
+ req->server_name_length = h->server_name.length;
+ nxt_unit_sptr_set(&req->server_name, p);
+ p = nxt_cpymem(p, h->server_name.start, h->server_name.length);
+ *p++ = '\0';
+
target_pos = p;
req->target_length = h->target.length;
nxt_unit_sptr_set(&req->target, p);
@@ -4726,7 +4752,6 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_app_request_t *r,
req->query.offset = 0;
}
- req->host_field = NXT_UNIT_NONE_FIELD;
req->content_length_field = NXT_UNIT_NONE_FIELD;
req->content_type_field = NXT_UNIT_NONE_FIELD;
req->cookie_field = NXT_UNIT_NONE_FIELD;
@@ -4746,10 +4771,7 @@ 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->host.start) {
- req->host_field = dst_field - req->fields;
-
- } else if (field->value == h->content_length.start) {
+ if (field->value == h->content_length.start) {
req->content_length_field = dst_field - req->fields;
} else if (field->value == h->content_type.start) {
diff --git a/src/nxt_router.h b/src/nxt_router.h
index a2da8ff9..dec56bd5 100644
--- a/src/nxt_router.h
+++ b/src/nxt_router.h
@@ -16,6 +16,8 @@ typedef struct nxt_http_request_s nxt_http_request_t;
#include <nxt_application.h>
+typedef struct nxt_http_pass_s nxt_http_pass_t;
+typedef struct nxt_http_routes_s nxt_http_routes_t;
typedef struct nxt_router_access_log_s nxt_router_access_log_t;
@@ -34,6 +36,7 @@ typedef struct {
uint32_t count;
uint32_t threads;
nxt_router_t *router;
+ nxt_http_routes_t *routes;
nxt_mp_t *mem_pool;
nxt_router_access_log_t *access_log;
@@ -137,7 +140,7 @@ typedef struct {
nxt_queue_link_t link;
nxt_router_conf_t *router_conf;
- nxt_app_t *application;
+ nxt_http_pass_t *pass;
/*
* A listen socket time can be shorter than socket configuration life
@@ -189,8 +192,11 @@ 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_app_parse_ctx_t *ar,
+ 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,
+ nxt_str_t *name);
void nxt_router_app_use(nxt_task_t *task, nxt_app_t *app, int i);
void nxt_router_listen_event_release(nxt_task_t *task, nxt_listen_event_t *lev,
nxt_socket_conf_joint_t *joint);
diff --git a/src/nxt_string.c b/src/nxt_string.c
index 1858b58b..7d8c1ce3 100644
--- a/src/nxt_string.c
+++ b/src/nxt_string.c
@@ -96,6 +96,19 @@ nxt_memcpy_lowcase(u_char *dst, const u_char *src, size_t length)
}
+void
+nxt_memcpy_upcase(u_char *dst, const u_char *src, size_t length)
+{
+ u_char c;
+
+ while (length != 0) {
+ c = *src++;
+ *dst++ = nxt_upcase(c);
+ length--;
+ }
+}
+
+
u_char *
nxt_cpystrn(u_char *dst, const u_char *src, size_t length)
{
diff --git a/src/nxt_string.h b/src/nxt_string.h
index 5f82cca8..22a63a17 100644
--- a/src/nxt_string.h
+++ b/src/nxt_string.h
@@ -43,6 +43,8 @@ nxt_memcpy(dst, src, length) \
NXT_EXPORT void nxt_memcpy_lowcase(u_char *dst, const u_char *src,
size_t length);
+NXT_EXPORT void nxt_memcpy_upcase(u_char *dst, const u_char *src,
+ size_t length);
/*
diff --git a/src/nxt_timer.c b/src/nxt_timer.c
index cba4755b..cb94b77c 100644
--- a/src/nxt_timer.c
+++ b/src/nxt_timer.c
@@ -159,9 +159,9 @@ nxt_timer_change(nxt_event_engine_t *engine, nxt_timer_t *timer,
static void
nxt_timer_changes_commit(nxt_event_engine_t *engine)
{
- nxt_timer_t *timer, **add, **add_end;
+ nxt_timer_t *timer;
nxt_timers_t *timers;
- nxt_timer_change_t *ch, *end;
+ nxt_timer_change_t *ch, *end, *add, *add_end;
timers = &engine->timers;
@@ -170,7 +170,7 @@ nxt_timer_changes_commit(nxt_event_engine_t *engine)
ch = timers->changes;
end = ch + timers->nchanges;
- add = (nxt_timer_t **) ch;
+ add = ch;
add_end = add;
while (ch < end) {
@@ -185,7 +185,8 @@ nxt_timer_changes_commit(nxt_event_engine_t *engine)
timer->time = ch->time;
- *add_end++ = timer;
+ add_end->timer = timer;
+ add_end++;
if (!nxt_timer_is_in_tree(timer)) {
break;
@@ -209,7 +210,7 @@ nxt_timer_changes_commit(nxt_event_engine_t *engine)
}
while (add < add_end) {
- timer = *add;
+ timer = add->timer;
nxt_debug(timer->task, "timer rbtree insert: %M±%d",
timer->time, timer->bias);
diff --git a/src/nxt_unit.c b/src/nxt_unit.c
index 24e51075..6339aec5 100644
--- a/src/nxt_unit.c
+++ b/src/nxt_unit.c
@@ -541,6 +541,8 @@ nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id,
memcpy(&fd, CMSG_DATA(cm), sizeof(int));
}
+ nxt_queue_init(&incoming_buf);
+
if (nxt_slow_path(buf_size < sizeof(nxt_port_msg_t))) {
nxt_unit_warn(ctx, "message too small (%d bytes)", (int) buf_size);
goto fail;
@@ -570,8 +572,6 @@ nxt_unit_process_msg(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *port_id,
}
if (port_msg->mmap) {
- nxt_queue_init(&incoming_buf);
-
if (nxt_unit_mmap_read(ctx, &recv_msg, &incoming_buf) != NXT_UNIT_OK) {
goto fail;
}
@@ -890,59 +890,6 @@ nxt_unit_field_hash(const char *name, size_t name_length)
void
-nxt_unit_split_host(char *host, uint32_t host_length,
- char **name, uint32_t *name_length, char **port, uint32_t *port_length)
-{
- char *cpos;
-
- static char default_host[] = "localhost";
- static char default_port[] = "80";
-
- if (nxt_slow_path(host == NULL || host_length == 0)) {
- *name = default_host;
- *name_length = nxt_length(default_host);
-
- *port = default_port;
- *port_length = nxt_length(default_port);
-
- return;
- }
-
- cpos = memchr(host, ':', host_length);
-
- if (nxt_slow_path(cpos == NULL)) {
- *name = host;
- *name_length = host_length;
-
- *port = default_port;
- *port_length = nxt_length(default_port);
-
- return;
- }
-
- if (nxt_slow_path(cpos == host)) {
- *name = default_host;
- *name_length = nxt_length(default_host);
-
- } else {
- *name = host;
- *name_length = cpos - host;
- }
-
- cpos++;
-
- if (nxt_slow_path(host + host_length == cpos)) {
- *port = default_port;
- *port_length = nxt_length(default_port);
-
- } else {
- *port = cpos;
- *port_length = host_length - (cpos - host);
- }
-}
-
-
-void
nxt_unit_request_group_dup_fields(nxt_unit_request_info_t *req)
{
uint32_t i, j;
@@ -957,10 +904,6 @@ nxt_unit_request_group_dup_fields(nxt_unit_request_info_t *req)
for (i = 0; i < r->fields_count; i++) {
switch (fields[i].hash) {
- case NXT_UNIT_HASH_HOST:
- r->host_field = i;
- break;
-
case NXT_UNIT_HASH_CONTENT_LENGTH:
r->content_length_field = i;
break;
@@ -1104,8 +1047,11 @@ nxt_unit_response_realloc(nxt_unit_request_info_t *req,
+ max_fields_count * sizeof(nxt_unit_field_t)
+ max_fields_size;
+ nxt_unit_req_debug(req, "realloc %"PRIu32"", buf_size);
+
buf = nxt_unit_response_buf_alloc(req, buf_size);
if (nxt_slow_path(buf == NULL)) {
+ nxt_unit_req_warn(req, "realloc: new buf allocation failed");
return NXT_UNIT_ERROR;
}
@@ -1120,23 +1066,29 @@ nxt_unit_response_realloc(nxt_unit_request_info_t *req,
f = resp->fields;
for (i = 0; i < req->response->fields_count; i++) {
- src = req->request->fields + i;
+ src = req->response->fields + i;
if (nxt_slow_path(src->skip != 0)) {
continue;
}
- if (nxt_slow_path(src->name_length + src->value_length
+ if (nxt_slow_path(src->name_length + src->value_length + 2
> (uint32_t) (buf->end - p)))
{
+ nxt_unit_req_warn(req, "realloc: not enough space for field"
+ " #%"PRIu32" (%p), (%"PRIu32" + %"PRIu32") required",
+ i, src, src->name_length, src->value_length);
+
goto fail;
}
nxt_unit_sptr_set(&f->name, p);
p = nxt_cpymem(p, nxt_unit_sptr_get(&src->name), src->name_length);
+ *p++ = '\0';
nxt_unit_sptr_set(&f->value, p);
p = nxt_cpymem(p, nxt_unit_sptr_get(&src->value), src->value_length);
+ *p++ = '\0';
f->hash = src->hash;
f->skip = 0;
@@ -1151,6 +1103,10 @@ nxt_unit_response_realloc(nxt_unit_request_info_t *req,
if (nxt_slow_path(req->response->piggyback_content_length
> (uint32_t) (buf->end - p)))
{
+ nxt_unit_req_warn(req, "realloc: not enought space for content"
+ " #%"PRIu32", %"PRIu32" required",
+ i, req->response->piggyback_content_length);
+
goto fail;
}
@@ -1219,7 +1175,7 @@ nxt_unit_response_add_field(nxt_unit_request_info_t *req,
buf = req->response_buf;
- if (nxt_slow_path(name_length + value_length
+ if (nxt_slow_path(name_length + value_length + 2
> (uint32_t) (buf->end - buf->free)))
{
nxt_unit_req_warn(req, "add_field: response buffer overflow");
@@ -1236,9 +1192,11 @@ nxt_unit_response_add_field(nxt_unit_request_info_t *req,
nxt_unit_sptr_set(&f->name, buf->free);
buf->free = nxt_cpymem(buf->free, name, name_length);
+ *buf->free++ = '\0';
nxt_unit_sptr_set(&f->value, buf->free);
buf->free = nxt_cpymem(buf->free, value, value_length);
+ *buf->free++ = '\0';
f->hash = nxt_unit_field_hash(name, name_length);
f->skip = 0;
@@ -1622,7 +1580,9 @@ nxt_unit_buf_next(nxt_unit_buf_t *buf)
lnk = &mmap_buf->link;
- if (lnk == nxt_queue_last(&req_impl->incoming_buf)) {
+ if (lnk == nxt_queue_last(&req_impl->incoming_buf)
+ || lnk == nxt_queue_last(&req_impl->outgoing_buf))
+ {
return NULL;
}
@@ -1767,6 +1727,9 @@ nxt_unit_response_write_cb(nxt_unit_request_info_t *req,
}
while (!read_info->eof) {
+ nxt_unit_req_debug(req, "write_cb, alloc %"PRIu32"",
+ read_info->buf_size);
+
buf = nxt_unit_response_buf_alloc(req, nxt_min(read_info->buf_size,
PORT_MMAP_DATA_SIZE));
if (nxt_slow_path(buf == NULL)) {
@@ -2506,14 +2469,12 @@ nxt_unit_mmap_read(nxt_unit_ctx_t *ctx, nxt_unit_recv_msg_t *recv_msg,
b->buf.end = b->buf.start + size;
b->hdr = hdr;
- nxt_unit_debug(ctx, "#%"PRIu32": mmap_read: [%p,%d] %d->%d,(%d,%d,%d)\n"
- "%.*s",
+ nxt_unit_debug(ctx, "#%"PRIu32": mmap_read: [%p,%d] %d->%d,(%d,%d,%d)",
recv_msg->port_msg.stream,
start, (int) size,
(int) hdr->src_pid, (int) hdr->dst_pid,
(int) hdr->id, (int) mmap_msg->chunk_id,
- (int) mmap_msg->size,
- (int) size, (char *) start);
+ (int) mmap_msg->size);
}
pthread_mutex_unlock(&process->incoming.mutex);
diff --git a/src/nxt_unit.h b/src/nxt_unit.h
index a3fcc541..532de20d 100644
--- a/src/nxt_unit.h
+++ b/src/nxt_unit.h
@@ -11,7 +11,7 @@
#include <sys/types.h>
#include <string.h>
-#include "nxt_unit_version.h"
+#include "nxt_version.h"
#include "nxt_unit_typedefs.h"
diff --git a/src/nxt_unit_field.h b/src/nxt_unit_field.h
index 0d490f31..d19db0f0 100644
--- a/src/nxt_unit_field.h
+++ b/src/nxt_unit_field.h
@@ -12,7 +12,6 @@
#include "nxt_unit_sptr.h"
enum {
- NXT_UNIT_HASH_HOST = 0xE6EB,
NXT_UNIT_HASH_CONTENT_LENGTH = 0x1EA0,
NXT_UNIT_HASH_CONTENT_TYPE = 0x5F7D,
NXT_UNIT_HASH_COOKIE = 0x23F2,
diff --git a/src/nxt_unit_request.h b/src/nxt_unit_request.h
index af5c29a1..88d569a6 100644
--- a/src/nxt_unit_request.h
+++ b/src/nxt_unit_request.h
@@ -19,12 +19,12 @@ struct nxt_unit_request_s {
uint8_t version_length;
uint8_t remote_length;
uint8_t local_length;
+ uint32_t server_name_length;
uint32_t target_length;
uint32_t path_length;
uint32_t query_length;
uint32_t fields_count;
- uint32_t host_field;
uint32_t content_length_field;
uint32_t content_type_field;
uint32_t cookie_field;
@@ -35,6 +35,7 @@ struct nxt_unit_request_s {
nxt_unit_sptr_t version;
nxt_unit_sptr_t remote;
nxt_unit_sptr_t local;
+ nxt_unit_sptr_t server_name;
nxt_unit_sptr_t target;
nxt_unit_sptr_t path;
nxt_unit_sptr_t query;
diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c
index da4a3864..0b4b31d7 100644
--- a/src/perl/nxt_perl_psgi.c
+++ b/src/perl/nxt_perl_psgi.c
@@ -51,6 +51,8 @@ static void nxt_perl_psgi_xs_init(pTHX);
static SV *nxt_perl_psgi_call_var_application(PerlInterpreter *my_perl,
SV *env, SV *app, nxt_unit_request_info_t *req);
+static SV *nxt_perl_psgi_call_method(PerlInterpreter *my_perl, SV *obj,
+ const char *method, nxt_unit_request_info_t *req);
/* For currect load XS modules */
EXTERN_C void boot_DynaLoader(pTHX_ CV *cv);
@@ -68,7 +70,7 @@ static SV *nxt_perl_psgi_env_create(PerlInterpreter *my_perl,
nxt_inline int nxt_perl_psgi_add_sptr(PerlInterpreter *my_perl, HV *hash_env,
const char *name, uint32_t name_len, nxt_unit_sptr_t *sptr, uint32_t len);
nxt_inline int nxt_perl_psgi_add_str(PerlInterpreter *my_perl, HV *hash_env,
- const char *name, uint32_t name_len, char *str, uint32_t len);
+ const char *name, uint32_t name_len, const char *str, uint32_t len);
nxt_inline int nxt_perl_psgi_add_value(PerlInterpreter *my_perl, HV *hash_env,
const char *name, uint32_t name_len, void *value);
@@ -84,10 +86,14 @@ static int nxt_perl_psgi_result_body(PerlInterpreter *my_perl,
SV *result, nxt_unit_request_info_t *req);
static int nxt_perl_psgi_result_body_ref(PerlInterpreter *my_perl,
SV *sv_body, nxt_unit_request_info_t *req);
-static ssize_t nxt_perl_psgi_io_read(nxt_unit_read_info_t *read_info,
- void *dst, size_t size);
+static int nxt_perl_psgi_result_body_fh(PerlInterpreter *my_perl, SV *sv_body,
+ nxt_unit_request_info_t *req);
+static ssize_t nxt_perl_psgi_io_read(nxt_unit_read_info_t *read_info, void *dst,
+ size_t size);
static int nxt_perl_psgi_result_array(PerlInterpreter *my_perl,
SV *result, nxt_unit_request_info_t *req);
+static void nxt_perl_psgi_result_cb(PerlInterpreter *my_perl, SV *result,
+ nxt_unit_request_info_t *req);
static nxt_int_t nxt_perl_psgi_init(nxt_task_t *task,
nxt_common_app_conf_t *conf);
@@ -97,8 +103,11 @@ static void nxt_perl_psgi_atexit(void);
typedef SV *(*nxt_perl_psgi_callback_f)(PerlInterpreter *my_perl,
SV *env, nxt_task_t *task);
-static PerlInterpreter *nxt_perl_psgi;
-static nxt_perl_psgi_io_arg_t nxt_perl_psgi_arg_input, nxt_perl_psgi_arg_error;
+static CV *nxt_perl_psgi_cb;
+static PerlInterpreter *nxt_perl_psgi;
+static nxt_perl_psgi_io_arg_t nxt_perl_psgi_arg_input;
+static nxt_perl_psgi_io_arg_t nxt_perl_psgi_arg_error;
+static nxt_unit_request_info_t *nxt_perl_psgi_request;
static uint32_t nxt_perl_psgi_compat[] = {
NXT_VERNUM, NXT_DEBUG,
@@ -109,6 +118,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = {
nxt_perl_psgi_compat,
nxt_string("perl"),
PERL_VERSION_STRING,
+ NULL,
nxt_perl_psgi_init,
};
@@ -201,6 +211,115 @@ XS(XS_NGINX__Unit__PSGI_exit)
}
+XS(XS_NGINX__Unit__Sandbox_write);
+XS(XS_NGINX__Unit__Sandbox_write)
+{
+ int rc;
+ char *body;
+ size_t len;
+
+ dXSARGS;
+
+ if (nxt_slow_path(items != 2)) {
+ Perl_croak(aTHX_ "Wrong number of arguments. Need one string");
+
+ XSRETURN_EMPTY;
+ }
+
+ body = SvPV(ST(1), len);
+
+ rc = nxt_unit_response_write(nxt_perl_psgi_request, body, len);
+ if (nxt_slow_path(rc != NXT_UNIT_OK)) {
+ Perl_croak(aTHX_ "Failed to write response body");
+
+ XSRETURN_EMPTY;
+ }
+
+ XSRETURN_IV(len);
+}
+
+
+nxt_inline void
+nxt_perl_psgi_cb_request_done(nxt_int_t status)
+{
+ nxt_unit_request_info_t *req;
+
+ req = nxt_perl_psgi_request;
+
+ if (req != NULL) {
+ nxt_unit_request_done(req, status);
+ nxt_perl_psgi_request = NULL;
+ }
+}
+
+
+XS(XS_NGINX__Unit__Sandbox_close);
+XS(XS_NGINX__Unit__Sandbox_close)
+{
+ I32 ax;
+
+ ax = POPMARK;
+
+ nxt_perl_psgi_cb_request_done(NXT_UNIT_OK);
+
+ XSRETURN_NO;
+}
+
+
+XS(XS_NGINX__Unit__Sandbox_cb);
+XS(XS_NGINX__Unit__Sandbox_cb)
+{
+ SV *obj;
+ int rc;
+ long array_len;
+
+ dXSARGS;
+
+ if (nxt_slow_path(items != 1)) {
+ nxt_perl_psgi_cb_request_done(NXT_UNIT_ERROR);
+
+ Perl_croak(aTHX_ "Wrong number of arguments");
+
+ XSRETURN_EMPTY;
+ }
+
+ if (nxt_slow_path(SvOK(ST(0)) == 0 || SvROK(ST(0)) == 0
+ || SvTYPE(SvRV(ST(0))) != SVt_PVAV))
+ {
+ nxt_perl_psgi_cb_request_done(NXT_UNIT_ERROR);
+
+ Perl_croak(aTHX_ "PSGI: An unexpected response was received "
+ "from Perl Application");
+
+ XSRETURN_EMPTY;
+ }
+
+ rc = nxt_perl_psgi_result_array(PERL_GET_CONTEXT, ST(0),
+ nxt_perl_psgi_request);
+ if (nxt_slow_path(rc != NXT_UNIT_OK)) {
+ nxt_perl_psgi_cb_request_done(NXT_UNIT_ERROR);
+
+ Perl_croak(aTHX_ (char *) NULL);
+
+ XSRETURN_EMPTY;
+ }
+
+ array_len = av_len((AV *) SvRV(ST(0)));
+
+ if (array_len < 2) {
+ obj = sv_bless(newRV_noinc((SV *) newHV()),
+ gv_stashpv("NGINX::Unit::Sandbox", GV_ADD));
+ ST(0) = obj;
+
+ XSRETURN(1);
+ }
+
+ nxt_perl_psgi_cb_request_done(NXT_UNIT_OK);
+
+ XSRETURN_EMPTY;
+}
+
+
static void
nxt_perl_psgi_xs_init(pTHX)
{
@@ -213,6 +332,14 @@ nxt_perl_psgi_xs_init(pTHX)
/* DynaLoader for Perl modules who use XS */
newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, __FILE__);
+
+ newXS("NGINX::Unit::Sandbox::write", XS_NGINX__Unit__Sandbox_write,
+ __FILE__);
+ newXS("NGINX::Unit::Sandbox::close", XS_NGINX__Unit__Sandbox_close,
+ __FILE__);
+
+ nxt_perl_psgi_cb = newXS("NGINX::Unit::Sandbox::cb",
+ XS_NGINX__Unit__Sandbox_cb, __FILE__);
}
@@ -251,6 +378,42 @@ nxt_perl_psgi_call_var_application(PerlInterpreter *my_perl,
}
+static SV *
+nxt_perl_psgi_call_method(PerlInterpreter *my_perl, SV *obj, const char *method,
+ nxt_unit_request_info_t *req)
+{
+ SV *result;
+
+ dSP;
+
+ ENTER;
+ SAVETMPS;
+
+ PUSHMARK(sp);
+ XPUSHs(obj);
+ PUTBACK;
+
+ call_method(method, G_EVAL|G_SCALAR);
+
+ SPAGAIN;
+
+ if (SvTRUE(ERRSV)) {
+ nxt_unit_req_error(req, "PSGI: Failed to call method '%s':\n%s",
+ method, SvPV_nolen(ERRSV));
+ result = NULL;
+
+ } else {
+ result = SvREFCNT_inc(POPs);
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return result;
+}
+
+
static u_char *
nxt_perl_psgi_module_create(nxt_task_t *task, const char *script)
{
@@ -259,6 +422,9 @@ nxt_perl_psgi_module_create(nxt_task_t *task, const char *script)
static nxt_str_t prefix = nxt_string(
"package NGINX::Unit::Sandbox;"
+ "sub new {"
+ " return bless {}, $_[0];"
+ "}"
"{my $app = do \""
);
@@ -450,8 +616,7 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl,
{
HV *hash_env;
AV *array_version;
- char *host_start, *port_start;
- uint32_t i, host_length, port_length;
+ uint32_t i;
nxt_unit_field_t *f;
nxt_unit_request_t *r;
@@ -506,12 +671,10 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl,
RC(nxt_perl_psgi_add_value(my_perl, hash_env, NL("psgi.nonblocking"),
&PL_sv_no));
RC(nxt_perl_psgi_add_value(my_perl, hash_env, NL("psgi.streaming"),
- &PL_sv_no));
+ &PL_sv_yes));
- if (r->query.offset) {
- RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("QUERY_STRING"),
- &r->query, r->query_length));
- }
+ RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("QUERY_STRING"),
+ &r->query, r->query_length));
RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("SERVER_PROTOCOL"),
&r->version, r->version_length));
RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("REMOTE_ADDR"),
@@ -519,6 +682,10 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl,
RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("SERVER_ADDR"),
&r->local, r->local_length));
+ RC(nxt_perl_psgi_add_sptr(my_perl, hash_env, NL("SERVER_NAME"),
+ &r->server_name, r->server_name_length));
+ RC(nxt_perl_psgi_add_str(my_perl, hash_env, NL("SERVER_PORT"), "80", 2));
+
for (i = 0; i < r->fields_count; i++) {
f = r->fields + i;
@@ -541,25 +708,6 @@ nxt_perl_psgi_env_create(PerlInterpreter *my_perl,
&f->value, f->value_length));
}
- if (r->host_field != NXT_UNIT_NONE_FIELD) {
- f = r->fields + r->host_field;
-
- host_start = nxt_unit_sptr_get(&f->value);
- host_length = f->value_length;
-
- } else {
- host_start = NULL;
- host_length = 0;
- }
-
- nxt_unit_split_host(host_start, host_length, &host_start, &host_length,
- &port_start, &port_length);
-
- RC(nxt_perl_psgi_add_str(my_perl, hash_env, NL("SERVER_NAME"),
- host_start, host_length));
- RC(nxt_perl_psgi_add_str(my_perl, hash_env, NL("SERVER_PORT"),
- port_start, port_length));
-
#undef NL
#undef RC
@@ -584,7 +732,7 @@ nxt_perl_psgi_add_sptr(PerlInterpreter *my_perl, HV *hash_env,
nxt_inline int
nxt_perl_psgi_add_str(PerlInterpreter *my_perl, HV *hash_env,
- const char *name, uint32_t name_len, char *str, uint32_t len)
+ const char *name, uint32_t name_len, const char *str, uint32_t len)
{
SV **ha;
@@ -765,6 +913,68 @@ nxt_perl_psgi_result_body(PerlInterpreter *my_perl, SV *sv_body,
}
+static int
+nxt_perl_psgi_result_body_ref(PerlInterpreter *my_perl, SV *sv_body,
+ nxt_unit_request_info_t *req)
+{
+ SV *data, *old_rs, *old_perl_rs;
+ int rc;
+ size_t len;
+ const char *body;
+
+ /*
+ * Servers should set the $/ special variable to the buffer size
+ * when reading content from $body using the getline method.
+ * This is done by setting $/ with a reference to an integer ($/ = \8192).
+ */
+
+ old_rs = PL_rs;
+ old_perl_rs = get_sv("/", GV_ADD);
+
+ PL_rs = sv_2mortal(newRV_noinc(newSViv(nxt_unit_buf_min())));
+
+ sv_setsv(old_perl_rs, PL_rs);
+
+ rc = NXT_UNIT_OK;
+
+ for ( ;; ) {
+ data = nxt_perl_psgi_call_method(my_perl, sv_body, "getline", req);
+ if (nxt_slow_path(data == NULL)) {
+ rc = NXT_UNIT_ERROR;
+ break;
+ }
+
+ body = SvPV(data, len);
+
+ if (len == 0) {
+ SvREFCNT_dec(data);
+
+ data = nxt_perl_psgi_call_method(my_perl, sv_body, "close", req);
+ if (nxt_fast_path(data != NULL)) {
+ SvREFCNT_dec(data);
+ }
+
+ break;
+ }
+
+ rc = nxt_unit_response_write(req, body, len);
+
+ SvREFCNT_dec(data);
+
+ if (nxt_slow_path(rc != NXT_UNIT_OK)) {
+ nxt_unit_req_error(req, "PSGI: Failed to write content from "
+ "Perl Application");
+ break;
+ }
+ };
+
+ PL_rs = old_rs;
+ sv_setsv(get_sv("/", GV_ADD), old_perl_rs);
+
+ return rc;
+}
+
+
typedef struct {
PerlInterpreter *my_perl;
PerlIO *fp;
@@ -772,7 +982,7 @@ typedef struct {
static int
-nxt_perl_psgi_result_body_ref(PerlInterpreter *my_perl, SV *sv_body,
+nxt_perl_psgi_result_body_fh(PerlInterpreter *my_perl, SV *sv_body,
nxt_unit_request_info_t *req)
{
IO *io;
@@ -882,10 +1092,44 @@ nxt_perl_psgi_result_array(PerlInterpreter *my_perl, SV *result,
return nxt_perl_psgi_result_body(my_perl, *sv_temp, req);
}
+ if (SvTYPE(SvRV(*sv_temp)) == SVt_PVGV) {
+ return nxt_perl_psgi_result_body_fh(my_perl, *sv_temp, req);
+ }
+
return nxt_perl_psgi_result_body_ref(my_perl, *sv_temp, req);
}
+static void
+nxt_perl_psgi_result_cb(PerlInterpreter *my_perl, SV *result,
+ nxt_unit_request_info_t *req)
+{
+ dSP;
+
+ ENTER;
+ SAVETMPS;
+
+ PUSHMARK(sp);
+ XPUSHs(newRV_noinc((SV*) nxt_perl_psgi_cb));
+ PUTBACK;
+
+ call_sv(result, G_EVAL|G_SCALAR);
+
+ SPAGAIN;
+
+ if (SvTRUE(ERRSV)) {
+ nxt_unit_error(NULL, "PSGI: Failed to execute result callback: \n%s",
+ SvPV_nolen(ERRSV));
+
+ nxt_perl_psgi_cb_request_done(NXT_UNIT_ERROR);
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+
static nxt_int_t
nxt_perl_psgi_init(nxt_task_t *task, nxt_common_app_conf_t *conf)
{
@@ -942,6 +1186,8 @@ nxt_perl_psgi_request_handler(nxt_unit_request_info_t *req)
input.my_perl = my_perl;
input.req = req;
+ nxt_perl_psgi_request = req;
+
/*
* Create environ variable for perl sub "application".
* > sub application {
@@ -962,23 +1208,26 @@ nxt_perl_psgi_request_handler(nxt_unit_request_info_t *req)
/* Call perl sub and get result as SV*. */
result = nxt_perl_psgi_call_var_application(my_perl, env, module->app, req);
- /*
- * We expect ARRAY ref like a
- * ['200', ['Content-Type' => "text/plain"], ["body"]]
- */
- if (nxt_slow_path(SvOK(result) == 0 || SvROK(result) == 0
- || SvTYPE(SvRV(result)) != SVt_PVAV))
- {
- nxt_unit_req_error(req, "PSGI: An unexpected response was received "
- "from Perl Application");
+ if (nxt_fast_path(SvOK(result) != 0 && SvROK(result) != 0)) {
- rc = NXT_UNIT_ERROR;
+ if (SvTYPE(SvRV(result)) == SVt_PVAV) {
+ rc = nxt_perl_psgi_result_array(my_perl, result, req);
+ nxt_unit_request_done(req, rc);
+ goto release;
+ }
- } else {
- rc = nxt_perl_psgi_result_array(my_perl, result, req);
+ if (SvTYPE(SvRV(result)) == SVt_PVCV) {
+ nxt_perl_psgi_result_cb(my_perl, result, req);
+ goto release;
+ }
}
- nxt_unit_request_done(req, rc);
+ nxt_unit_req_error(req, "PSGI: An unexpected response was received "
+ "from Perl Application");
+
+ nxt_unit_request_done(req, NXT_UNIT_ERROR);
+
+release:
SvREFCNT_dec(result);
SvREFCNT_dec(env);
diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c
index a08b8189..b2398abe 100644
--- a/src/ruby/nxt_ruby.c
+++ b/src/ruby/nxt_ruby.c
@@ -45,7 +45,7 @@ static int nxt_ruby_read_request(VALUE hash_env);
nxt_inline void nxt_ruby_add_sptr(VALUE hash_env,
const char *name, uint32_t name_len, nxt_unit_sptr_t *sptr, uint32_t len);
nxt_inline void nxt_ruby_add_str(VALUE hash_env,
- const char *name, uint32_t name_len, char *str, uint32_t len);
+ const char *name, uint32_t name_len, const char *str, uint32_t len);
static nxt_int_t nxt_ruby_rack_result_status(VALUE result);
static int nxt_ruby_rack_result_headers(VALUE result, nxt_int_t status);
static int nxt_ruby_hash_info(VALUE r_key, VALUE r_value, VALUE arg);
@@ -76,6 +76,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = {
compat,
nxt_string("ruby"),
ruby_version,
+ NULL,
nxt_ruby_init,
};
@@ -428,8 +429,7 @@ fail:
static int
nxt_ruby_read_request(VALUE hash_env)
{
- char *host_start, *port_start;
- uint32_t i, host_length, port_length;
+ uint32_t i;
nxt_unit_field_t *f;
nxt_unit_request_t *r;
@@ -442,16 +442,18 @@ nxt_ruby_read_request(VALUE hash_env)
nxt_ruby_add_sptr(hash_env, NL("REQUEST_URI"), &r->target,
r->target_length);
nxt_ruby_add_sptr(hash_env, NL("PATH_INFO"), &r->path, r->path_length);
- if (r->query.offset) {
- nxt_ruby_add_sptr(hash_env, NL("QUERY_STRING"), &r->query,
- r->query_length);
- }
+ nxt_ruby_add_sptr(hash_env, NL("QUERY_STRING"), &r->query,
+ r->query_length);
nxt_ruby_add_sptr(hash_env, NL("SERVER_PROTOCOL"), &r->version,
r->version_length);
nxt_ruby_add_sptr(hash_env, NL("REMOTE_ADDR"), &r->remote,
r->remote_length);
nxt_ruby_add_sptr(hash_env, NL("SERVER_ADDR"), &r->local, r->local_length);
+ nxt_ruby_add_sptr(hash_env, NL("SERVER_NAME"), &r->server_name,
+ r->server_name_length);
+ nxt_ruby_add_str(hash_env, NL("SERVER_PORT"), "80", 2);
+
for (i = 0; i < r->fields_count; i++) {
f = r->fields + i;
@@ -473,23 +475,6 @@ nxt_ruby_read_request(VALUE hash_env)
&f->value, f->value_length);
}
- if (r->host_field != NXT_UNIT_NONE_FIELD) {
- f = r->fields + r->host_field;
-
- host_start = nxt_unit_sptr_get(&f->value);
- host_length = f->value_length;
-
- } else {
- host_start = NULL;
- host_length = 0;
- }
-
- nxt_unit_split_host(host_start, host_length, &host_start, &host_length,
- &port_start, &port_length);
-
- nxt_ruby_add_str(hash_env, NL("SERVER_NAME"), host_start, host_length);
- nxt_ruby_add_str(hash_env, NL("SERVER_PORT"), port_start, port_length);
-
#undef NL
return NXT_UNIT_OK;
@@ -510,7 +495,7 @@ nxt_ruby_add_sptr(VALUE hash_env,
nxt_inline void
nxt_ruby_add_str(VALUE hash_env,
- const char *name, uint32_t name_len, char *str, uint32_t len)
+ const char *name, uint32_t name_len, const char *str, uint32_t len)
{
rb_hash_aset(hash_env, rb_str_new(name, name_len), rb_str_new(str, len));
}
diff --git a/test/java/content_type/app.java b/test/java/content_type/app.java
new file mode 100644
index 00000000..7d8a7418
--- /dev/null
+++ b/test/java/content_type/app.java
@@ -0,0 +1,89 @@
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet("/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ if (request.getServletPath().equals("/1")) {
+ response.setContentType("text/plain;charset=utf-8");
+ response.setHeader("X-Character-Encoding", response.getCharacterEncoding());
+ response.setHeader("X-Content-Type", response.getContentType());
+ return;
+ }
+
+ if (request.getServletPath().equals("/2")) {
+ response.setContentType("text/plain");
+ response.setHeader("X-Character-Encoding", response.getCharacterEncoding());
+ response.setHeader("X-Content-Type", response.getContentType());
+ return;
+ }
+
+ if (request.getServletPath().equals("/3")) {
+ response.setContentType("text/plain;charset=utf-8");
+ response.setCharacterEncoding("windows-1251");
+ response.setHeader("X-Character-Encoding", response.getCharacterEncoding());
+ response.setHeader("X-Content-Type", response.getContentType());
+ return;
+ }
+
+ if (request.getServletPath().equals("/4")) {
+ response.setCharacterEncoding("windows-1251");
+ response.setContentType("text/plain");
+ response.setHeader("X-Character-Encoding", response.getCharacterEncoding());
+ response.setHeader("X-Content-Type", response.getContentType());
+ return;
+ }
+
+ if (request.getServletPath().equals("/5")) {
+ response.setContentType("text/plain;charset=utf-8");
+ response.setCharacterEncoding(null);
+ response.setHeader("X-Character-Encoding", response.getCharacterEncoding());
+ response.setHeader("X-Content-Type", response.getContentType());
+ return;
+ }
+
+ if (request.getServletPath().equals("/6")) {
+ response.setContentType("text/plain;charset=utf-8");
+ response.setContentType(null);
+ response.setHeader("X-Character-Encoding", response.getCharacterEncoding());
+ response.setHeader("X-Content-Type", response.getContentType());
+ return;
+ }
+
+ if (request.getServletPath().equals("/7")) {
+ response.setContentType("text/plain;charset=utf-8");
+
+ PrintWriter out = response.getWriter();
+
+ response.setCharacterEncoding("windows-1251");
+ response.setHeader("X-Character-Encoding", response.getCharacterEncoding());
+ response.setHeader("X-Content-Type", response.getContentType());
+ return;
+ }
+
+ if (request.getServletPath().equals("/8")) {
+ response.setContentType("text/plain;charset=utf-8");
+
+ PrintWriter out = response.getWriter();
+
+ response.setContentType("text/html;charset=windows-1251");
+ response.setHeader("X-Character-Encoding", response.getCharacterEncoding());
+ response.setHeader("X-Content-Type", response.getContentType());
+ return;
+ }
+
+ response.sendError(404);
+ }
+}
diff --git a/test/java/cookies/app.java b/test/java/cookies/app.java
new file mode 100644
index 00000000..13cea6d1
--- /dev/null
+++ b/test/java/cookies/app.java
@@ -0,0 +1,30 @@
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet(urlPatterns = "/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null) {
+ for (Cookie c : cookies) {
+ if (c.getName().equals("var1")) {
+ response.addHeader("X-Cookie-1", c.getValue());
+ }
+ if (c.getName().equals("var2")) {
+ response.addHeader("X-Cookie-2", c.getValue());
+ }
+ }
+ }
+ }
+}
diff --git a/test/java/empty/app.java b/test/java/empty/app.java
new file mode 100644
index 00000000..b0fca631
--- /dev/null
+++ b/test/java/empty/app.java
@@ -0,0 +1,18 @@
+
+import java.io.IOException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet("/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ { }
+}
diff --git a/test/java/filter/app.java b/test/java/filter/app.java
new file mode 100644
index 00000000..a5da3997
--- /dev/null
+++ b/test/java/filter/app.java
@@ -0,0 +1,54 @@
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+@WebServlet(urlPatterns = "/")
+public class app extends HttpServlet
+{
+ @WebFilter(urlPatterns = "")
+ public static class filter implements Filter
+ {
+ @Override
+ public void init(FilterConfig filterConfig)
+ {
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException
+ {
+ response.getOutputStream().println("Extra Info");
+ response.setCharacterEncoding("utf-8");
+
+ ((HttpServletResponse) response).addHeader("X-Filter-Before", "1");
+
+ chain.doFilter(request, response);
+
+ ((HttpServletResponse) response).setHeader("X-Filter-After", "1");
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+ }
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ response.getOutputStream().println("This is servlet response");
+ response.setHeader("X-Filter-After", "0");
+ }
+}
diff --git a/test/java/forward/app.java b/test/java/forward/app.java
new file mode 100644
index 00000000..0dea17d6
--- /dev/null
+++ b/test/java/forward/app.java
@@ -0,0 +1,138 @@
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import java.util.Map;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+public class app extends HttpServlet
+{
+ private String id;
+
+ private class RequestWrapper extends HttpServletRequestWrapper
+ {
+ public RequestWrapper(HttpServletRequest r)
+ {
+ super(r);
+ }
+ }
+
+ private class ResponseWrapper extends HttpServletResponseWrapper
+ {
+ public ResponseWrapper(HttpServletResponse r)
+ {
+ super(r);
+ }
+ }
+
+ @Override
+ public void init(ServletConfig sc)
+ throws ServletException
+ {
+ id = sc.getInitParameter("id");
+ }
+
+ private RequestDispatcher getRequestDispatcher(HttpServletRequest request, String str)
+ {
+ String disp = request.getParameter("disp");
+
+ if (disp != null && disp.equals("ctx")) {
+ return request.getServletContext().getRequestDispatcher(str);
+ }
+
+ if (disp != null && disp.equals("name")) {
+ return request.getServletContext().getNamedDispatcher(str);
+ }
+
+ if (disp == null || disp.equals("req")) {
+ return request.getRequestDispatcher(str);
+ }
+
+ return null;
+ }
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ String dtype = "" + request.getDispatcherType();
+
+ response.addHeader("X-" + dtype + "-Id", id);
+ response.addHeader("X-" + dtype + "-Request-URI", "" + request.getRequestURI());
+ response.addHeader("X-" + dtype + "-Servlet-Path", "" + request.getServletPath());
+ response.addHeader("X-" + dtype + "-Path-Info", "" + request.getPathInfo());
+ response.addHeader("X-" + dtype + "-Query-String", "" + request.getQueryString());
+ response.addHeader("X-" + dtype + "-Dispatcher-Type", "" + request.getDispatcherType());
+
+ response.setContentType("text/plain; charset=utf-8");
+
+ Map<String, String[]> pmap = request.getParameterMap();
+
+ for (Map.Entry<String,String[]> p : pmap.entrySet()) {
+ response.addHeader("X-" + dtype + "-Param-" + p.getKey(), "" + String.join(",", p.getValue()));
+ }
+
+ PrintWriter out = response.getWriter();
+
+ if (id.equals("fwd")) {
+ String uri = request.getParameter("uri");
+
+ if (uri != null && request.getDispatcherType() != DispatcherType.FORWARD) {
+ response.addHeader("X-Forward-To", "" + uri);
+
+ out.println("Before forwarding.");
+
+ RequestDispatcher d = getRequestDispatcher(request, uri);
+
+ if (d == null) {
+ out.println("Dispatcher is null");
+ return;
+ }
+
+ try {
+ d.forward(new RequestWrapper(request), new ResponseWrapper(response));
+ } catch(Exception e) {
+ response.addHeader("X-Exception", "" + e);
+ }
+
+ response.addHeader("X-After-Forwarding", "you-should-not-see-this");
+
+ out.println("After forwarding.");
+
+ return;
+ }
+ }
+
+ if (id.equals("data")) {
+ response.addHeader("X-" + RequestDispatcher.FORWARD_REQUEST_URI, "" + request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI));
+ response.addHeader("X-" + RequestDispatcher.FORWARD_CONTEXT_PATH, "" + request.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
+ response.addHeader("X-" + RequestDispatcher.FORWARD_SERVLET_PATH, "" + request.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
+ response.addHeader("X-" + RequestDispatcher.FORWARD_PATH_INFO, "" + request.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
+ response.addHeader("X-" + RequestDispatcher.FORWARD_QUERY_STRING, "" + request.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
+
+ out.println("app.doGet(): #" + this + ", " + id);
+ out.println("RequestURI: " + request.getRequestURI());
+ out.println("ServletPath: " + request.getServletPath());
+ out.println("PathInfo: " + request.getPathInfo());
+ out.println("DispType: " + request.getDispatcherType());
+ out.println("QueryString: " + request.getQueryString());
+
+ for (Map.Entry<String,String[]> p : pmap.entrySet()) {
+ out.println("- " + p.getKey() + "=" + String.join(",", p.getValue()));
+ }
+
+ return;
+ }
+
+ response.sendError(404);
+ }
+}
diff --git a/test/java/forward/index.html b/test/java/forward/index.html
new file mode 100644
index 00000000..4f5a6379
--- /dev/null
+++ b/test/java/forward/index.html
@@ -0,0 +1 @@
+<html><body>This is index.html.</body></html>
diff --git a/test/java/forward/web.xml b/test/java/forward/web.xml
new file mode 100644
index 00000000..994adb37
--- /dev/null
+++ b/test/java/forward/web.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8" ?>
+
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+ version="3.0">
+
+ <servlet>
+ <servlet-name>fwd</servlet-name>
+ <servlet-class>app</servlet-class>
+ <init-param><param-name>id</param-name><param-value>fwd</param-value></init-param>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>fwd</servlet-name>
+ <url-pattern>/fwd/*</url-pattern>
+ </servlet-mapping>
+
+
+ <servlet>
+ <servlet-name>data</servlet-name>
+ <servlet-class>app</servlet-class>
+ <init-param><param-name>id</param-name><param-value>data</param-value></init-param>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>data</servlet-name>
+ <url-pattern>/data/*</url-pattern>
+ </servlet-mapping>
+
+ <servlet-mapping>
+ <servlet-name>data</servlet-name>
+ <url-pattern>/WEB-INF/index.html</url-pattern>
+ <url-pattern>/index.html</url-pattern>
+ </servlet-mapping>
+
+</web-app>
+
diff --git a/test/java/get_header/app.java b/test/java/get_header/app.java
new file mode 100644
index 00000000..c981835d
--- /dev/null
+++ b/test/java/get_header/app.java
@@ -0,0 +1,21 @@
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet("/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ response.addHeader("X-Reply", request.getHeader("X-Header"));
+ }
+}
diff --git a/test/java/get_header_names/app.java b/test/java/get_header_names/app.java
new file mode 100644
index 00000000..cd2f3097
--- /dev/null
+++ b/test/java/get_header_names/app.java
@@ -0,0 +1,27 @@
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet("/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ Enumeration<String> header_names = request.getHeaderNames();
+
+ for (int i = 0; header_names.hasMoreElements(); i++) {
+ response.addHeader("X-Reply-" + Integer.toString(i),
+ header_names.nextElement());
+ }
+ }
+}
diff --git a/test/java/get_headers/app.java b/test/java/get_headers/app.java
new file mode 100644
index 00000000..f2930a61
--- /dev/null
+++ b/test/java/get_headers/app.java
@@ -0,0 +1,27 @@
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet("/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ Enumeration<String> headers = request.getHeaders("X-Header");
+
+ for (int i = 0; headers.hasMoreElements(); i++) {
+ response.addHeader("X-Reply-" + Integer.toString(i),
+ headers.nextElement());
+ }
+ }
+}
diff --git a/test/java/get_params/app.java b/test/java/get_params/app.java
new file mode 100644
index 00000000..1965ae2a
--- /dev/null
+++ b/test/java/get_params/app.java
@@ -0,0 +1,50 @@
+
+import java.io.IOException;
+
+import java.util.Enumeration;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet(urlPatterns = "/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ response.addHeader("X-Var-1", request.getParameter("var1"));
+ response.addHeader("X-Var-2", "" + (request.getParameter("var2") != null));
+ response.addHeader("X-Var-3", "" + (request.getParameter("var3") != null));
+ response.addHeader("X-Var-4", request.getParameter("var4"));
+
+ Enumeration<String> parameter_names = request.getParameterNames();
+
+ String names = "";
+ for (int i = 0; parameter_names.hasMoreElements(); i++) {
+ names = names.concat(parameter_names.nextElement() + " ");
+ }
+ response.addHeader("X-Param-Names", names);
+
+ String[] parameter_values = request.getParameterValues("var4");
+
+ String values = "";
+ for (int i = 0; i < parameter_values.length; i++) {
+ values = values.concat(parameter_values[i] + " ");
+ }
+ response.addHeader("X-Param-Values", values);
+
+ Map <String, String[]> parameter_map = request.getParameterMap();
+
+ String map = "";
+ for (Map.Entry <String, String[]> p : parameter_map.entrySet()) {
+ map = map.concat(p.getKey() + "=" + String.join(",", p.getValue()) + " ");
+ }
+ response.addHeader("X-Param-Map", map);
+ }
+}
diff --git a/test/java/header/app.java b/test/java/header/app.java
new file mode 100644
index 00000000..02d56f4d
--- /dev/null
+++ b/test/java/header/app.java
@@ -0,0 +1,34 @@
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet("/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ response.setHeader("X-Set-Utf8-Value", "тест");
+ response.setHeader("X-Set-Utf8-Name-Имя", "x");
+
+ response.addHeader("X-Add-Utf8-Value", "тест");
+ response.addHeader("X-Add-Utf8-Name-Имя", "y");
+
+ response.addHeader("X-Add-Test", "v1");
+ response.addHeader("X-Add-Test", null);
+
+ response.setHeader("X-Set-Test1", "v1");
+ response.setHeader("X-Set-Test1", null);
+
+ response.setHeader("X-Set-Test2", "v1");
+ response.setHeader("X-Set-Test2", "");
+ }
+}
diff --git a/test/java/header_date/app.java b/test/java/header_date/app.java
new file mode 100644
index 00000000..cedd569c
--- /dev/null
+++ b/test/java/header_date/app.java
@@ -0,0 +1,22 @@
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet("/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ response.setDateHeader("X-Set-Date", 1000);
+ response.addDateHeader("X-Get-Date", request.getDateHeader("X-Header"));
+ }
+}
diff --git a/test/java/header_int/app.java b/test/java/header_int/app.java
new file mode 100644
index 00000000..3ac5478e
--- /dev/null
+++ b/test/java/header_int/app.java
@@ -0,0 +1,22 @@
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet("/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ response.setIntHeader("X-Set-Int", 1);
+ response.addHeader("X-Get-Int", Integer.toString(request.getIntHeader("X-Header")));
+ }
+}
diff --git a/test/java/include/app.java b/test/java/include/app.java
new file mode 100644
index 00000000..d7e36fc6
--- /dev/null
+++ b/test/java/include/app.java
@@ -0,0 +1,136 @@
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import java.util.Map;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+public class app extends HttpServlet
+{
+ private String id;
+
+ private class RequestWrapper extends HttpServletRequestWrapper
+ {
+ public RequestWrapper(HttpServletRequest r)
+ {
+ super(r);
+ }
+ }
+
+ private class ResponseWrapper extends HttpServletResponseWrapper
+ {
+ public ResponseWrapper(HttpServletResponse r)
+ {
+ super(r);
+ }
+ }
+
+ @Override
+ public void init(ServletConfig sc)
+ throws ServletException
+ {
+ id = sc.getInitParameter("id");
+ }
+
+ private RequestDispatcher getRequestDispatcher(HttpServletRequest request, String str)
+ {
+ String disp = request.getParameter("disp");
+
+ if (disp != null && disp.equals("ctx")) {
+ return request.getServletContext().getRequestDispatcher(str);
+ }
+
+ if (disp != null && disp.equals("name")) {
+ return request.getServletContext().getNamedDispatcher(str);
+ }
+
+ if (disp == null || disp.equals("req")) {
+ return request.getRequestDispatcher(str);
+ }
+
+ return null;
+ }
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ String dtype = "" + request.getDispatcherType();
+
+ response.addHeader("X-" + dtype + "-Id", id);
+ response.addHeader("X-" + dtype + "-Request-URI", "" + request.getRequestURI());
+ response.addHeader("X-" + dtype + "-Servlet-Path", "" + request.getServletPath());
+ response.addHeader("X-" + dtype + "-Path-Info", "" + request.getPathInfo());
+ response.addHeader("X-" + dtype + "-Query-String", "" + request.getQueryString());
+ response.addHeader("X-" + dtype + "-Dispatcher-Type", "" + request.getDispatcherType());
+
+ response.setContentType("text/plain; charset=utf-8");
+
+ PrintWriter out = response.getWriter();
+
+ if (id.equals("inc")) {
+ String uri = request.getParameter("uri");
+
+ if (uri != null && request.getDispatcherType() != DispatcherType.INCLUDE) {
+ response.addHeader("X-Include", "" + uri);
+
+ out.println("Before include.");
+
+ RequestDispatcher d = getRequestDispatcher(request, uri);
+
+ if (d == null) {
+ out.println("Dispatcher is null");
+ return;
+ }
+
+ try {
+ d.include(new RequestWrapper(request), new ResponseWrapper(response));
+ } catch(Exception e) {
+ response.addHeader("X-Exception", "" + e);
+ out.println("Exception: " + e);
+ }
+
+ response.addHeader("X-After-Include", "you-should-see-this");
+
+ out.println("After include.");
+
+ return;
+ }
+ }
+
+ if (id.equals("data")) {
+ out.println("app.doGet(): #" + this + ", " + id);
+ out.println("RequestURI: " + request.getRequestURI());
+ out.println("ServletPath: " + request.getServletPath());
+ out.println("PathInfo: " + request.getPathInfo());
+ out.println("DispType: " + request.getDispatcherType());
+ out.println("QueryString: " + request.getQueryString());
+
+ Map<String, String[]> pmap = request.getParameterMap();
+
+ for (Map.Entry<String,String[]> p : pmap.entrySet()) {
+ out.println("- " + p.getKey() + "=" + String.join(",", p.getValue()));
+ }
+
+ out.println(RequestDispatcher.INCLUDE_REQUEST_URI + ": " + request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI));
+ out.println(RequestDispatcher.INCLUDE_CONTEXT_PATH + ": " + request.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH));
+ out.println(RequestDispatcher.INCLUDE_SERVLET_PATH + ": " + request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH));
+ out.println(RequestDispatcher.INCLUDE_PATH_INFO + ": " + request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO));
+ out.println(RequestDispatcher.INCLUDE_QUERY_STRING + ": " + request.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING));
+
+ return;
+ }
+
+ response.sendError(404);
+ }
+}
diff --git a/test/java/include/index.html b/test/java/include/index.html
new file mode 100644
index 00000000..4f5a6379
--- /dev/null
+++ b/test/java/include/index.html
@@ -0,0 +1 @@
+<html><body>This is index.html.</body></html>
diff --git a/test/java/include/web.xml b/test/java/include/web.xml
new file mode 100644
index 00000000..2ed86f1d
--- /dev/null
+++ b/test/java/include/web.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8" ?>
+
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+ version="3.0">
+
+ <servlet>
+ <servlet-name>inc</servlet-name>
+ <servlet-class>app</servlet-class>
+ <init-param><param-name>id</param-name><param-value>inc</param-value></init-param>
+ </servlet>
+
+ <servlet>
+ <servlet-name>data</servlet-name>
+ <servlet-class>app</servlet-class>
+ <init-param><param-name>id</param-name><param-value>data</param-value></init-param>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>inc</servlet-name>
+ <url-pattern>/inc/*</url-pattern>
+ </servlet-mapping>
+
+ <servlet-mapping>
+ <servlet-name>data</servlet-name>
+ <url-pattern>/data/*</url-pattern>
+ </servlet-mapping>
+
+ <servlet-mapping>
+ <servlet-name>data</servlet-name>
+ <url-pattern>/WEB-INF/index.html</url-pattern>
+ <url-pattern>/index.html</url-pattern>
+ </servlet-mapping>
+
+</web-app>
+
diff --git a/test/java/jsp/index.jsp b/test/java/jsp/index.jsp
new file mode 100644
index 00000000..0af00a46
--- /dev/null
+++ b/test/java/jsp/index.jsp
@@ -0,0 +1,2 @@
+<%@ page contentType="text/plain"%>This is plain text response for "<%= request.getMethod() %> <%= request.getRequestURI() %>".
+<% response.addHeader("X-Unit-JSP", "ok"); %>
diff --git a/test/java/mirror/app.java b/test/java/mirror/app.java
new file mode 100644
index 00000000..45bc1d0d
--- /dev/null
+++ b/test/java/mirror/app.java
@@ -0,0 +1,37 @@
+
+import java.io.*;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet("/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ StringBuilder buffer = new StringBuilder();
+ BufferedReader reader = request.getReader();
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+ buffer.append(line);
+ }
+
+ String data = buffer.toString();
+
+ String dataLength = Integer.toString(data.length());
+ response.setHeader("Content-Length", dataLength);
+
+ response.setContentType("text/html");
+
+ PrintWriter out = response.getWriter();
+ out.print(data);
+ out.flush();
+ }
+}
diff --git a/test/java/path_translation/app.java b/test/java/path_translation/app.java
new file mode 100644
index 00000000..ce0b9368
--- /dev/null
+++ b/test/java/path_translation/app.java
@@ -0,0 +1,56 @@
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.InputStream;
+
+import java.util.Set;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet( urlPatterns = { "/", "/pt/*" } )
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ response.addHeader("X-Request-URI", "" + request.getRequestURI());
+ response.addHeader("X-Servlet-Path", "" + request.getServletPath());
+ response.addHeader("X-Path-Info", "" + request.getPathInfo());
+ response.addHeader("X-Query-String", "" + request.getQueryString());
+ response.addHeader("X-Path-Translated", "" + request.getPathTranslated());
+
+ response.setContentType("text/plain; charset=utf-8");
+
+ PrintWriter out = response.getWriter();
+ ServletContext ctx = request.getServletContext();
+
+ String path = request.getParameter("path");
+
+ if (path != null) {
+ response.addHeader("X-Real-Path", "" + ctx.getRealPath(path));
+ response.addHeader("X-Resource", "" + ctx.getResource(path));
+
+ Set<String> paths = ctx.getResourcePaths(path);
+
+ response.addHeader("X-Resource-Paths", "" + paths);
+
+ InputStream is = ctx.getResourceAsStream(path);
+
+ response.addHeader("X-Resource-As-Stream", "" + is);
+
+ if (is != null) {
+ final byte[] buf = new byte[1024];
+ int r = is.read(buf);
+
+ out.println(new String(buf, 0, r, "utf-8"));
+ }
+ }
+ }
+}
diff --git a/test/java/path_translation/index.html b/test/java/path_translation/index.html
new file mode 100644
index 00000000..4f5a6379
--- /dev/null
+++ b/test/java/path_translation/index.html
@@ -0,0 +1 @@
+<html><body>This is index.html.</body></html>
diff --git a/test/java/post_params/app.java b/test/java/post_params/app.java
new file mode 100644
index 00000000..0ed73d42
--- /dev/null
+++ b/test/java/post_params/app.java
@@ -0,0 +1,22 @@
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet(urlPatterns = "/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ response.addHeader("X-Var-1", request.getParameter("var1"));
+ response.addHeader("X-Var-2", "" + (request.getParameter("var2") != null));
+ response.addHeader("X-Var-3", "" + (request.getParameter("var3") != null));
+ }
+}
diff --git a/test/java/query_string/app.java b/test/java/query_string/app.java
new file mode 100644
index 00000000..7962336b
--- /dev/null
+++ b/test/java/query_string/app.java
@@ -0,0 +1,20 @@
+
+import java.io.IOException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet( urlPatterns = { "/" } )
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ response.addHeader("X-Query-String", "" + request.getQueryString());
+ }
+}
diff --git a/test/java/request_listeners/app.java b/test/java/request_listeners/app.java
new file mode 100644
index 00000000..6cbf7860
--- /dev/null
+++ b/test/java/request_listeners/app.java
@@ -0,0 +1,79 @@
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.annotation.WebListener;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebListener
+@WebServlet(urlPatterns = "/")
+public class app extends HttpServlet implements
+ ServletRequestListener,
+ ServletRequestAttributeListener
+{
+ private static String request_initialized = "";
+ private static String request_destroyed = "";
+ private static String attribute_added = "";
+ private static String attribute_removed = "";
+ private static String attribute_replaced = "";
+
+ @Override
+ public void requestInitialized(ServletRequestEvent sre)
+ {
+ HttpServletRequest r = (HttpServletRequest) sre.getServletRequest();
+
+ request_initialized = r.getRequestURI();
+ }
+
+ @Override
+ public void requestDestroyed(ServletRequestEvent sre)
+ {
+ HttpServletRequest r = (HttpServletRequest) sre.getServletRequest();
+
+ request_destroyed = r.getRequestURI();
+
+ attribute_added = "";
+ attribute_removed = "";
+ attribute_replaced = "";
+ }
+
+ @Override
+ public void attributeAdded(ServletRequestAttributeEvent event)
+ {
+ attribute_added += event.getName() + "=" + event.getValue() + ";";
+ }
+
+ @Override
+ public void attributeRemoved(ServletRequestAttributeEvent event)
+ {
+ attribute_removed += event.getName() + "=" + event.getValue() + ";";
+ }
+
+ @Override
+ public void attributeReplaced(ServletRequestAttributeEvent event)
+ {
+ attribute_replaced += event.getName() + "=" + event.getValue() + ";";
+ }
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ request.setAttribute("var", request.getParameter("var1"));
+ request.setAttribute("var", request.getParameter("var2"));
+ request.setAttribute("var", request.getParameter("var3"));
+
+ response.addHeader("X-Request-Initialized", request_initialized);
+ response.addHeader("X-Request-Destroyed", request_destroyed);
+ response.addHeader("X-Attr-Added", attribute_added);
+ response.addHeader("X-Attr-Removed", attribute_removed);
+ response.addHeader("X-Attr-Replaced", attribute_replaced);
+ }
+}
diff --git a/test/java/session/app.java b/test/java/session/app.java
new file mode 100644
index 00000000..84d3fa55
--- /dev/null
+++ b/test/java/session/app.java
@@ -0,0 +1,30 @@
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+@WebServlet(urlPatterns = "/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ HttpSession s = request.getSession();
+ String old_var1 = (String) s.getAttribute("var1");
+ s.setAttribute("var1", request.getParameter("var1"));
+
+ if (old_var1 == null) {
+ response.addHeader("X-Var-1", "null");
+ } else {
+ response.addHeader("X-Var-1", old_var1);
+ }
+
+ response.addHeader("X-Session-Id", s.getId());
+ response.addHeader("X-Session-New", "" + s.isNew());
+ }
+}
diff --git a/test/java/session_inactive/app.java b/test/java/session_inactive/app.java
new file mode 100644
index 00000000..f338fc89
--- /dev/null
+++ b/test/java/session_inactive/app.java
@@ -0,0 +1,27 @@
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+@WebServlet(urlPatterns = "/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ HttpSession s = request.getSession();
+
+ if (s.isNew()) {
+ s.setMaxInactiveInterval(2);
+ }
+
+ response.addHeader("X-Session-Id", s.getId());
+ response.addDateHeader("X-Session-Last-Access-Time", s.getLastAccessedTime());
+ response.addIntHeader("X-Session-Interval", s.getMaxInactiveInterval());
+ }
+}
diff --git a/test/java/session_invalidate/app.java b/test/java/session_invalidate/app.java
new file mode 100644
index 00000000..3f66290f
--- /dev/null
+++ b/test/java/session_invalidate/app.java
@@ -0,0 +1,23 @@
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+@WebServlet(urlPatterns = "/")
+public class app extends HttpServlet
+{
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ HttpSession s = request.getSession();
+
+ s.invalidate();
+
+ response.addHeader("X-Session-Id", s.getId());
+ }
+}
diff --git a/test/java/session_listeners/app.java b/test/java/session_listeners/app.java
new file mode 100644
index 00000000..603cc932
--- /dev/null
+++ b/test/java/session_listeners/app.java
@@ -0,0 +1,80 @@
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
+
+@WebServlet(urlPatterns = "/")
+public class app extends HttpServlet implements
+ HttpSessionListener,
+ HttpSessionIdListener,
+ HttpSessionAttributeListener
+{
+ private static String session_created = "";
+ private static String session_destroyed = "";
+ private static String session_id_changed = "";
+ private static String attribute_added = "";
+ private static String attribute_removed = "";
+ private static String attribute_replaced = "";
+
+ @Override
+ public void sessionCreated(HttpSessionEvent se)
+ {
+ session_created += se.getSession().getId();
+ }
+
+ @Override
+ public void sessionDestroyed(HttpSessionEvent se)
+ {
+ session_destroyed += se.getSession().getId();
+ }
+
+ @Override
+ public void sessionIdChanged(HttpSessionEvent event, String oldId)
+ {
+ session_id_changed += " " + oldId + "->" + event.getSession().getId();
+ }
+
+ @Override
+ public void attributeAdded(HttpSessionBindingEvent event)
+ {
+ attribute_added += event.getName() + "=" + event.getValue();
+ }
+
+ @Override
+ public void attributeRemoved(HttpSessionBindingEvent event)
+ {
+ attribute_removed += event.getName() + "=" + event.getValue();
+ }
+
+ @Override
+ public void attributeReplaced(HttpSessionBindingEvent event)
+ {
+ attribute_replaced += event.getName() + "=" + event.getValue();
+ }
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ HttpSession s = request.getSession();
+ s.setAttribute("var1", request.getParameter("var1"));
+
+ response.addHeader("X-Session-Id", s.getId());
+ response.addHeader("X-Session-Created", session_created);
+ response.addHeader("X-Session-Destroyed", session_destroyed);
+ response.addHeader("X-Attr-Added", attribute_added);
+ response.addHeader("X-Attr-Removed", attribute_removed);
+ response.addHeader("X-Attr-Replaced", attribute_replaced);
+ }
+}
diff --git a/test/java/session_listeners/web.xml b/test/java/session_listeners/web.xml
new file mode 100644
index 00000000..aedfe175
--- /dev/null
+++ b/test/java/session_listeners/web.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8" ?>
+
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+ version="3.0">
+ <listener>
+ <listener-class>app</listener-class>
+ </listener>
+ <listener>
+ <listener-class>app</listener-class>
+ </listener>
+</web-app>
+
diff --git a/test/java/url_pattern/app.java b/test/java/url_pattern/app.java
new file mode 100644
index 00000000..88b071a2
--- /dev/null
+++ b/test/java/url_pattern/app.java
@@ -0,0 +1,39 @@
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class app extends HttpServlet
+{
+ private String id;
+
+ @Override
+ public void init(ServletConfig sc)
+ throws ServletException
+ {
+ id = sc.getInitParameter("id");
+ }
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ response.addHeader("X-Id", id);
+ response.addHeader("X-Request-URI", "" + request.getRequestURI());
+ response.addHeader("X-Servlet-Path", "" + request.getServletPath());
+ response.setHeader("X-Path-Info", "" + request.getPathInfo());
+
+ response.setContentType("text/plain; charset=utf-8");
+
+ PrintWriter out = response.getWriter();
+ out.println("app.doGet(): #" + this + ", " + id);
+ out.println("RequestURI: " + request.getRequestURI());
+ out.println("ServletPath: " + request.getServletPath());
+ out.println("PathInfo: " + request.getPathInfo());
+ }
+}
diff --git a/test/java/url_pattern/web.xml b/test/java/url_pattern/web.xml
new file mode 100644
index 00000000..048400a6
--- /dev/null
+++ b/test/java/url_pattern/web.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8" ?>
+
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+ version="3.0">
+
+ <servlet>
+ <servlet-name>servlet0</servlet-name>
+ <servlet-class>app</servlet-class>
+ <init-param><param-name>id</param-name><param-value>servlet0</param-value></init-param>
+ </servlet>
+
+ <servlet>
+ <servlet-name>servlet1</servlet-name>
+ <servlet-class>app</servlet-class>
+ <init-param><param-name>id</param-name><param-value>servlet1</param-value></init-param>
+ </servlet>
+
+ <servlet>
+ <servlet-name>servlet2</servlet-name>
+ <servlet-class>app</servlet-class>
+ <init-param><param-name>id</param-name><param-value>servlet2</param-value></init-param>
+ </servlet>
+
+ <servlet>
+ <servlet-name>servlet3</servlet-name>
+ <servlet-class>app</servlet-class>
+ <init-param><param-name>id</param-name><param-value>servlet3</param-value></init-param>
+ </servlet>
+
+ <servlet>
+ <servlet-name>servlet4</servlet-name>
+ <servlet-class>app</servlet-class>
+ <init-param><param-name>id</param-name><param-value>servlet4</param-value></init-param>
+ </servlet>
+
+ <servlet>
+ <servlet-name>default</servlet-name>
+ <servlet-class>app</servlet-class>
+ <init-param><param-name>id</param-name><param-value>default</param-value></init-param>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>servlet0</servlet-name>
+ <url-pattern>/foo/*</url-pattern>
+ </servlet-mapping>
+
+ <servlet-mapping>
+ <servlet-name>servlet1</servlet-name>
+ <url-pattern>/foo/bar/*</url-pattern>
+ </servlet-mapping>
+
+ <servlet-mapping>
+ <servlet-name>servlet2</servlet-name>
+ <url-pattern>/baz/*</url-pattern>
+ </servlet-mapping>
+
+ <servlet-mapping>
+ <servlet-name>servlet3</servlet-name>
+ <url-pattern>/catalog</url-pattern>
+ </servlet-mapping>
+
+ <servlet-mapping>
+ <servlet-name>servlet4</servlet-name>
+ <url-pattern>*.bop</url-pattern>
+ </servlet-mapping>
+
+ <servlet-mapping>
+ <servlet-name>default</servlet-name>
+ <url-pattern>/</url-pattern>
+ </servlet-mapping>
+
+</web-app>
+
diff --git a/test/java/welcome_files/app.java b/test/java/welcome_files/app.java
new file mode 100644
index 00000000..ce922531
--- /dev/null
+++ b/test/java/welcome_files/app.java
@@ -0,0 +1,67 @@
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import javax.servlet.annotation.WebFilter;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+public class app extends HttpServlet
+{
+ @WebFilter(urlPatterns = "*.jsp")
+ public static class jsp_filter implements Filter
+ {
+ @Override
+ public void init(FilterConfig filterConfig) { }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException
+ {
+ ((HttpServletResponse) response).addHeader("X-JSP-Filter", "1");
+
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ public void destroy() { }
+ }
+
+ @WebFilter(urlPatterns = "*.txt")
+ public static class txt_filter implements Filter
+ {
+ @Override
+ public void init(FilterConfig filterConfig) { }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException
+ {
+ ((HttpServletResponse) response).addHeader("X-TXT-Filter", "1");
+
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ public void destroy() { }
+ }
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ response.addHeader("X-App-Servlet", "1");
+ response.setContentType("text/plain; charset=utf-8");
+
+ PrintWriter out = response.getWriter();
+ out.println("App Servlet");
+ }
+}
diff --git a/test/java/welcome_files/dir1/index.txt b/test/java/welcome_files/dir1/index.txt
new file mode 100644
index 00000000..e7784d20
--- /dev/null
+++ b/test/java/welcome_files/dir1/index.txt
@@ -0,0 +1 @@
+This is index.txt.
diff --git a/test/java/welcome_files/dir2/default.jsp b/test/java/welcome_files/dir2/default.jsp
new file mode 100644
index 00000000..48627641
--- /dev/null
+++ b/test/java/welcome_files/dir2/default.jsp
@@ -0,0 +1,3 @@
+<%@ page contentType="text/html"%>
+<html><body><p>You should see this on <a href="/dir2/">/dir2/</a> URL.</p></body></html>
+<% response.addHeader("X-Unit-JSP", "ok"); %>
diff --git a/test/java/welcome_files/dir2/index.html b/test/java/welcome_files/dir2/index.html
new file mode 100644
index 00000000..5b111825
--- /dev/null
+++ b/test/java/welcome_files/dir2/index.html
@@ -0,0 +1 @@
+<html><body><p>You should see this on <a href="/dir2/">/dir2/</a> URL.</p></body></html>
diff --git a/test/java/welcome_files/dir3/index.txt b/test/java/welcome_files/dir3/index.txt
new file mode 100644
index 00000000..8a2b7dea
--- /dev/null
+++ b/test/java/welcome_files/dir3/index.txt
@@ -0,0 +1 @@
+You should never see this.
diff --git a/test/java/welcome_files/dir4/index.html b/test/java/welcome_files/dir4/index.html
new file mode 100644
index 00000000..2cef75e2
--- /dev/null
+++ b/test/java/welcome_files/dir4/index.html
@@ -0,0 +1 @@
+<html><body><p>You should see this for <a href="/dir4/index.html">/dir4/index.html</a> or <a href="/dir4/">/dir4/</a> url.</body></html>
diff --git a/test/java/welcome_files/index.htm b/test/java/welcome_files/index.htm
new file mode 100644
index 00000000..97e34cf5
--- /dev/null
+++ b/test/java/welcome_files/index.htm
@@ -0,0 +1 @@
+<html><body><p>You should see this ONLY for <a href="/index.htm">/index.htm</a> url.</body></html>
diff --git a/test/java/welcome_files/web.xml b/test/java/welcome_files/web.xml
new file mode 100644
index 00000000..6bbc7c8e
--- /dev/null
+++ b/test/java/welcome_files/web.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8" ?>
+
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+ version="3.0">
+
+ <welcome-file-list>
+ <welcome-file>index.txt</welcome-file>
+ <welcome-file>default.jsp</welcome-file>
+ <welcome-file>index.html</welcome-file>
+ </welcome-file-list>
+
+ <servlet>
+ <servlet-name>app</servlet-name>
+ <servlet-class>app</servlet-class>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>app</servlet-name>
+ <url-pattern>/dir3/index.txt</url-pattern>
+ <url-pattern>/dir4/index.txt</url-pattern>
+ <url-pattern>/dir5/index.html</url-pattern>
+ </servlet-mapping>
+
+</web-app>
+
diff --git a/test/node/write_before_write_head/app.js b/test/node/write_before_write_head/app.js
index 9fe3a58d..6e3fb9a9 100755
--- a/test/node/write_before_write_head/app.js
+++ b/test/node/write_before_write_head/app.js
@@ -3,4 +3,5 @@
require('unit-http').createServer(function (req, res) {
res.write('blah');
res.writeHead(200, {'Content-Type': 'text/plain'});
+ res.end();
}).listen(7080);
diff --git a/test/perl/body_io_fake/IOFake.pm b/test/perl/body_io_fake/IOFake.pm
new file mode 100644
index 00000000..d2542aa5
--- /dev/null
+++ b/test/perl/body_io_fake/IOFake.pm
@@ -0,0 +1,33 @@
+package IOFake;
+
+sub new {
+ my $class = shift;
+ my $errors = shift;
+ my $self = {};
+
+ $self->{_count} = 2;
+ $self->{_errors} = $errors;
+
+ bless $self, $class;
+ return $self;
+}
+
+sub getline() {
+ my $self = shift;
+
+ if ($self->{_count} > 0) {
+ return $self->{_count}--;
+ }
+
+ $self->{_errors}->print('IOFake getline() $/ is ' . ${ $/ });
+
+ return;
+}
+
+sub close() {
+ my $self = shift;
+
+ $self->{_errors}->print('IOFake close() called');
+};
+
+1;
diff --git a/test/perl/body_io_fake/psgi.pl b/test/perl/body_io_fake/psgi.pl
new file mode 100644
index 00000000..6990bfaf
--- /dev/null
+++ b/test/perl/body_io_fake/psgi.pl
@@ -0,0 +1,11 @@
+use File::Basename;
+use lib dirname (__FILE__);
+use IOFake;
+
+my $app = sub {
+ my ($environ) = @_;
+
+ my $io = IOFake->new($environ->{'psgi.errors'});
+
+ return ['200', [ 'Content-Length' => '2' ], $io];
+};
diff --git a/test/perl/delayed_response/psgi.pl b/test/perl/delayed_response/psgi.pl
new file mode 100644
index 00000000..f934c3a7
--- /dev/null
+++ b/test/perl/delayed_response/psgi.pl
@@ -0,0 +1,10 @@
+my $app = sub {
+ my ($environ) = @_;
+
+ return sub {
+ (my $responder = shift)->([200, [
+ 'Content-Type' => 'text/plain',
+ 'Content-Length' => '12'
+ ], ["Hello World!"]]);
+ }
+};
diff --git a/test/perl/streaming_body/psgi.pl b/test/perl/streaming_body/psgi.pl
new file mode 100644
index 00000000..a3e54ee0
--- /dev/null
+++ b/test/perl/streaming_body/psgi.pl
@@ -0,0 +1,13 @@
+my $app = sub {
+ my ($environ) = @_;
+
+ return sub {
+ my $writer = (my $responder = shift)->([200, [
+ 'Content-Type' => 'text/plain',
+ 'Content-Length' => '12'
+ ]]);
+
+ $writer->write("Hello World!");
+ $writer->close;
+ };
+};
diff --git a/test/php/date_time/index.php b/test/php/date_time/index.php
index 4e06fdf9..42992c3f 100644
--- a/test/php/date_time/index.php
+++ b/test/php/date_time/index.php
@@ -1,4 +1,5 @@
<?php
-$d = new DateTime('2011-01-01T15:03:01.012345Z');
+date_default_timezone_set('Europe/Moscow');
+$d = new DateTime('2011-01-01T15:03:01.012345');
echo $d->format('u');
?>
diff --git a/test/php/highlight_file_exec/index.php b/test/php/highlight_file_exec/index.php
deleted file mode 100644
index adcd5ed8..00000000
--- a/test/php/highlight_file_exec/index.php
+++ /dev/null
@@ -1,4 +0,0 @@
-<?php
-highlight_file('index.php');
-exec('pwd');
-?>
diff --git a/test/php/query_string/index.php b/test/php/query_string/index.php
new file mode 100644
index 00000000..5691324d
--- /dev/null
+++ b/test/php/query_string/index.php
@@ -0,0 +1,4 @@
+<?php
+header('Content-Length: 0');
+header('Query-String: ' . $_SERVER['QUERY_STRING']);
+?>
diff --git a/test/php/time_exec/index.php b/test/php/time_exec/index.php
new file mode 100644
index 00000000..05d59d28
--- /dev/null
+++ b/test/php/time_exec/index.php
@@ -0,0 +1,4 @@
+<?php
+echo 'time: ' . time();
+echo 'exec: ' . exec('pwd');
+?>
diff --git a/test/python/host/wsgi.py b/test/python/host/wsgi.py
new file mode 100644
index 00000000..db7de306
--- /dev/null
+++ b/test/python/host/wsgi.py
@@ -0,0 +1,7 @@
+def application(env, start_response):
+ start_response('200', [
+ ('Content-Length', '0'),
+ ('X-Server-Name', env.get('SERVER_NAME')),
+ ('X-Http-Host', str(env.get('HTTP_HOST')))
+ ])
+ return []
diff --git a/test/test_access_log.py b/test/test_access_log.py
index c8464796..d6741c28 100644
--- a/test/test_access_log.py
+++ b/test/test_access_log.py
@@ -23,9 +23,9 @@ class TestUnitAccessLog(unit.TestUnitApplicationPython):
self.load('mirror')
(resp, sock) = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, start=True, body='01234')
time.sleep(0.2)
@@ -34,9 +34,9 @@ class TestUnitAccessLog(unit.TestUnitApplicationPython):
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',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, sock=sock, body='0123456789')
time.sleep(0.2)
diff --git a/test/test_configuration.py b/test/test_configuration.py
index 02705afe..52a67d38 100644
--- a/test/test_configuration.py
+++ b/test/test_configuration.py
@@ -132,7 +132,6 @@ class TestUnitConfiguration(unit.TestUnitControl):
self.skip_sanitizer = True
self.skip_alerts.extend([
r'failed to apply previous configuration',
- r'sendmsg.+failed',
r'process \d+ exited on signal'
])
@@ -217,5 +216,53 @@ class TestUnitConfiguration(unit.TestUnitControl):
}
}), '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
+ }
+ },
+ "applications": {
+ name: {
+ "type": "python",
+ "processes": { "spare": 0 },
+ "path": "/app",
+ "module": "wsgi"
+ }
+ }
+ }))
+
+ @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
+
+ conf = {
+ "applications":
+ {"app-" + str(a): {
+ "type": "python",
+ "processes": { "spare": 0 },
+ "path": "/app",
+ "module": "wsgi"
+ } for a in range(apps)
+ },
+ "listeners": {
+ "*:" + str(7000 + a): {
+ "application": "app-" + str(a)
+ } for a in range(apps)
+ }
+ }
+
+ self.assertIn('success', self.conf(conf))
+
if __name__ == '__main__':
TestUnitConfiguration.main()
diff --git a/test/test_go_application.py b/test/test_go_application.py
index fd80bf5b..1ecc2536 100644
--- a/test/test_go_application.py
+++ b/test/test_go_application.py
@@ -4,12 +4,7 @@ import unit
class TestUnitGoApplication(unit.TestUnitApplicationGo):
def setUpClass():
- u = unit.TestUnit()
-
- if u.architecture == '32bit':
- raise unittest.SkipTest('Skip Go tests for x86')
-
- u.check_modules('go')
+ unit.TestUnit().check_modules('go')
def test_go_application_variables(self):
self.load('variables')
@@ -19,7 +14,8 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo):
resp = self.post(headers={
'Host': 'localhost',
'Content-Type': 'text/html',
- 'Custom-Header': 'blah'
+ 'Custom-Header': 'blah',
+ 'Connection': 'close'
}, body=body)
self.assertEqual(resp['status'], 200, 'status')
@@ -41,7 +37,8 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo):
'Server-Protocol': 'HTTP/1.1',
'Server-Protocol-Major': '1',
'Server-Protocol-Minor': '1',
- 'Custom-Header': 'blah'
+ 'Custom-Header': 'blah',
+ 'Connection': 'close'
}, 'headers')
self.assertEqual(resp['body'], body, 'body')
@@ -57,8 +54,8 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo):
self.load('post_variables')
resp = self.post(headers={
- 'Content-Type': 'application/x-www-form-urlencoded',
'Host': 'localhost',
+ 'Content-Type': 'application/x-www-form-urlencoded',
'Connection': 'close'
}, body='var1=val1&var2=&var3')
@@ -79,17 +76,17 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo):
self.load('mirror')
(resp, sock) = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, start=True, body='0123456789' * 500)
self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1')
resp = self.post(headers={
- 'Connection': 'close',
+ 'Host': 'localhost',
'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Connection': 'close'
}, sock=sock, body='0123456789')
self.assertEqual(resp['body'], '0123456789', 'keep-alive 2')
@@ -98,8 +95,8 @@ class TestUnitGoApplication(unit.TestUnitApplicationGo):
self.load('cookies')
resp = self.get(headers={
- 'Cookie': 'var1=val1; var2=val2',
'Host': 'localhost',
+ 'Cookie': 'var1=val1; var2=val2',
'Connection': 'close'
})
diff --git a/test/test_http_header.py b/test/test_http_header.py
index b850831d..f2294371 100644
--- a/test/test_http_header.py
+++ b/test/test_http_header.py
@@ -10,7 +10,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython):
self.load('custom_header')
resp = self.get(headers={
- 'Custom-Header': ' ,'
+ 'Host': 'localhost',
+ 'Custom-Header': ' ,',
+ 'Connection': 'close'
})
self.assertEqual(resp['status'], 200, 'value leading sp status')
@@ -21,7 +23,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython):
self.load('custom_header')
resp = self.get(headers={
- 'Custom-Header': '\t,'
+ 'Host': 'localhost',
+ 'Custom-Header': '\t,',
+ 'Connection': 'close'
})
self.assertEqual(resp['status'], 200, 'value leading htab status')
@@ -32,7 +36,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython):
self.load('custom_header')
resp = self.get(headers={
- 'Custom-Header': ', '
+ 'Host': 'localhost',
+ 'Custom-Header': ', ',
+ 'Connection': 'close'
})
self.assertEqual(resp['status'], 200, 'value trailing sp status')
@@ -43,7 +49,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython):
self.load('custom_header')
resp = self.get(headers={
- 'Custom-Header': ',\t'
+ 'Host': 'localhost',
+ 'Custom-Header': ',\t',
+ 'Connection': 'close'
})
self.assertEqual(resp['status'], 200, 'value trailing htab status')
@@ -54,7 +62,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython):
self.load('custom_header')
resp = self.get(headers={
- 'Custom-Header': ' , '
+ 'Host': 'localhost',
+ 'Custom-Header': ' , ',
+ 'Connection': 'close'
})
self.assertEqual(resp['status'], 200, 'value both sp status')
@@ -65,7 +75,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython):
self.load('custom_header')
resp = self.get(headers={
- 'Custom-Header': '\t,\t'
+ 'Host': 'localhost',
+ 'Custom-Header': '\t,\t',
+ 'Connection': 'close'
})
self.assertEqual(resp['status'], 200, 'value both htab status')
@@ -76,7 +88,9 @@ class TestUnitHTTPHeader(unit.TestUnitApplicationPython):
self.load('custom_header')
resp = self.get(headers={
- 'Custom-Header': '(),/:;<=>?@[\]{}\t !#$%&\'*+-.^_`|~'
+ 'Host': 'localhost',
+ 'Custom-Header': '(),/:;<=>?@[\]{}\t !#$%&\'*+-.^_`|~',
+ 'Connection': 'close'
})
self.assertEqual(resp['status'], 200, 'value chars status')
@@ -113,7 +127,9 @@ Connection: close
self.load('empty')
resp = self.get(headers={
- ' Custom-Header': 'blah'
+ 'Host': 'localhost',
+ ' Custom-Header': 'blah',
+ 'Connection': 'close'
})
self.assertEqual(resp['status'], 400, 'field leading sp')
@@ -122,7 +138,9 @@ Connection: close
self.load('empty')
resp = self.get(headers={
- '\tCustom-Header': 'blah'
+ 'Host': 'localhost',
+ '\tCustom-Header': 'blah',
+ 'Connection': 'close'
})
self.assertEqual(resp['status'], 400, 'field leading htab')
@@ -131,7 +149,9 @@ Connection: close
self.load('empty')
resp = self.get(headers={
- 'Custom-Header ': 'blah'
+ 'Host': 'localhost',
+ 'Custom-Header ': 'blah',
+ 'Connection': 'close'
})
self.assertEqual(resp['status'], 400, 'field trailing sp')
@@ -140,27 +160,204 @@ Connection: close
self.load('empty')
resp = self.get(headers={
- 'Custom-Header\t': 'blah'
+ 'Host': 'localhost',
+ 'Custom-Header\t': 'blah',
+ 'Connection': 'close'
})
self.assertEqual(resp['status'], 400, 'field trailing htab')
- @unittest.expectedFailure
- def test_http_header_transfer_encoding_chunked(self):
+ def test_http_header_content_length_big(self):
self.load('empty')
- resp = self.http(b"""GET / HTTP/1.1
-Host: localhost
-Transfer-Encoding: chunked
-Connection: close
+ self.assertEqual(self.post(headers={
+ 'Host': 'localhost',
+ 'Content-Length': str(2 ** 64),
+ 'Connection': 'close'
+ }, body='X' * 1000)['status'], 400, 'Content-Length big')
-a
-0123456789
-0
+ def test_http_header_content_length_negative(self):
+ self.load('empty')
-""", raw=True)
+ 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')
+
+ 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')
+
+ 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')
+
+ 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')
+
+ def test_http_header_host_empty(self):
+ self.load('host')
+
+ 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')
+
+ def test_http_header_host_big(self):
+ self.load('empty')
+
+ 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'
+ })
+
+ 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')
+
+ def test_http_header_host_port_empty(self):
+ self.load('host')
+
+ 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')
+
+ def test_http_header_host_literal(self):
+ self.load('host')
+
+ 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')
+
+ def test_http_header_host_literal_ipv6(self):
+ self.load('host')
+
+ 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')
+
+ def test_http_header_host_trailing_period(self):
+ self.load('host')
+
+ 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')
+
+ def test_http_header_host_trailing_period_2(self):
+ self.load('host')
+
+ 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')
+
+ def test_http_header_host_case_insensitive(self):
+ self.load('host')
+
+ 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')
+
+ 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')
+
+ def test_http_header_host_slash(self):
+ self.load('empty')
+
+ 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(resp['status'], 200, 'transfer encoding chunked')
+ self.assertEqual(self.get(headers={
+ 'Host': ['localhost', 'example.com'],
+ 'Connection': 'close'
+ })['status'], 400, 'Host multiple fields')
if __name__ == '__main__':
TestUnitHTTPHeader.main()
diff --git a/test/test_java_application.py b/test/test_java_application.py
new file mode 100644
index 00000000..d603ed0f
--- /dev/null
+++ b/test/test_java_application.py
@@ -0,0 +1,753 @@
+import time
+import unittest
+import unit
+
+class TestUnitJavaApplication(unit.TestUnitApplicationJava):
+
+ def setUpClass():
+ unit.TestUnit().check_modules('java')
+
+ def test_java_application_cookies(self):
+ self.load('cookies')
+
+ 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')
+
+ def test_java_application_filter(self):
+ self.load('filter')
+
+ headers = self.get()['headers']
+
+ 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')
+
+ def test_java_application_get_variables(self):
+ self.load('get_params')
+
+ 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')
+
+ 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']
+
+ self.assertEqual(headers['X-Var-1'], 'val1', 'POST variables')
+ self.assertEqual(headers['X-Var-2'], 'true', 'POST variables 2')
+ self.assertEqual(headers['X-Var-3'], 'false', 'POST variables 3')
+
+ def test_java_application_session(self):
+ self.load('session')
+
+ headers = self.get(url='/?var1=val1')['headers']
+ session_id = headers['X-Session-Id']
+
+ 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']
+
+ 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')
+
+ def test_java_application_session_active(self):
+ self.load('session_inactive')
+
+ resp = self.get()
+ 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')
+
+ time.sleep(1)
+
+ resp = self.get(headers={
+ 'Host': 'localhost',
+ 'Cookie': 'JSESSIONID=' + session_id,
+ 'Connection': 'close'
+ })
+
+ 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'
+ })
+
+ 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'
+ })
+
+ self.assertEqual(resp['headers']['X-Session-Id'], session_id,
+ 'session active 3')
+
+ def test_java_application_session_inactive(self):
+ self.load('session_inactive')
+
+ resp = self.get()
+ session_id = resp['headers']['X-Session-Id']
+
+ time.sleep(3)
+
+ resp = self.get(headers={
+ 'Host': 'localhost',
+ 'Cookie': 'JSESSIONID=' + session_id,
+ 'Connection': 'close'
+ })
+
+ self.assertNotEqual(resp['headers']['X-Session-Id'], session_id,
+ 'session inactive')
+
+ def test_java_application_session_invalidate(self):
+ self.load('session_invalidate')
+
+ resp = self.get()
+ session_id = resp['headers']['X-Session-Id']
+
+ resp = self.get(headers={
+ 'Host': 'localhost',
+ 'Cookie': 'JSESSIONID=' + session_id,
+ 'Connection': 'close'
+ })
+
+ self.assertNotEqual(resp['headers']['X-Session-Id'], session_id,
+ 'session invalidate')
+
+ def test_java_application_session_listeners(self):
+ self.load('session_listeners')
+
+ 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')
+
+ 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')
+
+ def test_java_application_jsp(self):
+ self.load('jsp')
+
+ headers = self.get(url='/index.jsp')['headers']
+
+ self.assertEqual(headers['X-Unit-JSP'], 'ok', 'JSP Ok header')
+
+ def test_java_application_url_pattern(self):
+ self.load('url_pattern')
+
+ 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-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-Path-Info'], '/index.bop', '#2 path info')
+
+ headers = self.get(url='/baz')['headers']
+
+ self.assertEqual(headers['X-Id'], 'servlet2', '#3 Servlet2 request')
+ self.assertEqual(headers['X-Request-URI'], '/baz', '#3 request URI')
+ self.assertEqual(headers['X-Servlet-Path'], '/baz', '#3 servlet path')
+ self.assertEqual(headers['X-Path-Info'], 'null', '#3 path info')
+
+ 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-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-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-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-Path-Info'], 'null', '#7 path info')
+
+ 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-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-Servlet-Path'], '/foo', '#9 servlet path')
+ self.assertEqual(headers['X-Path-Info'], '/baz', '#9 path info')
+
+ headers = self.get()['headers']
+
+ self.assertEqual(headers['X-Id'], 'default', '#10 default request')
+ self.assertEqual(headers['X-Request-URI'], '/', '#10 request URI')
+ self.assertEqual(headers['X-Servlet-Path'], '/', '#10 servlet path')
+ self.assertEqual(headers['X-Path-Info'], 'null', '#10 path info')
+
+ 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-Path-Info'], 'null', '#11 path info')
+
+ def test_java_application_header(self):
+ self.load('header')
+
+ 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-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')
+
+ def test_java_application_content_type(self):
+ self.load('content_type')
+
+ 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')
+
+ 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')
+
+ 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')
+
+ 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')
+
+ 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')
+
+ 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')
+
+
+ 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')
+
+ 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')
+
+ def test_java_application_welcome_files(self):
+ self.load('welcome_files')
+
+ headers = self.get()['headers']
+
+ resp = self.get(url='/dir1')
+
+ self.assertEqual(resp['status'], 302, 'dir redirect expected')
+
+ 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')
+
+ headers = self.get(url='/dir2/')['headers']
+
+ self.assertEqual(headers['X-Unit-JSP'], 'ok', 'JSP Ok header')
+ self.assertEqual(headers['X-JSP-Filter'], '1', 'JSP Filter header')
+
+ headers = self.get(url='/dir3/')['headers']
+
+ 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')
+
+ headers = self.get(url='/dir5/')['headers']
+
+ 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')
+
+ 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')
+
+ 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')
+
+ 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')
+
+ 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')
+ 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')
+
+ def test_java_application_named_dispatcher_forward(self):
+ self.load('forward')
+
+ 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')
+
+ def test_java_application_request_uri_include(self):
+ self.load('include')
+
+ resp = self.get(url='/inc?uri=/data/test')
+ 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')
+
+ def test_java_application_named_dispatcher_include(self):
+ self.load('include')
+
+ resp = self.get(url='/inc?disp=name&uri=data')
+ 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')
+
+ 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'],
+ headers['X-Real-Path'] + headers['X-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')
+
+ 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')
+
+ 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')
+
+ def test_java_application_query_empty(self):
+ self.load('query_string')
+
+ 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')
+
+ def test_java_application_empty(self):
+ self.load('empty')
+
+ self.assertEqual(self.get()['status'], 200, 'empty')
+
+ 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(resp['body'], '0123456789' * 500, 'keep-alive 1')
+
+ resp = self.post(headers={
+ 'Connection': 'close',
+ 'Content-Type': 'text/html',
+ 'Host': 'localhost'
+ }, sock=sock, body='0123456789')
+
+ self.assertEqual(resp['body'], '0123456789', 'keep-alive 2')
+
+ def test_java_application_http_10(self):
+ self.load('empty')
+
+ self.assertEqual(self.get(http_10=True)['status'], 200, 'HTTP 1.0')
+
+ def test_java_application_no_method(self):
+ self.load('empty')
+
+ self.assertEqual(self.post()['status'], 405, 'no method')
+
+ 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')
+
+ def test_java_application_get_header_empty(self):
+ self.load('get_header')
+
+ self.assertNotIn('X-Reply', self.get()['headers'], 'get header empty')
+
+ 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']
+
+ self.assertEqual(headers['X-Reply-0'], 'blah', 'get headers')
+ self.assertEqual(headers['X-Reply-1'], 'blah', 'get headers 2')
+
+ def test_java_application_get_headers_empty(self):
+ self.load('get_headers')
+
+ 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')
+
+ 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']
+
+ self.assertEqual(headers['X-Set-Int'], '1', 'set int header')
+ self.assertEqual(headers['X-Get-Int'], '2', 'get int header')
+
+ def test_java_application_header_date(self):
+ self.load('header_date')
+
+ date = 'Fri, 15 Mar 2019 14:45:34 GMT'
+
+ headers = self.get(headers={
+ 'X-Header': date,
+ 'Content-Type': 'text/html',
+ 'Host': 'localhost'
+ })['headers']
+
+ 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()
diff --git a/test/test_node_application.py b/test/test_node_application.py
index 5dedb5a3..cd64fefa 100644
--- a/test/test_node_application.py
+++ b/test/test_node_application.py
@@ -28,7 +28,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
resp = self.post(headers={
'Host': 'localhost',
'Content-Type': 'text/html',
- 'Custom-Header': 'blah'
+ 'Custom-Header': 'blah',
+ 'Connection': 'close'
}, body=body)
self.assertEqual(resp['status'], 200, 'status')
@@ -43,10 +44,11 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
raw_headers = headers.pop('Request-Raw-Headers')
self.assertRegex(raw_headers, r'^(?:Host|localhost|Content-Type|' \
- 'text\/html|Custom-Header|blah|Content-Length|17|,)+$',
- 'raw headers')
+ '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',
@@ -91,17 +93,17 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
self.load('mirror')
(resp, sock) = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, start=True, body='0123456789' * 500)
self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1')
resp = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'close',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, sock=sock, body='0123456789')
self.assertEqual(resp['body'], '0123456789', 'keep-alive 2')
@@ -112,7 +114,6 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
self.assertEqual(self.get()['body'], '6\r\nbuffer\r\n0\r\n\r\n',
'write buffer')
- @unittest.expectedFailure
def test_node_application_write_callback(self):
self.load('write_callback')
@@ -121,11 +122,10 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
self.assertTrue(self.waitforfiles(self.testdir + '/node/callback'),
'write callback')
- def test_node_application_write_before_writeHead(self):
- self.skip_alerts.append(r'process \d+ exited on signal')
+ def test_node_application_write_before_write_head(self):
self.load('write_before_write_head')
- self.get()
+ self.assertEqual(self.get()['status'], 200, 'write before writeHead')
def test_node_application_double_end(self):
self.load('double_end')
@@ -144,7 +144,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
resp = self.get(headers={
'Host': 'localhost',
- 'X-Remove': 'X-Header'
+ 'X-Remove': 'X-Header',
+ 'Connection': 'close'
})
self.assertEqual(resp['headers']['Was-Header'], 'true', 'was header')
self.assertEqual(resp['headers']['Has-Header'], 'false', 'has header')
@@ -155,7 +156,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
self.assertEqual(self.get(headers={
'Host': 'localhost',
- 'X-Remove': 'blah'
+ 'X-Remove': 'blah',
+ 'Connection': 'close'
})['headers']['Has-Header'], 'true', 'remove header nonexisting')
def test_node_application_update_header(self):
@@ -182,7 +184,6 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
self.assertEqual(self.get()['headers']['X-Type'], 'number',
'get header type')
- @unittest.expectedFailure
def test_node_application_header_name_case(self):
self.load('header_name_case')
@@ -197,20 +198,20 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
self.assertEqual(self.post(headers={
'Host': 'localhost',
- 'Content-Type': 'text/html'
+ 'Content-Type': 'text/html',
+ 'Connection': 'close'
}, body='callback')['status'], 200, 'promise handler request')
self.assertTrue(self.waitforfiles(self.testdir + '/node/callback'),
'promise handler')
- @unittest.expectedFailure
def test_node_application_promise_handler_write_after_end(self):
- self.skip_alerts.append(r'process \d+ exited on signal')
self.load('promise_handler')
self.assertEqual(self.post(headers={
'Host': 'localhost',
'Content-Type': 'text/html',
- 'X-Write-Call': '1'
+ 'X-Write-Call': '1',
+ 'Connection': 'close'
}, body='callback')['status'], 200,
'promise handler request write after end')
@@ -219,7 +220,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
self.assertEqual(self.post(headers={
'Host': 'localhost',
- 'Content-Type': 'text/html'
+ 'Content-Type': 'text/html',
+ 'Connection': 'close'
}, body='end')['status'], 200, 'promise end request')
self.assertTrue(self.waitforfiles(self.testdir + '/node/callback'),
'promise end')
@@ -229,7 +231,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
self.post(headers={
'Host': 'localhost',
- 'Content-Type': 'text/html'
+ 'Content-Type': 'text/html',
+ 'Connection': 'close'
}, body='callback1')
self.assertTrue(self.waitforfiles(self.testdir + '/node/callback1'),
@@ -237,7 +240,8 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
self.post(headers={
'Host': 'localhost',
- 'Content-Type': 'text/html'
+ 'Content-Type': 'text/html',
+ 'Connection': 'close'
}, body='callback2')
self.assertTrue(self.waitforfiles(self.testdir + '/node/callback2'),
@@ -249,13 +253,11 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
self.assertNotIn('status', self.get(), 'header name valid')
- @unittest.expectedFailure
def test_node_application_header_value_object(self):
self.load('header_value_object')
self.assertIn('X-Header', self.get()['headers'], 'header value object')
- @unittest.expectedFailure
def test_node_application_get_header_names(self):
self.load('get_header_names')
@@ -267,12 +269,14 @@ class TestUnitNodeApplication(unit.TestUnitApplicationNode):
self.assertEqual(self.get(headers={
'Host': 'localhost',
- 'X-Header': 'length'
+ 'X-Header': 'length',
+ 'Connection': 'close'
})['headers']['X-Has-Header'], 'false', 'has header length')
self.assertEqual(self.get(headers={
'Host': 'localhost',
- 'X-Header': 'Date'
+ 'X-Header': 'Date',
+ 'Connection': 'close'
})['headers']['X-Has-Header'], 'false', 'has header date')
def test_node_application_write_multiple(self):
diff --git a/test/test_perl_application.py b/test/test_perl_application.py
index c9cb3f0c..b169baab 100644
--- a/test/test_perl_application.py
+++ b/test/test_perl_application.py
@@ -14,7 +14,8 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl):
resp = self.post(headers={
'Host': 'localhost',
'Content-Type': 'text/html',
- 'Custom-Header': 'blah'
+ 'Custom-Header': 'blah',
+ 'Connection': 'close'
}, body=body)
self.assertEqual(resp['status'], 200, 'status')
@@ -30,6 +31,7 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl):
'date header')
self.assertDictEqual(headers, {
+ 'Connection': 'close',
'Content-Length': str(len(body)),
'Content-Type': 'text/html',
'Request-Method': 'POST',
@@ -43,7 +45,7 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl):
'Psgi-Multiprocess': '1',
'Psgi-Run-Once': '',
'Psgi-Nonblocking': '',
- 'Psgi-Streaming': ''
+ 'Psgi-Streaming': '1'
}, 'headers')
self.assertEqual(resp['body'], body, 'body')
@@ -55,6 +57,25 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl):
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')
+
+ resp = self.get(url='/?')
+
+ self.assertEqual(resp['status'], 200, 'query string empty status')
+ 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')
+
@unittest.expectedFailure
def test_perl_application_server_port(self):
self.load('server_port')
@@ -151,20 +172,49 @@ class TestUnitPerlApplication(unit.TestUnitApplicationPerl):
self.load('variables')
(resp, sock) = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, start=True, body='0123456789' * 500)
self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1')
resp = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'close',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, sock=sock, body='0123456789')
self.assertEqual(resp['body'], '0123456789', 'keep-alive 2')
+ def test_perl_body_io_fake(self):
+ self.load('body_io_fake')
+
+ 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.assertIsNotNone(
+ self.search_in_log(r'\[error\].+IOFake close\(\) called'),
+ 'body io fake close')
+
+ def test_perl_delayed_response(self):
+ self.load('delayed_response')
+
+ resp = self.get()
+
+ self.assertEqual(resp['status'], 200, 'status')
+ self.assertEqual(resp['body'], 'Hello World!', 'body')
+
+ def test_perl_streaming_body(self):
+ self.load('streaming_body')
+
+ resp = self.get()
+
+ self.assertEqual(resp['status'], 200, 'status')
+ self.assertEqual(resp['body'], 'Hello World!', 'body')
+
if __name__ == '__main__':
TestUnitPerlApplication.main()
diff --git a/test/test_php_application.py b/test/test_php_application.py
index e0058d9a..ac74359d 100644
--- a/test/test_php_application.py
+++ b/test/test_php_application.py
@@ -7,9 +7,11 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP):
def setUpClass():
unit.TestUnit().check_modules('php')
- def search_disabled(self, name):
- p = re.compile(name + '\(\) has been disabled')
- return self.search_in_log(p)
+ def before_disable_functions(self):
+ body = self.get()['body']
+
+ self.assertRegex(body, r'time: \d+', 'disable_functions before time')
+ self.assertRegex(body, r'exec: \/\w+', 'disable_functions before exec')
def test_php_application_variables(self):
self.load('variables')
@@ -19,7 +21,8 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP):
resp = self.post(headers={
'Host': 'localhost',
'Content-Type': 'text/html',
- 'Custom-Header': 'blah'
+ 'Custom-Header': 'blah',
+ 'Connection': 'close'
}, body=body)
self.assertEqual(resp['status'], 200, 'status')
@@ -39,6 +42,7 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP):
headers.pop('Content-type')
self.assertDictEqual(headers, {
+ 'Connection': 'close',
'Content-Length': str(len(body)),
'Request-Method': 'POST',
'Request-Uri': '/',
@@ -48,6 +52,33 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP):
}, 'headers')
self.assertEqual(resp['body'], body, 'body')
+ def test_php_application_query_string(self):
+ self.load('query_string')
+
+ resp = self.get(url='/?var1=val1&var2=val2')
+
+ self.assertEqual(resp['headers']['Query-String'], 'var1=val1&var2=val2',
+ 'query string')
+
+ def test_php_application_query_string_empty(self):
+ self.load('query_string')
+
+ resp = self.get(url='/?')
+
+ self.assertEqual(resp['status'], 200, 'query string empty status')
+ 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')
+
def test_php_application_phpinfo(self):
self.load('phpinfo')
@@ -69,17 +100,17 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP):
self.load('mirror')
(resp, sock) = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, start=True, body='0123456789' * 500)
self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1')
resp = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'close',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, sock=sock, body='0123456789')
self.assertEqual(resp['body'], '0123456789', 'keep-alive 2')
@@ -210,109 +241,97 @@ class TestUnitPHPApplication(unit.TestUnitApplicationPHP):
'ini value repeat')
def test_php_application_disable_functions_exec(self):
- self.load('highlight_file_exec')
-
- self.conf({"admin": { "disable_functions": "exec" }},
- 'applications/highlight_file_exec/options')
-
- self.get()
-
- self.assertIsNotNone(self.search_disabled('exec'),
- 'disable_functions exec')
- self.assertIsNone(self.search_disabled('highlight_file'),
- 'disable_functions highlight_file')
+ self.load('time_exec')
- def test_php_application_disable_functions_highlight_file(self):
- self.load('highlight_file_exec')
+ self.before_disable_functions()
- self.conf({"admin": { "disable_functions": "highlight_file" }},
- 'applications/highlight_file_exec/options')
+ self.conf({"admin": { "disable_functions": "exec" }},
+ 'applications/time_exec/options')
- self.get()
+ body = self.get()['body']
- self.assertIsNone(self.search_disabled('exec'),
- 'disable_functions exec')
- self.assertIsNotNone(self.search_disabled('highlight_file'),
- 'disable_functions highlight_file')
+ self.assertRegex(body, r'time: \d+', 'disable_functions time')
+ self.assertNotRegex(body, r'exec: \/\w+', 'disable_functions exec')
def test_php_application_disable_functions_comma(self):
- self.load('highlight_file_exec')
+ self.load('time_exec')
+
+ self.before_disable_functions()
- self.conf({"admin": { "disable_functions": "exec,highlight_file" }},
- 'applications/highlight_file_exec/options')
+ self.conf({"admin": { "disable_functions": "exec,time" }},
+ 'applications/time_exec/options')
- self.get()
+ body = self.get()['body']
- self.assertIsNotNone(self.search_disabled('exec'),
- 'disable_functions exec')
- self.assertIsNotNone(self.search_disabled('highlight_file'),
- 'disable_functions highlight_file')
+ self.assertNotRegex(body, r'time: \d+', 'disable_functions comma time')
+ self.assertNotRegex(body, r'exec: \/\w+',
+ 'disable_functions comma exec')
def test_php_application_disable_functions_space(self):
- self.load('highlight_file_exec')
+ self.load('time_exec')
- self.conf({"admin": { "disable_functions": "exec highlight_file" }},
- 'applications/highlight_file_exec/options')
+ self.before_disable_functions()
- self.get()
+ self.conf({"admin": { "disable_functions": "exec time" }},
+ 'applications/time_exec/options')
- self.assertIsNotNone(self.search_disabled('exec'),
- 'disable_functions exec')
- self.assertIsNotNone(self.search_disabled('highlight_file'),
- 'disable_functions highlight_file')
+ body = self.get()['body']
+
+ self.assertNotRegex(body, r'time: \d+', 'disable_functions space time')
+ self.assertNotRegex(body, r'exec: \/\w+',
+ 'disable_functions space exec')
def test_php_application_disable_functions_user(self):
- self.load('highlight_file_exec')
+ self.load('time_exec')
+
+ self.before_disable_functions()
self.conf({"user": { "disable_functions": "exec" }},
- 'applications/highlight_file_exec/options')
+ 'applications/time_exec/options')
- self.get()
+ body = self.get()['body']
- self.assertIsNotNone(self.search_disabled('exec'),
- 'disable_functions exec')
- self.assertIsNone(self.search_disabled('highlight_file'),
- 'disable_functions highlight_file')
+ self.assertRegex(body, r'time: \d+', 'disable_functions user time')
+ self.assertNotRegex(body, r'exec: \/\w+', 'disable_functions user exec')
def test_php_application_disable_functions_nonexistent(self):
- self.load('highlight_file_exec')
+ self.load('time_exec')
+
+ self.before_disable_functions()
self.conf({"admin": { "disable_functions": "blah" }},
- 'applications/highlight_file_exec/options')
+ 'applications/time_exec/options')
- self.get()
+ body = self.get()['body']
- self.assertIsNone(self.search_disabled('exec'),
- 'disable_functions exec')
- self.assertIsNone(self.search_disabled('highlight_file'),
- 'disable_functions highlight_file')
+ 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.get()
-
- self.assertIsNone(self.search_disabled('DateTime'),
+ self.assertRegex(self.get()['body'], r'012345',
'disable_classes before')
self.conf({"admin": { "disable_classes": "DateTime" }},
'applications/date_time/options')
- self.get()
-
- self.assertIsNotNone(self.search_disabled('DateTime'),
- 'disable_classes')
+ 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.conf({"user": { "disable_classes": "DateTime" }},
'applications/date_time/options')
- self.get()
-
- self.assertIsNotNone(self.search_disabled('DateTime'),
- 'disable_classes user')
+ self.assertNotRegex(self.get()['body'], r'012345',
+ 'disable_classes before')
if __name__ == '__main__':
TestUnitPHPApplication.main()
diff --git a/test/test_python_application.py b/test/test_python_application.py
index 667047bc..a8631085 100644
--- a/test/test_python_application.py
+++ b/test/test_python_application.py
@@ -1,3 +1,4 @@
+import time
import unittest
import unit
@@ -14,7 +15,8 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython):
resp = self.post(headers={
'Host': 'localhost',
'Content-Type': 'text/html',
- 'Custom-Header': 'blah'
+ 'Custom-Header': 'blah',
+ 'Connection': 'close'
}, body=body)
self.assertEqual(resp['status'], 200, 'status')
@@ -30,6 +32,7 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython):
'date header')
self.assertDictEqual(headers, {
+ 'Connection': 'close',
'Content-Length': str(len(body)),
'Content-Type': 'text/html',
'Request-Method': 'POST',
@@ -53,6 +56,24 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython):
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')
+
+ resp = self.get(url='/?')
+
+ self.assertEqual(resp['status'], 200, 'query string empty status')
+ self.assertEqual(resp['headers']['Query-String'], '',
+ 'query string empty')
+
+ def test_python_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')
+
@unittest.expectedFailure
def test_python_application_server_port(self):
self.load('server_port')
@@ -67,13 +88,12 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython):
'204 header transfer encoding')
def test_python_application_ctx_iter_atexit(self):
- self.skip_alerts.append(r'sendmsg.+failed')
self.load('ctx_iter_atexit')
resp = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'close',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, body='0123456789')
self.assertEqual(resp['status'], 200, 'ctx iter status')
@@ -86,6 +106,8 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython):
self.stop()
+ time.sleep(0.2)
+
self.assertIsNotNone(self.search_in_log(r'RuntimeError'),
'ctx iter atexit')
@@ -93,26 +115,22 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython):
self.load('mirror')
(resp, sock) = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, start=True, body='0123456789' * 500)
self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1')
resp = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'close',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ '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'sendmsg.+failed',
- r'recvmsg.+failed'
- ])
self.load('mirror')
body = '0123456789'
@@ -121,9 +139,9 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython):
for i in range(conns):
(resp, sock) = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, start=True, body=body)
self.assertEqual(resp['body'], body, 'keep-alive open')
@@ -136,9 +154,9 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython):
for i in range(conns):
(resp, sock) = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, start=True, sock=socks[i], body=body)
self.assertEqual(resp['body'], body, 'keep-alive request')
@@ -149,9 +167,9 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython):
for i in range(conns):
resp = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'close',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, sock=socks[i], body=body)
self.assertEqual(resp['body'], body, 'keep-alive close')
@@ -161,15 +179,14 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython):
}, 'applications/mirror/processes'), 'reconfigure 3')
def test_python_keepalive_reconfigure_2(self):
- self.skip_alerts.append(r'sendmsg.+failed')
self.load('mirror')
body = '0123456789'
(resp, sock) = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, start=True, body=body)
self.assertEqual(resp['body'], body, 'reconfigure 2 keep-alive 1')
@@ -177,9 +194,9 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython):
self.load('empty')
(resp, sock) = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'close',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, start=True, sock=sock, body=body)
self.assertEqual(resp['status'], 200, 'reconfigure 2 keep-alive 2')
@@ -195,7 +212,6 @@ class TestUnitPythonApplication(unit.TestUnitApplicationPython):
self.assertEqual(resp, {}, 'reconfigure 2 keep-alive 3')
def test_python_keepalive_reconfigure_3(self):
- self.skip_alerts.append(r'sendmsg.+failed')
self.load('empty')
(resp, sock) = self.http(b"""GET / HTTP/1.1
@@ -214,7 +230,6 @@ Connection: close
self.assertEqual(resp['status'], 200, 'reconfigure 3')
def test_python_atexit(self):
- self.skip_alerts.append(r'sendmsg.+failed')
self.load('atexit')
self.get()
diff --git a/test/test_python_procman.py b/test/test_python_procman.py
index 65268d49..2efe59c0 100644
--- a/test/test_python_procman.py
+++ b/test/test_python_procman.py
@@ -226,7 +226,7 @@ class TestUnitPythonProcman(unit.TestUnitApplicationPython):
(resp, sock) = self.get(headers={
'Host': 'localhost',
'Connection': 'keep-alive'
- }, start=True)
+ }, start=True, read_timeout=1)
self.assertEqual(len(self.pids_for_process()), 1,
'keepalive connection 1')
diff --git a/test/test_routing.py b/test/test_routing.py
new file mode 100644
index 00000000..07097fc8
--- /dev/null
+++ b/test/test_routing.py
@@ -0,0 +1,458 @@
+import unittest
+import unit
+
+class TestUnitRouting(unit.TestUnitApplicationProto):
+
+ def setUpClass():
+ unit.TestUnit().check_modules('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"
+ },
+ "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.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')
+
+ def test_routes_match_method_negative(self):
+ 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.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')
+
+ 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.assertEqual(self.get()['status'], 200, 'method wildcard left GET')
+ 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')
+
+ 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')
+
+ def test_routes_match_method_wildcard(self):
+ 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_method_case_insensitive(self):
+ 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_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.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')
+
+ 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"
+ }
+ }
+ }), '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({}, '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.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')
+
+ 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')
+
+ 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.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')
+
+ 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"
+ }
+ }
+ }), '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')
+
+ 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')
+
+ 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')
+
+ 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')
+
+ 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')
+
+ 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')
+
+ 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')
+
+ 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')
+
+ def test_routes_match_uri_positive(self):
+ 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')
+
+ 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')
+
+ 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.assertEqual(self.get(url='/%62%6c%61%68')['status'], 200,
+ 'match uri normalize')
+
+ 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.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.assertEqual(self.get()['status'], 500, 'routes loop')
+
+if __name__ == '__main__':
+ TestUnitRouting.main()
diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py
index 57ab88cd..262fc497 100644
--- a/test/test_ruby_application.py
+++ b/test/test_ruby_application.py
@@ -14,7 +14,8 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby):
resp = self.post(headers={
'Host': 'localhost',
'Content-Type': 'text/html',
- 'Custom-Header': 'blah'
+ 'Custom-Header': 'blah',
+ 'Connection': 'close'
}, body=body)
self.assertEqual(resp['status'], 200, 'status')
@@ -30,6 +31,7 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby):
'date header')
self.assertDictEqual(headers, {
+ 'Connection': 'close',
'Content-Length': str(len(body)),
'Content-Type': 'text/html',
'Request-Method': 'POST',
@@ -56,6 +58,25 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby):
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')
+
+ resp = self.get(url='/?')
+
+ self.assertEqual(resp['status'], 200, 'query string empty status')
+ 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')
+
@unittest.expectedFailure
def test_ruby_application_server_port(self):
self.load('server_port')
@@ -189,7 +210,6 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby):
'errors write int')
def test_ruby_application_at_exit(self):
- self.skip_alerts.append(r'sendmsg.+failed')
self.load('at_exit')
self.get()
@@ -268,17 +288,17 @@ class TestUnitRubyApplication(unit.TestUnitApplicationRuby):
self.load('mirror')
(resp, sock) = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, start=True, body='0123456789' * 500)
self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1')
resp = self.post(headers={
+ 'Host': 'localhost',
'Connection': 'close',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, sock=sock, body='0123456789')
self.assertEqual(resp['body'], '0123456789', 'keep-alive 2')
diff --git a/test/test_settings.py b/test/test_settings.py
index b4ac33dc..13bfad49 100644
--- a/test/test_settings.py
+++ b/test/test_settings.py
@@ -14,7 +14,7 @@ class TestUnitSettings(unit.TestUnitApplicationPython):
self.conf({'http': { 'header_read_timeout': 2 }}, 'settings')
(resp, sock) = self.http(b"""GET / HTTP/1.1
-""", start=True, raw=True)
+""", start=True, read_timeout=1, raw=True)
time.sleep(3)
@@ -28,22 +28,20 @@ Connection: close
def test_settings_header_read_timeout_update(self):
self.load('empty')
- r = None
-
self.conf({'http': { 'header_read_timeout': 4 }}, 'settings')
(resp, sock) = self.http(b"""GET / HTTP/1.1
-""", start=True, raw=True, no_recv=True)
+""", start=True, read_timeout=1, raw=True, no_recv=True)
time.sleep(2)
(resp, sock) = self.http(b"""Host: localhost
-""", start=True, sock=sock, raw=True, no_recv=True)
+""", start=True, sock=sock, read_timeout=1, raw=True, no_recv=True)
time.sleep(2)
(resp, sock) = self.http(b"""X-Blah: blah
-""", start=True, sock=sock, raw=True)
+""", start=True, sock=sock, read_timeout=1, raw=True)
if len(resp) != 0:
sock.close()
@@ -68,7 +66,7 @@ Host: localhost
Content-Length: 10
Connection: close
-""", start=True, raw_resp=True, raw=True)
+""", start=True, raw_resp=True, read_timeout=1, raw=True)
time.sleep(3)
@@ -86,15 +84,17 @@ Host: localhost
Content-Length: 10
Connection: close
-""", start=True, raw=True)
+""", start=True, read_timeout=1, raw=True)
time.sleep(2)
- (resp, sock) = self.http(b"""012""", start=True, sock=sock, 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, raw=True)
+ (resp, sock) = self.http(b"""345""", start=True, sock=sock,
+ read_timeout=1, raw=True)
time.sleep(2)
@@ -120,6 +120,7 @@ Connection: close
Host: localhost
Content-Type: text/html
Content-Length: %d
+Connection: close
""" % data_len + ('X' * data_len)
@@ -142,15 +143,15 @@ Content-Length: %d
self.conf({'http': { 'idle_timeout': 2 }}, 'settings')
(resp, sock) = self.get(headers={
- 'Connection': 'keep-alive',
- 'Host': 'localhost'
- }, start=True)
+ 'Host': 'localhost',
+ 'Connection': 'keep-alive'
+ }, start=True, read_timeout=1)
time.sleep(3)
resp = self.get(headers={
- 'Connection': 'close',
- 'Host': 'localhost'
+ 'Host': 'localhost',
+ 'Connection': 'close'
}, sock=sock)
self.assertEqual(resp['status'], 408, 'status idle timeout')
diff --git a/test/test_tls.py b/test/test_tls.py
index fa5c9754..2131bf30 100644
--- a/test/test_tls.py
+++ b/test/test_tls.py
@@ -306,23 +306,25 @@ basicConstraints = critical,CA:TRUE""" % {
self.assertEqual(self.get_ssl()['status'], 200,
'certificate chain intermediate server')
+ @unittest.expectedFailure
def test_tls_reconfigure(self):
self.load('empty')
self.certificate()
- (resp, sock) = self.http(b"""GET / HTTP/1.1
-""", start=True, raw=True, no_recv=True)
-
- self.add_tls()
+ (resp, sock) = self.get(headers={
+ 'Host': 'localhost',
+ 'Connection': 'keep-alive'
+ }, start=True)
- resp = self.http(b"""Host: localhost
-Connection: close
+ self.assertEqual(resp['status'], 200, 'initial status')
-""", sock=sock, raw=True)
+ self.add_tls()
- self.assertEqual(resp['status'], 200, 'update status')
- self.assertEqual(self.get_ssl()['status'], 200, 'update 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')
@@ -332,17 +334,17 @@ Connection: close
self.add_tls(application='mirror')
(resp, sock) = self.post_ssl(headers={
+ 'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, start=True, body='0123456789')
self.assertEqual(resp['body'], '0123456789', 'keepalive 1')
resp = self.post_ssl(headers={
+ 'Host': 'localhost',
'Connection': 'close',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, sock=sock, body='0123456789')
self.assertEqual(resp['body'], '0123456789', 'keepalive 2')
@@ -356,8 +358,8 @@ Connection: close
self.add_tls()
(resp, sock) = self.get_ssl(headers={
- 'Connection': 'keep-alive',
- 'Host': 'localhost'
+ 'Host': 'localhost',
+ 'Connection': 'keep-alive'
}, start=True)
self.conf({
@@ -367,8 +369,8 @@ Connection: close
try:
resp = self.get_ssl(headers={
- 'Connection': 'close',
- 'Host': 'localhost'
+ 'Host': 'localhost',
+ 'Connection': 'close'
}, sock=sock)
except:
resp = None
@@ -395,9 +397,9 @@ Connection: close
self.add_tls(application='mirror')
(resp, sock) = self.post_ssl(headers={
+ 'Host': 'localhost',
'Connection': 'keep-alive',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, start=True, body='0123456789')
app_id = self.findall(r'(\d+)#\d+ "mirror" application started')[0]
@@ -408,9 +410,9 @@ Connection: close
'#)(\d+)#\d+ "mirror" application started'))
resp = self.post_ssl(headers={
+ 'Host': 'localhost',
'Connection': 'close',
- 'Content-Type': 'text/html',
- 'Host': 'localhost'
+ 'Content-Type': 'text/html'
}, sock=sock, body='0123456789')
self.assertEqual(resp['status'], 200, 'application respawn status')
diff --git a/test/unit.py b/test/unit.py
index e88ed684..6cca7f48 100644
--- a/test/unit.py
+++ b/test/unit.py
@@ -179,7 +179,8 @@ class TestUnit(unittest.TestCase):
self._started = True
- self.skip_alerts = [r'read signalfd\(4\) failed']
+ self.skip_alerts = [r'read signalfd\(4\) failed', r'sendmsg.+failed',
+ r'recvmsg.+failed']
self.skip_sanitizer = False
def _stop(self):
@@ -235,7 +236,7 @@ class TestUnit(unittest.TestCase):
if sanitizer_errors:
self._print_path_to_log()
- self.assertFalse(sanitizer_error, 'sanitizer error(s)')
+ self.assertFalse(sanitizer_errors, 'sanitizer error(s)')
if found:
print('skipped.')
@@ -285,7 +286,6 @@ class TestUnitHTTP(TestUnit):
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'
- blocking = False if 'blocking' not in kwargs else kwargs['blocking']
headers = ({
'Host': 'localhost',
@@ -309,6 +309,9 @@ class TestUnitHTTP(TestUnit):
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)
@@ -319,8 +322,6 @@ class TestUnitHTTP(TestUnit):
sock.close()
return None
- sock.setblocking(blocking)
-
else:
sock = kwargs['sock']
@@ -335,7 +336,12 @@ class TestUnitHTTP(TestUnit):
headers['Content-Length'] = len(body)
for header, value in headers.items():
- req += header + ': ' + str(value) + crlf
+ if isinstance(value, list):
+ for v in value:
+ req += header + ': ' + str(v) + crlf
+
+ else:
+ req += header + ': ' + str(value) + crlf
req = (req + crlf).encode() + body
@@ -350,8 +356,9 @@ class TestUnitHTTP(TestUnit):
resp = ''
if 'no_recv' not in kwargs:
- enc = 'utf-8' if 'encoding' not in kwargs else kwargs['encoding']
- resp = self.recvall(sock).decode(enc)
+ 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')
@@ -377,9 +384,9 @@ class TestUnitHTTP(TestUnit):
def put(self, **kwargs):
return self.http('PUT', **kwargs)
- def recvall(self, sock, buff_size=4096):
+ def recvall(self, sock, read_timeout=5, buff_size=4096):
data = b''
- while select.select([sock], [], [], 1)[0]:
+ while select.select([sock], [], [], read_timeout)[0]:
try:
part = sock.recv(buff_size)
except:
@@ -428,7 +435,7 @@ class TestUnitControl(TestUnitHTTP):
# TODO http client
def conf(self, conf, path='/config'):
- if isinstance(conf, dict):
+ if isinstance(conf, dict) or isinstance(conf, list):
conf = json.dumps(conf)
if path[:1] != '/':
@@ -593,6 +600,72 @@ class TestUnitApplicationNode(TestUnitApplicationProto):
}
})
+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({
@@ -637,15 +710,27 @@ class TestUnitApplicationTLS(TestUnitApplicationProto):
return self.conf(k.read() + c.read(), '/certificates/' + crt)
def get_ssl(self, **kwargs):
- return self.get(blocking=True, wrapper=self.context.wrap_socket,
+ return self.get(wrapper=self.context.wrap_socket,
**kwargs)
def post_ssl(self, **kwargs):
- return self.post(blocking=True, wrapper=self.context.wrap_socket,
+ return self.post(wrapper=self.context.wrap_socket,
**kwargs)
def get_server_certificate(self, addr=('127.0.0.1', 7080)):
- return ssl.get_server_certificate(addr)
+
+ 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:
diff --git a/version b/version
new file mode 100644
index 00000000..07985001
--- /dev/null
+++ b/version
@@ -0,0 +1,5 @@
+
+# Copyright (C) NGINX, Inc.
+
+NXT_VERSION=1.8.0
+NXT_VERNUM=10800