diff options
209 files changed, 19382 insertions, 658 deletions
@@ -19,3 +19,4 @@ d411e7fdee9e03036adb652f8d9f4c45a420bdd5 1.6 abb8cfb421f608df1c23f5c333c5f049a79a681a 1.7-1 0f04ef991fbc1dadbc590ab7fb229d4f3d6357bc 1.7.1 fe0d5eb09b66e77a2b66455faa51d3fa04146d3d 1.7.1-1 +0a18a14d169f156f8e2daca35aa86d5a6dd9b1ae 1.8.0 @@ -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 @@ -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 \ @@ -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 <defan@nginx.com>"> + +<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 <defan@nginx.com>"> + +<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 <defan@nginx.com>"> + +<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 <defan@nginx.com>"> + +<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 <defan@nginx.com>"> + +<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 <defan@nginx.com>"> + +<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 |