diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml new file mode 100644 index 0000000000..12b87cb3c1 --- /dev/null +++ b/.github/workflows/semantic-pull-request.yml @@ -0,0 +1,30 @@ +name: "Lint PR" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + main: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Configure which types are allowed. + # Default: https://github.com/commitizen/conventional-commit-types + types: | + bugfix # bug fixes + change # backward incompatible changes + doc # documentation changes including code comments + editor # code editor related configurations + feature # implementing a new feature + optimize # performance optimizations + refactor # code refactoring and other code rearrangement + style # coding style changes + tests # test suite changes diff --git a/.gitignore b/.gitignore index eff337246b..5a8d398bb8 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ test/blib test.sh t.sh t/t.sh +t/servroot* test/t/servroot/ releng reset @@ -170,3 +171,10 @@ tthread addr2line hup theaders +src/ngx_http_lua_autoconf.h +src/autoconf.h +src/filters.c +src/filters.h +src/ringbuf.c +src/ringbuf.h +src/pipe.[ch] diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 0000000000..f1a8a2e255 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,43 @@ +--- +pull_request_rules: + - name: warn on conflicts + conditions: + - conflict + actions: + comment: + message: This pull request is now in conflict :( + label: + add: + - conflict + - name: remove conflict label if not needed + conditions: + - -conflict + actions: + label: + remove: + - conflict + - name: add label needs-test-cases + conditions: + - files~=^src/ + - -files~=^t/ + actions: + label: + add: + - needs-test-cases + - name: remove label needs-test-cases + conditions: + - label=needs-test-cases + - files~=^src/ + - files~=^t/ + actions: + label: + remove: + - needs-test-cases + - name: add label could-be-merged + conditions: + - "#approved-reviews-by>=2" + - status-success=Travis CI - Pull Request + actions: + label: + add: + - could-be-merged diff --git a/.travis.yml b/.travis.yml index 9834c8a933..4aebcf6996 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ -sudo: required -dist: trusty +dist: focal + +branches: + only: + - "master" os: linux @@ -7,24 +10,33 @@ language: c compiler: - gcc - - clang addons: apt: packages: - - axel - - cpanminus - - libtest-base-perl - - libtext-diff-perl - - liburi-perl - - libwww-perl - - libtest-longstring-perl - - liblist-moreutils-perl - - libgd-dev + - ack + - axel + - cpanminus + - libtest-base-perl + - libtext-diff-perl + - liburi-perl + - libwww-perl + - libtest-longstring-perl + - liblist-moreutils-perl + - libgd-dev + - time + - cmake + - libunwind-dev + - wget + - libbrotli1 + - lsb-release + - wget + - gnupg + - ca-certificates cache: directories: - - download-cache + - download-cache env: global: @@ -34,11 +46,12 @@ env: - LUAJIT_LIB=$LUAJIT_PREFIX/lib - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 - LUA_INCLUDE_DIR=$LUAJIT_INC - - PCRE_VER=8.41 - - PCRE_PREFIX=/opt/pcre - - PCRE_LIB=$PCRE_PREFIX/lib - - PCRE_INC=$PCRE_PREFIX/include - - OPENSSL_PREFIX=/opt/ssl + #- PCRE2_PREFIX=/usr/local/openresty/pcre2 + - PCRE2_PREFIX=/opt/pcre2 + - PCRE2_LIB=$PCRE2_PREFIX/lib + - PCRE2_INC=$PCRE2_PREFIX/include + #- OPENSSL_PREFIX=/usr/local/openresty/openssl3 + - OPENSSL_PREFIX=/opt/openssl3 - OPENSSL_LIB=$OPENSSL_PREFIX/lib - OPENSSL_INC=$OPENSSL_PREFIX/include - LIBDRIZZLE_PREFIX=/opt/drizzle @@ -47,30 +60,40 @@ env: - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH - DRIZZLE_VER=2011.07.21 - TEST_NGINX_SLEEP=0.006 - matrix: - - NGINX_VERSION=1.13.6 OPENSSL_VER=1.0.2n OPENSSL_PATCH_VER=1.0.2h - - NGINX_VERSION=1.13.6 OPENSSL_VER=1.1.0g OPENSSL_PATCH_VER=1.1.0d + - MALLOC_PERTURB_=9 + jobs: + - NGINX_VERSION=1.29.2 OPENSSL_VER=3.5.4 OPENSSL_PATCH_VER=3.5.4 TEST_NGINX_TIMEOUT=5 PCRE2_VER=10.46 + - NGINX_VERSION=1.29.2 OPENSSL_VER=3.5.4 OPENSSL_PATCH_VER=3.5.4 TEST_NGINX_TIMEOUT=5 PCRE2_VER=10.46 TEST_NGINX_USE_HTTP2=1 + - NGINX_VERSION=1.29.2 OPENSSL_VER=3.5.4 OPENSSL_PATCH_VER=3.5.4 TEST_NGINX_USE_HTTP3=1 TEST_NGINX_QUIC_IDLE_TIMEOUT=3 PCRE2_VER=10.46 + - NGINX_VERSION=1.29.2 BORINGSSL=1 TEST_NGINX_USE_HTTP3=1 TEST_NGINX_QUIC_IDLE_TIMEOUT=3 PCRE2_VER=10.46 services: - - memcache - - redis-server - - mysql + - memcached + - redis + - mysql before_install: - '! grep -n -P ''(?<=.{80}).+'' --color `find src -name ''*.c''` `find . -name ''*.h''` || (echo "ERROR: Found C source lines exceeding 80 columns." > /dev/stderr; exit 1)' - '! grep -n -P ''\t+'' --color `find src -name ''*.c''` `find . -name ''*.h''` || (echo "ERROR: Cannot use tabs." > /dev/stderr; exit 1)' - - sudo cpanm --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1) + - /usr/bin/env perl $(command -v cpanm) --sudo --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1) + - wget -O - https://openresty.org/package/pubkey.gpg | sudo apt-key add - + - echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list + - sudo apt-get update + - sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openresty-pcre2 openresty-openssl3 openresty-pcre2-dev openresty-openssl3-dev + install: - - if [ ! -f download-cache/drizzle7-$DRIZZLE_VER.tar.gz ]; then wget -P download-cache http://openresty.org/download/drizzle7-$DRIZZLE_VER.tar.gz; fi - - if [ ! -f download-cache/pcre-$PCRE_VER.tar.gz ]; then wget -P download-cache http://ftp.cs.stanford.edu/pub/exim/pcre/pcre-$PCRE_VER.tar.gz; fi - - if [ ! -f download-cache/openssl-$OPENSSL_VER.tar.gz ]; then wget -P download-cache https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz; fi + - if [ ! -f download-cache/drizzle7-$DRIZZLE_VER.tar.gz ]; then wget -P download-cache https://github.com/openresty/openresty-deps-prebuild/releases/download/v20230902/drizzle7-$DRIZZLE_VER.tar.gz; fi + - if [ -n "$PCRE2_VER" ] && [ ! -f download-cache/pcre2-$PCRE2_VER.tar.gz ]; then wget -P download-cache https://github.com/PCRE2Project/pcre2/releases/download/pcre2-${PCRE2_VER}/pcre2-${PCRE2_VER}.tar.gz; fi + - if [ -n "$OPENSSL_VER" ] && [ ! -f download-cache/openssl-$OPENSSL_VER.tar.gz ]; then wget -P download-cache https://github.com/openssl/openssl/releases/download/openssl-$OPENSSL_VER/openssl-$OPENSSL_VER.tar.gz || wget -P download-cache https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz || wget -P download-cache https://www.openssl.org/source/old/${OPENSSL_VER//[a-z]/}/openssl-$OPENSSL_VER.tar.gz; fi + - wget https://github.com/openresty/openresty-deps-prebuild/releases/download/v20230902/boringssl-20230902-x64-focal.tar.gz + - wget https://github.com/openresty/openresty-deps-prebuild/releases/download/v20230902/curl-h3-x64-focal.tar.gz - git clone https://github.com/openresty/test-nginx.git - git clone https://github.com/openresty/openresty.git ../openresty - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx - git clone https://github.com/openresty/openresty-devel-utils.git - git clone https://github.com/openresty/mockeagain.git - - git clone https://github.com/openresty/lua-cjson.git + - git clone https://github.com/openresty/lua-cjson.git lua-cjson - git clone https://github.com/openresty/lua-upstream-nginx-module.git ../lua-upstream-nginx-module - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module - git clone https://github.com/openresty/nginx-eval-module.git ../nginx-eval-module @@ -86,14 +109,24 @@ install: - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache - git clone https://github.com/openresty/lua-resty-mysql.git ../lua-resty-mysql + - git clone https://github.com/spacewander/lua-resty-rsa.git ../lua-resty-rsa + - git clone https://github.com/openresty/lua-resty-string.git ../lua-resty-string - git clone https://github.com/openresty/stream-lua-nginx-module.git ../stream-lua-nginx-module - - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git + - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git luajit2 before_script: - - mysql -uroot -e 'create database ngx_test; grant all on ngx_test.* to "ngx_test"@"%" identified by "ngx_test"; flush privileges;' + - mysql -uroot -e "create database ngx_test; CREATE USER 'ngx_test'@'%' IDENTIFIED WITH mysql_native_password BY 'ngx_test'; grant all on ngx_test.* to 'ngx_test'@'%'; flush privileges;" script: + - sudo tar -C / -xf curl-h3-x64-focal.tar.gz + - export PATH=$PWD/work/nginx/sbin:$PWD/openresty-devel-utils:/opt/curl-h3/bin:$PATH + - ngx-releng > check.txt || true + - lines=`wc -l check.txt | awk '{print $1}'`; if [ $lines -gt 5 ]; then cat check.txt; exit 1; fi - sudo iptables -I OUTPUT 1 -p udp --dport 10086 -j REJECT + - sudo iptables -I OUTPUT -p tcp --dst 127.0.0.2 --dport 12345 -j DROP + - sudo iptables -I OUTPUT -p udp --dst 127.0.0.2 --dport 12345 -j DROP + - sudo ip route add prohibit 0.0.0.1/32 + - sudo sysctl -w kernel.pid_max=10000 - cd luajit2/ - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT -msse4.2' > build.log 2>&1 || (cat build.log && exit 1) - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) @@ -104,28 +137,23 @@ script: - sudo make install-libdrizzle-1.0 > build.log 2>&1 || (cat build.log && exit 1) - cd ../mockeagain/ && make CC=$CC -j$JOBS && cd .. - cd lua-cjson/ && make -j$JOBS && sudo make install && cd .. - - tar zxf download-cache/pcre-$PCRE_VER.tar.gz - - cd pcre-$PCRE_VER/ - - ./configure --prefix=$PCRE_PREFIX --enable-jit --enable-utf --enable-unicode-properties > build.log 2>&1 || (cat build.log && exit 1) - - make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1) - - sudo PATH=$PATH make install > build.log 2>&1 || (cat build.log && exit 1) - - cd .. - - tar zxf download-cache/openssl-$OPENSSL_VER.tar.gz - - cd openssl-$OPENSSL_VER/ - - patch -p1 < ../../openresty/patches/openssl-$OPENSSL_PATCH_VER-sess_set_get_cb_yield.patch - - ./config no-threads shared enable-ssl3 enable-ssl3-method -g --prefix=$OPENSSL_PREFIX -DPURIFY > build.log 2>&1 || (cat build.log && exit 1) - - make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1) - - sudo make PATH=$PATH install_sw > build.log 2>&1 || (cat build.log && exit 1) - - cd .. - - export PATH=$PWD/work/nginx/sbin:$PWD/openresty-devel-utils:$PATH + - if [ -n "$PCRE2_VER" ]; then tar zxf download-cache/pcre2-$PCRE2_VER.tar.gz; cd pcre2-$PCRE2_VER/; ./configure --prefix=$PCRE2_PREFIX --enable-jit --enable-utf > build.log 2>&1 || (cat build.log && exit 1); make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1); sudo PATH=$PATH make install > build.log 2>&1 || (cat build.log && exit 1); cd ..; fi + - if [ -n "$OPENSSL_VER" ]; then tar zxf download-cache/openssl-$OPENSSL_VER.tar.gz; cd openssl-$OPENSSL_VER/; patch -p1 < ../../openresty/patches/openssl-$OPENSSL_PATCH_VER-sess_set_get_cb_yield.patch; ./config shared enable-ssl3 enable-ssl3-method -g --prefix=$OPENSSL_PREFIX --libdir=lib -DPURIFY > build.log 2>&1 || (cat build.log && exit 1); make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1); sudo make PATH=$PATH install_sw > build.log 2>&1 || (cat build.log && exit 1); cd ..; fi + - if [ -n "$BORINGSSL" ]; then sudo rm -fr /usr/local/openresty/openssl3/ && sudo mkdir -p /usr/local/openresty/openssl3 && sudo tar -C /usr/local/openresty/openssl3 -xf boringssl-20230902-x64-focal.tar.gz --strip-components=1; fi - export NGX_BUILD_CC=$CC + - sh util/build-without-ssl.sh $NGINX_VERSION > build.log 2>&1 || (cat build.log && exit 1) + - sh util/build-with-dd.sh $NGINX_VERSION > build.log 2>&1 || (cat build.log && exit 1) + - rm -fr buildroot - sh util/build.sh $NGINX_VERSION > build.log 2>&1 || (cat build.log && exit 1) - nginx -V + - python3 ./util/nc_server.py & - ldd `which nginx`|grep -E 'luajit|ssl|pcre' - export LD_PRELOAD=$PWD/mockeagain/mockeagain.so - export LD_LIBRARY_PATH=$PWD/mockeagain:$LD_LIBRARY_PATH + - export TEST_NGINX_HTTP3_CRT=$PWD/t/cert/http3/http3.crt + - export TEST_NGINX_HTTP3_KEY=$PWD/t/cert/http3/http3.key - export TEST_NGINX_RESOLVER=8.8.4.4 - dig +short myip.opendns.com @resolver1.opendns.com || exit 0 - dig +short @$TEST_NGINX_RESOLVER openresty.org || exit 0 - dig +short @$TEST_NGINX_RESOLVER agentzh.org || exit 0 - - prove -Itest-nginx/lib -r t + - /usr/bin/env perl $(command -v prove) -I. -Itest-nginx/inc -Itest-nginx/lib -r t/ diff --git a/README.markdown b/README.markdown index 4a07ba01a4..961ecb593a 100644 --- a/README.markdown +++ b/README.markdown @@ -1,14 +1,13 @@ - - Name ==== ngx_http_lua_module - Embed the power of Lua into Nginx HTTP Servers. -*This module is not distributed with the Nginx source.* See [the installation instructions](#installation). +This module is a core component of [OpenResty](https://openresty.org). If you are using this module, +then you are essentially using OpenResty :) + +*This module is not distributed with the Nginx source.* See +[the installation instructions](#installation). Table of Contents ================= @@ -16,6 +15,7 @@ Table of Contents * [Name](#name) * [Status](#status) * [Version](#version) +* [Videos](#videos) * [Synopsis](#synopsis) * [Description](#description) * [Typical Uses](#typical-uses) @@ -23,13 +23,12 @@ Table of Contents * [Installation](#installation) * [Building as a dynamic module](#building-as-a-dynamic-module) * [C Macro Configurations](#c-macro-configurations) - * [Installation on Ubuntu 11.10](#installation-on-ubuntu-1110) * [Community](#community) * [English Mailing List](#english-mailing-list) * [Chinese Mailing List](#chinese-mailing-list) * [Code Repository](#code-repository) * [Bugs and Patches](#bugs-and-patches) -* [Lua/LuaJIT bytecode support](#lualuajit-bytecode-support) +* [LuaJIT bytecode support](#luajit-bytecode-support) * [System Environment Variable Support](#system-environment-variable-support) * [HTTP 1.0 support](#http-10-support) * [Statically Linking Pure Lua Modules](#statically-linking-pure-lua-modules) @@ -46,6 +45,7 @@ Table of Contents * [Missing data on short circuited requests](#missing-data-on-short-circuited-requests) * [TODO](#todo) * [Changes](#changes) +* [Build And Test](#build-and-test) * [Test Suite](#test-suite) * [Copyright and License](#copyright-and-license) * [See Also](#see-also) @@ -53,6 +53,7 @@ Table of Contents * [Nginx API for Lua](#nginx-api-for-lua) * [Obsolete Sections](#obsolete-sections) * [Special PCRE Sequences](#special-pcre-sequences) + * [Lua/LuaJIT bytecode support](#lualuajit-bytecode-support) Status ====== @@ -62,7 +63,36 @@ Production ready. Version ======= -This document describes ngx_lua [v0.10.11](https://github.com/openresty/lua-nginx-module/tags) released on 3 November 2017. +This document describes ngx_lua +[v0.10.28](https://github.com/openresty/lua-nginx-module/tags), which was released +on 17 Jan, 2025. + +Videos +====== + +* YouTube video "[Hello World HTTP Example with OpenResty/Lua](https://youtu.be/eSfYLvVQMxw)" + + [![Hello World HTTP Example with OpenResty/Lua](https://img.youtube.com/vi/eSfYLvVQMxw/0.jpg)](https://youtu.be/eSfYLvVQMxw) + +* YouTube video "[Write Your Own Lua Modules in OpenResty/Nginx Applications](https://youtu.be/vfYxOMl5LVY)" + + [![Write Your Own Lua Modules in OpenResty/Nginx Applications](https://img.youtube.com/vi/vfYxOMl5LVY/0.jpg)](https://youtu.be/vfYxOMl5LVY) + +* YouTube video "[OpenResty's resty Command-Line Utility Demo](https://youtu.be/L1c7aw4mSOo)" + + [![OpenResty's resty Command-Line Utility Demo](https://img.youtube.com/vi/L1c7aw4mSOo/0.jpg)](https://youtu.be/L1c7aw4mSOo) + +* YouTube video "[Measure Execution Time of Lua Code Correctly in OpenResty](https://youtu.be/VkRYW_qLoME)" + + [![Measure Execution Time of Lua Code Correctly in OpenResty](https://img.youtube.com/vi/VkRYW_qLoME/0.jpg)](https://youtu.be/VkRYW_qLoME) + +* YouTube video "[Precompile Lua Modules into LuaJIT Bytecode to Speedup OpenResty Startup](https://youtu.be/EP7c0BM2yNo)" + + [![Precompile Lua Modules into LuaJIT Bytecode to Speedup OpenResty Startup](https://img.youtube.com/vi/EP7c0BM2yNo/0.jpg)](https://youtu.be/EP7c0BM2yNo) + +You are welcome to subscribe to our [official YouTube channel, OpenResty](https://www.youtube.com/channel/UCXVmwF-UCScv2ftsGoMqxhw). + +[Back to TOC](#table-of-contents) Synopsis ======== @@ -187,12 +217,27 @@ Synopsis Description =========== -This module embeds Lua, via the standard Lua 5.1 interpreter or [LuaJIT 2.0/2.1](http://luajit.org/luajit.html), into Nginx and by leveraging Nginx's subrequests, allows the integration of the powerful Lua threads (Lua coroutines) into the Nginx event model. +This module embeds [LuaJIT 2.0/2.1](https://luajit.org/luajit.html) into Nginx. +It is a core component of [OpenResty](https://openresty.org). If you are using +this module, then you are essentially using OpenResty. + +Since version `v0.10.16` of this module, the standard Lua +interpreter (also known as "PUC-Rio Lua") is not supported anymore. This +document interchangeably uses the terms "Lua" and "LuaJIT" to refer to the +LuaJIT interpreter. -Unlike [Apache's mod_lua](https://httpd.apache.org/docs/trunk/mod/mod_lua.html) and [Lighttpd's mod_magnet](http://redmine.lighttpd.net/wiki/1/Docs:ModMagnet), Lua code executed using this module can be *100% non-blocking* on network traffic as long as the [Nginx API for Lua](#nginx-api-for-lua) provided by this module is used to handle -requests to upstream services such as MySQL, PostgreSQL, Memcached, Redis, or upstream HTTP web services. +By leveraging Nginx's subrequests, this module allows the integration of the +powerful Lua threads (known as Lua "coroutines") into the Nginx event model. -At least the following Lua libraries and Nginx modules can be used with this ngx_lua module: +Unlike [Apache's mod_lua](https://httpd.apache.org/docs/trunk/mod/mod_lua.html) +and [Lighttpd's mod_magnet](http://redmine.lighttpd.net/wiki/1/Docs:ModMagnet), +Lua code executed using this module can be *100% non-blocking* on network +traffic as long as the [Nginx API for Lua](#nginx-api-for-lua) provided by +this module is used to handle requests to upstream services such as MySQL, +PostgreSQL, Memcached, Redis, or upstream HTTP web services. + +At least the following Lua libraries and Nginx modules can be used with this +module: * [lua-resty-memcached](https://github.com/openresty/lua-resty-memcached) * [lua-resty-mysql](https://github.com/openresty/lua-resty-mysql) @@ -211,15 +256,26 @@ At least the following Lua libraries and Nginx modules can be used with this ngx * [ngx_proxy](http://nginx.org/en/docs/http/ngx_http_proxy_module.html) * [ngx_fastcgi](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html) -Almost all the Nginx modules can be used with this ngx_lua module by means of [ngx.location.capture](#ngxlocationcapture) or [ngx.location.capture_multi](#ngxlocationcapture_multi) but it is recommended to use those `lua-resty-*` libraries instead of creating subrequests to access the Nginx upstream modules because the former is usually much more flexible and memory-efficient. +Almost any Nginx modules can be used with this ngx_lua module by means of +[ngx.location.capture](#ngxlocationcapture) or +[ngx.location.capture_multi](#ngxlocationcapture_multi) but it is +recommended to use those `lua-resty-*` libraries instead of creating +subrequests to access the Nginx upstream modules because the former is usually +much more flexible and memory-efficient. -The Lua interpreter or LuaJIT instance is shared across all the requests in a single nginx worker process but request contexts are segregated using lightweight Lua coroutines. +The Lua interpreter (also known as "Lua State" or "LuaJIT VM instance") is +shared across all the requests in a single Nginx worker process to minimize +memory use. Request contexts are segregated using lightweight Lua coroutines. -Loaded Lua modules persist in the nginx worker process level resulting in a small memory footprint in Lua even when under heavy loads. +Loaded Lua modules persist in the Nginx worker process level resulting in a +small memory footprint in Lua even when under heavy loads. -This module is plugged into NGINX's "http" subsystem so it can only speaks downstream communication protocols in the HTTP family (HTTP 0.9/1.0/1.1/2.0, WebSockets, and etc). -If you want to do generic TCP communications with the downstream clients, then you should use the [ngx_stream_lua](https://github.com/openresty/stream-lua-nginx-module#readme) module instead -which has a compatible Lua API. +This module is plugged into Nginx's "http" subsystem so it can only speak +downstream communication protocols in the HTTP family (HTTP 0.9/1.0/1.1/2.0, +WebSockets, etc...). If you want to do generic TCP communications with the +downstream clients, then you should use the +[ngx_stream_lua](https://github.com/openresty/stream-lua-nginx-module#readme) +module instead, which offers a compatible Lua API. [Back to TOC](#table-of-contents) @@ -228,7 +284,7 @@ Typical Uses Just to name a few: -* Mashup'ing and processing outputs of various nginx upstream outputs (proxy, drizzle, postgres, redis, memcached, and etc) in Lua, +* Mashup'ing and processing outputs of various Nginx upstream outputs (proxy, drizzle, postgres, redis, memcached, etc.) in Lua, * doing arbitrarily complex access control and security checks in Lua before requests actually reach the upstream backends, * manipulating response headers in an arbitrary way (by Lua) * fetching backend information from external storage backends (like redis, memcached, mysql, postgresql) and use that information to choose which upstream backend to access on-the-fly, @@ -236,11 +292,14 @@ Just to name a few: * doing very complex URL dispatch in Lua at rewrite phase, * using Lua to implement advanced caching mechanism for Nginx's subrequests and arbitrary locations. -The possibilities are unlimited as the module allows bringing together various elements within Nginx as well as exposing the power of the Lua language to the user. The module provides the full flexibility of scripting while offering performance levels comparable with native C language programs both in terms of CPU time as well as memory footprint. This is particularly the case when LuaJIT 2.x is enabled. - -Other scripting language implementations typically struggle to match this performance level. +The possibilities are unlimited as the module allows bringing together various +elements within Nginx as well as exposing the power of the Lua language to the +user. The module provides the full flexibility of scripting while offering +performance levels comparable with native C language programs both in terms of +CPU time as well as memory footprint thanks to LuaJIT 2.x. -The Lua state (Lua VM instance) is shared across all the requests handled by a single nginx worker process to minimize memory use. +Other scripting language implementations typically struggle to match this +performance level. [Back to TOC](#table-of-contents) @@ -249,6 +308,14 @@ Nginx Compatibility The latest version of this module is compatible with the following versions of Nginx: +* 1.29.x (last tested: 1.29.2) +* 1.27.x (last tested: 1.27.1) +* 1.25.x (last tested: 1.25.1) +* 1.21.x (last tested: 1.21.4) +* 1.19.x (last tested: 1.19.3) +* 1.17.x (last tested: 1.17.8) +* 1.15.x (last tested: 1.15.8) +* 1.14.x * 1.13.x (last tested: 1.13.6) * 1.12.x * 1.11.x (last tested: 1.11.2) @@ -265,22 +332,36 @@ Nginx cores older than 1.6.0 (exclusive) are *not* supported. Installation ============ -It is *highly* recommended to use [OpenResty releases](http://openresty.org) which integrate Nginx, ngx_lua, LuaJIT 2.1, as well as other powerful companion Nginx modules and Lua libraries. It is discouraged to build this module with nginx yourself since it is tricky to set up exactly right. Also, the stock nginx cores have various limitations and long standing bugs that can make some of this modules' features become disabled, not work properly, or run slower. +It is *highly* recommended to use [OpenResty releases](https://openresty.org) +which bundle Nginx, ngx_lua (this module), LuaJIT, as well as other powerful +companion Nginx modules and Lua libraries. + +It is discouraged to build this module with Nginx yourself since it is tricky +to set up exactly right. + +Note that Nginx, LuaJIT, and OpenSSL official releases have various limitations +and long-standing bugs that can cause some of this module's features to be +disabled, not work properly, or run slower. Official OpenResty releases are +recommended because they bundle [OpenResty's optimized LuaJIT 2.1 fork](https://github.com/openresty/luajit2) and +[Nginx/OpenSSL +patches](https://github.com/openresty/openresty/tree/master/patches). Alternatively, ngx_lua can be manually compiled into Nginx: -1. Install LuaJIT 2.0 or 2.1 (recommended) or Lua 5.1 (Lua 5.2 is *not* supported yet). LuaJIT can be downloaded from the [LuaJIT project website](http://luajit.org/download.html) and Lua 5.1, from the [Lua project website](http://www.lua.org/). Some distribution package managers also distribute LuaJIT and/or Lua. -1. Download the latest version of the ngx_devel_kit (NDK) module [HERE](https://github.com/simpl/ngx_devel_kit/tags). -1. Download the latest version of ngx_lua [HERE](https://github.com/openresty/lua-nginx-module/tags). -1. Download the latest version of Nginx [HERE](http://nginx.org/) (See [Nginx Compatibility](#nginx-compatibility)) +1. LuaJIT can be downloaded from the [latest release of OpenResty's LuaJIT fork](https://github.com/openresty/luajit2/releases). The official LuaJIT 2.x releases are also supported, although performance will be significantly lower for reasons elaborated above +1. Download the latest version of the ngx_devel_kit (NDK) module [HERE](https://github.com/simplresty/ngx_devel_kit/tags) +1. Download the latest version of ngx_lua [HERE](https://github.com/openresty/lua-nginx-module/tags) +1. Download the latest supported version of Nginx [HERE](https://nginx.org/) (See [Nginx Compatibility](#nginx-compatibility)) +1. Download the latest version of the lua-resty-core [HERE](https://github.com/openresty/lua-resty-core) +1. Download the latest version of the lua-resty-lrucache [HERE](https://github.com/openresty/lua-resty-lrucache) Build the source with this module: ```bash - wget 'http://nginx.org/download/nginx-1.13.6.tar.gz' - tar -xzvf nginx-1.13.6.tar.gz - cd nginx-1.13.6/ + wget 'https://openresty.org/download/nginx-1.19.3.tar.gz' + tar -xzvf nginx-1.19.3.tar.gz + cd nginx-1.19.3/ # tell nginx's build system where to find LuaJIT 2.0: export LUAJIT_LIB=/path/to/luajit/lib @@ -290,13 +371,9 @@ Build the source with this module: export LUAJIT_LIB=/path/to/luajit/lib export LUAJIT_INC=/path/to/luajit/include/luajit-2.1 - # or tell where to find Lua if using Lua instead: - #export LUA_LIB=/path/to/lua/lib - #export LUA_INC=/path/to/lua/include - # Here we assume Nginx is to be installed under /opt/nginx/. ./configure --prefix=/opt/nginx \ - --with-ld-opt="-Wl,-rpath,/path/to/luajit-or-lua/lib" \ + --with-ld-opt="-Wl,-rpath,/path/to/luajit/lib" \ --add-module=/path/to/ngx_devel_kit \ --add-module=/path/to/lua-nginx-module @@ -304,10 +381,22 @@ Build the source with this module: # current nginx build. # You can get usually those options using command nginx -V - # you can change the parallism number 2 below to fit the number of spare CPU cores in your + # you can change the parallelism number 2 below to fit the number of spare CPU cores in your # machine. make -j2 make install + + # Note that this version of lug-nginx-module not allow to set `lua_load_resty_core off;` any more. + # So, you have to install `lua-resty-core` and `lua-resty-lrucache` manually as below. + + cd lua-resty-core + make install PREFIX=/opt/nginx + cd lua-resty-lrucache + make install PREFIX=/opt/nginx + + # add necessary `lua_package_path` directive to `nginx.conf`, in the http context + + lua_package_path "/opt/nginx/lib/lua/?.lua;;"; ``` [Back to TOC](#table-of-contents) @@ -316,7 +405,7 @@ Building as a dynamic module ---------------------------- Starting from NGINX 1.9.11, you can also compile this module as a dynamic module, by using the `--add-dynamic-module=PATH` option instead of `--add-module=PATH` on the -`./configure` command line above. And then you can explicitly load the module in your `nginx.conf` via the [load_module](http://nginx.org/en/docs/ngx_core_module.html#load_module) +`./configure` command line above. And then you can explicitly load the module in your `nginx.conf` via the [load_module](https://nginx.org/en/docs/ngx_core_module.html#load_module) directive, for example, ```nginx @@ -330,44 +419,19 @@ directive, for example, C Macro Configurations ---------------------- -While building this module either via OpenResty or with the NGINX core, you can define the following C macros via the C compiler options: +While building this module either via OpenResty or with the Nginx core, you can define the following C macros via the C compiler options: * `NGX_LUA_USE_ASSERT` When defined, will enable assertions in the ngx_lua C code base. Recommended for debugging or testing builds. It can introduce some (small) runtime overhead when enabled. This macro was first introduced in the `v0.9.10` release. * `NGX_LUA_ABORT_AT_PANIC` - When the Lua/LuaJIT VM panics, ngx_lua will instruct the current nginx worker process to quit gracefully by default. By specifying this C macro, ngx_lua will abort the current nginx worker process (which usually result in a core dump file) immediately. This option is useful for debugging VM panics. This option was first introduced in the `v0.9.8` release. -* `NGX_LUA_NO_FFI_API` - Excludes pure C API functions for FFI-based Lua API for NGINX (as required by [lua-resty-core](https://github.com/openresty/lua-resty-core#readme), for example). Enabling this macro can make the resulting binary code size smaller. + When the LuaJIT VM panics, ngx_lua will instruct the current nginx worker process to quit gracefully by default. By specifying this C macro, ngx_lua will abort the current nginx worker process (which usually results in a core dump file) immediately. This option is useful for debugging VM panics. This option was first introduced in the `v0.9.8` release. -To enable one or more of these macros, just pass extra C compiler options to the `./configure` script of either NGINX or OpenResty. For instance, +To enable one or more of these macros, just pass extra C compiler options to the `./configure` script of either Nginx or OpenResty. For instance, ./configure --with-cc-opt="-DNGX_LUA_USE_ASSERT -DNGX_LUA_ABORT_AT_PANIC" -[Back to TOC](#table-of-contents) - -Installation on Ubuntu 11.10 ----------------------------- - -Note that it is recommended to use LuaJIT 2.0 or LuaJIT 2.1 instead of the standard Lua 5.1 interpreter wherever possible. - -If the standard Lua 5.1 interpreter is required however, run the following command to install it from the Ubuntu repository: - -```bash - - apt-get install -y lua5.1 liblua5.1-0 liblua5.1-0-dev -``` - -Everything should be installed correctly, except for one small tweak. - -Library name `liblua.so` has been changed in liblua5.1 package, it only comes with `liblua5.1.so`, which needs to be symlinked to `/usr/lib` so it could be found during the configuration process. - -```bash - - ln -s /usr/lib/x86_64-linux-gnu/liblua5.1.so /usr/lib/liblua.so -``` - [Back to TOC](#table-of-contents) Community @@ -392,7 +456,8 @@ The [openresty](https://groups.google.com/group/openresty) mailing list is for C Code Repository =============== -The code repository of this project is hosted on github at [openresty/lua-nginx-module](https://github.com/openresty/lua-nginx-module). +The code repository of this project is hosted on GitHub at +[openresty/lua-nginx-module](https://github.com/openresty/lua-nginx-module). [Back to TOC](#table-of-contents) @@ -406,12 +471,14 @@ Please submit bug reports, wishlists, or patches by [Back to TOC](#table-of-contents) -Lua/LuaJIT bytecode support -=========================== +LuaJIT bytecode support +======================= -As from the `v0.5.0rc32` release, all `*_by_lua_file` configure directives (such as [content_by_lua_file](#content_by_lua_file)) support loading Lua 5.1 and LuaJIT 2.0/2.1 raw bytecode files directly. +Watch YouTube video "[Measure Execution Time of Lua Code Correctly in OpenResty](https://youtu.be/VkRYW_qLoME)" -Please note that the bytecode format used by LuaJIT 2.0/2.1 is not compatible with that used by the standard Lua 5.1 interpreter. So if using LuaJIT 2.0/2.1 with ngx_lua, LuaJIT compatible bytecode files must be generated as shown: +[![Precompile Lua Modules into LuaJIT Bytecode to Speedup OpenResty Startup](https://img.youtube.com/vi/EP7c0BM2yNo/0.jpg)](https://youtu.be/EP7c0BM2yNo) + +As from the `v0.5.0rc32` release, all `*_by_lua_file` configure directives (such as [content_by_lua_file](#content_by_lua_file)) support loading LuaJIT 2.0/2.1 raw bytecode files directly: ```bash @@ -427,38 +494,29 @@ The `-bg` option can be used to include debug information in the LuaJIT bytecode Please refer to the official LuaJIT documentation on the `-b` option for more details: - - -Also, the bytecode files generated by LuaJIT 2.1 is *not* compatible with LuaJIT 2.0, and vice versa. The support for LuaJIT 2.1 bytecode was first added in ngx_lua v0.9.3. - -Similarly, if using the standard Lua 5.1 interpreter with ngx_lua, Lua compatible bytecode files must be generated using the `luac` commandline utility as shown: - -```bash - - luac -o /path/to/output_file.luac /path/to/input_file.lua -``` - -Unlike as with LuaJIT, debug information is included in standard Lua 5.1 bytecode files by default. This can be striped out by specifying the `-s` option as shown: + -```bash - - luac -s -o /path/to/output_file.luac /path/to/input_file.lua -``` +Note that the bytecode files generated by LuaJIT 2.1 is *not* compatible with +LuaJIT 2.0, and vice versa. The support for LuaJIT 2.1 bytecode was first added +in ngx_lua v0.9.3. -Attempts to load standard Lua 5.1 bytecode files into ngx_lua instances linked to LuaJIT 2.0/2.1 or vice versa, will result in an error message, such as that below, being logged into the Nginx `error.log` file: +Attempts to load standard Lua 5.1 bytecode files into ngx_lua instances linked +to LuaJIT 2.0/2.1 (or vice versa) will result in an Nginx error message such as +the one below: [error] 13909#0: *1 failed to load Lua inlined code: bad byte-code header in /path/to/test_file.luac -Loading bytecode files via the Lua primitives like `require` and `dofile` should always work as expected. +Loading bytecode files via the Lua primitives like `require` and +`dofile` should always work as expected. [Back to TOC](#table-of-contents) System Environment Variable Support =================================== -If you want to access the system environment variable, say, `foo`, in Lua via the standard Lua API [os.getenv](http://www.lua.org/manual/5.1/manual.html#pdf-os.getenv), then you should also list this environment variable name in your `nginx.conf` file via the [env directive](http://nginx.org/en/docs/ngx_core_module.html#env). For example, +If you want to access the system environment variable, say, `foo`, in Lua via the standard Lua API [os.getenv](https://www.lua.org/manual/5.1/manual.html#pdf-os.getenv), then you should also list this environment variable name in your `nginx.conf` file via the [env directive](https://nginx.org/en/docs/ngx_core_module.html#env). For example, ```nginx @@ -486,9 +544,12 @@ To force `curl` to send HTTP 1.0 requests, use the `-0` option. Statically Linking Pure Lua Modules =================================== -When LuaJIT 2.x is used, it is possible to statically link the bytecode of pure Lua modules into the Nginx executable. +With LuaJIT 2.x, it is possible to statically link the bytecode of pure Lua +modules into the Nginx executable. -Basically you use the `luajit` executable to compile `.lua` Lua module files to `.o` object files containing the exported bytecode data, and then link the `.o` files directly in your Nginx build. +You can use the `luajit` executable to compile `.lua` Lua +module files to `.o` object files containing the exported bytecode +data, and then link the `.o` files directly in your Nginx build. Below is a trivial example to demonstrate this. Consider that we have the following `.lua` file named `foo.lua`: @@ -506,7 +567,10 @@ Below is a trivial example to demonstrate this. Consider that we have the follow And then we compile this `.lua` file to `foo.o` file: - /path/to/luajit/bin/luajit -bg foo.lua foo.o +```bash + + /path/to/luajit/bin/luajit -bg foo.lua foo.o +``` What matters here is the name of the `.lua` file, which determines how you use this module later on the Lua land. The file name `foo.o` does not matter at all except the `.o` file extension (which tells `luajit` what output format is used). If you want to strip the Lua debug information from the resulting bytecode, you can just specify the `-b` option above instead of `-bg`. @@ -545,7 +609,7 @@ When you have multiple `.lua` files to compile and link, then just specify their ./configure --with-ld-opt="/path/to/foo.o /path/to/bar.o" ... ``` -If you have just too many `.o` files, then it might not be feasible to name them all in a single command. In this case, you can build a static library (or archive) for your `.o` files, as in +If you have too many `.o` files, then it might not be feasible to name them all in a single command. In this case, you can build a static library (or archive) for your `.o` files, as in ```bash @@ -567,9 +631,17 @@ where `/path/to/lib` is the path of the directory containing the `libmyluafiles. Data Sharing within an Nginx Worker =================================== -To globally share data among all the requests handled by the same nginx worker process, encapsulate the shared data into a Lua module, use the Lua `require` builtin to import the module, and then manipulate the shared data in Lua. This works because required Lua modules are loaded only once and all coroutines will share the same copy of the module (both its code and data). Note however that Lua global variables (note, not module-level variables) WILL NOT persist between requests because of the one-coroutine-per-request isolation design. +To globally share data among all the requests handled by the same Nginx worker +process, encapsulate the shared data into a Lua module, use the Lua +`require` builtin to import the module, and then manipulate the +shared data in Lua. This works because required Lua modules are loaded only +once and all coroutines will share the same copy of the module (both its code +and data). -Here is a complete small example: +Note that the use of global Lua variables is *strongly discouraged*, as it may +lead to unexpected race conditions between concurrent requests. + +Here is a small example on sharing data within an Nginx worker via a Lua module: ```lua @@ -602,16 +674,16 @@ and then accessing it from `nginx.conf`: ``` The `mydata` module in this example will only be loaded and run on the first request to the location `/lua`, -and all subsequent requests to the same nginx worker process will use the reloaded instance of the +and all subsequent requests to the same Nginx worker process will use the reloaded instance of the module as well as the same copy of the data in it, until a `HUP` signal is sent to the Nginx master process to force a reload. This data sharing technique is essential for high performance Lua applications based on this module. -Note that this data sharing is on a *per-worker* basis and not on a *per-server* basis. That is, when there are multiple nginx worker processes under an Nginx master, data sharing cannot cross the process boundary between these workers. +Note that this data sharing is on a *per-worker* basis and not on a *per-server* basis. That is, when there are multiple Nginx worker processes under an Nginx master, data sharing cannot cross the process boundary between these workers. -It is usually recommended to share read-only data this way. You can also share changeable data among all the concurrent requests of each nginx worker process as +It is usually recommended to share read-only data this way. You can also share changeable data among all the concurrent requests of each Nginx worker process as long as there is *no* nonblocking I/O operations (including [ngx.sleep](#ngxsleep)) in the middle of your calculations. As long as you do not give the -control back to the nginx event loop and ngx_lua's light thread +control back to the Nginx event loop and ngx_lua's light thread scheduler (even implicitly), there can never be any race conditions in between. For this reason, always be very careful when you want to share changeable data on the worker level. Buggy optimizations can easily lead to hard-to-debug @@ -620,8 +692,8 @@ race conditions under load. If server-wide data sharing is required, then use one or more of the following approaches: 1. Use the [ngx.shared.DICT](#ngxshareddict) API provided by this module. -1. Use only a single nginx worker and a single server (this is however not recommended when there is a multi core CPU or multiple CPUs in a single machine). -1. Use data storage mechanisms such as `memcached`, `redis`, `MySQL` or `PostgreSQL`. [The OpenResty bundle](http://openresty.org) associated with this module comes with a set of companion Nginx modules and Lua libraries that provide interfaces with these data storage mechanisms. +1. Use only a single Nginx worker and a single server (this is however not recommended when there is a multi core CPU or multiple CPUs in a single machine). +1. Use data storage mechanisms such as `memcached`, `redis`, `MySQL` or `PostgreSQL`. [The OpenResty official releases](https://openresty.org) come with a set of companion Nginx modules and Lua libraries that provide interfaces with these data storage mechanisms. [Back to TOC](#table-of-contents) @@ -632,6 +704,7 @@ Known Issues TCP socket connect operation issues ----------------------------------- + The [tcpsock:connect](#tcpsockconnect) method may indicate `success` despite connection failures such as with `Connection Refused` errors. However, later attempts to manipulate the cosocket object will fail and return the actual error status message generated by the failed connect operation. @@ -642,14 +715,15 @@ This issue is due to limitations in the Nginx event model and only appears to af Lua Coroutine Yielding/Resuming ------------------------------- -* Because Lua's `dofile` and `require` builtins are currently implemented as C functions in both Lua 5.1 and LuaJIT 2.0/2.1, if the Lua file being loaded by `dofile` or `require` invokes [ngx.location.capture*](#ngxlocationcapture), [ngx.exec](#ngxexec), [ngx.exit](#ngxexit), or other API functions requiring yielding in the *top-level* scope of the Lua file, then the Lua error "attempt to yield across C-call boundary" will be raised. To avoid this, put these calls requiring yielding into your own Lua functions in the Lua file instead of the top-level scope of the file. -* As the standard Lua 5.1 interpreter's VM is not fully resumable, the methods [ngx.location.capture](#ngxlocationcapture), [ngx.location.capture_multi](#ngxlocationcapture_multi), [ngx.redirect](#ngxredirect), [ngx.exec](#ngxexec), and [ngx.exit](#ngxexit) cannot be used within the context of a Lua [pcall()](http://www.lua.org/manual/5.1/manual.html#pdf-pcall) or [xpcall()](http://www.lua.org/manual/5.1/manual.html#pdf-xpcall) or even the first line of the `for ... in ...` statement when the standard Lua 5.1 interpreter is used and the `attempt to yield across metamethod/C-call boundary` error will be produced. Please use LuaJIT 2.x, which supports a fully resumable VM, to avoid this. + +* Because Lua's `dofile` and `require` builtins are currently implemented as C functions in LuaJIT 2.0/2.1, if the Lua file being loaded by `dofile` or `require` invokes [ngx.location.capture*](#ngxlocationcapture), [ngx.exec](#ngxexec), [ngx.exit](#ngxexit), or other API functions requiring yielding in the *top-level* scope of the Lua file, then the Lua error "attempt to yield across C-call boundary" will be raised. To avoid this, put these calls requiring yielding into your own Lua functions in the Lua file instead of the top-level scope of the file. [Back to TOC](#table-of-contents) Lua Variable Scope ------------------ -Care must be taken when importing modules and this form should be used: + +Care must be taken when importing modules, and this form should be used: ```lua @@ -686,7 +760,6 @@ It is therefore *highly* recommended to always declare such within an appropriat local function foo() return 123 end ``` - To find all instances of Lua global variables in your Lua code, run the [lua-releng tool](https://github.com/openresty/nginx-devel-utils/blob/master/lua-releng) across all `.lua` source files: $ lua-releng @@ -703,7 +776,8 @@ This tool will guarantee that local variables in the Lua module functions are al Locations Configured by Subrequest Directives of Other Modules -------------------------------------------------------------- -The [ngx.location.capture](#ngxlocationcapture) and [ngx.location.capture_multi](#ngxlocationcapture_multi) directives cannot capture locations that include the [add_before_body](http://nginx.org/en/docs/http/ngx_http_addition_module.html#add_before_body), [add_after_body](http://nginx.org/en/docs/http/ngx_http_addition_module.html#add_after_body), [auth_request](http://nginx.org/en/docs/http/ngx_http_auth_request_module.html#auth_request), [echo_location](http://github.com/openresty/echo-nginx-module#echo_location), [echo_location_async](http://github.com/openresty/echo-nginx-module#echo_location_async), [echo_subrequest](http://github.com/openresty/echo-nginx-module#echo_subrequest), or [echo_subrequest_async](http://github.com/openresty/echo-nginx-module#echo_subrequest_async) directives. + +The [ngx.location.capture](#ngxlocationcapture) and [ngx.location.capture_multi](#ngxlocationcapture_multi) directives cannot capture locations that include the [add_before_body](http://nginx.org/en/docs/http/ngx_http_addition_module.html#add_before_body), [add_after_body](http://nginx.org/en/docs/http/ngx_http_addition_module.html#add_after_body), [auth_request](https://nginx.org/en/docs/http/ngx_http_auth_request_module.html#auth_request), [echo_location](http://github.com/openresty/echo-nginx-module#echo_location), [echo_location_async](http://github.com/openresty/echo-nginx-module#echo_location_async), [echo_subrequest](http://github.com/openresty/echo-nginx-module#echo_subrequest), or [echo_subrequest_async](http://github.com/openresty/echo-nginx-module#echo_subrequest_async) directives. ```nginx @@ -732,11 +806,11 @@ will not work as expected. Cosockets Not Available Everywhere ---------------------------------- -Due to internal limitations in the nginx core, the cosocket API is disabled in the following contexts: [set_by_lua*](#set_by_lua), [log_by_lua*](#log_by_lua), [header_filter_by_lua*](#header_filter_by_lua), and [body_filter_by_lua](#body_filter_by_lua). +Due to internal limitations in the Nginx core, the cosocket API is disabled in the following contexts: [set_by_lua*](#set_by_lua), [log_by_lua*](#log_by_lua), [header_filter_by_lua*](#header_filter_by_lua), and [body_filter_by_lua](#body_filter_by_lua). -The cosockets are currently also disabled in the [init_by_lua*](#init_by_lua) and [init_worker_by_lua*](#init_worker_by_lua) directive contexts but we may add support for these contexts in the future because there is no limitation in the nginx core (or the limitation might be worked around). +The cosockets are currently also disabled in the [init_by_lua*](#init_by_lua) and [init_worker_by_lua*](#init_worker_by_lua) directive contexts but we may add support for these contexts in the future because there is no limitation in the Nginx core (or the limitation might be worked around). -There exists a work-around, however, when the original context does *not* need to wait for the cosocket results. That is, creating a zero-delay timer via the [ngx.timer.at](#ngxtimerat) API and do the cosocket results in the timer handler, which runs asynchronously as to the original context creating the timer. +There exists a workaround, however, when the original context does *not* need to wait for the cosocket results. That is, creating a zero-delay timer via the [ngx.timer.at](#ngxtimerat) API and do the cosocket results in the timer handler, which runs asynchronously as to the original context creating the timer. [Back to TOC](#table-of-contents) @@ -745,7 +819,7 @@ Special Escaping Sequences **NOTE** Following the `v0.9.17` release, this pitfall can be avoided by using the `*_by_lua_block {}` configuration directives. -PCRE sequences such as `\d`, `\s`, or `\w`, require special attention because in string literals, the backslash character, `\`, is stripped out by both the Lua language parser and by the nginx config file parser before processing if not within a `*_by_lua_block {}` directive. So the following snippet will not work as expected: +PCRE sequences such as `\d`, `\s`, or `\w`, require special attention because in string literals, the backslash character, `\`, is stripped out by both the Lua language parser and by the Nginx config file parser before processing if not within a `*_by_lua_block {}` directive. So the following snippet will not work as expected: ```nginx @@ -840,7 +914,7 @@ As noted earlier, PCRE sequences presented within `*_by_lua_block {}` directives # nginx.conf location /test { content_by_lua_block { - local regex = "\d+" + local regex = [[\d+]] local m = ngx.re.match("hello, 1234", regex) if m then ngx.say(m[0]) else ngx.say("not matched!") end } @@ -848,6 +922,7 @@ As noted earlier, PCRE sequences presented within `*_by_lua_block {}` directives # evaluates to "1234" ``` +**NOTE** You are recommended to use `by_lua_file` when the Lua code is very long. [Back to TOC](#table-of-contents) @@ -891,33 +966,13 @@ TODO ==== * cosocket: implement LuaSocket's unconnected UDP API. -* port this module to the "datagram" subsystem of NGINX for implementing general UDP servers instead of HTTP -servers in Lua. For example, -```lua - - datagram { - server { - listen 1953; - handler_by_lua_block { - -- custom Lua code implementing the special UDP server... - } - } - } -``` -* shm: implement a "shared queue API" to complement the existing [shared dict](#lua_shared_dict) API. * cosocket: add support in the context of [init_by_lua*](#init_by_lua). -* cosocket: implement the `bind()` method for stream-typed cosockets. -* cosocket: pool-based backend concurrency level control: implement automatic `connect` queueing when the backend concurrency exceeds its connection pool limit. * cosocket: review and merge aviramc's [patch](https://github.com/openresty/lua-nginx-module/pull/290) for adding the `bsdrecv` method. -* add new API function `ngx.resp.add_header` to emulate the standard `add_header` config directive. -* review and apply vadim-pavlov's patch for [ngx.location.capture](#ngxlocationcapture)'s `extra_headers` option -* use `ngx_hash_t` to optimize the built-in header look-up process for [ngx.req.set_header](#ngxreqset_header), [ngx.header.HEADER](#ngxheaderheader), and etc. -* add configure options for different strategies of handling the cosocket connection exceeding in the pools. -* add directives to run Lua codes when nginx stops. +* cosocket: add configure options for different strategies of handling the cosocket connection exceeding in the pools. +* use `ngx_hash_t` to optimize the built-in header look-up process for [ngx.req.set_header](#ngxreqset_header), and etc. * add `ignore_resp_headers`, `ignore_resp_body`, and `ignore_resp` options to [ngx.location.capture](#ngxlocationcapture) and [ngx.location.capture_multi](#ngxlocationcapture_multi) methods, to allow micro performance tuning on the user side. * add automatic Lua code time slicing support by yielding and resuming the Lua VM actively via Lua's debug hooks. * add `stat` mode similar to [mod_lua](https://httpd.apache.org/docs/trunk/mod/mod_lua.html). -* cosocket: add client SSL certificate support. [Back to TOC](#table-of-contents) @@ -926,10 +981,27 @@ Changes The changes made in every release of this module are listed in the change logs of the OpenResty bundle: - + [Back to TOC](#table-of-contents) +Build And Test +============== + +This module uses `.travis.yml` as the CI configuration. +You can always check `.travis.yml` for the latest CI configuration. + +For developers, you need to run tests locally. You can use `util/run-ci.sh` +to easily set up the environment and execute the test suite. + +To run the Test from the beginning: + +```shell +git clone https://github.com/openresty/lua-nginx-module.git +cd lua-nginx-module +bash util/run-ci.sh +``` + Test Suite ========== @@ -941,7 +1013,7 @@ The following dependencies are required to run the test suite: * Test::Nginx: * Nginx modules: - * [ngx_devel_kit](https://github.com/simpl/ngx_devel_kit) + * [ngx_devel_kit](https://github.com/simplresty/ngx_devel_kit) * [ngx_set_misc](https://github.com/openresty/set-misc-nginx-module) * [ngx_auth_request](http://mdounin.ru/files/ngx_http_auth_request_module-0.2.tar.gz) (this is not needed if you're using Nginx 1.5.4+. * [ngx_echo](https://github.com/openresty/echo-nginx-module) @@ -959,7 +1031,7 @@ The order in which these modules are added during configuration is important bec filtering chain determines the final output, for example. The correct adding order is shown above. * 3rd-party Lua libraries: - * [lua-cjson](http://www.kyne.com.au/~mark/software/lua-cjson.php) + * [lua-cjson](https://www.kyne.au/~mark/software/lua-cjson.php) * Applications: * mysql: create database 'ngx_test', grant all privileges to user 'ngx_test', password is 'ngx_test' @@ -974,7 +1046,6 @@ To run the whole test suite in the default testing mode: export PATH=/path/to/your/nginx/sbin:$PATH prove -I/path/to/test-nginx/lib -r t - To run specific test files: cd /path/to/lua-nginx-module @@ -984,7 +1055,7 @@ To run specific test files: To run a specific test block in a particular test file, add the line `--- ONLY` to the test block you want to run, and then use the `prove` utility to run that `.t` file. -There are also various testing modes based on mockeagain, valgrind, and etc. Refer to the [Test::Nginx documentation](http://search.cpan.org/perldoc?Test::Nginx) for more details for various advanced testing modes. See also the test reports for the Nginx test cluster running on Amazon EC2: . +There are also various testing modes based on mockeagain, valgrind, and etc. Refer to the [Test::Nginx documentation](https://search.cpan.org/perldoc?Test::Nginx) for more details for various advanced testing modes. See also the test reports for the Nginx test cluster running on Amazon EC2: . [Back to TOC](#table-of-contents) @@ -995,7 +1066,7 @@ This module is licensed under the BSD license. Copyright (C) 2009-2017, by Xiaozhe Wang (chaoslawful) . -Copyright (C) 2009-2017, by Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. +Copyright (C) 2009-2025, by Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. All rights reserved. @@ -1012,26 +1083,35 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND See Also ======== -* [ngx_stream_lua_module](https://github.com/openresty/stream-lua-nginx-module#readme) for an official port of this module for the NGINX "stream" subsystem (doing generic downstream TCP communications). +Blog posts: + +* [Introduction to Lua-Land CPU Flame Graphs](https://blog.openresty.com/en/lua-cpu-flame-graph/?src=gh_ngxlua) +* [How OpenResty and Nginx Allocate and Manage Memory](https://blog.openresty.com/en//how-or-alloc-mem?src=gh_ngxlua) +* [How OpenResty and Nginx Shared Memory Zones Consume RAM](https://blog.openresty.com/en/how-nginx-shm-consume-ram/?src=gh_ngxlua) +* [Memory Fragmentation in OpenResty and Nginx's Shared Memory Zones](https://blog.openresty.com/en/nginx-shm-frag/?src=gh_ngxlua) + +Other related modules and libraries: + +* [ngx_stream_lua_module](https://github.com/openresty/stream-lua-nginx-module#readme) for an official port of this module for the Nginx "stream" subsystem (doing generic downstream TCP communications). * [lua-resty-memcached](https://github.com/openresty/lua-resty-memcached) library based on ngx_lua cosocket. * [lua-resty-redis](https://github.com/openresty/lua-resty-redis) library based on ngx_lua cosocket. * [lua-resty-mysql](https://github.com/openresty/lua-resty-mysql) library based on ngx_lua cosocket. * [lua-resty-upload](https://github.com/openresty/lua-resty-upload) library based on ngx_lua cosocket. * [lua-resty-dns](https://github.com/openresty/lua-resty-dns) library based on ngx_lua cosocket. * [lua-resty-websocket](https://github.com/openresty/lua-resty-websocket) library for both WebSocket server and client, based on ngx_lua cosocket. -* [lua-resty-string](https://github.com/openresty/lua-resty-string) library based on [LuaJIT FFI](http://luajit.org/ext_ffi.html). +* [lua-resty-string](https://github.com/openresty/lua-resty-string) library based on [LuaJIT FFI](https://luajit.org/ext_ffi.html). * [lua-resty-lock](https://github.com/openresty/lua-resty-lock) library for a nonblocking simple lock API. * [lua-resty-cookie](https://github.com/cloudflare/lua-resty-cookie) library for HTTP cookie manipulation. -* [Routing requests to different MySQL queries based on URI arguments](http://openresty.org/#RoutingMySQLQueriesBasedOnURIArgs) -* [Dynamic Routing Based on Redis and Lua](http://openresty.org/#DynamicRoutingBasedOnRedis) -* [Using LuaRocks with ngx_lua](http://openresty.org/#UsingLuaRocks) +* [Routing requests to different MySQL queries based on URI arguments](https://openresty.org/#RoutingMySQLQueriesBasedOnURIArgs) +* [Dynamic Routing Based on Redis and Lua](https://openresty.org/#DynamicRoutingBasedOnRedis) +* [Using LuaRocks with ngx_lua](https://openresty.org/#UsingLuaRocks) * [Introduction to ngx_lua](https://github.com/openresty/lua-nginx-module/wiki/Introduction) -* [ngx_devel_kit](https://github.com/simpl/ngx_devel_kit) +* [ngx_devel_kit](https://github.com/simplresty/ngx_devel_kit) * [echo-nginx-module](http://github.com/openresty/echo-nginx-module) * [drizzle-nginx-module](http://github.com/openresty/drizzle-nginx-module) * [postgres-nginx-module](https://github.com/FRiCKLE/ngx_postgres) * [memc-nginx-module](http://github.com/openresty/memc-nginx-module) -* [The OpenResty bundle](http://openresty.org) +* [The OpenResty bundle](https://openresty.org) * [Nginx Systemtap Toolkit](https://github.com/openresty/nginx-systemtap-toolkit) [Back to TOC](#table-of-contents) @@ -1039,10 +1119,12 @@ See Also Directives ========== +* [lua_load_resty_core](#lua_load_resty_core) * [lua_capture_error_log](#lua_capture_error_log) * [lua_use_default_type](#lua_use_default_type) * [lua_malloc_trim](#lua_malloc_trim) * [lua_code_cache](#lua_code_cache) +* [lua_thread_cache_max_entries](#lua_thread_cache_max_entries) * [lua_regex_cache_max_entries](#lua_regex_cache_max_entries) * [lua_regex_match_limit](#lua_regex_match_limit) * [lua_package_path](#lua_package_path) @@ -1053,12 +1135,16 @@ Directives * [init_worker_by_lua](#init_worker_by_lua) * [init_worker_by_lua_block](#init_worker_by_lua_block) * [init_worker_by_lua_file](#init_worker_by_lua_file) +* [exit_worker_by_lua_block](#exit_worker_by_lua_block) +* [exit_worker_by_lua_file](#exit_worker_by_lua_file) * [set_by_lua](#set_by_lua) * [set_by_lua_block](#set_by_lua_block) * [set_by_lua_file](#set_by_lua_file) * [content_by_lua](#content_by_lua) * [content_by_lua_block](#content_by_lua_block) * [content_by_lua_file](#content_by_lua_file) +* [server_rewrite_by_lua_block](#server_rewrite_by_lua_block) +* [server_rewrite_by_lua_file](#server_rewrite_by_lua_file) * [rewrite_by_lua](#rewrite_by_lua) * [rewrite_by_lua_block](#rewrite_by_lua_block) * [rewrite_by_lua_file](#rewrite_by_lua_file) @@ -1076,13 +1162,18 @@ Directives * [log_by_lua_file](#log_by_lua_file) * [balancer_by_lua_block](#balancer_by_lua_block) * [balancer_by_lua_file](#balancer_by_lua_file) +* [balancer_keepalive](#balancer_keepalive) * [lua_need_request_body](#lua_need_request_body) +* [ssl_client_hello_by_lua_block](#ssl_client_hello_by_lua_block) +* [ssl_client_hello_by_lua_file](#ssl_client_hello_by_lua_file) * [ssl_certificate_by_lua_block](#ssl_certificate_by_lua_block) * [ssl_certificate_by_lua_file](#ssl_certificate_by_lua_file) * [ssl_session_fetch_by_lua_block](#ssl_session_fetch_by_lua_block) * [ssl_session_fetch_by_lua_file](#ssl_session_fetch_by_lua_file) * [ssl_session_store_by_lua_block](#ssl_session_store_by_lua_block) * [ssl_session_store_by_lua_file](#ssl_session_store_by_lua_file) +* [proxy_ssl_verify_by_lua_block](#proxy_ssl_verify_by_lua_block) +* [proxy_ssl_verify_by_lua_file](#proxy_ssl_verify_by_lua_file) * [lua_shared_dict](#lua_shared_dict) * [lua_socket_connect_timeout](#lua_socket_connect_timeout) * [lua_socket_send_timeout](#lua_socket_send_timeout) @@ -1095,8 +1186,13 @@ Directives * [lua_ssl_ciphers](#lua_ssl_ciphers) * [lua_ssl_crl](#lua_ssl_crl) * [lua_ssl_protocols](#lua_ssl_protocols) +* [lua_ssl_certificate](#lua_ssl_certificate) +* [lua_ssl_certificate_key](#lua_ssl_certificate_key) * [lua_ssl_trusted_certificate](#lua_ssl_trusted_certificate) * [lua_ssl_verify_depth](#lua_ssl_verify_depth) +* [lua_ssl_key_log](#lua_ssl_key_log) +* [lua_ssl_conf_command](#lua_ssl_conf_command) +* [lua_upstream_skip_openssl_default_verify](#lua_upstream_skip_openssl_default_verify) * [lua_http10_buffering](#lua_http10_buffering) * [rewrite_by_lua_no_postpone](#rewrite_by_lua_no_postpone) * [access_by_lua_no_postpone](#access_by_lua_no_postpone) @@ -1104,25 +1200,48 @@ Directives * [lua_check_client_abort](#lua_check_client_abort) * [lua_max_pending_timers](#lua_max_pending_timers) * [lua_max_running_timers](#lua_max_running_timers) +* [lua_sa_restart](#lua_sa_restart) +* [lua_worker_thread_vm_pool_size](#lua_worker_thread_vm_pool_size) The basic building blocks of scripting Nginx with Lua are directives. Directives are used to specify when the user Lua code is run and how the result will be used. Below is a diagram showing the order in which directives are executed. -![Lua Nginx Modules Directives](https://cloud.githubusercontent.com/assets/2137369/15272097/77d1c09e-1a37-11e6-97ef-d9767035fc3e.png) +![Lua Nginx Modules Directives](./doc/images/lua_nginx_modules_directives.drawio.png) [Back to TOC](#table-of-contents) +lua_load_resty_core +------------------- + +**syntax:** *lua_load_resty_core on|off* + +**default:** *lua_load_resty_core on* + +**context:** *http* + +This directive is deprecated since the `v0.10.16` release of this +module. The `resty.core` module from +[lua-resty-core](https://github.com/openresty/lua-resty-core) is now mandatorily +loaded during the Lua VM initialization. Specifying this directive will have no +effect. + +This directive was first introduced in the `v0.10.15` release and +used to optionally load the `resty.core` module. + +[Back to TOC](#directives) + lua_capture_error_log --------------------- + **syntax:** *lua_capture_error_log size* **default:** *none* **context:** *http* -Enables a buffer of the specified `size` for capturing all the nginx error log message data (not just those produced -by this module or the nginx http subsystem, but everything) without touching files or disks. +Enables a buffer of the specified `size` for capturing all the Nginx error log message data (not just those produced +by this module or the Nginx http subsystem, but everything) without touching files or disks. You can use units like `k` and `m` in the `size` value, as in @@ -1147,15 +1266,15 @@ also remove these already read from the global capturing buffer, making room for any new error log data. For this reason, the user should not configure this buffer to be too big if the user read the buffered error log data fast enough. -Note that the log level specified in the standard [error_log](http://nginx.org/r/error_log) directive +Note that the log level specified in the standard [error_log](https://nginx.org/r/error_log) directive *does* have effect on this capturing facility. It only captures log -messages of a level no lower than the specified log level in the [error_log](http://nginx.org/r/error_log) directive. +messages of a level no lower than the specified log level in the [error_log](https://nginx.org/r/error_log) directive. The user can still choose to set an even higher filtering log level on the fly via the Lua API function [errlog.set_filter_level](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/errlog.md#set_filter_level). -So it is more flexible than the static [error_log](http://nginx.org/r/error_log) directive. +So it is more flexible than the static [error_log](https://nginx.org/r/error_log) directive. It is worth noting that there is no way to capture the debugging logs -without building OpenResty or NGINX with the `./configure` +without building OpenResty or Nginx with the `./configure` option `--with-debug`. And enabling debugging logs is strongly discouraged in production builds due to high overhead. @@ -1165,13 +1284,14 @@ This directive was first introduced in the `v0.10.9` release. lua_use_default_type -------------------- + **syntax:** *lua_use_default_type on | off* **default:** *lua_use_default_type on* **context:** *http, server, location, location if* -Specifies whether to use the MIME type specified by the [default_type](http://nginx.org/en/docs/http/ngx_http_core_module.html#default_type) directive for the default value of the `Content-Type` response header. Deactivate this directive if a default `Content-Type` response header for Lua request handlers is not desired. +Specifies whether to use the MIME type specified by the [default_type](https://nginx.org/en/docs/http/ngx_http_core_module.html#default_type) directive for the default value of the `Content-Type` response header. Deactivate this directive if a default `Content-Type` response header for Lua request handlers is not desired. This directive is turned on by default. @@ -1181,6 +1301,7 @@ This directive was first introduced in the `v0.9.1` release. lua_malloc_trim --------------- + **syntax:** *lua_malloc_trim <request-count>* **default:** *lua_malloc_trim 1000* @@ -1188,7 +1309,7 @@ lua_malloc_trim **context:** *http* Asks the underlying `libc` runtime library to release its cached free memory back to the operating system every -`N` requests processed by the NGINX core. By default, `N` is 1000. You can configure the request count +`N` requests processed by the Nginx core. By default, `N` is 1000. You can configure the request count by using your own numbers. Smaller numbers mean more frequent releases, which may introduce higher CPU time consumption and smaller memory footprint while larger numbers usually lead to less CPU time overhead and relatively larger memory footprint. Just tune the number for your own use cases. @@ -1200,8 +1321,8 @@ Configuring the argument to `0` essentially turns off the periodical memory trim lua_malloc_trim 0; # turn off trimming completely ``` -The current implementation uses an NGINX log phase handler to do the request counting. So the appearance of the -[log_subrequest on](http://nginx.org/en/docs/http/ngx_http_core_module.html#log_subrequest) directives in `nginx.conf` +The current implementation uses an Nginx log phase handler to do the request counting. So the appearance of the +[log_subrequest on](https://nginx.org/en/docs/http/ngx_http_core_module.html#log_subrequest) directives in `nginx.conf` may make the counting faster when subrequests are involved. By default, only "main requests" count. Note that this directive does *not* affect the memory allocated by LuaJIT's own allocator based on the `mmap` @@ -1247,8 +1368,30 @@ development as it has a significant negative impact on overall performance. For [Back to TOC](#directives) +lua_thread_cache_max_entries +---------------------------- + +**syntax:** *lua_thread_cache_max_entries <num>* + +**default:** *lua_thread_cache_max_entries 1024* + +**context:** *http* + +Specifies the maximum number of entries allowed in the worker process level lua thread object cache. + +This cache recycles the lua thread GC objects among all our "light threads". + +A zero value of `` disables the cache. + +Note that this feature requires OpenResty's LuaJIT with the new C API `lua_resetthread`. + +This feature was first introduced in version `v0.10.9`. + +[Back to TOC](#directives) + lua_regex_cache_max_entries --------------------------- + **syntax:** *lua_regex_cache_max_entries <num>* **default:** *lua_regex_cache_max_entries 1024* @@ -1273,6 +1416,7 @@ Do not activate the `o` option for regular expressions (and/or `replace` string lua_regex_match_limit --------------------- + **syntax:** *lua_regex_match_limit <num>* **default:** *lua_regex_match_limit 0* @@ -1334,9 +1478,33 @@ init_by_lua **NOTE** Use of this directive is *discouraged* following the `v0.9.17` release. Use the [init_by_lua_block](#init_by_lua_block) directive instead. -Runs the Lua code specified by the argument `` on the global Lua VM level when the Nginx master process (if any) is loading the Nginx config file. +Similar to the [init_by_lua_block](#init_by_lua_block) directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). + +For instance, + +```nginx + + init_by_lua ' + print("I need no extra escaping here, for example: \r\nblah") + ' +``` + +This directive was first introduced in the `v0.5.5` release. + +[Back to TOC](#directives) + +init_by_lua_block +----------------- + +**syntax:** *init_by_lua_block { lua-script }* + +**context:** *http* + +**phase:** *loading-config* + -When Nginx receives the `HUP` signal and starts reloading the config file, the Lua VM will also be re-created and `init_by_lua` will run again on the new Lua VM. In case that the [lua_code_cache](#lua_code_cache) directive is turned off (default on), the `init_by_lua` handler will run upon every request because in this special mode a standalone Lua VM is always created for each request. +When Nginx receives the `HUP` signal and starts reloading the config file, the Lua VM will also be re-created and `init_by_lua_block` will run again on the new Lua VM. In case that the [lua_code_cache](#lua_code_cache) directive is turned off (default on), the `init_by_lua_block` handler will run upon every request because in this special mode a standalone Lua VM is always created for each request. Usually you can pre-load Lua modules at server start-up by means of this hook and take advantage of modern operating systems' copy-on-write (COW) optimization. Here is an example for pre-loading Lua modules: @@ -1349,7 +1517,7 @@ Usually you can pre-load Lua modules at server start-up by means of this hook an location = /api { content_by_lua_block { -- the following require() will just return - -- the alrady loaded module from package.loaded: + -- the already loaded module from package.loaded: ngx.say(require "cjson".encode{dog = 5, cat = 6}) } } @@ -1363,25 +1531,25 @@ You can also initialize the [lua_shared_dict](#lua_shared_dict) shm storage at t lua_shared_dict dogs 1m; init_by_lua_block { - local dogs = ngx.shared.dogs; + local dogs = ngx.shared.dogs dogs:set("Tom", 56) } server { location = /api { content_by_lua_block { - local dogs = ngx.shared.dogs; + local dogs = ngx.shared.dogs ngx.say(dogs:get("Tom")) } } } ``` -But note that, the [lua_shared_dict](#lua_shared_dict)'s shm storage will not be cleared through a config reload (via the `HUP` signal, for example). So if you do *not* want to re-initialize the shm storage in your `init_by_lua` code in this case, then you just need to set a custom flag in the shm storage and always check the flag in your `init_by_lua` code. +But note that, the [lua_shared_dict](#lua_shared_dict)'s shm storage will not be cleared through a config reload (via the `HUP` signal, for example). So if you do *not* want to re-initialize the shm storage in your `init_by_lua_block` code in this case, then you just need to set a custom flag in the shm storage and always check the flag in your `init_by_lua_block` code. -Because the Lua code in this context runs before Nginx forks its worker processes (if any), data or code loaded here will enjoy the [Copy-on-write (COW)](http://en.wikipedia.org/wiki/Copy-on-write) feature provided by many operating systems among all the worker processes, thus saving a lot of memory. +Because the Lua code in this context runs before Nginx forks its worker processes (if any), data or code loaded here will enjoy the [Copy-on-write (COW)](https://en.wikipedia.org/wiki/Copy-on-write) feature provided by many operating systems among all the worker processes, thus saving a lot of memory. -Do *not* initialize your own Lua global variables in this context because use of Lua global variables have performance penalties and can lead to global namespace pollution (see the [Lua Variable Scope](#lua-variable-scope) section for more details). The recommended way is to use proper [Lua module](http://www.lua.org/manual/5.1/manual.html#5.3) files (but do not use the standard Lua function [module()](http://www.lua.org/manual/5.1/manual.html#pdf-module) to define Lua modules because it pollutes the global namespace as well) and call [require()](http://www.lua.org/manual/5.1/manual.html#pdf-require) to load your own module files in `init_by_lua` or other contexts ([require()](http://www.lua.org/manual/5.1/manual.html#pdf-require) does cache the loaded Lua modules in the global `package.loaded` table in the Lua registry so your modules will only loaded once for the whole Lua VM instance). +Do *not* initialize your own Lua global variables in this context because use of Lua global variables have performance penalties and can lead to global namespace pollution (see the [Lua Variable Scope](#lua-variable-scope) section for more details). The recommended way is to use proper [Lua module](https://www.lua.org/manual/5.1/manual.html#5.3) files (but do not use the standard Lua function [module()](https://www.lua.org/manual/5.1/manual.html#pdf-module) to define Lua modules because it pollutes the global namespace as well) and call [require()](https://www.lua.org/manual/5.1/manual.html#pdf-require) to load your own module files in `init_by_lua_block` or other contexts ([require()](https://www.lua.org/manual/5.1/manual.html#pdf-require) does cache the loaded Lua modules in the global `package.loaded` table in the Lua registry so your modules will only loaded once for the whole Lua VM instance). Only a small set of the [Nginx API for Lua](#nginx-api-for-lua) is supported in this context: @@ -1394,34 +1562,12 @@ Basically you can safely use Lua libraries that do blocking I/O in this very con You should be very careful about potential security vulnerabilities in your Lua code registered in this context because the Nginx master process is often run under the `root` account. -This directive was first introduced in the `v0.5.5` release. - -[Back to TOC](#directives) - -init_by_lua_block ------------------ - -**syntax:** *init_by_lua_block { lua-script }* - -**context:** *http* - -**phase:** *loading-config* - -Similar to the [init_by_lua](#init_by_lua) directive except that this directive inlines -the Lua source directly -inside a pair of curly braces (`{}`) instead of in an NGINX string literal (which requires -special character escaping). - -For instance, - -```nginx +This directive was first introduced in the `v0.9.17` release. - init_by_lua_block { - print("I need no extra escaping here, for example: \r\nblah") - } -``` +See also the following blog posts for more details on OpenResty and Nginx's shared memory zones: -This directive was first introduced in the `v0.9.17` release. +* [How OpenResty and Nginx Shared Memory Zones Consume RAM](https://blog.openresty.com/en/how-nginx-shm-consume-ram/?src=gh_ngxlua) +* [Memory Fragmentation in OpenResty and Nginx's Shared Memory Zones](https://blog.openresty.com/en/nginx-shm-frag/?src=gh_ngxlua) [Back to TOC](#directives) @@ -1434,7 +1580,7 @@ init_by_lua_file **phase:** *loading-config* -Equivalent to [init_by_lua](#init_by_lua), except that the file specified by `` contains the Lua code or [Lua/LuaJIT bytecode](#lualuajit-bytecode-support) to be executed. +Equivalent to [init_by_lua_block](#init_by_lua_block), except that the file specified by `` contains the Lua code or [LuaJIT bytecode](#luajit-bytecode-support) to be executed. When a relative path like `foo/bar.lua` is given, they will be turned into the absolute path relative to the `server prefix` path determined by the `-p PATH` command-line option while starting the Nginx server. @@ -1453,13 +1599,40 @@ init_worker_by_lua **NOTE** Use of this directive is *discouraged* following the `v0.9.17` release. Use the [init_worker_by_lua_block](#init_worker_by_lua_block) directive instead. -Runs the specified Lua code upon every Nginx worker process's startup when the master process is enabled. When the master process is disabled, this hook will just run after [init_by_lua*](#init_by_lua). +Similar to the [init_worker_by_lua_block](#init_worker_by_lua_block) directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). -This hook is often used to create per-worker reoccurring timers (via the [ngx.timer.at](#ngxtimerat) Lua API), either for backend health-check or other timed routine work. Below is an example, +For instance, ```nginx init_worker_by_lua ' + print("I need no extra escaping here, for example: \r\nblah") + '; +``` + +This directive was first introduced in the `v0.9.5` release. + +This hook no longer runs in the cache manager and cache loader processes since the `v0.10.12` release. + +[Back to TOC](#directives) + +init_worker_by_lua_block +------------------------ + +**syntax:** *init_worker_by_lua_block { lua-script }* + +**context:** *http* + +**phase:** *starting-worker* + +Runs the specified Lua code upon every Nginx worker process's startup when the master process is enabled. When the master process is disabled, this hook will just run after [init_by_lua*](#init_by_lua_block). + +This hook is often used to create per-worker reoccurring timers (via the [ngx.timer.at](#ngxtimerat) Lua API), either for backend health-check or other timed routine work. Below is an example, + +```nginx + + init_worker_by_lua_block { local delay = 3 -- in seconds local new_timer = ngx.timer.at local log = ngx.log @@ -1475,6 +1648,8 @@ This hook is often used to create per-worker reoccurring timers (via the [ngx.ti return end end + + -- do something in timer end local hdl, err = new_timer(delay, check) @@ -1482,58 +1657,74 @@ This hook is often used to create per-worker reoccurring timers (via the [ngx.ti log(ERR, "failed to create timer: ", err) return end - '; + + -- other job in init_worker_by_lua + } ``` +This directive was first introduced in the `v0.9.17` release. + +This hook no longer runs in the cache manager and cache loader processes since the `v0.10.12` release. + +[Back to TOC](#directives) + +init_worker_by_lua_file +----------------------- + +**syntax:** *init_worker_by_lua_file <lua-file-path>* + +**context:** *http* + +**phase:** *starting-worker* + +Similar to [init_worker_by_lua_block](#init_worker_by_lua_block), but accepts the file path to a Lua source file or Lua bytecode file. + This directive was first introduced in the `v0.9.5` release. This hook no longer runs in the cache manager and cache loader processes since the `v0.10.12` release. [Back to TOC](#directives) -init_worker_by_lua_block +exit_worker_by_lua_block ------------------------ -**syntax:** *init_worker_by_lua_block { lua-script }* +**syntax:** *exit_worker_by_lua_block { lua-script }* **context:** *http* -**phase:** *starting-worker* +**phase:** *exiting-worker* -Similar to the [init_worker_by_lua](#init_worker_by_lua) directive except that this directive inlines -the Lua source directly -inside a pair of curly braces (`{}`) instead of in an NGINX string literal (which requires -special character escaping). +Runs the specified Lua code upon every Nginx worker process's exit when the master process is enabled. When the master process is disabled, this hook will run before the Nginx process exits. -For instance, +This hook is often used to release resources allocated by each worker (e.g. resources allocated by [init_worker_by_lua*](#init_worker_by_lua_block)), or to prevent workers from exiting abnormally. + +For example, ```nginx - init_worker_by_lua_block { - print("I need no extra escaping here, for example: \r\nblah") + exit_worker_by_lua_block { + print("log from exit_worker_by_lua_block") } ``` -This directive was first introduced in the `v0.9.17` release. +It's not allowed to create a timer (even a 0-delay timer) here since it runs after all timers have been processed. -This hook no longer runs in the cache manager and cache loader processes since the `v0.10.12` release. +This directive was first introduced in the `v0.10.18` release. [Back to TOC](#directives) -init_worker_by_lua_file +exit_worker_by_lua_file ----------------------- -**syntax:** *init_worker_by_lua_file <lua-file-path>* +**syntax:** *exit_worker_by_lua_file <path-to-lua-script-file>* **context:** *http* -**phase:** *starting-worker* - -Similar to [init_worker_by_lua](#init_worker_by_lua), but accepts the file path to a Lua source file or Lua bytecode file. +**phase:** *exiting-worker* -This directive was first introduced in the `v0.9.5` release. +Similar to [exit_worker_by_lua_block](#exit_worker_by_lua_block), but accepts the file path to a Lua source file or Lua bytecode file. -This hook no longer runs in the cache manager and cache loader processes since the `v0.10.12` release. +This directive was first introduced in the `v0.10.18` release. [Back to TOC](#directives) @@ -1548,14 +1739,41 @@ set_by_lua **NOTE** Use of this directive is *discouraged* following the `v0.9.17` release. Use the [set_by_lua_block](#set_by_lua_block) directive instead. -Executes code specified in `` with optional input arguments `$arg1 $arg2 ...`, and returns string output to `$res`. -The code in `` can make [API calls](#nginx-api-for-lua) and can retrieve input arguments from the `ngx.arg` table (index starts from `1` and increases sequentially). +Similar to the [set_by_lua_block](#set_by_lua_block) directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping), and +1. this directive support extra arguments after the Lua script. + +For example, + +```nginx + + set_by_lua $res ' return 32 + math.cos(32) '; + # $res now has the value "32.834223360507" or alike. +``` + +As from the `v0.5.0rc29` release, Nginx variable interpolation is disabled in the `` argument of this directive and therefore, the dollar sign character (`$`) can be used directly. + +This directive requires the [ngx_devel_kit](https://github.com/simplresty/ngx_devel_kit) module. + +[Back to TOC](#directives) + +set_by_lua_block +---------------- + +**syntax:** *set_by_lua_block $res { lua-script }* + +**context:** *server, server if, location, location if* + +**phase:** *rewrite* + +Executes code specified inside a pair of curly braces (`{}`), and returns string output to `$res`. +The code inside a pair of curly braces (`{}`) can make [API calls](#nginx-api-for-lua) and can retrieve input arguments from the `ngx.arg` table (index starts from `1` and increases sequentially). This directive is designed to execute short, fast running code blocks as the Nginx event loop is blocked during code execution. Time consuming code sequences should therefore be avoided. This directive is implemented by injecting custom commands into the standard [ngx_http_rewrite_module](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html)'s command list. Because [ngx_http_rewrite_module](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html) does not support nonblocking I/O in its commands, Lua APIs requiring yielding the current Lua "light thread" cannot work in this directive. -At least the following API functions are currently disabled within the context of `set_by_lua`: +At least the following API functions are currently disabled within the context of `set_by_lua_block`: * Output API functions (e.g., [ngx.say](#ngxsay) and [ngx.send_headers](#ngxsend_headers)) * Control API functions (e.g., [ngx.exit](#ngxexit)) @@ -1571,13 +1789,13 @@ a time. However, a workaround is possible using the [ngx.var.VARIABLE](#ngxvarva location /foo { set $diff ''; # we have to predefine the $diff variable here - set_by_lua $sum ' + set_by_lua_block $sum { local a = 32 local b = 56 - ngx.var.diff = a - b; -- write to $diff directly - return a + b; -- return the $sum value normally - '; + ngx.var.diff = a - b -- write to $diff directly + return a + b -- return the $sum value normally + } echo "sum = $sum, diff = $diff"; } @@ -1588,55 +1806,28 @@ This directive can be freely mixed with all directives of the [ngx_http_rewrite_ ```nginx set $foo 32; - set_by_lua $bar 'return tonumber(ngx.var.foo) + 1'; + set_by_lua_block $bar { return tonumber(ngx.var.foo) + 1 } set $baz "bar: $bar"; # $baz == "bar: 33" ``` -As from the `v0.5.0rc29` release, Nginx variable interpolation is disabled in the `` argument of this directive and therefore, the dollar sign character (`$`) can be used directly. +No special escaping is required in the Lua code block. + +This directive requires the [ngx_devel_kit](https://github.com/simplresty/ngx_devel_kit) module. -This directive requires the [ngx_devel_kit](https://github.com/simpl/ngx_devel_kit) module. +This directive was first introduced in the `v0.9.17` release. [Back to TOC](#directives) -set_by_lua_block ----------------- +set_by_lua_file +--------------- -**syntax:** *set_by_lua_block $res { lua-script }* +**syntax:** *set_by_lua_file $res <path-to-lua-script-file> [$arg1 $arg2 ...]* **context:** *server, server if, location, location if* **phase:** *rewrite* -Similar to the [set_by_lua](#set_by_lua) directive except that - -1. this directive inlines the Lua source directly -inside a pair of curly braces (`{}`) instead of in an NGINX string literal (which requires -special character escaping), and -1. this directive does not support extra arguments after the Lua script as in [set_by_lua](#set_by_lua). - -For example, - -```nginx - - set_by_lua_block $res { return 32 + math.cos(32) } - # $res now has the value "32.834223360507" or alike. -``` - -No special escaping is required in the Lua code block. - -This directive was first introduced in the `v0.9.17` release. - -[Back to TOC](#directives) - -set_by_lua_file ---------------- -**syntax:** *set_by_lua_file $res <path-to-lua-script-file> [$arg1 $arg2 ...]* - -**context:** *server, server if, location, location if* - -**phase:** *rewrite* - -Equivalent to [set_by_lua](#set_by_lua), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [Lua/LuaJIT bytecode](#lualuajit-bytecode-support) to be executed. +Equivalent to [set_by_lua_block](#set_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. Nginx variable interpolation is supported in the `` argument string of this directive. But special care must be taken for injection attacks. @@ -1647,7 +1838,7 @@ and the Nginx config must be reloaded each time the Lua source file is modified. The Lua code cache can be temporarily disabled during development by switching [lua_code_cache](#lua_code_cache) `off` in `nginx.conf` to avoid reloading Nginx. -This directive requires the [ngx_devel_kit](https://github.com/simpl/ngx_devel_kit) module. +This directive requires the [ngx_devel_kit](https://github.com/simplresty/ngx_devel_kit) module. [Back to TOC](#directives) @@ -1662,10 +1853,17 @@ content_by_lua **NOTE** Use of this directive is *discouraged* following the `v0.9.17` release. Use the [content_by_lua_block](#content_by_lua_block) directive instead. -Acts as a "content handler" and executes Lua code string specified in `` for every request. -The Lua code may make [API calls](#nginx-api-for-lua) and is executed as a new spawned coroutine in an independent global environment (i.e. a sandbox). +Similar to the [content_by_lua_block](#content_by_lua_block) directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). -Do not use this directive and other content handler directives in the same location. For example, this directive and the [proxy_pass](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) directive should not be used in the same location. +For instance, + +```nginx + + content_by_lua ' + ngx.say("I need no extra escaping here, for example: \r\nblah") + '; +``` [Back to TOC](#directives) @@ -1678,11 +1876,6 @@ content_by_lua_block **phase:** *content* -Similar to the [content_by_lua](#content_by_lua) directive except that this directive inlines -the Lua source directly -inside a pair of curly braces (`{}`) instead of in an NGINX string literal (which requires -special character escaping). - For instance, ```nginx @@ -1692,6 +1885,11 @@ For instance, } ``` +Acts as a "content handler" and executes Lua code string specified in `{ lua-script }` for every request. +The Lua code may make [API calls](#nginx-api-for-lua) and is executed as a new spawned coroutine in an independent global environment (i.e. a sandbox). + +Do not use this directive and other content handler directives in the same location. For example, this directive and the [proxy_pass](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) directive should not be used in the same location. + This directive was first introduced in the `v0.9.17` release. [Back to TOC](#directives) @@ -1705,7 +1903,9 @@ content_by_lua_file **phase:** *content* -Equivalent to [content_by_lua](#content_by_lua), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [Lua/LuaJIT bytecode](#lualuajit-bytecode-support) to be executed. +Equivalent to [content_by_lua_block](#content_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. + +If the file is not found, a `404 Not Found` status code will be returned, and a `503 Service Temporarily Unavailable` status code will be returned in case of errors in reading other files. Nginx variables can be used in the `` string to provide flexibility. This however carries some risks and is not ordinarily recommended. @@ -1732,6 +1932,97 @@ But be very careful about malicious user inputs and always carefully validate or [Back to TOC](#directives) +server_rewrite_by_lua_block +--------------------------- + +**syntax:** *server_rewrite_by_lua_block { lua-script }* + +**context:** *http, server* + +**phase:** *server rewrite* + +Acts as a server rewrite phase handler and executes Lua code string specified in `{ lua-script }` for every request. +The Lua code may make [API calls](#nginx-api-for-lua) and is executed as a new spawned coroutine in an independent global environment (i.e. a sandbox). + +```nginx + + server { + ... + + server_rewrite_by_lua_block { + ngx.ctx.a = "server_rewrite_by_lua_block in http" + } + + location /lua { + content_by_lua_block { + ngx.say(ngx.ctx.a) + ngx.log(ngx.INFO, ngx.ctx.a) + } + } + } +``` + +Just as any other rewrite phase handlers, [server_rewrite_by_lua_block](#server_rewrite_by_lua_block) also runs in subrequests. + +```nginx + + server { + server_rewrite_by_lua_block { + ngx.log(ngx.INFO, "is_subrequest:", ngx.is_subrequest) + } + + location /lua { + content_by_lua_block { + local res = ngx.location.capture("/sub") + ngx.print(res.body) + } + } + + location /sub { + content_by_lua_block { + ngx.say("OK") + } + } + } +``` + +Note that when calling `ngx.exit(ngx.OK)` within a [server_rewrite_by_lua_block](#server_rewrite_by_lua_block) handler, the Nginx request processing control flow will still continue to the content handler. To terminate the current request from within a [server_rewrite_by_lua_block](#server_rewrite_by_lua_block) handler, call [ngx.exit](#ngxexit) with status >= 200 (`ngx.HTTP_OK`) and status < 300 (`ngx.HTTP_SPECIAL_RESPONSE`) for successful quits and `ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)` (or its friends) for failures. + + +```nginx + + server_rewrite_by_lua_block { + ngx.exit(503) + } + + location /bar { + ... + # never exec + } +``` + + +[Back to TOC](#directives) + +server_rewrite_by_lua_file +-------------------------- + +**syntax:** *server_rewrite_by_lua_file <path-to-lua-script-file>* + +**context:** *http, server* + +**phase:** *server rewrite* + +Equivalent to [server_rewrite_by_lua_block](#server_rewrite_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.10.22` release, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. + +Nginx variables can be used in the `` string to provide flexibility. This however carries some risks and is not ordinarily recommended. + +When a relative path like `foo/bar.lua` is given, they will be turned into the absolute path relative to the `server prefix` path determined by the `-p PATH` command-line option while starting the Nginx server. + +When the Lua code cache is turned on (by default), the user code is loaded once at the first request and cached and the Nginx config must be reloaded each time the Lua source file is modified. The Lua code cache can be temporarily disabled during development by switching [lua_code_cache](#lua_code_cache) `off` in `nginx.conf` to avoid reloading Nginx. + +[Back to TOC](#directives) + rewrite_by_lua -------------- @@ -1743,7 +2034,30 @@ rewrite_by_lua **NOTE** Use of this directive is *discouraged* following the `v0.9.17` release. Use the [rewrite_by_lua_block](#rewrite_by_lua_block) directive instead. -Acts as a rewrite phase handler and executes Lua code string specified in `` for every request. +Similar to the [rewrite_by_lua_block](#rewrite_by_lua_block) directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). + +For instance, + +```nginx + + rewrite_by_lua ' + do_something("hello, world!\nhiya\n") + '; +``` + +[Back to TOC](#directives) + +rewrite_by_lua_block +-------------------- + +**syntax:** *rewrite_by_lua_block { lua-script }* + +**context:** *http, server, location, location if* + +**phase:** *rewrite tail* + +Acts as a rewrite phase handler and executes Lua code string specified in `{ lua-script }` for every request. The Lua code may make [API calls](#nginx-api-for-lua) and is executed as a new spawned coroutine in an independent global environment (i.e. a sandbox). Note that this handler always runs *after* the standard [ngx_http_rewrite_module](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html). So the following will work as expected: @@ -1753,12 +2067,14 @@ Note that this handler always runs *after* the standard [ngx_http_rewrite_module location /foo { set $a 12; # create and initialize $a set $b ""; # create and initialize $b - rewrite_by_lua 'ngx.var.b = tonumber(ngx.var.a) + 1'; + rewrite_by_lua_block { + ngx.var.b = tonumber(ngx.var.a) + 1 + } echo "res = $b"; } ``` -because `set $a 12` and `set $b ""` run *before* [rewrite_by_lua](#rewrite_by_lua). +because `set $a 12` and `set $b ""` run *before* [rewrite_by_lua_block](#rewrite_by_lua_block). On the other hand, the following will not work as expected: @@ -1767,7 +2083,9 @@ On the other hand, the following will not work as expected: ? location /foo { ? set $a 12; # create and initialize $a ? set $b ''; # create and initialize $b - ? rewrite_by_lua 'ngx.var.b = tonumber(ngx.var.a) + 1'; + ? rewrite_by_lua_block { + ? ngx.var.b = tonumber(ngx.var.a) + 1 + ? } ? if ($b = '13') { ? rewrite ^ /bar redirect; ? break; @@ -1777,7 +2095,7 @@ On the other hand, the following will not work as expected: ? } ``` -because `if` runs *before* [rewrite_by_lua](#rewrite_by_lua) even if it is placed after [rewrite_by_lua](#rewrite_by_lua) in the config. +because `if` runs *before* [rewrite_by_lua_block](#rewrite_by_lua_block) even if it is placed after [rewrite_by_lua_block](#rewrite_by_lua_block) in the config. The right way of doing this is as follows: @@ -1786,18 +2104,18 @@ The right way of doing this is as follows: location /foo { set $a 12; # create and initialize $a set $b ''; # create and initialize $b - rewrite_by_lua ' + rewrite_by_lua_block { ngx.var.b = tonumber(ngx.var.a) + 1 if tonumber(ngx.var.b) == 13 then - return ngx.redirect("/bar"); + return ngx.redirect("/bar") end - '; + } echo "res = $b"; } ``` -Note that the [ngx_eval](http://www.grid.net.ru/nginx/eval.en.html) module can be approximated by using [rewrite_by_lua](#rewrite_by_lua). For example, +Note that the [ngx_eval](http://www.grid.net.ru/nginx/eval.en.html) module can be approximated by using [rewrite_by_lua_block](#rewrite_by_lua_block). For example, ```nginx @@ -1824,62 +2142,39 @@ can be implemented in ngx_lua as: } location / { - rewrite_by_lua ' + rewrite_by_lua_block { local res = ngx.location.capture("/check-spam") if res.body == "spam" then return ngx.redirect("/terms-of-use.html") end - '; + } fastcgi_pass ...; } ``` -Just as any other rewrite phase handlers, [rewrite_by_lua](#rewrite_by_lua) also runs in subrequests. +Just as any other rewrite phase handlers, [rewrite_by_lua_block](#rewrite_by_lua_block) also runs in subrequests. -Note that when calling `ngx.exit(ngx.OK)` within a [rewrite_by_lua](#rewrite_by_lua) handler, the nginx request processing control flow will still continue to the content handler. To terminate the current request from within a [rewrite_by_lua](#rewrite_by_lua) handler, calling [ngx.exit](#ngxexit) with status >= 200 (`ngx.HTTP_OK`) and status < 300 (`ngx.HTTP_SPECIAL_RESPONSE`) for successful quits and `ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)` (or its friends) for failures. +Note that when calling `ngx.exit(ngx.OK)` within a [rewrite_by_lua_block](#rewrite_by_lua_block) handler, the Nginx request processing control flow will still continue to the content handler. To terminate the current request from within a [rewrite_by_lua_block](#rewrite_by_lua_block) handler, call [ngx.exit](#ngxexit) with status >= 200 (`ngx.HTTP_OK`) and status < 300 (`ngx.HTTP_SPECIAL_RESPONSE`) for successful quits and `ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)` (or its friends) for failures. -If the [ngx_http_rewrite_module](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html)'s [rewrite](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#rewrite) directive is used to change the URI and initiate location re-lookups (internal redirections), then any [rewrite_by_lua](#rewrite_by_lua) or [rewrite_by_lua_file](#rewrite_by_lua_file) code sequences within the current location will not be executed. For example, +If the [ngx_http_rewrite_module](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html)'s [rewrite](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#rewrite) directive is used to change the URI and initiate location re-lookups (internal redirections), then any [rewrite_by_lua_block](#rewrite_by_lua_block) or [rewrite_by_lua_file_block](#rewrite_by_lua_file_block) code sequences within the current location will not be executed. For example, ```nginx location /foo { rewrite ^ /bar; - rewrite_by_lua 'ngx.exit(503)'; + rewrite_by_lua_block { + ngx.exit(503) + } } location /bar { ... } ``` -Here the Lua code `ngx.exit(503)` will never run. This will be the case if `rewrite ^ /bar last` is used as this will similarly initiate an internal redirection. If the `break` modifier is used instead, there will be no internal redirection and the `rewrite_by_lua` code will be executed. - -The `rewrite_by_lua` code will always run at the end of the `rewrite` request-processing phase unless [rewrite_by_lua_no_postpone](#rewrite_by_lua_no_postpone) is turned on. +Here the Lua code `ngx.exit(503)` will never run. This will be the case if `rewrite ^ /bar last` is used as this will similarly initiate an internal redirection. If the `break` modifier is used instead, there will be no internal redirection and the `rewrite_by_lua_block` code will be executed. -[Back to TOC](#directives) - -rewrite_by_lua_block --------------------- - -**syntax:** *rewrite_by_lua_block { lua-script }* - -**context:** *http, server, location, location if* - -**phase:** *rewrite tail* - -Similar to the [rewrite_by_lua](#rewrite_by_lua) directive except that this directive inlines -the Lua source directly -inside a pair of curly braces (`{}`) instead of in an NGINX string literal (which requires -special character escaping). - -For instance, - -```nginx - - rewrite_by_lua_block { - do_something("hello, world!\nhiya\n") - } -``` +The `rewrite_by_lua_block` code will always run at the end of the `rewrite` request-processing phase unless [rewrite_by_lua_no_postpone](#rewrite_by_lua_no_postpone) is turned on. This directive was first introduced in the `v0.9.17` release. @@ -1894,7 +2189,7 @@ rewrite_by_lua_file **phase:** *rewrite tail* -Equivalent to [rewrite_by_lua](#rewrite_by_lua), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [Lua/LuaJIT bytecode](#lualuajit-bytecode-support) to be executed. +Equivalent to [rewrite_by_lua_block](#rewrite_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. Nginx variables can be used in the `` string to provide flexibility. This however carries some risks and is not ordinarily recommended. @@ -1919,7 +2214,30 @@ access_by_lua **NOTE** Use of this directive is *discouraged* following the `v0.9.17` release. Use the [access_by_lua_block](#access_by_lua_block) directive instead. -Acts as an access phase handler and executes Lua code string specified in `` for every request. +Similar to the [access_by_lua_block](#access_by_lua_block) directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). + +For instance, + +```nginx + + access_by_lua ' + do_something("hello, world!\nhiya\n") + '; +``` + +[Back to TOC](#directives) + +access_by_lua_block +------------------- + +**syntax:** *access_by_lua_block { lua-script }* + +**context:** *http, server, location, location if* + +**phase:** *access tail* + +Acts as an access phase handler and executes Lua code string specified in `{ = 200 (`ngx.HTTP_OK`) and status < 300 (`ngx.HTTP_SPECIAL_RESPONSE`) for successful quits and `ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)` (or its friends) for failures. +Note that when calling `ngx.exit(ngx.OK)` within a [access_by_lua_block](#access_by_lua_block) handler, the Nginx request processing control flow will still continue to the content handler. To terminate the current request from within a [access_by_lua_block](#access_by_lua_block) handler, call [ngx.exit](#ngxexit) with status >= 200 (`ngx.HTTP_OK`) and status < 300 (`ngx.HTTP_SPECIAL_RESPONSE`) for successful quits and `ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)` (or its friends) for failures. Starting from the `v0.9.20` release, you can use the [access_by_lua_no_postpone](#access_by_lua_no_postpone) directive to control when to run this handler inside the "access" request-processing phase -of NGINX. - -[Back to TOC](#directives) - -access_by_lua_block -------------------- - -**syntax:** *access_by_lua_block { lua-script }* - -**context:** *http, server, location, location if* - -**phase:** *access tail* - -Similar to the [access_by_lua](#access_by_lua) directive except that this directive inlines -the Lua source directly -inside a pair of curly braces (`{}`) instead of in an NGINX string literal (which requires -special character escaping). - -For instance, - -```nginx - - access_by_lua_block { - do_something("hello, world!\nhiya\n") - } -``` +of Nginx. This directive was first introduced in the `v0.9.17` release. @@ -2023,7 +2316,7 @@ access_by_lua_file **phase:** *access tail* -Equivalent to [access_by_lua](#access_by_lua), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [Lua/LuaJIT bytecode](#lualuajit-bytecode-support) to be executed. +Equivalent to [access_by_lua_block](#access_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. Nginx variables can be used in the `` string to provide flexibility. This however carries some risks and is not ordinarily recommended. @@ -2048,23 +2341,16 @@ header_filter_by_lua **NOTE** Use of this directive is *discouraged* following the `v0.9.17` release. Use the [header_filter_by_lua_block](#header_filter_by_lua_block) directive instead. -Uses Lua code specified in `` to define an output header filter. - -Note that the following API functions are currently disabled within this context: - -* Output API functions (e.g., [ngx.say](#ngxsay) and [ngx.send_headers](#ngxsend_headers)) -* Control API functions (e.g., [ngx.redirect](#ngxredirect) and [ngx.exec](#ngxexec)) -* Subrequest API functions (e.g., [ngx.location.capture](#ngxlocationcapture) and [ngx.location.capture_multi](#ngxlocationcapture_multi)) -* Cosocket API functions (e.g., [ngx.socket.tcp](#ngxsockettcp) and [ngx.req.socket](#ngxreqsocket)). +Similar to the [header_filter_by_lua_block](#header_filter_by_lua_block) directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). -Here is an example of overriding a response header (or adding one if absent) in our Lua header filter: +For instance, ```nginx - location / { - proxy_pass http://mybackend; - header_filter_by_lua 'ngx.header.Foo = "blah"'; - } + header_filter_by_lua ' + ngx.header["content-length"] = nil + '; ``` This directive was first introduced in the `v0.2.1rc20` release. @@ -2080,17 +2366,24 @@ header_filter_by_lua_block **phase:** *output-header-filter* -Similar to the [header_filter_by_lua](#header_filter_by_lua) directive except that this directive inlines -the Lua source directly -inside a pair of curly braces (`{}`) instead of in an NGINX string literal (which requires -special character escaping). +Uses Lua code specified in `{ lua-script }` to define an output header filter. -For instance, +Note that the following API functions are currently disabled within this context: + +* Output API functions (e.g., [ngx.say](#ngxsay) and [ngx.send_headers](#ngxsend_headers)) +* Control API functions (e.g., [ngx.redirect](#ngxredirect) and [ngx.exec](#ngxexec)) +* Subrequest API functions (e.g., [ngx.location.capture](#ngxlocationcapture) and [ngx.location.capture_multi](#ngxlocationcapture_multi)) +* Cosocket API functions (e.g., [ngx.socket.tcp](#ngxsockettcp) and [ngx.req.socket](#ngxreqsocket)). + +Here is an example of overriding a response header (or adding one if absent) in our Lua header filter: ```nginx - header_filter_by_lua_block { - ngx.header["content-length"] = nil + location / { + proxy_pass http://mybackend; + header_filter_by_lua_block { + ngx.header.Foo = "blah" + } } ``` @@ -2107,7 +2400,7 @@ header_filter_by_lua_file **phase:** *output-header-filter* -Equivalent to [header_filter_by_lua](#header_filter_by_lua), except that the file specified by `` contains the Lua code, or as from the `v0.5.0rc32` release, the [Lua/LuaJIT bytecode](#lualuajit-bytecode-support) to be executed. +Equivalent to [header_filter_by_lua_block](#header_filter_by_lua_block), except that the file specified by `` contains the Lua code, or as from the `v0.5.0rc32` release, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. When a relative path like `foo/bar.lua` is given, they will be turned into the absolute path relative to the `server prefix` path determined by the `-p PATH` command-line option while starting the Nginx server. @@ -2126,7 +2419,32 @@ body_filter_by_lua **NOTE** Use of this directive is *discouraged* following the `v0.9.17` release. Use the [body_filter_by_lua_block](#body_filter_by_lua_block) directive instead. -Uses Lua code specified in `` to define an output body filter. +Similar to the [body_filter_by_lua_block](#body_filter_by_lua_block) directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). + +For instance, + +```nginx + + body_filter_by_lua ' + local data, eof = ngx.arg[1], ngx.arg[2] + '; +``` + +This directive was first introduced in the `v0.5.0rc32` release. + +[Back to TOC](#directives) + +body_filter_by_lua_block +------------------------ + +**syntax:** *body_filter_by_lua_block { lua-script-str }* + +**context:** *http, server, location, location if* + +**phase:** *output-body-filter* + +Uses Lua code specified in `{ lua-script }` to define an output body filter. The input data chunk is passed via [ngx.arg](#ngxarg)\[1\] (as a Lua string value) and the "eof" flag indicating the end of the response body data stream is passed via [ngx.arg](#ngxarg)\[2\] (as a Lua boolean value). @@ -2147,7 +2465,9 @@ The Lua code can pass its own modified version of the input data chunk to the do location / { proxy_pass http://mybackend; - body_filter_by_lua 'ngx.arg[1] = string.upper(ngx.arg[1])'; + body_filter_by_lua_block { + ngx.arg[1] = string.upper(ngx.arg[1]) + } } ``` @@ -2161,7 +2481,7 @@ Likewise, new "eof" flag can also be specified by setting a boolean value to [ng echo hello world; echo hiya globe; - body_filter_by_lua ' + body_filter_by_lua_block { local chunk = ngx.arg[1] if string.match(chunk, "hello") then ngx.arg[2] = true -- new eof @@ -2170,7 +2490,7 @@ Likewise, new "eof" flag can also be specified by setting a boolean value to [ng -- just throw away any remaining chunk data ngx.arg[1] = nil - '; + } } ``` @@ -2189,12 +2509,16 @@ When the Lua code may change the length of the response body, then it is require location /foo { # fastcgi_pass/proxy_pass/... - header_filter_by_lua_block { ngx.header.content_length = nil } - body_filter_by_lua 'ngx.arg[1] = string.len(ngx.arg[1]) .. "\\n"'; + header_filter_by_lua_block { + ngx.header.content_length = nil + } + body_filter_by_lua_block { + ngx.arg[1] = string.len(ngx.arg[1]) .. "\n" + } } ``` -Note that the following API functions are currently disabled within this context due to the limitations in NGINX output filter's current implementation: +Note that the following API functions are currently disabled within this context due to the limitations in Nginx output filter's current implementation: * Output API functions (e.g., [ngx.say](#ngxsay) and [ngx.send_headers](#ngxsend_headers)) * Control API functions (e.g., [ngx.exit](#ngxexit) and [ngx.exec](#ngxexec)) @@ -2203,66 +2527,64 @@ Note that the following API functions are currently disabled within this context Nginx output filters may be called multiple times for a single request because response body may be delivered in chunks. Thus, the Lua code specified by in this directive may also run multiple times in the lifetime of a single HTTP request. -This directive was first introduced in the `v0.5.0rc32` release. +This directive was first introduced in the `v0.9.17` release. [Back to TOC](#directives) -body_filter_by_lua_block ------------------------- +body_filter_by_lua_file +----------------------- -**syntax:** *body_filter_by_lua_block { lua-script-str }* +**syntax:** *body_filter_by_lua_file <path-to-lua-script-file>* **context:** *http, server, location, location if* **phase:** *output-body-filter* -Similar to the [body_filter_by_lua](#body_filter_by_lua) directive except that this directive inlines -the Lua source directly -inside a pair of curly braces (`{}`) instead of in an NGINX string literal (which requires -special character escaping). +Equivalent to [body_filter_by_lua_block](#body_filter_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. -For instance, +When a relative path like `foo/bar.lua` is given, they will be turned into the absolute path relative to the `server prefix` path determined by the `-p PATH` command-line option while starting the Nginx server. -```nginx +This directive was first introduced in the `v0.5.0rc32` release. - body_filter_by_lua_block { - local data, eof = ngx.arg[1], ngx.arg[2] - } -``` +[Back to TOC](#directives) -This directive was first introduced in the `v0.9.17` release. +log_by_lua +---------- -[Back to TOC](#directives) +**syntax:** *log_by_lua <lua-script-str>* -body_filter_by_lua_file ------------------------ +**context:** *http, server, location, location if* + +**phase:** *log* -**syntax:** *body_filter_by_lua_file <path-to-lua-script-file>* +**NOTE** Use of this directive is *discouraged* following the `v0.9.17` release. Use the [log_by_lua_block](#log_by_lua_block) directive instead. -**context:** *http, server, location, location if* +Similar to the [log_by_lua_block](#log_by_lua_block) directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). -**phase:** *output-body-filter* +For instance, -Equivalent to [body_filter_by_lua](#body_filter_by_lua), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [Lua/LuaJIT bytecode](#lualuajit-bytecode-support) to be executed. +```nginx -When a relative path like `foo/bar.lua` is given, they will be turned into the absolute path relative to the `server prefix` path determined by the `-p PATH` command-line option while starting the Nginx server. + log_by_lua ' + print("I need no extra escaping here, for example: \r\nblah") + '; +``` -This directive was first introduced in the `v0.5.0rc32` release. +This directive was first introduced in the `v0.5.0rc31` release. [Back to TOC](#directives) -log_by_lua ----------- +log_by_lua_block +---------------- -**syntax:** *log_by_lua <lua-script-str>* +**syntax:** *log_by_lua_block { lua-script }* **context:** *http, server, location, location if* **phase:** *log* -**NOTE** Use of this directive is *discouraged* following the `v0.9.17` release. Use the [log_by_lua_block](#log_by_lua_block) directive instead. - -Runs the Lua source code inlined as the `` at the `log` request processing phase. This does not replace the current access logs, but runs before. +Runs the Lua source code inlined as the `{ lua-script }` at the `log` request processing phase. This does not replace the current access logs, but runs before. Note that the following API functions are currently disabled within this context: @@ -2281,7 +2603,7 @@ Here is an example of gathering average data for [$upstream_response_time](http: location / { proxy_pass http://mybackend; - log_by_lua ' + log_by_lua_block { local log_dict = ngx.shared.log_dict local upstream_time = tonumber(ngx.var.upstream_response_time) @@ -2294,7 +2616,7 @@ Here is an example of gathering average data for [$upstream_response_time](http: log_dict:add("upstream_time-nb", 0) log_dict:incr("upstream_time-nb", 1) end - '; + } } location = /status { @@ -2314,33 +2636,6 @@ Here is an example of gathering average data for [$upstream_response_time](http: } ``` -This directive was first introduced in the `v0.5.0rc31` release. - -[Back to TOC](#directives) - -log_by_lua_block ----------------- - -**syntax:** *log_by_lua_block { lua-script }* - -**context:** *http, server, location, location if* - -**phase:** *log* - -Similar to the [log_by_lua](#log_by_lua) directive except that this directive inlines -the Lua source directly -inside a pair of curly braces (`{}`) instead of in an NGINX string literal (which requires -special character escaping). - -For instance, - -```nginx - - log_by_lua_block { - print("I need no extra escaping here, for example: \r\nblah") - } -``` - This directive was first introduced in the `v0.9.17` release. [Back to TOC](#directives) @@ -2354,7 +2649,7 @@ log_by_lua_file **phase:** *log* -Equivalent to [log_by_lua](#log_by_lua), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [Lua/LuaJIT bytecode](#lualuajit-bytecode-support) to be executed. +Equivalent to [log_by_lua_block](#log_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. When a relative path like `foo/bar.lua` is given, they will be turned into the absolute path relative to the `server prefix` path determined by the `-p PATH` command-line option while starting the Nginx server. @@ -2393,13 +2688,13 @@ For instance, } ``` -The resulting Lua load balancer can work with any existing nginx upstream modules -like [ngx_proxy](http://nginx.org/en/docs/http/ngx_http_proxy_module.html) and -[ngx_fastcgi](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html). +The resulting Lua load balancer can work with any existing Nginx upstream modules +like [ngx_proxy](https://nginx.org/en/docs/http/ngx_http_proxy_module.html) and +[ngx_fastcgi](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html). Also, the Lua load balancer can work with the standard upstream connection pool mechanism, -i.e., the standard [keepalive](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive) directive. -Just ensure that the [keepalive](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive) directive +i.e., the standard [keepalive](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive) directive. +Just ensure that the [keepalive](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive) directive is used *after* this `balancer_by_lua_block` directive in a single `upstream {}` configuration block. The Lua load balancer can totally ignore the list of servers defined in the `upstream {}` block @@ -2408,8 +2703,8 @@ and select peer from a completely dynamic server list (even changing per request from the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. The Lua code handler registered by this directive might get called more than once in a single -downstream request when the nginx upstream mechanism retries the request on conditions -specified by directives like the [proxy_next_upstream](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream) +downstream request when the Nginx upstream mechanism retries the request on conditions +specified by directives like the [proxy_next_upstream](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream) directive. This Lua code execution context does not support yielding, so Lua APIs that may yield @@ -2431,7 +2726,7 @@ balancer_by_lua_file **phase:** *content* -Equivalent to [balancer_by_lua_block](#balancer_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [Lua/LuaJIT bytecode](#lualuajit-bytecode-support) to be executed. +Equivalent to [balancer_by_lua_block](#balancer_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. When a relative path like `foo/bar.lua` is given, they will be turned into the absolute path relative to the `server prefix` path determined by the `-p PATH` command-line option while starting the Nginx server. @@ -2439,6 +2734,29 @@ This directive was first introduced in the `v0.10.0` release. [Back to TOC](#directives) +balancer_keepalive +------------------ + +**syntax:** *balancer_keepalive <total-connections>* + +**context:** *upstream* + +**phase:** *loading-config* + +The `total-connections` parameter sets the maximum number of idle +keepalive connections to upstream servers that are preserved in the cache of +each worker process. When this number is exceeded, the least recently used +connections are closed. + +It should be particularly noted that the keepalive directive does not limit the +total number of connections to upstream servers that an nginx worker process +can open. The connections parameter should be set to a number small enough to +let upstream servers process new incoming connections as well. + +This directive was first introduced in the `v0.10.21` release. + +[Back to TOC](#directives) + lua_need_request_body --------------------- @@ -2450,7 +2768,7 @@ lua_need_request_body **phase:** *depends on usage* -Determines whether to force the request body data to be read before running rewrite/access/access_by_lua* or not. The Nginx core does not read the client request body by default and if request body data is required, then this directive should be turned `on` or the [ngx.req.read_body](#ngxreqread_body) function should be called within the Lua code. +Determines whether to force the request body data to be read before running rewrite/access/content_by_lua* or not. The Nginx core does not read the client request body by default and if request body data is required, then this directive should be turned `on` or the [ngx.req.read_body](#ngxreqread_body) function should be called within the Lua code. To read the request body data within the [$request_body](http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_body) variable, [client_body_buffer_size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size) must have the same value as [client_max_body_size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size). Because when the content length exceeds [client_body_buffer_size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size) but less than [client_max_body_size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size), Nginx will buffer the data into a temporary file on the disk, which will lead to empty value in the [$request_body](http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_body) variable. @@ -2467,6 +2785,129 @@ This also applies to [access_by_lua*](#access_by_lua). [Back to TOC](#directives) +ssl_client_hello_by_lua_block +----------------------------- + +**syntax:** *ssl_client_hello_by_lua_block { lua-script }* + +**context:** *http, server* + +**phase:** *right-after-client-hello-message-was-processed* + +This directive runs user Lua code when Nginx is about to post-process the SSL client hello message for the downstream +SSL (https) connections. + +It is particularly useful for dynamically setting the SSL protocols according to the SNI. + +It is also useful to do some custom operations according to the per-connection information in the client hello message. + +For example, one can parse custom client hello extension and do the corresponding handling in pure Lua. + +This Lua handler will always run whether the SSL session is resumed (via SSL session IDs or TLS session tickets) or not. +While the `ssl_certificate_by_lua*` Lua handler will only runs when initiating a full SSL handshake. + +The [ngx.ssl.clienthello](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/clienthello.md) Lua modules +provided by the [lua-resty-core](https://github.com/openresty/lua-resty-core/#readme) +library are particularly useful in this context. + +Note that this handler runs in extremely early stage of SSL handshake, before the SSL client hello extensions are parsed. +So you can not use some Lua API like `ssl.server_name()` which is dependent on the later stage's processing. + +Also note that only the directive in default server is valid for several virtual servers with the same IP address and port. + +Below is a trivial example using the +[ngx.ssl.clienthello](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/clienthello.md) module +at the same time: + +```nginx + + server { + listen 443 ssl; + server_name test.com; + ssl_certificate /path/to/cert.crt; + ssl_certificate_key /path/to/key.key; + ssl_client_hello_by_lua_block { + local ssl_clt = require "ngx.ssl.clienthello" + local host, err = ssl_clt.get_client_hello_server_name() + if host == "test.com" then + ssl_clt.set_protocols({"TLSv1", "TLSv1.1"}) + elseif host == "test2.com" then + ssl_clt.set_protocols({"TLSv1.2", "TLSv1.3"}) + elseif not host then + ngx.log(ngx.ERR, "failed to get the SNI name: ", err) + ngx.exit(ngx.ERROR) + else + ngx.log(ngx.ERR, "unknown SNI name: ", host) + ngx.exit(ngx.ERROR) + end + } + ... + } + server { + listen 443 ssl; + server_name test2.com; + ssl_certificate /path/to/cert.crt; + ssl_certificate_key /path/to/key.key; + ... + } +``` + +See more information in the [ngx.ssl.clienthello](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/clienthello.md) +Lua modules' official documentation. + +Uncaught Lua exceptions in the user Lua code immediately abort the current SSL session, so does the +[ngx.exit](#ngxexit) call with an error code like `ngx.ERROR`. + +This Lua code execution context *does* support yielding, so Lua APIs that may yield +(like cosockets, sleeping, and "light threads") +are enabled in this context + +Note, you need to configure the [ssl_certificate](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate) +and [ssl_certificate_key](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_key) +to avoid the following error while starting NGINX: + + + nginx: [emerg] no ssl configured for the server + + +This directive requires OpenSSL 1.1.1 or greater. + +If you are using the [official pre-built +packages](https://openresty.org/en/linux-packages.html) for +[OpenResty](https://openresty.org/) 1.21.4.1 or later, then everything should +work out of the box. + +If you are not using the Nginx core shipped with +[OpenResty](https://openresty.org) 1.21.4.1 or later, you will need to apply +patches to the standard Nginx core: + + + +**Note for HTTP/3 (QUIC) users**: When using this directive with HTTP/3 connections, certain yield operations may fail if the QUIC SSL Lua yield patch is not applied to your OpenSSL installation. OpenResty packages include this patch by default, but if you are building lua-nginx-module separately, you may need to apply the patch manually to ensure proper yield/resume functionality for HTTP/3 connections in SSL Lua phases. The patch can be found at: [nginx-1.27.1-quic_ssl_lua_yield.patch](https://github.com/openresty/openresty/blob/master/patches/nginx/1.27.1/nginx-1.27.1-quic_ssl_lua_yield.patch) + +This directive was first introduced in the `v0.10.21` release. + +[Back to TOC](#directives) + +ssl_client_hello_by_lua_file +---------------------------- + +**syntax:** *ssl_client_hello_by_lua_file <path-to-lua-script-file>* + +**context:** *http, server* + +**phase:** *right-after-client-hello-message-was-processed* + +Equivalent to [ssl_client_hello_by_lua_block](#ssl_client_hello_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. + +When a relative path like `foo/bar.lua` is given, they will be turned into the absolute path relative to the `server prefix` path determined by the `-p PATH` command-line option while starting the Nginx server. + +**Note for HTTP/3 (QUIC) users**: When using this directive with HTTP/3 connections, certain yield operations may fail if the QUIC SSL Lua yield patch is not applied to your OpenSSL installation. OpenResty packages include this patch by default, but if you are building lua-nginx-module separately, you may need to apply the patch manually to ensure proper yield/resume functionality for HTTP/3 connections in SSL Lua phases. The patch can be found at: [nginx-1.27.1-quic_ssl_lua_yield.patch](https://github.com/openresty/openresty/blob/master/patches/nginx/1.27.1/nginx-1.27.1-quic_ssl_lua_yield.patch) + +This directive was first introduced in the `v0.10.21` release. + +[Back to TOC](#directives) + ssl_certificate_by_lua_block ---------------------------- @@ -2476,7 +2917,7 @@ ssl_certificate_by_lua_block **phase:** *right-before-SSL-handshake* -This directive runs user Lua code when NGINX is about to start the SSL handshake for the downstream +This directive runs user Lua code when Nginx is about to start the SSL handshake for the downstream SSL (https) connections. It is particularly useful for setting the SSL certificate chain and the corresponding private key on a per-request @@ -2498,9 +2939,9 @@ library are particularly useful in this context. You can use the Lua API offered to manipulate the SSL certificate chain and private key for the current SSL connection being initiated. -This Lua handler does not run at all, however, when NGINX/OpenSSL successfully resumes +This Lua handler does not run at all, however, when Nginx/OpenSSL successfully resumes the SSL session via SSL session IDs or TLS session tickets for the current SSL connection. In -other words, this Lua handler only runs when NGINX has to initiate a full SSL handshake. +other words, this Lua handler only runs when Nginx has to initiate a full SSL handshake. Below is a trivial example using the [ngx.ssl](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md) module @@ -2533,8 +2974,8 @@ This Lua code execution context *does* support yielding, so Lua APIs that may yi (like cosockets, sleeping, and "light threads") are enabled in this context. -Note, however, you still need to configure the [ssl_certificate](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate) and -[ssl_certificate_key](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_key) +Note, however, you still need to configure the [ssl_certificate](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate) and +[ssl_certificate_key](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_key) directives even though you will not use this static certificate and private key at all. This is because the NGINX core requires their appearance otherwise you are seeing the following error while starting NGINX: @@ -2543,14 +2984,20 @@ while starting NGINX: nginx: [emerg] no ssl configured for the server -This directive currently requires the following NGINX core patch to work correctly: +This directive requires OpenSSL 1.0.2e or greater. + +If you are using the [official pre-built +packages](https://openresty.org/en/linux-packages.html) for +[OpenResty](https://openresty.org/) 1.9.7.2 or later, then everything should +work out of the box. - +If you are not using the Nginx core shipped with +[OpenResty](https://openresty.org) 1.9.7.2 or later, you will need to apply +patches to the standard Nginx core: -The bundled version of the NGINX core in OpenResty 1.9.7.2 (or above) already has this -patch applied. + -Furthermore, one needs at least OpenSSL 1.0.2e for this directive to work. +**Note for HTTP/3 (QUIC) users**: When using this directive with HTTP/3 connections, certain yield operations may fail if the QUIC SSL Lua yield patch is not applied to your OpenSSL installation. OpenResty packages include this patch by default, but if you are building lua-nginx-module separately, you may need to apply the patch manually to ensure proper yield/resume functionality for HTTP/3 connections in SSL Lua phases. The patch can be found at: [nginx-1.27.1-quic_ssl_lua_yield.patch](https://github.com/openresty/openresty/blob/master/patches/nginx/1.27.1/nginx-1.27.1-quic_ssl_lua_yield.patch) This directive was first introduced in the `v0.10.0` release. @@ -2565,10 +3012,12 @@ ssl_certificate_by_lua_file **phase:** *right-before-SSL-handshake* -Equivalent to [ssl_certificate_by_lua_block](#ssl_certificate_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [Lua/LuaJIT bytecode](#lualuajit-bytecode-support) to be executed. +Equivalent to [ssl_certificate_by_lua_block](#ssl_certificate_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. When a relative path like `foo/bar.lua` is given, they will be turned into the absolute path relative to the `server prefix` path determined by the `-p PATH` command-line option while starting the Nginx server. +**Note for HTTP/3 (QUIC) users**: When using this directive with HTTP/3 connections, certain yield operations may fail if the QUIC SSL Lua yield patch is not applied to your OpenSSL installation. OpenResty packages include this patch by default, but if you are building lua-nginx-module separately, you may need to apply the patch manually to ensure proper yield/resume functionality for HTTP/3 connections in SSL Lua phases. The patch can be found at: [nginx-1.27.1-quic_ssl_lua_yield.patch](https://github.com/openresty/openresty/blob/master/patches/nginx/1.27.1/nginx-1.27.1-quic_ssl_lua_yield.patch) + This directive was first introduced in the `v0.10.0` release. [Back to TOC](#directives) @@ -2602,14 +3051,14 @@ SSL session resumption can then get immediately initiated and bypass the full SS Please note that TLS session tickets are very different and it is the clients' responsibility to cache the SSL session state when session tickets are used. SSL session resumptions based on TLS session tickets would happen automatically without going through this hook (nor the -[ssl_session_store_by_lua_block](#ssl_session_store_by_lua) hook). This hook is mainly +[ssl_session_store_by_lua*](#ssl_session_store_by_lua_block) hook). This hook is mainly for older or less capable SSL clients that can only do SSL sessions by session IDs. When [ssl_certificate_by_lua*](#ssl_certificate_by_lua_block) is specified at the same time, this hook usually runs before [ssl_certificate_by_lua*](#ssl_certificate_by_lua_block). When the SSL session is found and successfully loaded for the current SSL connection, SSL session resumption will happen and thus bypass the [ssl_certificate_by_lua*](#ssl_certificate_by_lua_block) -hook completely. In this case, NGINX also bypasses the [ssl_session_store_by_lua_block](#ssl_session_store_by_lua) +hook completely. In this case, Nginx also bypasses the [ssl_session_store_by_lua*](#ssl_session_store_by_lua_block) hook, for obvious reasons. To easily test this hook locally with a modern web browser, you can temporarily put the following line @@ -2619,23 +3068,27 @@ in your https server block to disable the TLS session ticket support: But do not forget to comment this line out before publishing your site to the world. -If you are using the [official pre-built packages](http://openresty.org/en/linux-packages.html) for [OpenResty](https://openresty.org/) +If you are using the [official pre-built packages](https://openresty.org/en/linux-packages.html) for [OpenResty](https://openresty.org/) 1.11.2.1 or later, then everything should work out of the box. -If you are using OpenSSL libraries not provided by [OpenResty](https://openresty.org), -then you need to apply the following patch for OpenSSL 1.0.2h or later: +If you are not using one of the [OpenSSL +packages](https://openresty.org/en/linux-packages.html) provided by +[OpenResty](https://openresty.org), you will need to apply patches to OpenSSL +in order to use this directive: - + -If you are not using the NGINX core shipped with [OpenResty](https://openresty.org) 1.11.2.1 or later, then you need to -apply the following patch to the standard NGINX core 1.11.2 or later: +Similarly, if you are not using the Nginx core shipped with +[OpenResty](https://openresty.org) 1.11.2.1 or later, you will need to apply +patches to the standard Nginx core: - + This directive was first introduced in the `v0.10.6` release. -Note that: this directive is only allowed to used in **http context** from the `v0.10.7` release -(because SSL session resumption happens before server name dispatch). +Note that this directive can only be used in the **http context** starting +with the `v0.10.7` release since SSL session resumption happens +before server name dispatch. [Back to TOC](#directives) @@ -2648,7 +3101,7 @@ ssl_session_fetch_by_lua_file **phase:** *right-before-SSL-handshake* -Equivalent to [ssl_session_fetch_by_lua_block](#ssl_session_fetch_by_lua_block), except that the file specified by `` contains the Lua code, or rather, the [Lua/LuaJIT bytecode](#lualuajit-bytecode-support) to be executed. +Equivalent to [ssl_session_fetch_by_lua_block](#ssl_session_fetch_by_lua_block), except that the file specified by `` contains the Lua code, or rather, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. When a relative path like `foo/bar.lua` is given, they will be turned into the absolute path relative to the `server prefix` path determined by the `-p PATH` command-line option while starting the Nginx server. @@ -2705,7 +3158,7 @@ ssl_session_store_by_lua_file **phase:** *right-after-SSL-handshake* -Equivalent to [ssl_session_store_by_lua_block](#ssl_session_store_by_lua_block), except that the file specified by `` contains the Lua code, or rather, the [Lua/LuaJIT bytecode](#lualuajit-bytecode-support) to be executed. +Equivalent to [ssl_session_store_by_lua_block](#ssl_session_store_by_lua_block), except that the file specified by `` contains the Lua code, or rather, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. When a relative path like `foo/bar.lua` is given, they will be turned into the absolute path relative to the `server prefix` path determined by the `-p PATH` command-line option while starting the Nginx server. @@ -2716,6 +3169,84 @@ Note that: this directive is only allowed to used in **http context** from the ` [Back to TOC](#directives) +proxy_ssl_verify_by_lua_block +----------------------------- + +**syntax:** *proxy_ssl_verify_by_lua_block { lua-script }* + +**context:** *location* + +**phase:** *right-after-server-certificate-message-was-processed* + +This directive runs user Lua code when Nginx is about to post-process the SSL server certificate message for the upstream SSL (https) connections. + +It is particularly useful to parse upstream server certificate and do some custom operations in pure lua. + +The [ngx.ssl.proxysslverify](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/proxysslverify.md) Lua modules provided by the [lua-resty-core](https://github.com/openresty/lua-resty-core/#readme) +library are particularly useful in this context. + +Below is a trivial example using the +[ngx.ssl.proxysslverify](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/proxysslverify.md) module +at the same time: + +```nginx + + server { + listen 443 ssl; + server_name test.com; + ssl_certificate /path/to/cert.crt; + ssl_certificate_key /path/to/key.key; + + location /t { + proxy_ssl_certificate /path/to/cert.crt; + proxy_ssl_certificate_key /path/to/key.key; + proxy_pass https://upstream; + + proxy_ssl_verify_by_lua_block { + local proxy_ssl_vfy = require "ngx.ssl.proxysslverify" + local cert = proxy_ssl_vfy.get_verify_cert() + + -- ocsp to verify cert + -- check crl + proxy_ssl_vfy.set_verify_result() + ... + } + } + ... + } +``` + +See more information in the [ngx.ssl.proxysslverify](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/proxysslverify.md) +Lua modules' official documentation. + +Uncaught Lua exceptions in the user Lua code immediately abort the current SSL session, so does the +[ngx.exit](#ngxexit) call with an error code like `ngx.ERROR`. + +This Lua code execution context *does* support yielding, so Lua APIs that may yield +(like cosockets, sleeping, and "light threads") +are enabled in this context + +Note, `ngx.ctx` in proxy_ssl_verify_by_lua_block is belonging to upstream connection, not downstream connection, so it's different from `ngx.ctx` in contexts like ssl_certificate_by_lua etc. + +This directive requires OpenSSL 3.0.2 or greater. + +[Back to TOC](#directives) + +proxy_ssl_verify_by_lua_file +---------------------------- + +**syntax:** *proxy_ssl_verify_by_lua_file <path-to-lua-script-file>* + +**context:** *location* + +**phase:** *right-after-server-certificate-message-was-processed* + +Equivalent to [proxy_ssl_verify_by_lua_block](#proxy_ssl_verify_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. + +When a relative path like `foo/bar.lua` is given, they will be turned into the absolute path relative to the `server prefix` path determined by the `-p PATH` command-line option while starting the Nginx server. + +[Back to TOC](#directives) + lua_shared_dict --------------- @@ -2729,7 +3260,7 @@ lua_shared_dict Declares a shared memory zone, ``, to serve as storage for the shm based Lua dictionary `ngx.shared.`. -Shared memory zones are always shared by all the nginx worker processes in the current nginx server instance. +Shared memory zones are always shared by all the Nginx worker processes in the current Nginx server instance. The `` argument accepts size units such as `k` and `m`: @@ -2848,7 +3379,7 @@ Default to 30 connections for every pool. When the connection pool exceeds the available size limit, the least recently used (idle) connection already in the pool will be closed to make room for the current connection. -Note that the cosocket connection pool is per nginx worker process rather than per nginx server instance, so size limit specified here also applies to every single nginx worker process. +Note that the cosocket connection pool is per Nginx worker process rather than per Nginx server instance, so size limit specified here also applies to every single Nginx worker process. This directive was first introduced in the `v0.5.0rc1` release. @@ -2880,7 +3411,7 @@ lua_socket_log_errors **context:** *http, server, location* -This directive can be used to toggle error logging when a failure occurs for the TCP or UDP cosockets. If you are already doing proper error handling and logging in your Lua code, then it is recommended to turn this directive off to prevent data flushing in your nginx error log files (which is usually rather expensive). +This directive can be used to toggle error logging when a failure occurs for the TCP or UDP cosockets. If you are already doing proper error handling and logging in your Lua code, then it is recommended to turn this directive off to prevent data flushing in your Nginx error log files (which is usually rather expensive). This directive was first introduced in the `v0.5.13` release. @@ -2912,58 +3443,158 @@ lua_ssl_crl **context:** *http, server, location* -Specifies a file with revoked certificates (CRL) in the PEM format used to verify the certificate of the SSL/TLS server in the [tcpsock:sslhandshake](#tcpsocksslhandshake) method. - -This directive was first introduced in the `v0.9.11` release. +Specifies a file with revoked certificates (CRL) in the PEM format used to verify the certificate of the SSL/TLS server in the [tcpsock:sslhandshake](#tcpsocksslhandshake) method. + +This directive was first introduced in the `v0.9.11` release. + +[Back to TOC](#directives) + +lua_ssl_protocols +----------------- + +**syntax:** *lua_ssl_protocols \[SSLv2\] \[SSLv3\] \[TLSv1\] [TLSv1.1] [TLSv1.2] [TLSv1.3]* + +**default:** *lua_ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3* + +**context:** *http, server, location* + +Enables the specified protocols for requests to a SSL/TLS server in the [tcpsock:sslhandshake](#tcpsocksslhandshake) method. + +The support for the `TLSv1.3` parameter requires version `v0.10.12` *and* OpenSSL 1.1.1. +From version v0.10.25, the default value change from `SSLV3 TLSv1 TLSv1.1 TLSv1.2` to `TLSv1 TLSv1.1 TLSv1.2 TLSv1.3`. + +This directive was first introduced in the `v0.9.11` release. + +[Back to TOC](#directives) + +lua_ssl_certificate +------------------- + +**syntax:** *lua_ssl_certificate <file>* + +**default:** *none* + +**context:** *http, server, location* + +Specifies the file path to the SSL/TLS certificate in PEM format used for the [tcpsock:sslhandshake](#tcpsocksslhandshake) method. + +This directive allows you to specify the SSL/TLS certificate that will be presented to server during the SSL/TLS handshake process. + +This directive was first introduced in the `v0.10.26` release. + +See also [lua_ssl_certificate_key](#lua_ssl_certificate_key) and [lua_ssl_verify_depth](#lua_ssl_verify_depth). + +[Back to TOC](#directives) + +lua_ssl_certificate_key +----------------------- + +**syntax:** *lua_ssl_certificate_key <file>* + +**default:** *none* + +**context:** *http, server, location* + +Specifies the file path to the private key associated with the SSL/TLS certificate used in the [tcpsock:sslhandshake](#tcpsocksslhandshake) method. + +This directive allows you to specify the private key file corresponding to the SSL/TLS certificate specified by lua_ssl_certificate. The private key should be in PEM format and must match the certificate. + +This directive was first introduced in the `v0.10.26` release. + +See also [lua_ssl_certificate](#lua_ssl_certificate) and [lua_ssl_verify_depth](#lua_ssl_verify_depth). + +[Back to TOC](#directives) + +lua_ssl_trusted_certificate +--------------------------- + +**syntax:** *lua_ssl_trusted_certificate <file>* + +**default:** *none* + +**context:** *http, server, location* + +Specifies a file path with trusted CA certificates in the PEM format used to verify the certificate of the SSL/TLS server in the [tcpsock:sslhandshake](#tcpsocksslhandshake) method. + +This directive was first introduced in the `v0.9.11` release. + +See also [lua_ssl_verify_depth](#lua_ssl_verify_depth). + +[Back to TOC](#directives) + +lua_ssl_verify_depth +-------------------- + +**syntax:** *lua_ssl_verify_depth <number>* + +**default:** *lua_ssl_verify_depth 1* + +**context:** *http, server, location* + +Sets the verification depth in the server certificates chain. + +This directive was first introduced in the `v0.9.11` release. + +See also [lua_ssl_certificate](#lua_ssl_certificate), [lua_ssl_certificate_key](#lua_ssl_certificate_key) and [lua_ssl_trusted_certificate](#lua_ssl_trusted_certificate). + +[Back to TOC](#directives) + +lua_ssl_key_log +--------------- + +**syntax:** *lua_ssl_key_log <file>* + +**default:** *none* + +**context:** *http, server, location* + +Enables logging of client connection SSL keys in the [tcpsock:sslhandshake](#tcpsocksslhandshake) method and specifies the path to the key log file. Keys are logged in the SSLKEYLOGFILE format compatible with Wireshark. [Back to TOC](#directives) -lua_ssl_protocols ------------------ +lua_ssl_conf_command +-------------------- -**syntax:** *lua_ssl_protocols \[SSLv2\] \[SSLv3\] \[TLSv1\] [TLSv1.1] [TLSv1.2]* +**syntax:** *lua_ssl_conf_command <command>* -**default:** *lua_ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2* +**default:** *no* **context:** *http, server, location* -Enables the specified protocols for requests to a SSL/TLS server in the [tcpsock:sslhandshake](#tcpsocksslhandshake) method. +Sets arbitrary OpenSSL configuration [commands](https://www.openssl.org/docs/man1.1.1/man3/SSL_CONF_cmd.html). -This directive was first introduced in the `v0.9.11` release. +The directive is supported when using OpenSSL 1.0.2 or higher and nginx 1.19.4 or higher. According to the specify command, higher OpenSSL version may be needed. -[Back to TOC](#directives) +Several `lua_ssl_conf_command` directives can be specified on the same level: -lua_ssl_trusted_certificate ---------------------------- +```nginx -**syntax:** *lua_ssl_trusted_certificate <file>* + lua_ssl_conf_command Options PrioritizeChaCha; + lua_ssl_conf_command Ciphersuites TLS_CHACHA20_POLY1305_SHA256; +``` -**default:** *no* +Configuration commands are applied after OpenResty own configuration for SSL, so they can be used to override anything set by OpenResty. -**context:** *http, server, location* +Note though that configuring OpenSSL directly with `lua_ssl_conf_command` might result in a behaviour OpenResty does not expect, and should be done with care. -Specifies a file path with trusted CA certificates in the PEM format used to verify the certificate of the SSL/TLS server in the [tcpsock:sslhandshake](#tcpsocksslhandshake) method. +This directive was first introduced in the `v0.10.21` release. -This directive was first introduced in the `v0.9.11` release. -See also [lua_ssl_verify_depth](#lua_ssl_verify_depth). [Back to TOC](#directives) -lua_ssl_verify_depth +lua_upstream_skip_openssl_default_verify -------------------- -**syntax:** *lua_ssl_verify_depth <number>* - -**default:** *lua_ssl_verify_depth 1* +**syntax:** *lua_upstream_skip_openssl_default_verify on|off* -**context:** *http, server, location* +**default:** *lua_upstream_skip_openssl_default_verify off* -Sets the verification depth in the server certificates chain. +**context:** *location, location-if* -This directive was first introduced in the `v0.9.11` release. +When using proxy_ssl_verify_by_lua directive, `lua_upstream_skip_openssl_default_verify` controls whether to skip default openssl's verify function, that means using pure Lua code to verify upstream server certificate. -See also [lua_ssl_trusted_certificate](#lua_ssl_trusted_certificate). +This directive is turned `off` by default. [Back to TOC](#directives) @@ -3027,7 +3658,7 @@ lua_transform_underscores_in_response_headers **context:** *http, server, location, location-if* -Controls whether to transform underscores (`_`) in the response header names specified in the [ngx.header.HEADER](#ngxheaderheader) API to hypens (`-`). +Controls whether to transform underscores (`_`) in the response header names specified in the [ngx.header.HEADER](#ngxheaderheader) API to hyphens (`-`). This directive was first introduced in the `v0.5.0rc32` release. @@ -3098,7 +3729,7 @@ lua_max_running_timers Controls the maximum number of "running timers" allowed. -Running timers are those timers whose user callback functions are still running. +Running timers are those timers whose user callback functions are still running or `lightthreads` spawned in callback functions are still running. When exceeding this limit, Nginx will stop running the callbacks of newly expired timers and log an error message "N lua_max_running_timers are not enough" where "N" is the current value of this directive. @@ -3106,6 +3737,44 @@ This directive was first introduced in the `v0.8.0` release. [Back to TOC](#directives) +lua_sa_restart +-------------- + +**syntax:** *lua_sa_restart on|off* + +**default:** *lua_sa_restart on* + +**context:** *http* + +When enabled, this module will set the `SA_RESTART` flag on Nginx workers signal dispositions. + +This allows Lua I/O primitives to not be interrupted by Nginx's handling of various signals. + +This directive was first introduced in the `v0.10.14` release. + +[Back to TOC](#directives) + +lua_worker_thread_vm_pool_size +------------------------------ + +**syntax:** *lua_worker_thread_vm_pool_size <size>* + +**default:** *lua_worker_thread_vm_pool_size 10* + +**context:** *http* + +Specifies the size limit of the Lua VM pool (default 100) that will be used in the [ngx.run_worker_thread](#ngxrun_worker_thread) API. + +Also, it is not allowed to create Lua VMs that exceeds the pool size limit. + +The Lua VM in the VM pool is used to execute Lua code in separate thread. + +The pool is global at Nginx worker level. And it is used to reuse Lua VMs between requests. + +**Warning:** Each worker thread uses a separate Lua VM and caches the Lua VM for reuse in subsequent operations. Configuring too many worker threads can result in consuming a lot of memory. + +[Back to TOC](#directives) + Nginx API for Lua ================= @@ -3163,6 +3832,7 @@ Nginx API for Lua * [ngx.decode_args](#ngxdecode_args) * [ngx.encode_base64](#ngxencode_base64) * [ngx.decode_base64](#ngxdecode_base64) +* [ngx.decode_base64mime](#ngxdecode_base64mime) * [ngx.crc32_short](#ngxcrc32_short) * [ngx.crc32_long](#ngxcrc32_long) * [ngx.hmac_sha1](#ngxhmac_sha1) @@ -3208,6 +3878,7 @@ Nginx API for Lua * [ngx.shared.DICT.capacity](#ngxshareddictcapacity) * [ngx.shared.DICT.free_space](#ngxshareddictfree_space) * [ngx.socket.udp](#ngxsocketudp) +* [udpsock:bind](#udpsockbind) * [udpsock:setpeername](#udpsocksetpeername) * [udpsock:send](#udpsocksend) * [udpsock:receive](#udpsockreceive) @@ -3215,10 +3886,14 @@ Nginx API for Lua * [udpsock:settimeout](#udpsocksettimeout) * [ngx.socket.stream](#ngxsocketstream) * [ngx.socket.tcp](#ngxsockettcp) +* [tcpsock:bind](#tcpsockbind) * [tcpsock:connect](#tcpsockconnect) +* [tcpsock:getfd](#getfd) +* [tcpsock:setclientcert](#tcpsocksetclientcert) * [tcpsock:sslhandshake](#tcpsocksslhandshake) * [tcpsock:send](#tcpsocksend) * [tcpsock:receive](#tcpsockreceive) +* [tcpsock:receiveany](#tcpsockreceiveany) * [tcpsock:receiveuntil](#tcpsockreceiveuntil) * [tcpsock:close](#tcpsockclose) * [tcpsock:settimeout](#tcpsocksettimeout) @@ -3244,6 +3919,7 @@ Nginx API for Lua * [ngx.config.ngx_lua_version](#ngxconfigngx_lua_version) * [ngx.worker.exiting](#ngxworkerexiting) * [ngx.worker.pid](#ngxworkerpid) +* [ngx.worker.pids](#ngxworkerpids) * [ngx.worker.count](#ngxworkercount) * [ngx.worker.id](#ngxworkerid) * [ngx.semaphore](#ngxsemaphore) @@ -3257,12 +3933,14 @@ Nginx API for Lua * [coroutine.wrap](#coroutinewrap) * [coroutine.running](#coroutinerunning) * [coroutine.status](#coroutinestatus) +* [ngx.run_worker_thread](#ngxrun_worker_thread) [Back to TOC](#table-of-contents) Introduction ------------ + The various `*_by_lua`, `*_by_lua_block` and `*_by_lua_file` configuration directives serve as gateways to the Lua API within the `nginx.conf` file. The Nginx Lua API described below can only be called within the user Lua code run in the context of these configuration directives. The API is exposed to Lua in the form of two standard packages `ngx` and `ndk`. These packages are in the default global scope within ngx_lua and are always available within ngx_lua directives. @@ -3282,7 +3960,7 @@ The packages can be introduced into external Lua modules like this: return _M ``` -Use of the [package.seeall](http://www.lua.org/manual/5.1/manual.html#pdf-package.seeall) flag is strongly discouraged due to its various bad side-effects. +Use of the [package.seeall](https://www.lua.org/manual/5.1/manual.html#pdf-package.seeall) flag is strongly discouraged due to its various bad side-effects. It is also possible to directly require the packages in external Lua modules: @@ -3300,6 +3978,7 @@ Network I/O operations in user code should only be done through the Nginx Lua AP ngx.arg ------- + **syntax:** *val = ngx.arg\[index\]* **context:** *set_by_lua*, body_filter_by_lua** @@ -3337,9 +4016,10 @@ The data chunk and "eof" flag passed to the downstream Nginx output filters can ngx.var.VARIABLE ---------------- + **syntax:** *ngx.var.VAR_NAME* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, balancer_by_lua** Read and write Nginx variable values. @@ -3349,7 +4029,7 @@ Read and write Nginx variable values. ngx.var.some_nginx_variable_name = value ``` -Note that only already defined nginx variables can be written to. +Note that only already defined Nginx variables can be written to. For example: ```nginx @@ -3357,15 +4037,16 @@ For example: location /foo { set $my_var ''; # this line is required to create $my_var at config time content_by_lua_block { - ngx.var.my_var = 123; + ngx.var.my_var = 123 ... } } ``` -That is, nginx variables cannot be created on-the-fly. +That is, Nginx variables cannot be created on-the-fly. Here is a list of pre-defined +[Nginx variables](http://nginx.org/en/docs/varindex.html). -Some special nginx variables like `$args` and `$limit_rate` can be assigned a value, +Some special Nginx variables like `$args` and `$limit_rate` can be assigned a value, many others are not, like `$query_string`, `$arg_PARAMETER`, and `$http_NAME`. Nginx regex group capturing variables `$1`, `$2`, `$3`, and etc, can be read by this @@ -3388,7 +4069,7 @@ Setting `ngx.var.Foo` to a `nil` value will unset the `$Foo` Nginx variable. to prevent (temporary) memory leaking within the current request's lifetime. Another way of caching the result is to use the [ngx.ctx](#ngxctx) table. -Undefined NGINX variables are evaluated to `nil` while uninitialized (but defined) NGINX variables are evaluated to an empty Lua string. +Undefined Nginx variables are evaluated to `nil` while uninitialized (but defined) Nginx variables are evaluated to an empty Lua string. This API requires a relatively expensive metamethod call and it is recommended to avoid using it on hot code paths. @@ -3396,7 +4077,8 @@ This API requires a relatively expensive metamethod call and it is recommended t Core constants -------------- -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, *log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** + +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, *log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** ```lua @@ -3407,7 +4089,7 @@ Core constants ngx.DECLINED (-5) ``` -Note that only three of these constants are utilized by the [Nginx API for Lua](#nginx-api-for-lua) (i.e., [ngx.exit](#ngxexit) accepts `NGX_OK`, `NGX_ERROR`, and `NGX_DECLINED` as input). +Note that only three of these constants are utilized by the [Nginx API for Lua](#nginx-api-for-lua) (i.e., [ngx.exit](#ngxexit) accepts `ngx.OK`, `ngx.ERROR`, and `ngx.DECLINED` as input). ```lua @@ -3422,7 +4104,8 @@ The `ngx.DECLINED` constant was first introduced in the `v0.5.0rc19` release. HTTP method constants --------------------- -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** + +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** ngx.HTTP_GET @@ -3448,7 +4131,8 @@ These constants are usually used in [ngx.location.capture](#ngxlocationcapture) HTTP status constants --------------------- -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** + +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** ```nginx @@ -3465,6 +4149,7 @@ HTTP status constants value = ngx.HTTP_SEE_OTHER (303) value = ngx.HTTP_NOT_MODIFIED (304) value = ngx.HTTP_TEMPORARY_REDIRECT (307) (first added in the v0.9.20 release) + value = ngx.HTTP_PERMANENT_REDIRECT (308) value = ngx.HTTP_BAD_REQUEST (400) value = ngx.HTTP_UNAUTHORIZED (401) value = ngx.HTTP_PAYMENT_REQUIRED (402) (first added in the v0.9.20 release) @@ -3480,7 +4165,8 @@ HTTP status constants value = ngx.HTTP_CLOSE (444) (first added in the v0.9.20 release) value = ngx.HTTP_ILLEGAL (451) (first added in the v0.9.20 release) value = ngx.HTTP_INTERNAL_SERVER_ERROR (500) - value = ngx.HTTP_METHOD_NOT_IMPLEMENTED (501) + value = ngx.HTTP_NOT_IMPLEMENTED (501) + value = ngx.HTTP_METHOD_NOT_IMPLEMENTED (501) (kept for compatibility) value = ngx.HTTP_BAD_GATEWAY (502) (first added in the v0.9.20 release) value = ngx.HTTP_SERVICE_UNAVAILABLE (503) value = ngx.HTTP_GATEWAY_TIMEOUT (504) (first added in the v0.3.1rc38 release) @@ -3492,7 +4178,8 @@ HTTP status constants Nginx log level constants ------------------------- -**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** + +**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** ```lua @@ -3513,11 +4200,12 @@ These constants are usually used by the [ngx.log](#ngxlog) method. print ----- + **syntax:** *print(...)* -**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** -Writes argument values into the nginx `error.log` file with the `ngx.NOTICE` log level. +Writes argument values into the Nginx `error.log` file with the `ngx.NOTICE` log level. It is equivalent to @@ -3534,7 +4222,8 @@ There is a hard coded `2048` byte limitation on error message lengths in the Ngi ngx.ctx ------- -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua** + +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, exit_worker_by_lua** This table can be used to store per-request Lua context data and has a life time identical to the current request (as with the Nginx variables). @@ -3599,7 +4288,7 @@ Then `GET /main` will give the output Here, modification of the `ngx.ctx.blah` entry in the subrequest does not affect the one in the parent request. This is because they have two separate versions of `ngx.ctx.blah`. -Internal redirection will destroy the original request `ngx.ctx` data (if any) and the new request will have an empty `ngx.ctx` table. For instance, +Internal redirects (triggered by nginx configuration directives like `error_page`, `try_files`, `index`, etc.) will destroy the original request `ngx.ctx` data (if any) and the new request will have an empty `ngx.ctx` table. For instance, ```nginx @@ -3626,6 +4315,14 @@ Then `GET /orig` will give rather than the original `"hello"` value. +Because HTTP request is created after SSL handshake, the `ngx.ctx` created +in [ssl_certificate_by_lua*](#ssl_certificate_by_lua), [ssl_session_store_by_lua*](#ssl_session_store_by_lua), [ssl_session_fetch_by_lua*](#ssl_session_fetch_by_lua) and [ssl_client_hello_by_lua*](#ssl_client_hello_by_lua) +is not available in the following phases like [rewrite_by_lua*](#rewrite_by_lua). + +Since `v0.10.18`, the `ngx.ctx` created during a SSL handshake +will be inherited by the requests which share the same TCP connection established by the handshake. +Note that overwrite values in `ngx.ctx` in the http request phases (like `rewrite_by_lua*`) will only take affect in the current http request. + Arbitrary data values, including Lua closures and nested tables, can be inserted into this "magic" table. It also allows the registration of custom meta methods. Overriding `ngx.ctx` with a new Lua table is also supported, for example, @@ -3678,13 +4375,14 @@ That is, let the caller pass the `ctx` table explicitly via a function argument. ngx.location.capture -------------------- + **syntax:** *res = ngx.location.capture(uri, options?)* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** Issues a synchronous but still non-blocking *Nginx Subrequest* using `uri`. -Nginx's subrequests provide a powerful way to make non-blocking internal requests to other locations configured with disk file directory or *any* other nginx C modules like `ngx_proxy`, `ngx_fastcgi`, `ngx_memc`, +Nginx's subrequests provide a powerful way to make non-blocking internal requests to other locations configured with disk file directory or *any* other Nginx C modules like `ngx_proxy`, `ngx_fastcgi`, `ngx_memc`, `ngx_postgres`, `ngx_drizzle`, and even ngx_lua itself and etc etc etc. Also note that subrequests just mimic the HTTP interface but there is *no* extra HTTP/TCP traffic *nor* IPC involved. Everything works internally, efficiently, on the C level. @@ -3733,7 +4431,7 @@ URI query strings can be concatenated to URI itself, for instance, ``` Named locations like `@foo` are not allowed due to a limitation in -the nginx core. Use normal locations combined with the `internal` directive to +the Nginx core. Use normal locations combined with the `internal` directive to prepare internal-only locations. An optional option table can be fed as the second @@ -3745,12 +4443,14 @@ argument, which supports the options: specify the subrequest's request body (string value only). * `args` specify the subrequest's URI query arguments (both string value and Lua tables are accepted) +* `headers` + specify the subrequest's request headers (Lua table only). this headers will override the original headers of the subrequest. * `ctx` specify a Lua table to be the [ngx.ctx](#ngxctx) table for the subrequest. It can be the current request's [ngx.ctx](#ngxctx) table, which effectively makes the parent and its subrequest to share exactly the same context table. This option was first introduced in the `v0.3.1rc25` release. * `vars` take a Lua table which holds the values to set the specified Nginx variables in the subrequest as this option's value. This option was first introduced in the `v0.3.1rc31` release. * `copy_all_vars` - specify whether to copy over all the Nginx variable values of the current request to the subrequest in question. modifications of the nginx variables in the subrequest will not affect the current (parent) request. This option was first introduced in the `v0.3.1rc31` release. + specify whether to copy over all the Nginx variable values of the current request to the subrequest in question. modifications of the Nginx variables in the subrequest will not affect the current (parent) request. This option was first introduced in the `v0.3.1rc31` release. * `share_all_vars` specify whether to share all the Nginx variables of the subrequest with the current (parent) request. modifications of the Nginx variables in the subrequest will affect the current (parent) request. Enabling this option may lead to hard-to-debug issues due to bad side-effects and is considered bad and harmful. Only enable this option when you completely know what you are doing. * `always_forward_body` @@ -3793,13 +4493,13 @@ The `args` option can also take plain query strings: ```lua ngx.location.capture('/foo?a=1', - { args = 'b=3&c=%3a' } } + { args = 'b=3&c=%3a' } ) ``` This is functionally identical to the previous examples. -The `share_all_vars` option controls whether to share nginx variables among the current request and its subrequests. +The `share_all_vars` option controls whether to share Nginx variables among the current request and its subrequests. If this option is set to `true`, then the current request and associated subrequests will share the same Nginx variable scope. Hence, changes to Nginx variables made by a subrequest will affect the current request. Care should be taken in using this option as variable scope sharing can have unexpected side effects. The `args`, `vars`, or `copy_all_vars` options are generally preferable instead. @@ -3817,7 +4517,7 @@ This option is set to `false` by default set $dog 'hello'; content_by_lua_block { res = ngx.location.capture("/other", - { share_all_vars = true }); + { share_all_vars = true }) ngx.print(res.body) ngx.say(ngx.var.uri, ": ", ngx.var.dog) @@ -3845,7 +4545,7 @@ The `copy_all_vars` option provides a copy of the parent request's Nginx variabl set $dog 'hello'; content_by_lua_block { res = ngx.location.capture("/other", - { copy_all_vars = true }); + { copy_all_vars = true }) ngx.print(res.body) ngx.say(ngx.var.uri, ": ", ngx.var.dog) @@ -3883,7 +4583,7 @@ unescaping them in the Nginx config file. set $cat ''; content_by_lua_block { res = ngx.location.capture("/other", - { vars = { dog = "hello", cat = 32 }}); + { vars = { dog = "hello", cat = 32 }}) ngx.print(res.body) } @@ -3896,6 +4596,33 @@ Accessing `/lua` will yield the output dog = hello cat = 32 +The `headers` option can be used to specify the request headers for the subrequest. The value of this option should be a Lua table where the keys are the header names and the values are the header values. For example, + +```lua + +location /foo { + content_by_lua_block { + ngx.print(ngx.var.http_x_test) + } +} + +location /lua { + content_by_lua_block { + local res = ngx.location.capture("/foo", { + headers = { + ["X-Test"] = "aa", + } + }) + ngx.print(res.body) + } +} +``` + +Accessing `/lua` will yield the output + + + aa + The `ctx` option can be used to specify a custom Lua table to serve as the [ngx.ctx](#ngxctx) table for the subrequest. @@ -3911,8 +4638,8 @@ The `ctx` option can be used to specify a custom Lua table to serve as the [ngx. local ctx = {} res = ngx.location.capture("/sub", { ctx = ctx }) - ngx.say(ctx.foo); - ngx.say(ngx.ctx.foo); + ngx.say(ctx.foo) + ngx.say(ngx.ctx.foo) } } ``` @@ -3930,13 +4657,13 @@ It is also possible to use this `ctx` option to share the same [ngx.ctx](#ngxctx location /sub { content_by_lua_block { - ngx.ctx.foo = "bar"; + ngx.ctx.foo = "bar" } } location /lua { content_by_lua_block { res = ngx.location.capture("/sub", { ctx = ngx.ctx }) - ngx.say(ngx.ctx.foo); + ngx.say(ngx.ctx.foo) } } ``` @@ -3956,7 +4683,7 @@ in gzipped responses that cannot be handled properly in Lua code. Original reque When the `body` option is not specified and the `always_forward_body` option is false (the default value), the `POST` and `PUT` subrequests will inherit the request bodies of the parent request (if any). -There is a hard-coded upper limit on the number of concurrent subrequests possible for every main request. In older versions of Nginx, the limit was `50` concurrent subrequests and in more recent versions, Nginx `1.1.x` onwards, this was increased to `200` concurrent subrequests. When this limit is exceeded, the following error message is added to the `error.log` file: +There is a hard-coded upper limit on the number of subrequests possible for every main request. In older versions of Nginx, the limit was `50` concurrent subrequests and in more recent versions, Nginx `1.9.5` onwards, the same limit is changed to limit the depth of recursive subrequests. When this limit is exceeded, the following error message is added to the `error.log` file: [error] 13983#0: *1 subrequests cycle while processing "/uri" @@ -3970,6 +4697,7 @@ Please also refer to restrictions on capturing locations configured by [subreque ngx.location.capture_multi -------------------------- + **syntax:** *res1, res2, ... = ngx.location.capture_multi({ {uri, options?}, {uri, options?}, ... })* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -4010,7 +4738,9 @@ Lua tables can be used for both requests and responses when the number of subreq table.insert(reqs, { "/memcached" }) -- issue all the requests at once and wait until they all return - local resps = { ngx.location.capture_multi(reqs) } + local resps = { + ngx.location.capture_multi(reqs) + } -- loop over the responses table for i, resp in ipairs(resps) do @@ -4035,6 +4765,7 @@ Please also refer to restrictions on capturing locations configured by [subreque ngx.status ---------- + **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua** Read and write the current request's response status. This should be called @@ -4046,7 +4777,7 @@ before sending out the response headers. status = ngx.status ``` -Setting `ngx.status` after the response header is sent out has no effect but leaving an error message in your nginx's error log file: +Setting `ngx.status` after the response header is sent out has no effect but leaving an error message in your Nginx's error log file: attempt to set ngx.status after sending out response headers @@ -4056,6 +4787,7 @@ Setting `ngx.status` after the response header is sent out has no effect but lea ngx.header.HEADER ----------------- + **syntax:** *ngx.header.HEADER = VALUE* **syntax:** *value = ngx.header.HEADER* @@ -4071,9 +4803,9 @@ The header names are matched case-insensitively. ```lua -- equivalent to ngx.header["Content-Type"] = 'text/plain' - ngx.header.content_type = 'text/plain'; + ngx.header.content_type = 'text/plain' - ngx.header["X-My-Header"] = 'blah blah'; + ngx.header["X-My-Header"] = 'blah blah' ``` Multi-value headers can be set this way: @@ -4111,17 +4843,17 @@ Setting a slot to `nil` effectively removes it from the response headers: ```lua - ngx.header["X-My-Header"] = nil; + ngx.header["X-My-Header"] = nil ``` The same applies to assigning an empty table: ```lua - ngx.header["X-My-Header"] = {}; + ngx.header["X-My-Header"] = {} ``` -Setting `ngx.header.HEADER` after sending out response headers (either explicitly with [ngx.send_headers](#ngxsend_headers) or implicitly with [ngx.print](#ngxprint) and similar) will throw out a Lua exception. +Setting `ngx.header.HEADER` after sending out response headers (either explicitly with [ngx.send_headers](#ngxsend_headers) or implicitly with [ngx.print](#ngxprint) and similar) will log an error message. Reading `ngx.header.HEADER` will return the value of the response header named `HEADER`. @@ -4164,13 +4896,17 @@ to be returned when reading `ngx.header.Foo`. Note that `ngx.header` is not a normal Lua table and as such, it is not possible to iterate through it using the Lua `ipairs` function. +Note: this function throws a Lua error if `HEADER` or +`VALUE` contain unsafe characters (control characters). + For reading *request* headers, use the [ngx.req.get_headers](#ngxreqget_headers) function instead. [Back to TOC](#nginx-api-for-lua) ngx.resp.get_headers -------------------- -**syntax:** *headers = ngx.resp.get_headers(max_headers?, raw?)* + +**syntax:** *headers, err = ngx.resp.get_headers(max_headers?, raw?)* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, balancer_by_lua** @@ -4178,7 +4914,12 @@ Returns a Lua table holding all the current response headers for the current req ```lua - local h = ngx.resp.get_headers() + local h, err = ngx.resp.get_headers() + + if err == "truncated" then + -- one can choose to ignore or reject the current response here + end + for k, v in pairs(h) do ... end @@ -4186,18 +4927,21 @@ Returns a Lua table holding all the current response headers for the current req This function has the same signature as [ngx.req.get_headers](#ngxreqget_headers) except getting response headers instead of request headers. +Note that a maximum of 100 response headers are parsed by default (including those with the same name) and that additional response headers are silently discarded to guard against potential denial of service attacks. Since `v0.10.13`, when the limit is exceeded, it will return a second value which is the string `"truncated"`. + This API was first introduced in the `v0.9.5` release. [Back to TOC](#nginx-api-for-lua) ngx.req.is_internal ------------------- + **syntax:** *is_internal = ngx.req.is_internal()* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua** Returns a boolean indicating whether the current request is an "internal request", i.e., -a request initiated from inside the current nginx server instead of from the client side. +a request initiated from inside the current Nginx server instead of from the client side. Subrequests are all internal requests and so are requests after internal redirects. @@ -4207,6 +4951,7 @@ This API was first introduced in the `v0.9.20` release. ngx.req.start_time ------------------ + **syntax:** *secs = ngx.req.start_time()* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua** @@ -4228,13 +4973,14 @@ See also [ngx.now](#ngxnow) and [ngx.update_time](#ngxupdate_time). ngx.req.http_version -------------------- + **syntax:** *num = ngx.req.http_version()* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua** Returns the HTTP version number for the current request as a Lua number. -Current possible values are 2.0, 1.0, 1.1, and 0.9. Returns `nil` for unrecognized values. +Current possible values are 3.0, 2.0, 1.0, 1.1, and 0.9. Returns `nil` for unrecognized values. This method was first introduced in the `v0.7.17` release. @@ -4242,6 +4988,7 @@ This method was first introduced in the `v0.7.17` release. ngx.req.raw_header ------------------ + **syntax:** *str = ngx.req.raw_header(no_request_line?)* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua** @@ -4290,9 +5037,10 @@ This method does not work in HTTP/2 requests yet. ngx.req.get_method ------------------ + **syntax:** *method_name = ngx.req.get_method()* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, balancer_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, balancer_by_lua*, log_by_lua** Retrieves the current request's request method name. Strings like `"GET"` and `"POST"` are returned instead of numerical [method constants](#http-method-constants). @@ -4306,6 +5054,7 @@ See also [ngx.req.set_method](#ngxreqset_method). ngx.req.set_method ------------------ + **syntax:** *ngx.req.set_method(method_id)* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua** @@ -4322,7 +5071,8 @@ See also [ngx.req.get_method](#ngxreqget_method). ngx.req.set_uri --------------- -**syntax:** *ngx.req.set_uri(uri, jump?)* + +**syntax:** *ngx.req.set_uri(uri, jump?, binary?)* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua** @@ -4332,7 +5082,7 @@ The optional boolean `jump` argument can trigger location rematch (or location j Location jump will not be triggered otherwise, and only the current request's URI will be modified, which is also the default behavior. This function will return but with no returned values when the `jump` argument is `false` or absent altogether. -For example, the following nginx config snippet +For example, the following Nginx config snippet ```nginx @@ -4392,6 +5142,9 @@ which is functionally equivalent to } ``` +Note: this function throws a Lua error if the `uri` argument +contains unsafe characters (control characters). + Note that it is not possible to use this interface to rewrite URI arguments and that [ngx.req.set_uri_args](#ngxreqset_uri_args) should be used for this instead. For instance, Nginx config ```nginx @@ -4415,12 +5168,25 @@ or ngx.req.set_uri("/foo", true) ``` +Starting from `0.10.16` of this module, this function accepts an +optional boolean `binary` argument to allow arbitrary binary URI +data. By default, this `binary` argument is false and this function +will throw out a Lua error such as the one below when the `uri` +argument contains any control characters (ASCII Code 0 ~ 0x08, 0x0A ~ 0x1F and 0x7F). + + + [error] 23430#23430: *1 lua entry thread aborted: runtime error: + content_by_lua(nginx.conf:44):3: ngx.req.set_uri unsafe byte "0x00" + in "\x00foo" (maybe you want to set the 'binary' argument?) + + This interface was first introduced in the `v0.3.1rc14` release. [Back to TOC](#nginx-api-for-lua) ngx.req.set_uri_args -------------------- + **syntax:** *ngx.req.set_uri_args(args)* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua** @@ -4439,7 +5205,12 @@ or a Lua table holding the query arguments' key-value pairs, as in ngx.req.set_uri_args({ a = 3, b = "hello world" }) ``` -where in the latter case, this method will escape argument keys and values according to the URI escaping rule. +In the former case, i.e., when the whole query-string is provided directly, +the input Lua string should already be well-formed with the URI encoding. +For security considerations, this method will automatically escape any control and +whitespace characters (ASCII code 0x00 ~ 0x20 and 0x7F) in the Lua string. + +In the latter case, this method will escape argument keys and values according to the URI escaping rule. Multi-value arguments are also supported: @@ -4448,7 +5219,9 @@ Multi-value arguments are also supported: ngx.req.set_uri_args({ a = 3, b = {5, 6} }) ``` -which will result in a query string like `a=3&b=5&b=6`. +which will result in a query string like `a=3&b=5&b=6` or `b=5&b=6&a=3`. + +**Note that when using Lua table as the `arg` argument, the order of the arguments in the result query string which change from time to time. If you would like to get an ordered result, you need to use Lua string as the `arg` argument.** This interface was first introduced in the `v0.3.1rc13` release. @@ -4458,17 +5231,24 @@ See also [ngx.req.set_uri](#ngxreqset_uri). ngx.req.get_uri_args -------------------- -**syntax:** *args = ngx.req.get_uri_args(max_args?)* + +**syntax:** *args, err = ngx.req.get_uri_args(max_args?, tab?)* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, balancer_by_lua** -Returns a Lua table holding all the current request URL query arguments. +Returns a Lua table holding all the current request URL query arguments. An optional `tab` argument +can be used to reuse the table returned by this method. ```nginx location = /test { content_by_lua_block { - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err == "truncated" then + -- one can choose to ignore or reject the current request here + end + for key, val in pairs(args) do if type(val) == "table" then ngx.say(key, ": ", table.concat(val, ", ")) @@ -4515,12 +5295,12 @@ That is, they will take Lua boolean values `true`. However, they are different f Empty key arguments are discarded. `GET /test?=hello&=world` will yield an empty output for instance. -Updating query arguments via the nginx variable `$args` (or `ngx.var.args` in Lua) at runtime is also supported: +Updating query arguments via the Nginx variable `$args` (or `ngx.var.args` in Lua) at runtime is also supported: ```lua ngx.var.args = "a=3&b=42" - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() ``` Here the `args` table will always look like @@ -4532,20 +5312,23 @@ Here the `args` table will always look like regardless of the actual request query string. -Note that a maximum of 100 request arguments are parsed by default (including those with the same name) and that additional request arguments are silently discarded to guard against potential denial of service attacks. +Note that a maximum of 100 request arguments are parsed by default (including those with the same name) and that additional request arguments are silently discarded to guard against potential denial of service attacks. Since `v0.10.13`, when the limit is exceeded, it will return a second value which is the string `"truncated"`. However, the optional `max_args` function argument can be used to override this limit: ```lua - local args = ngx.req.get_uri_args(10) + local args, err = ngx.req.get_uri_args(10) + if err == "truncated" then + -- one can choose to ignore or reject the current request here + end ``` This argument can be set to zero to remove the limit and to process all request arguments received: ```lua - local args = ngx.req.get_uri_args(0) + local args, err = ngx.req.get_uri_args(0) ``` Removing the `max_args` cap is strongly discouraged. @@ -4554,6 +5337,7 @@ Removing the `max_args` cap is strongly discouraged. ngx.req.get_post_args --------------------- + **syntax:** *args, err = ngx.req.get_post_args(max_args?)* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua** @@ -4566,6 +5350,11 @@ Returns a Lua table holding all the current request POST query arguments (of the content_by_lua_block { ngx.req.read_body() local args, err = ngx.req.get_post_args() + + if err == "truncated" then + -- one can choose to ignore or reject the current request here + end + if not args then ngx.say("failed to get post args: ", err) return @@ -4634,20 +5423,23 @@ That is, they will take Lua boolean values `true`. However, they are different f Empty key arguments are discarded. `POST /test` with body `=hello&=world` will yield empty outputs for instance. -Note that a maximum of 100 request arguments are parsed by default (including those with the same name) and that additional request arguments are silently discarded to guard against potential denial of service attacks. +Note that a maximum of 100 request arguments are parsed by default (including those with the same name) and that additional request arguments are silently discarded to guard against potential denial of service attacks. Since `v0.10.13`, when the limit is exceeded, it will return a second value which is the string `"truncated"`. However, the optional `max_args` function argument can be used to override this limit: ```lua - local args = ngx.req.get_post_args(10) + local args, err = ngx.req.get_post_args(10) + if err == "truncated" then + -- one can choose to ignore or reject the current request here + end ``` This argument can be set to zero to remove the limit and to process all request arguments received: ```lua - local args = ngx.req.get_post_args(0) + local args, err = ngx.req.get_post_args(0) ``` Removing the `max_args` cap is strongly discouraged. @@ -4656,7 +5448,8 @@ Removing the `max_args` cap is strongly discouraged. ngx.req.get_headers ------------------- -**syntax:** *headers = ngx.req.get_headers(max_headers?, raw?)* + +**syntax:** *headers, err = ngx.req.get_headers(max_headers?, raw?)* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua** @@ -4664,7 +5457,12 @@ Returns a Lua table holding all the current request headers. ```lua - local h = ngx.req.get_headers() + local h, err = ngx.req.get_headers() + + if err == "truncated" then + -- one can choose to ignore or reject the current request here + end + for k, v in pairs(h) do ... end @@ -4695,20 +5493,24 @@ the value of `ngx.req.get_headers()["Foo"]` will be a Lua (array) table such as: {"foo", "bar", "baz"} ``` -Note that a maximum of 100 request headers are parsed by default (including those with the same name) and that additional request headers are silently discarded to guard against potential denial of service attacks. +Note that a maximum of 100 request headers are parsed by default (including those with the same name) and that additional request headers are silently discarded to guard against potential denial of service attacks. Since `v0.10.13`, when the limit is exceeded, it will return a second value which is the string `"truncated"`. However, the optional `max_headers` function argument can be used to override this limit: ```lua - local headers = ngx.req.get_headers(10) + local headers, err = ngx.req.get_headers(10) + + if err == "truncated" then + -- one can choose to ignore or reject the current request here + end ``` This argument can be set to zero to remove the limit and to process all request headers received: ```lua - local headers = ngx.req.get_headers(0) + local headers, err = ngx.req.get_headers(0) ``` Removing the `max_headers` cap is strongly discouraged. @@ -4730,14 +5532,22 @@ The `__index` metamethod will not be added when the `raw` argument is set to `tr ngx.req.set_header ------------------ + **syntax:** *ngx.req.set_header(header_name, header_value)* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua** Set the current request's request header named `header_name` to value `header_value`, overriding any existing ones. +The input Lua string `header_name` and `header_value` should already be well-formed with the URI encoding. +For security considerations, this method will automatically escape " ", """, "(", ")", ",", "/", ":", ";", "?", +"<", "=", ">", "?", "@", "[", "]", "\", "{", "}", 0x00-0x1F, 0x7F-0xFF in `header_name` and automatically escape +"0x00-0x08, 0x0A-0x0F, 0x7F in `header_value`. + By default, all the subrequests subsequently initiated by [ngx.location.capture](#ngxlocationcapture) and [ngx.location.capture_multi](#ngxlocationcapture_multi) will inherit the new header. +It is not a Lua's equivalent of nginx `proxy_set_header` directive (same is true about [ngx.req.clear_header](#ngxreqclear_header)). `proxy_set_header` only affects the upstream request while `ngx.req.set_header` change the incoming request. Record the http headers in the access log file will show the difference. But you still can use it as an alternative of nginx `proxy_set_header` directive as long as you know the difference. + Here is an example of setting the `Content-Type` header: ```lua @@ -4777,10 +5587,14 @@ is equivalent to ngx.req.clear_header("X-Foo") ``` +Note: this function throws a Lua error if `header_name` or +`header_value` contain unsafe characters (control characters). + [Back to TOC](#nginx-api-for-lua) ngx.req.clear_header -------------------- + **syntax:** *ngx.req.clear_header(header_name)* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua** @@ -4791,6 +5605,7 @@ Clears the current request's request header named `header_name`. None of the cur ngx.req.read_body ----------------- + **syntax:** *ngx.req.read_body()* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -4822,6 +5637,7 @@ This function was first introduced in the `v0.3.1rc17` release. ngx.req.discard_body -------------------- + **syntax:** *ngx.req.discard_body()* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -4840,19 +5656,22 @@ See also [ngx.req.read_body](#ngxreqread_body). ngx.req.get_body_data --------------------- -**syntax:** *data = ngx.req.get_body_data()* + +**syntax:** *data = ngx.req.get_body_data(max_bytes?)* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, log_by_lua** Retrieves in-memory request body data. It returns a Lua string rather than a Lua table holding all the parsed query arguments. Use the [ngx.req.get_post_args](#ngxreqget_post_args) function instead if a Lua table is required. +The optional `max_bytes` argument can be used when you don't need the entire body. + This function returns `nil` if 1. the request body has not been read, 1. the request body has been read into disk temporary files, 1. or the request body has zero size. -If the request body has not been read yet, call [ngx.req.read_body](#ngxreqread_body) first (or turned on [lua_need_request_body](#lua_need_request_body) to force this module to read the request body. This is not recommended however). +If the request body has not been read yet, call [ngx.req.read_body](#ngxreqread_body) first (or turn on [lua_need_request_body](#lua_need_request_body) to force this module to read the request body. This is not recommended however). If the request body has been read into disk files, try calling the [ngx.req.get_body_file](#ngxreqget_body_file) function instead. @@ -4868,6 +5687,7 @@ See also [ngx.req.get_body_file](#ngxreqget_body_file). ngx.req.get_body_file --------------------- + **syntax:** *file_name = ngx.req.get_body_file()* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -4876,12 +5696,14 @@ Retrieves the file name for the in-file request body data. Returns `nil` if the The returned file is read only and is usually cleaned up by Nginx's memory pool. It should not be manually modified, renamed, or removed in Lua code. -If the request body has not been read yet, call [ngx.req.read_body](#ngxreqread_body) first (or turned on [lua_need_request_body](#lua_need_request_body) to force this module to read the request body. This is not recommended however). +If the request body has not been read yet, call [ngx.req.read_body](#ngxreqread_body) first (or turn on [lua_need_request_body](#lua_need_request_body) to force this module to read the request body. This is not recommended however). If the request body has been read into memory, try calling the [ngx.req.get_body_data](#ngxreqget_body_data) function instead. To force in-file request bodies, try turning on [client_body_in_file_only](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_in_file_only). +Note that this function is also work for balancer phase but it needs to call [balancer.recreate_request](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/balancer.md#recreate_request) to make the change take effect after set the request body data or headers. + This function was first introduced in the `v0.3.1rc17` release. See also [ngx.req.get_body_data](#ngxreqget_body_data). @@ -4890,13 +5712,18 @@ See also [ngx.req.get_body_data](#ngxreqget_body_data). ngx.req.set_body_data --------------------- + **syntax:** *ngx.req.set_body_data(data)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, balancer_by_lua*,* Set the current request's request body using the in-memory data specified by the `data` argument. -If the current request's request body has not been read, then it will be properly discarded. When the current request's request body has been read into memory or buffered into a disk file, then the old request body's memory will be freed or the disk file will be cleaned up immediately, respectively. +If the request body has not been read yet, call [ngx.req.read_body](#ngxreqread_body) first (or turn on [lua_need_request_body](#lua_need_request_body) to force this module to read the request body. This is not recommended however). Additionally, the request body must not have been previously discarded by [ngx.req.discard_body](#ngxreqdiscard_body). + +Whether the previous request body has been read into memory or buffered into a disk file, it will be freed or the disk file will be cleaned up immediately, respectively. + +Note that this function is also work for balancer phase but it needs to call [balancer.recreate_request](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/balancer.md#recreate_request) to make the change take effect after set the request body data or headers. This function was first introduced in the `v0.3.1rc18` release. @@ -4906,17 +5733,20 @@ See also [ngx.req.set_body_file](#ngxreqset_body_file). ngx.req.set_body_file --------------------- + **syntax:** *ngx.req.set_body_file(file_name, auto_clean?)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, balancer_by_lua*,* Set the current request's request body using the in-file data specified by the `file_name` argument. +If the request body has not been read yet, call [ngx.req.read_body](#ngxreqread_body) first (or turn on [lua_need_request_body](#lua_need_request_body) to force this module to read the request body. This is not recommended however). Additionally, the request body must not have been previously discarded by [ngx.req.discard_body](#ngxreqdiscard_body). + If the optional `auto_clean` argument is given a `true` value, then this file will be removed at request completion or the next time this function or [ngx.req.set_body_data](#ngxreqset_body_data) are called in the same request. The `auto_clean` is default to `false`. Please ensure that the file specified by the `file_name` argument exists and is readable by an Nginx worker process by setting its permission properly to avoid Lua exception errors. -If the current request's request body has not been read, then it will be properly discarded. When the current request's request body has been read into memory or buffered into a disk file, then the old request body's memory will be freed or the disk file will be cleaned up immediately, respectively. +Whether the previous request body has been read into memory or buffered into a disk file, it will be freed or the disk file will be cleaned up immediately, respectively. This function was first introduced in the `v0.3.1rc18` release. @@ -4926,11 +5756,12 @@ See also [ngx.req.set_body_data](#ngxreqset_body_data). ngx.req.init_body ----------------- + **syntax:** *ngx.req.init_body(buffer_size?)* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua** -Creates a new blank request body for the current request and inializes the buffer for later request body data writing via the [ngx.req.append_body](#ngxreqappend_body) and [ngx.req.finish_body](#ngxreqfinish_body) APIs. +Creates a new blank request body for the current request and initializes the buffer for later request body data writing via the [ngx.req.append_body](#ngxreqappend_body) and [ngx.req.finish_body](#ngxreqfinish_body) APIs. If the `buffer_size` argument is specified, then its value will be used for the size of the memory buffer for body writing with [ngx.req.append_body](#ngxreqappend_body). If the argument is omitted, then the value specified by the standard [client_body_buffer_size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size) directive will be used instead. @@ -4957,6 +5788,7 @@ This function was first introduced in the `v0.5.11` release. ngx.req.append_body ------------------- + **syntax:** *ngx.req.append_body(data_chunk)* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -4977,6 +5809,7 @@ See also [ngx.req.init_body](#ngxreqinit_body). ngx.req.finish_body ------------------- + **syntax:** *ngx.req.finish_body()* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -4993,26 +5826,29 @@ See also [ngx.req.init_body](#ngxreqinit_body). ngx.req.socket -------------- + **syntax:** *tcpsock, err = ngx.req.socket()* **syntax:** *tcpsock, err = ngx.req.socket(raw)* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** -Returns a read-only cosocket object that wraps the downstream connection. Only [receive](#tcpsockreceive) and [receiveuntil](#tcpsockreceiveuntil) methods are supported on this object. +Returns a read-only cosocket object that wraps the downstream connection. Only [receive](#tcpsockreceive), [receiveany](#tcpsockreceiveany) and [receiveuntil](#tcpsockreceiveuntil) methods are supported on this object. In case of error, `nil` will be returned as well as a string describing the error. +**Note:** This method will block while waiting for client request body to be fully received. Block time depends on the [client_body_timeout](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_timeout) directive and maximum body size specified by the [client_max_body_size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size) directive. If read timeout occurs or client body size exceeds the defined limit, this function will not return and `408 Request Time-out` or `413 Request Entity Too Large` response will be returned to the client instead. + The socket object returned by this method is usually used to read the current request's body in a streaming fashion. Do not turn on the [lua_need_request_body](#lua_need_request_body) directive, and do not mix this call with [ngx.req.read_body](#ngxreqread_body) and [ngx.req.discard_body](#ngxreqdiscard_body). If any request body data has been pre-read into the Nginx core request header buffer, the resulting cosocket object will take care of this to avoid potential data loss resulting from such pre-reading. Chunked request bodies are not yet supported in this API. -Since the `v0.9.0` release, this function accepts an optional boolean `raw` argument. When this argument is `true`, this function returns a full-duplex cosocket object wrapping around the raw downstream connection socket, upon which you can call the [receive](#tcpsockreceive), [receiveuntil](#tcpsockreceiveuntil), and [send](#tcpsocksend) methods. +Since the `v0.9.0` release, this function accepts an optional boolean `raw` argument. When this argument is `true`, this function returns a full-duplex cosocket object wrapping around the raw downstream connection socket, upon which you can call the [receive](#tcpsockreceive), [receiveany](#tcpsockreceiveany), [receiveuntil](#tcpsockreceiveuntil), and [send](#tcpsocksend) methods. When the `raw` argument is `true`, it is required that no pending data from any previous [ngx.say](#ngxsay), [ngx.print](#ngxprint), or [ngx.send_headers](#ngxsend_headers) calls exists. So if you have these downstream output calls previously, you should call [ngx.flush(true)](#ngxflush) before calling `ngx.req.socket(true)` to ensure that there is no pending output data. If the request body has not been read yet, then this "raw socket" can also be used to read the request body. -You can use the "raw request socket" returned by `ngx.req.socket(true)` to implement fancy protocols like [WebSocket](http://en.wikipedia.org/wiki/WebSocket), or just emit your own raw HTTP response header or body data. You can refer to the [lua-resty-websocket library](https://github.com/openresty/lua-resty-websocket) for a real world example. +You can use the "raw request socket" returned by `ngx.req.socket(true)` to implement fancy protocols like [WebSocket](https://en.wikipedia.org/wiki/WebSocket), or just emit your own raw HTTP response header or body data. You can refer to the [lua-resty-websocket library](https://github.com/openresty/lua-resty-websocket) for a real world example. This function was first introduced in the `v0.5.0rc1` release. @@ -5020,6 +5856,7 @@ This function was first introduced in the `v0.5.0rc1` release. ngx.exec -------- + **syntax:** *ngx.exec(uri, args?)* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -5028,9 +5865,9 @@ Does an internal redirect to `uri` with `args` and is similar to the [echo_exec] ```lua - ngx.exec('/some-location'); - ngx.exec('/some-location', 'a=3&b=5&c=6'); - ngx.exec('/some-location?a=3&b=5', 'c=6'); + ngx.exec('/some-location') + ngx.exec('/some-location', 'a=3&b=5&c=6') + ngx.exec('/some-location?a=3&b=5', 'c=6') ``` The optional second `args` can be used to specify extra URI query arguments, for example: @@ -5059,7 +5896,7 @@ Named locations are also supported but the second `args` argument will be ignore location /foo { content_by_lua_block { - ngx.exec("@bar", "a=goodbye"); + ngx.exec("@bar", "a=goodbye") } } @@ -5087,18 +5924,23 @@ It is recommended that a coding style that combines this method call with the `r ngx.redirect ------------ + **syntax:** *ngx.redirect(uri, status?)* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** Issue an `HTTP 301` or `302` redirection to `uri`. +Note: this function throws a Lua error if the `uri` argument +contains unsafe characters (control characters). + The optional `status` parameter specifies the HTTP status code to be used. The following status codes are supported right now: * `301` * `302` (default) * `303` * `307` +* `308` It is `302` (`ngx.HTTP_MOVED_TEMPORARILY`) by default. @@ -5142,7 +5984,7 @@ is equivalent to the following Lua code ```lua - return ngx.redirect('/foo'); -- Lua code + return ngx.redirect('/foo') -- Lua code ``` while @@ -5175,6 +6017,7 @@ It is recommended that a coding style that combines this method call with the `r ngx.send_headers ---------------- + **syntax:** *ok, err = ngx.send_headers()* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -5190,6 +6033,7 @@ before content is output with [ngx.say](#ngxsay) or [ngx.print](#ngxprint) or wh ngx.headers_sent ---------------- + **syntax:** *value = ngx.headers_sent* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -5202,6 +6046,7 @@ This API was first introduced in ngx_lua v0.3.1rc6. ngx.print --------- + **syntax:** *ok, err = ngx.print(...)* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -5243,6 +6088,7 @@ Please note that both `ngx.print` and [ngx.say](#ngxsay) will always invoke the ngx.say ------- + **syntax:** *ok, err = ngx.say(...)* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -5253,9 +6099,10 @@ Just as [ngx.print](#ngxprint) but also emit a trailing newline. ngx.log ------- + **syntax:** *ngx.log(log_level, ...)* -**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** Log arguments concatenated to error.log with the given logging level. @@ -5269,6 +6116,7 @@ There is a hard coded `2048` byte limitation on error message lengths in the Ngi ngx.flush --------- + **syntax:** *ok, err = ngx.flush(wait?)* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -5289,11 +6137,12 @@ Since `v0.8.3` this function returns `1` on success, or returns `nil` and a stri ngx.exit -------- + **syntax:** *ngx.exit(status)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** -When `status >= 200` (i.e., `ngx.HTTP_OK` and above), it will interrupt the execution of the current request and return status code to nginx. +When `status >= 200` (i.e., `ngx.HTTP_OK` and above), it will interrupt the execution of the current request and return status code to Nginx. When `status == 0` (i.e., `ngx.OK`), it will only quit the current phase handler (or the content handler if the [content_by_lua*](#content_by_lua) directive is used) and continue to run later phases (if any) for the current request. @@ -5332,7 +6181,7 @@ Number literals can be used directly as the argument, for instance, ngx.exit(501) ``` -Note that while this method accepts all [HTTP status constants](#http-status-constants) as input, it only accepts `NGX_OK` and `NGX_ERROR` of the [core constants](#core-constants). +Note that while this method accepts all [HTTP status constants](#http-status-constants) as input, it only accepts `ngx.OK` and `ngx.ERROR` of the [core constants](#core-constants). Also note that this method call terminates the processing of the current request and that it is recommended that a coding style that combines this method call with the `return` statement, i.e., `return ngx.exit(...)` be used to reinforce the fact that the request processing is being terminated. @@ -5344,6 +6193,7 @@ an asynchronous operation and will return immediately. This behavior may change ngx.eof ------- + **syntax:** *ok, err = ngx.eof()* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -5379,11 +6229,12 @@ Since `v0.8.3` this function returns `1` on success, or returns `nil` and a stri ngx.sleep --------- + **syntax:** *ngx.sleep(seconds)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** -Sleeps for the specified seconds without blocking. One can specify time resolution up to 0.001 seconds (i.e., one milliseconds). +Sleeps for the specified seconds without blocking. One can specify time resolution up to 0.001 seconds (i.e., one millisecond). Behind the scene, this method makes use of the Nginx timers. @@ -5395,19 +6246,29 @@ This method was introduced in the `0.5.0rc30` release. ngx.escape_uri -------------- -**syntax:** *newstr = ngx.escape_uri(str)* -**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**syntax:** *newstr = ngx.escape_uri(str, type?)* -Escape `str` as a URI component. +**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** + +Since `v0.10.16`, this function accepts an optional `type` argument. +It accepts the following values (defaults to `2`): + +* `0`: escapes `str` as a full URI. And the characters +` ` (space), `#`, `%`, +`?`, 0x00 ~ 0x1F, 0x7F ~ 0xFF will be escaped. +* `2`: escape `str` as a URI component. All characters except +alphabetic characters, digits, `-`, `.`, `_`, +`~` will be encoded as `%XX`. [Back to TOC](#nginx-api-for-lua) ngx.unescape_uri ---------------- + **syntax:** *newstr = ngx.unescape_uri(str)* -**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua** +**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** Unescape `str` as an escaped URI component. @@ -5424,13 +6285,31 @@ gives the output b r56 7 +Invalid escaping sequences are handled in a conventional way: `%`s are left unchanged. Also, characters that should not appear in escaped string are simply left unchanged. + +For example, + +```lua + + ngx.say(ngx.unescape_uri("try %search%%20%again%")) +``` + +gives the output + + + try %search% %again% + + +(Note that `%20` following `%` got unescaped, even it can be considered a part of invalid sequence.) + [Back to TOC](#nginx-api-for-lua) ngx.encode_args --------------- + **syntax:** *str = ngx.encode_args(table)* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_client_hello_by_lua** Encode the Lua table to a query args string according to the URI encoded rules. @@ -5485,13 +6364,14 @@ This method was first introduced in the `v0.3.1rc27` release. ngx.decode_args --------------- -**syntax:** *table = ngx.decode_args(str, max_args?)* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**syntax:** *table, err = ngx.decode_args(str, max_args?)* + +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Decodes a URI encoded query-string into a Lua table. This is the inverse function of [ngx.encode_args](#ngxencode_args). -The optional `max_args` argument can be used to specify the maximum number of arguments parsed from the `str` argument. By default, a maximum of 100 request arguments are parsed (including those with the same name) and that additional URI arguments are silently discarded to guard against potential denial of service attacks. +The optional `max_args` argument can be used to specify the maximum number of arguments parsed from the `str` argument. By default, a maximum of 100 request arguments are parsed (including those with the same name) and that additional URI arguments are silently discarded to guard against potential denial of service attacks. Since `v0.10.13`, when the limit is exceeded, it will return a second value which is the string `"truncated"`. This argument can be set to zero to remove the limit and to process all request arguments received: @@ -5508,11 +6388,12 @@ This method was introduced in the `v0.5.0rc29`. ngx.encode_base64 ----------------- + **syntax:** *newstr = ngx.encode_base64(str, no_padding?)* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** -Encodes `str` to a base64 digest. +Encodes `str` to a base64 digest. For base64url encoding use [`base64.encode_base64url`](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/base64.md#encode_base64url). Since the `0.9.16` release, an optional boolean-typed `no_padding` argument can be specified to control whether the base64 padding should be appended to the resulting digest (default to `false`, i.e., with padding enabled). @@ -5520,19 +6401,39 @@ Since the `0.9.16` release, an optional boolean-typed `no_padding` argument can ngx.decode_base64 ----------------- + **syntax:** *newstr = ngx.decode_base64(str)* +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** + +Decodes the `str` argument as a base64 digest to the raw form. For base64url decoding use [`base64.decode_base64url`](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/base64.md#decode_base64url). + +The `str` should be standard 'base64' encoding for RFC 3548 or RFC 4648, and will returns `nil` if is not well formed or any characters not in the base encoding alphabet. Padding may be omitted from the input. + +[Back to TOC](#nginx-api-for-lua) + +ngx.decode_base64mime +--------------------- +**syntax:** *newstr = ngx.decode_base64mime(str)* + **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** -Decodes the `str` argument as a base64 digest to the raw form. Returns `nil` if `str` is not well formed. +**requires:** `resty.core.base64` or `resty.core` + +Decodes the `str` argument as a base64 digest to the raw form. +The `str` follows base64 transfer encoding for MIME (RFC 2045), and will discard characters outside the base encoding alphabet. +Returns `nil` if `str` is not well formed. + + '''Note:''' This method requires the resty.core.base64 or resty.core modules from the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. [Back to TOC](#nginx-api-for-lua) ngx.crc32_short --------------- + **syntax:** *intval = ngx.crc32_short(str)* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Calculates the CRC-32 (Cyclic Redundancy Code) digest for the `str` argument. @@ -5546,9 +6447,10 @@ This API was first introduced in the `v0.3.1rc8` release. ngx.crc32_long -------------- + **syntax:** *intval = ngx.crc32_long(str)* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Calculates the CRC-32 (Cyclic Redundancy Code) digest for the `str` argument. @@ -5562,11 +6464,12 @@ This API was first introduced in the `v0.3.1rc8` release. ngx.hmac_sha1 ------------- + **syntax:** *digest = ngx.hmac_sha1(secret_key, str)* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** -Computes the [HMAC-SHA1](http://en.wikipedia.org/wiki/HMAC) digest of the argument `str` and turns the result using the secret key ``. +Computes the [HMAC-SHA1](https://en.wikipedia.org/wiki/HMAC) digest of the argument `str` and turns the result using the secret key ``. The raw binary form of the `HMAC-SHA1` digest will be generated, use [ngx.encode_base64](#ngxencode_base64), for example, to encode the result to a textual representation if desired. @@ -5594,9 +6497,10 @@ This function was first introduced in the `v0.3.1rc29` release. ngx.md5 ------- + **syntax:** *digest = ngx.md5(str)* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Returns the hexadecimal representation of the MD5 digest of the `str` argument. @@ -5605,7 +6509,9 @@ For example, ```nginx location = /md5 { - content_by_lua_block { ngx.say(ngx.md5("hello")) } + content_by_lua_block { + ngx.say(ngx.md5("hello")) + } } ``` @@ -5621,9 +6527,10 @@ See [ngx.md5_bin](#ngxmd5_bin) if the raw binary MD5 digest is required. ngx.md5_bin ----------- + **syntax:** *digest = ngx.md5_bin(str)* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Returns the binary form of the MD5 digest of the `str` argument. @@ -5633,9 +6540,10 @@ See [ngx.md5](#ngxmd5) if the hexadecimal form of the MD5 digest is required. ngx.sha1_bin ------------ + **syntax:** *digest = ngx.sha1_bin(str)* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Returns the binary form of the SHA-1 digest of the `str` argument. @@ -5647,9 +6555,10 @@ This function was first introduced in the `v0.5.0rc6`. ngx.quote_sql_str ----------------- + **syntax:** *quoted_value = ngx.quote_sql_str(raw_value)* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Returns a quoted SQL string literal according to the MySQL quoting rules. @@ -5657,11 +6566,12 @@ Returns a quoted SQL string literal according to the MySQL quoting rules. ngx.today --------- + **syntax:** *str = ngx.today()* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** -Returns current date (in the format `yyyy-mm-dd`) from the nginx cached time (no syscall involved unlike Lua's date library). +Returns current date (in the format `yyyy-mm-dd`) from the Nginx cached time (no syscall involved unlike Lua's date library). This is the local time. @@ -5669,11 +6579,12 @@ This is the local time. ngx.time -------- + **syntax:** *secs = ngx.time()* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** -Returns the elapsed seconds from the epoch for the current time stamp from the nginx cached time (no syscall involved unlike Lua's date library). +Returns the elapsed seconds from the epoch for the current time stamp from the Nginx cached time (no syscall involved unlike Lua's date library). Updates of the Nginx time cache can be forced by calling [ngx.update_time](#ngxupdate_time) first. @@ -5681,11 +6592,12 @@ Updates of the Nginx time cache can be forced by calling [ngx.update_time](#ngxu ngx.now ------- + **syntax:** *secs = ngx.now()* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** -Returns a floating-point number for the elapsed time in seconds (including milliseconds as the decimal part) from the epoch for the current time stamp from the nginx cached time (no syscall involved unlike Lua's date library). +Returns a floating-point number for the elapsed time in seconds (including milliseconds as the decimal part) from the epoch for the current time stamp from the Nginx cached time (no syscall involved unlike Lua's date library). You can forcibly update the Nginx time cache by calling [ngx.update_time](#ngxupdate_time) first. @@ -5695,9 +6607,10 @@ This API was first introduced in `v0.3.1rc32`. ngx.update_time --------------- + **syntax:** *ngx.update_time()* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** Forcibly updates the Nginx current time cache. This call involves a syscall and thus has some overhead, so do not abuse it. @@ -5707,11 +6620,12 @@ This API was first introduced in `v0.3.1rc32`. ngx.localtime ------------- + **syntax:** *str = ngx.localtime()* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** -Returns the current time stamp (in the format `yyyy-mm-dd hh:mm:ss`) of the nginx cached time (no syscall involved unlike Lua's [os.date](http://www.lua.org/manual/5.1/manual.html#pdf-os.date) function). +Returns the current time stamp (in the format `yyyy-mm-dd hh:mm:ss`) of the Nginx cached time (no syscall involved unlike Lua's [os.date](https://www.lua.org/manual/5.1/manual.html#pdf-os.date) function). This is the local time. @@ -5719,11 +6633,12 @@ This is the local time. ngx.utctime ----------- + **syntax:** *str = ngx.utctime()* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** -Returns the current time stamp (in the format `yyyy-mm-dd hh:mm:ss`) of the nginx cached time (no syscall involved unlike Lua's [os.date](http://www.lua.org/manual/5.1/manual.html#pdf-os.date) function). +Returns the current time stamp (in the format `yyyy-mm-dd hh:mm:ss`) of the Nginx cached time (no syscall involved unlike Lua's [os.date](https://www.lua.org/manual/5.1/manual.html#pdf-os.date) function). This is the UTC time. @@ -5731,9 +6646,10 @@ This is the UTC time. ngx.cookie_time --------------- + **syntax:** *str = ngx.cookie_time(sec)* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** Returns a formatted string can be used as the cookie expiration time. The parameter `sec` is the time stamp in seconds (like those returned from [ngx.time](#ngxtime)). @@ -5747,11 +6663,12 @@ Returns a formatted string can be used as the cookie expiration time. The parame ngx.http_time ------------- + **syntax:** *str = ngx.http_time(sec)* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** -Returns a formated string can be used as the http header time (for example, being used in `Last-Modified` header). The parameter `sec` is the time stamp in seconds (like those returned from [ngx.time](#ngxtime)). +Returns a formatted string can be used as the http header time (for example, being used in `Last-Modified` header). The parameter `sec` is the time stamp in seconds (like those returned from [ngx.time](#ngxtime)). ```nginx @@ -5763,9 +6680,10 @@ Returns a formated string can be used as the http header time (for example, bein ngx.parse_http_time ------------------- + **syntax:** *sec = ngx.parse_http_time(str)* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** Parse the http time string (as returned by [ngx.http_time](#ngxhttp_time)) into seconds. Returns the seconds or `nil` if the input string is in bad forms. @@ -5781,19 +6699,21 @@ Parse the http time string (as returned by [ngx.http_time](#ngxhttp_time)) into ngx.is_subrequest ----------------- + **syntax:** *value = ngx.is_subrequest* **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua** -Returns `true` if the current request is an nginx subrequest, or `false` otherwise. +Returns `true` if the current request is an Nginx subrequest, or `false` otherwise. [Back to TOC](#nginx-api-for-lua) ngx.re.match ------------ + **syntax:** *captures, err = ngx.re.match(subject, regex, options?, ctx?, res_table?)* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** Matches the `subject` string using the Perl compatible regular expression `regex` with the optional `options`. @@ -5933,7 +6853,7 @@ The `ctx` table argument combined with the `a` regex modifier can be used to con Note that, the `options` argument is not optional when the `ctx` argument is specified and that the empty Lua string (`""`) must be used as placeholder for `options` if no meaningful regex options are required. -This method requires the PCRE library enabled in Nginx. ([Known Issue With Special Escaping Sequences](#special-escaping-sequences)). +This method requires the PCRE library enabled in Nginx ([Known Issue With Special Escaping Sequences](#special-escaping-sequences)). To confirm that PCRE JIT is enabled, activate the Nginx debug log by adding the `--with-debug` option to Nginx or OpenResty's `./configure` script. Then, enable the "debug" error log level in `error_log` directive. The following message will be generated if PCRE JIT is enabled: @@ -5949,11 +6869,12 @@ This feature was introduced in the `v0.2.1rc11` release. ngx.re.find ----------- + **syntax:** *from, to, err = ngx.re.find(subject, regex, options?, ctx?, nth?)* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** -Similar to [ngx.re.match](#ngxrematch) but only returns the beginning index (`from`) and end index (`to`) of the matched substring. The returned indexes are 1-based and can be fed directly into the [string.sub](http://www.lua.org/manual/5.1/manual.html#pdf-string.sub) API function to obtain the matched substring. +Similar to [ngx.re.match](#ngxrematch) but only returns the beginning index (`from`) and end index (`to`) of the matched substring. The returned indexes are 1-based and can be fed directly into the [string.sub](https://www.lua.org/manual/5.1/manual.html#pdf-string.sub) API function to obtain the matched substring. In case of errors (like bad regexes or any PCRE runtime errors), this API function returns two `nil` values followed by a string describing the error. @@ -6003,9 +6924,10 @@ This API function was first introduced in the `v0.9.2` release. ngx.re.gmatch ------------- + **syntax:** *iterator, err = ngx.re.gmatch(subject, regex, options?)* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** Similar to [ngx.re.match](#ngxrematch), but returns a Lua iterator instead, so as to let the user programmer iterate all the matches over the `` string argument with the PCRE `regex`. @@ -6073,7 +6995,7 @@ The optional `options` argument takes exactly the same semantics as the [ngx.re. The current implementation requires that the iterator returned should only be used in a single request. That is, one should *not* assign it to a variable belonging to persistent namespace like a Lua package. -This method requires the PCRE library enabled in Nginx. ([Known Issue With Special Escaping Sequences](#special-escaping-sequences)). +This method requires the PCRE library enabled in Nginx ([Known Issue With Special Escaping Sequences](#special-escaping-sequences)). This feature was first introduced in the `v0.2.1rc12` release. @@ -6081,9 +7003,10 @@ This feature was first introduced in the `v0.2.1rc12` release. ngx.re.sub ---------- + **syntax:** *newstr, n, err = ngx.re.sub(subject, regex, replace, options?)* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** Substitutes the first match of the Perl compatible regular expression `regex` on the `subject` argument string with the string or function argument `replace`. The optional `options` argument has exactly the same meaning as in [ngx.re.match](#ngxrematch). @@ -6094,13 +7017,13 @@ When the `replace` is a string, then it is treated as a special template for str ```lua local newstr, n, err = ngx.re.sub("hello, 1234", "([0-9])[0-9]", "[$0][$1]") - if newstr then - -- newstr == "hello, [12][1]34" - -- n == 1 - else + if not newstr then ngx.log(ngx.ERR, "error: ", err) return end + + -- newstr == "hello, [12][1]34" + -- n == 1 ``` where `$0` referring to the whole substring matched by the pattern and `$1` referring to the first parenthesized capturing substring. @@ -6110,8 +7033,8 @@ Curly braces can also be used to disambiguate variable names from the background ```lua local newstr, n, err = ngx.re.sub("hello, 1234", "[0-9]", "${0}00") - -- newstr == "hello, 100234" - -- n == 1 + -- newstr == "hello, 100234" + -- n == 1 ``` Literal dollar sign characters (`$`) in the `replace` string argument can be escaped by another dollar sign, for instance, @@ -6119,8 +7042,8 @@ Literal dollar sign characters (`$`) in the `replace` string argument can be esc ```lua local newstr, n, err = ngx.re.sub("hello, 1234", "[0-9]", "$$") - -- newstr == "hello, $234" - -- n == 1 + -- newstr == "hello, $234" + -- n == 1 ``` Do not use backlashes to escape dollar signs; it will not work as expected. @@ -6132,14 +7055,15 @@ When the `replace` argument is of type "function", then it will be invoked with local func = function (m) return "[" .. m[0] .. "][" .. m[1] .. "]" end + local newstr, n, err = ngx.re.sub("hello, 1234", "( [0-9] ) [0-9]", func, "x") - -- newstr == "hello, [12][1]34" - -- n == 1 + -- newstr == "hello, [12][1]34" + -- n == 1 ``` The dollar sign characters in the return value of the `replace` function argument are not special at all. -This method requires the PCRE library enabled in Nginx. ([Known Issue With Special Escaping Sequences](#special-escaping-sequences)). +This method requires the PCRE library enabled in Nginx ([Known Issue With Special Escaping Sequences](#special-escaping-sequences)). This feature was first introduced in the `v0.2.1rc13` release. @@ -6147,9 +7071,10 @@ This feature was first introduced in the `v0.2.1rc13` release. ngx.re.gsub ----------- + **syntax:** *newstr, n, err = ngx.re.gsub(subject, regex, replace, options?)* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** Just like [ngx.re.sub](#ngxresub), but does global substitution. @@ -6158,13 +7083,13 @@ Here is some examples: ```lua local newstr, n, err = ngx.re.gsub("hello, world", "([a-z])[a-z]+", "[$0,$1]", "i") - if newstr then - -- newstr == "[hello,h], [world,w]" - -- n == 2 - else + if not newstr then ngx.log(ngx.ERR, "error: ", err) return end + + -- newstr == "[hello,h], [world,w]" + -- n == 2 ``` ```lua @@ -6173,11 +7098,11 @@ Here is some examples: return "[" .. m[0] .. "," .. m[1] .. "]" end local newstr, n, err = ngx.re.gsub("hello, world", "([a-z])[a-z]+", func, "i") - -- newstr == "[hello,h], [world,w]" - -- n == 2 + -- newstr == "[hello,h], [world,w]" + -- n == 2 ``` -This method requires the PCRE library enabled in Nginx. ([Known Issue With Special Escaping Sequences](#special-escaping-sequences)). +This method requires the PCRE library enabled in Nginx ([Known Issue With Special Escaping Sequences](#special-escaping-sequences)). This feature was first introduced in the `v0.2.1rc15` release. @@ -6185,15 +7110,16 @@ This feature was first introduced in the `v0.2.1rc15` release. ngx.shared.DICT --------------- + **syntax:** *dict = ngx.shared.DICT* **syntax:** *dict = ngx.shared\[name_var\]* -**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** Fetching the shm-based Lua dictionary object for the shared memory zone named `DICT` defined by the [lua_shared_dict](#lua_shared_dict) directive. -Shared memory zones are always shared by all the nginx worker processes in the current nginx server instance. +Shared memory zones are always shared by all the Nginx worker processes in the current Nginx server instance. The resulting object `dict` has the following methods: @@ -6219,7 +7145,7 @@ The resulting object `dict` has the following methods: * [capacity](#ngxshareddictcapacity) * [free_space](#ngxshareddictfree_space) -All these methods are *atomic* operations, that is, safe from concurrent accesses from multiple nginx worker processes for the same `lua_shared_dict` zone. +All these methods are *atomic* operations, that is, safe from concurrent accesses from multiple Nginx worker processes for the same `lua_shared_dict` zone. Here is an example: @@ -6271,9 +7197,10 @@ This feature was first introduced in the `v0.3.1rc22` release. ngx.shared.DICT.get ------------------- + **syntax:** *value, flags = ngx.shared.DICT:get(key)* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Retrieving the value in the dictionary [ngx.shared.DICT](#ngxshareddict) for the key `key`. If the key does not exist or has expired, then `nil` will be returned. @@ -6309,9 +7236,10 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.get_stale ------------------------- + **syntax:** *value, flags, stale = ngx.shared.DICT:get_stale(key)* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Similar to the [get](#ngxshareddictget) method but returns the value even if the key has already expired. @@ -6327,9 +7255,10 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.set ------------------- + **syntax:** *success, err, forcible = ngx.shared.DICT:set(key, value, exptime?, flags?)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Unconditionally sets a key-value pair into the shm-based dictionary [ngx.shared.DICT](#ngxshareddict). Returns three values: @@ -6345,6 +7274,10 @@ The optional `flags` argument specifies a user flags value associated with the e When it fails to allocate memory for the current key-value item, then `set` will try removing existing items in the storage according to the Least-Recently Used (LRU) algorithm. Note that, LRU takes priority over expiration time here. If up to tens of existing items have been removed and the storage left is still insufficient (either due to the total capacity limit specified by [lua_shared_dict](#lua_shared_dict) or memory segmentation), then the `err` return value will be `no memory` and `success` will be `false`. +If the sizes of items in the dictionary are not multiples or even powers of a certain value (like 2), it is easier to encounter `no memory` error because of memory fragmentation. It is recommended to use different dictionaries for different sizes of items. + +When you encounter `no memory` error, you can also evict more least-recently-used items by retrying this method call more times to to make room for the current item. + If this method succeeds in storing the current item by forcibly removing other not-yet-expired items in the dictionary via LRU, the `forcible` return value will be `true`. If it stores the item without forcibly removing other valid items, then the return value `forcible` will be `false`. The first argument to this method must be the dictionary object itself, for example, @@ -6375,9 +7308,10 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.safe_set ------------------------ + **syntax:** *ok, err = ngx.shared.DICT:safe_set(key, value, exptime?, flags?)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Similar to the [set](#ngxshareddictset) method, but never overrides the (least recently used) unexpired items in the store when running out of storage in the shared memory zone. In this case, it will immediately return `nil` and the string "no memory". @@ -6389,9 +7323,10 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.add ------------------- + **syntax:** *success, err, forcible = ngx.shared.DICT:add(key, value, exptime?, flags?)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Just like the [set](#ngxshareddictset) method, but only stores the key-value pair into the dictionary [ngx.shared.DICT](#ngxshareddict) if the key does *not* exist. @@ -6405,9 +7340,10 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.safe_add ------------------------ + **syntax:** *ok, err = ngx.shared.DICT:safe_add(key, value, exptime?, flags?)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Similar to the [add](#ngxshareddictadd) method, but never overrides the (least recently used) unexpired items in the store when running out of storage in the shared memory zone. In this case, it will immediately return `nil` and the string "no memory". @@ -6419,9 +7355,10 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.replace ----------------------- + **syntax:** *success, err, forcible = ngx.shared.DICT:replace(key, value, exptime?, flags?)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Just like the [set](#ngxshareddictset) method, but only stores the key-value pair into the dictionary [ngx.shared.DICT](#ngxshareddict) if the key *does* exist. @@ -6435,9 +7372,10 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.delete ---------------------- + **syntax:** *ngx.shared.DICT:delete(key)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Unconditionally removes the key-value pair from the shm-based dictionary [ngx.shared.DICT](#ngxshareddict). @@ -6451,9 +7389,12 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.incr -------------------- -**syntax:** *newval, err, forcible? = ngx.shared.DICT:incr(key, value, init?)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**syntax:** *newval, err, forcible? = ngx.shared.DICT:incr(key, value, init?, init_ttl?)* + +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** + +**optional requirement:** `resty.core.shdict` or `resty.core` Increments the (numerical) value for `key` in the shm-based dictionary [ngx.shared.DICT](#ngxshareddict) by the step value `value`. Returns the new resulting number if the operation is successfully completed or `nil` and an error message otherwise. @@ -6464,6 +7405,25 @@ When the key does not exist or has already expired in the shared dictionary, Like the [add](#ngxshareddictadd) method, it also overrides the (least recently used) unexpired items in the store when running out of storage in the shared memory zone. +The optional `init_ttl` argument specifies expiration time (in seconds) of the value when it is initialized via the `init` argument. The time resolution is `0.001` seconds. If `init_ttl` takes the value `0` (which is the default), then the item will never expire. This argument cannot be provided without providing the `init` argument as well, and has no effect if the value already exists (e.g., if it was previously inserted via [set](#ngxshareddictset) or the likes). + +**Note:** Usage of the `init_ttl` argument requires the `resty.core.shdict` or `resty.core` modules from the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. Example: + +```lua + + require "resty.core" + + local cats = ngx.shared.cats + local newval, err = cats:incr("black_cats", 1, 0, 0.1) + + print(newval) -- 1 + + ngx.sleep(0.2) + + local val, err = cats:get("black_cats") + print(val) -- nil +``` + The `forcible` return value will always be `nil` when the `init` argument is not specified. If this method succeeds in storing the current item by forcibly removing other not-yet-expired items in the dictionary via LRU, the `forcible` return value will be `true`. If it stores the item without forcibly removing other valid items, then the return value `forcible` will be `false`. @@ -6476,15 +7436,18 @@ This method was first introduced in the `v0.3.1rc22` release. The optional `init` parameter was first added in the `v0.10.6` release. +The optional `init_ttl` parameter was introduced in the `v0.10.12rc2` release. + See also [ngx.shared.DICT](#ngxshareddict). [Back to TOC](#nginx-api-for-lua) ngx.shared.DICT.lpush --------------------- + **syntax:** *length, err = ngx.shared.DICT:lpush(key, value)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Inserts the specified (numerical or string) `value` at the head of the list named `key` in the shm-based dictionary [ngx.shared.DICT](#ngxshareddict). Returns the number of elements in the list after the push operation. @@ -6500,9 +7463,10 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.rpush --------------------- + **syntax:** *length, err = ngx.shared.DICT:rpush(key, value)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Similar to the [lpush](#ngxshareddictlpush) method, but inserts the specified (numerical or string) `value` at the tail of the list named `key`. @@ -6514,9 +7478,10 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.lpop -------------------- + **syntax:** *val, err = ngx.shared.DICT:lpop(key)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Removes and returns the first element of the list named `key` in the shm-based dictionary [ngx.shared.DICT](#ngxshareddict). @@ -6530,9 +7495,10 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.rpop -------------------- + **syntax:** *val, err = ngx.shared.DICT:rpop(key)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Removes and returns the last element of the list named `key` in the shm-based dictionary [ngx.shared.DICT](#ngxshareddict). @@ -6546,9 +7512,10 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.llen -------------------- + **syntax:** *len, err = ngx.shared.DICT:llen(key)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Returns the number of elements in the list named `key` in the shm-based dictionary [ngx.shared.DICT](#ngxshareddict). @@ -6562,9 +7529,10 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.ttl ------------------- + **syntax:** *ttl, err = ngx.shared.DICT:ttl(key)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** **requires:** `resty.core.shdict` or `resty.core` @@ -6599,9 +7567,10 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.expire ---------------------- + **syntax:** *success, err = ngx.shared.DICT:expire(key, exptime)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** **requires:** `resty.core.shdict` or `resty.core` @@ -6638,11 +7607,12 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.flush_all ------------------------- + **syntax:** *ngx.shared.DICT:flush_all()* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** -Flushes out all the items in the dictionary. This method does not actuall free up all the memory blocks in the dictionary but just marks all the existing items as expired. +Flushes out all the items in the dictionary. This method does not actually free up all the memory blocks in the dictionary but just marks all the existing items as expired. This feature was first introduced in the `v0.5.0rc17` release. @@ -6652,13 +7622,14 @@ See also [ngx.shared.DICT.flush_expired](#ngxshareddictflush_expired) and [ngx.s ngx.shared.DICT.flush_expired ----------------------------- + **syntax:** *flushed = ngx.shared.DICT:flush_expired(max_count?)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Flushes out the expired items in the dictionary, up to the maximal number specified by the optional `max_count` argument. When the `max_count` argument is given `0` or not given at all, then it means unlimited. Returns the number of items that have actually been flushed. -Unlike the [flush_all](#ngxshareddictflush_all) method, this method actually free up the memory used by the expired items. +Unlike the [flush_all](#ngxshareddictflush_all) method, this method actually frees up the memory used by the expired items. This feature was first introduced in the `v0.6.3` release. @@ -6668,9 +7639,10 @@ See also [ngx.shared.DICT.flush_all](#ngxshareddictflush_all) and [ngx.shared.DI ngx.shared.DICT.get_keys ------------------------ + **syntax:** *keys = ngx.shared.DICT:get_keys(max_count?)* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Fetch a list of the keys from the dictionary, up to ``. @@ -6684,9 +7656,10 @@ This feature was first introduced in the `v0.7.3` release. ngx.shared.DICT.capacity ------------------------ + **syntax:** *capacity_bytes = ngx.shared.DICT:capacity()* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** **requires:** `resty.core.shdict` or `resty.core` @@ -6707,7 +7680,7 @@ This feature was first introduced in the `v0.10.11` release. **Note:** This method requires the `resty.core.shdict` or `resty.core` modules from the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. -This feature requires at least nginx core version `0.7.3`. +This feature requires at least Nginx core version `0.7.3`. See also [ngx.shared.DICT](#ngxshareddict). @@ -6715,15 +7688,16 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.shared.DICT.free_space -------------------------- + **syntax:** *free_page_bytes = ngx.shared.DICT:free_space()* -**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** **requires:** `resty.core.shdict` or `resty.core` Retrieves the free page size in bytes for the shm-based dictionary [ngx.shared.DICT](#ngxshareddict). -**Note:** The memory for ngx.shared.DICT is allocated via the nginx slab allocator which has each slot for +**Note:** The memory for ngx.shared.DICT is allocated via the Nginx slab allocator which has each slot for data size ranges like \~8, 9\~16, 17\~32, ..., 1025\~2048, 2048\~ bytes. And pages are assigned to a slot if there is no room in already assigned pages for the slot. @@ -6749,7 +7723,7 @@ This feature was first introduced in the `v0.10.11` release. **Note:** This method requires the `resty.core.shdict` or `resty.core` modules from the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. -This feature requires at least nginx core version `1.11.7`. +This feature requires at least Nginx core version `1.11.7`. See also [ngx.shared.DICT](#ngxshareddict). @@ -6757,12 +7731,14 @@ See also [ngx.shared.DICT](#ngxshareddict). ngx.socket.udp -------------- + **syntax:** *udpsock = ngx.socket.udp()* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Creates and returns a UDP or datagram-oriented unix domain socket object (also known as one type of the "cosocket" objects). The following methods are supported on this object: +* [bind](#udpsockbind) * [setpeername](#udpsocksetpeername) * [send](#udpsocksend) * [receive](#udpsockreceive) @@ -6777,13 +7753,44 @@ See also [ngx.socket.tcp](#ngxsockettcp). [Back to TOC](#nginx-api-for-lua) +udpsock:bind +------------ +**syntax:** *ok, err = udpsock:bind(address)* + +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*,ssl_session_fetch_by_lua*,ssl_client_hello_by_lua** + +Just like the standard [proxy_bind](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_bind) directive, this api makes the outgoing connection to a upstream server originate from the specified local IP address. + +Only IP addresses can be specified as the `address` argument. + +Here is an example for connecting to a TCP server from the specified local IP address: + +```nginx + + location /test { + content_by_lua_block { + local sock = ngx.socket.udp() + -- assume "192.168.1.10" is the local ip address + local ok, err = sock:bind("192.168.1.10") + if not ok then + ngx.say("failed to bind: ", err) + return + end + sock:close() + } + } +``` + +[Back to TOC](#nginx-api-for-lua) + udpsock:setpeername ------------------- + **syntax:** *ok, err = udpsock:setpeername(host, port)* **syntax:** *ok, err = udpsock:setpeername("unix:/path/to/unix-domain.socket")* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Attempts to connect a UDP socket object to a remote server or to a datagram unix domain socket file. Because the datagram protocol is actually connection-less, this method does not really establish a "connection", but only just set the name of the remote peer for subsequent read/write operations. @@ -6828,6 +7835,9 @@ Since the `v0.7.18` release, connecting to a datagram unix domain socket file is ngx.say("failed to connect to the datagram unix domain socket: ", err) return end + + -- do something after connect + -- such as sock:send or sock:receive ``` assuming the datagram service is listening on the unix domain socket file `/tmp/some-datagram-service.sock` and the client socket will use the "autobind" feature on Linux. @@ -6840,9 +7850,10 @@ This method was first introduced in the `v0.5.7` release. udpsock:send ------------ + **syntax:** *ok, err = udpsock:send(data)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Sends data on the current UDP or datagram unix domain socket object. @@ -6856,9 +7867,10 @@ This feature was first introduced in the `v0.5.7` release. udpsock:receive --------------- + **syntax:** *data, err = udpsock:receive(size?)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Receives data from the UDP or datagram unix domain socket object with an optional receive buffer size argument, `size`. @@ -6891,9 +7903,10 @@ This feature was first introduced in the `v0.5.7` release. udpsock:close ------------- + **syntax:** *ok, err = udpsock:close()* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Closes the current UDP or datagram unix domain socket. It returns the `1` in case of success and returns `nil` with a string describing the error otherwise. @@ -6905,9 +7918,10 @@ This feature was first introduced in the `v0.5.7` release. udpsock:settimeout ------------------ + **syntax:** *udpsock:settimeout(time)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Set the timeout value in milliseconds for subsequent socket operations (like [receive](#udpsockreceive)). @@ -6929,13 +7943,16 @@ This API function was first added to the `v0.10.1` release. ngx.socket.tcp -------------- + **syntax:** *tcpsock = ngx.socket.tcp()* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Creates and returns a TCP or stream-oriented unix domain socket object (also known as one type of the "cosocket" objects). The following methods are supported on this object: +* [bind](#tcpsockbind) * [connect](#tcpsockconnect) +* [setclientcert](#tcpsocksetclientcert) * [sslhandshake](#tcpsocksslhandshake) * [send](#tcpsocksend) * [receive](#tcpsockreceive) @@ -6943,13 +7960,14 @@ Creates and returns a TCP or stream-oriented unix domain socket object (also kno * [settimeout](#tcpsocksettimeout) * [settimeouts](#tcpsocksettimeouts) * [setoption](#tcpsocksetoption) +* [receiveany](#tcpsockreceiveany) * [receiveuntil](#tcpsockreceiveuntil) * [setkeepalive](#tcpsocksetkeepalive) * [getreusedtimes](#tcpsockgetreusedtimes) It is intended to be compatible with the TCP API of the [LuaSocket](http://w3.impa.br/~diego/software/luasocket/tcp.html) library but is 100% nonblocking out of the box. Also, we introduce some new APIs to provide more functionalities. -The cosocket object created by this API function has exactly the same lifetime as the Lua handler creating it. So never pass the cosocket object to any other Lua handler (including ngx.timer callback functions) and never share the cosocket object between different NGINX requests. +The cosocket object created by this API function has exactly the same lifetime as the Lua handler creating it. So never pass the cosocket object to any other Lua handler (including ngx.timer callback functions) and never share the cosocket object between different Nginx requests. For every cosocket object's underlying connection, if you do not explicitly close it (via [close](#tcpsockclose)) or put it back to the connection @@ -6972,13 +7990,51 @@ See also [ngx.socket.udp](#ngxsocketudp). [Back to TOC](#nginx-api-for-lua) +tcpsock:bind +------------ +**syntax:** *ok, err = tcpsock:bind(address, port?)* + +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*,ssl_session_fetch_by_lua*,ssl_client_hello_by_lua** + +Just like the standard [proxy_bind](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_bind) directive, this api makes the outgoing connection to a upstream server originate from the specified local IP address. + +IP addresses can be specified as the `address` argument. +The optional `port` argument is usually used in the transparent proxy. + +Here is an example for connecting to a TCP server from the specified local IP address: + +```nginx + + location /test { + content_by_lua_block { + local sock = ngx.socket.tcp() + -- assume "192.168.1.10" is the local ip address + local ok, err = sock:bind("192.168.1.10") + if not ok then + ngx.say("failed to bind") + return + end + local ok, err = sock:connect("192.168.1.67", 80) + if not ok then + ngx.say("failed to connect server: ", err) + return + end + ngx.say("successfully connected!") + sock:close() + } + } +``` + +[Back to TOC](#nginx-api-for-lua) + tcpsock:connect --------------- + **syntax:** *ok, err = tcpsock:connect(host, port, options_table?)* **syntax:** *ok, err = tcpsock:connect("unix:/path/to/unix-domain.socket", options_table?)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Attempts to connect a TCP socket object to a remote server or to a stream unix domain socket file without blocking. @@ -7025,6 +8081,9 @@ Connecting to a Unix Domain Socket file is also possible: ngx.say("failed to connect to the memcached unix domain socket: ", err) return end + + -- do something after connect + -- such as sock:send or sock:receive ``` assuming memcached (or something else) is listening on the unix domain socket file `/tmp/memcached.sock`. @@ -7047,17 +8106,95 @@ An optional Lua table can be specified as the last argument to this method to sp * `pool` specify a custom name for the connection pool being used. If omitted, then the connection pool name will be generated from the string template `":"` or `""`. +* `pool_size` + specify the size of the connection pool. If omitted and no + `backlog` option was provided, no pool will be created. If omitted + but `backlog` was provided, the pool will be created with a default + size equal to the value of the [lua_socket_pool_size](#lua_socket_pool_size) + directive. + The connection pool holds up to `pool_size` alive connections + ready to be reused by subsequent calls to [connect](#tcpsockconnect), but + note that there is no upper limit to the total number of opened connections + outside of the pool. If you need to restrict the total number of opened + connections, specify the `backlog` option. + When the connection pool would exceed its size limit, the least recently used + (kept-alive) connection already in the pool will be closed to make room for + the current connection. + Note that the cosocket connection pool is per Nginx worker process rather + than per Nginx server instance, so the size limit specified here also applies + to every single Nginx worker process. Also note that the size of the connection + pool cannot be changed once it has been created. + This option was first introduced in the `v0.10.14` release. + +* `backlog` + if specified, this module will limit the total number of opened connections + for this pool. No more connections than `pool_size` can be opened + for this pool at any time. If `pool_size` number of connections are in use, + subsequent connect operations will be queued into a queue equal to this + option's value (the "backlog" queue). + If the number of queued connect operations is equal to `backlog`, + subsequent connect operations will fail and return `nil` plus the + error string `"too many waiting connect operations"`. + The queued connect operations will be resumed once the number of active + connections becomes less than `pool_size`. + The queued connect operation will abort once they have been queued for more + than `connect_timeout`, controlled by + [settimeouts](#tcpsocksettimeouts), and will return `nil` plus + the error string `"timeout"`. + This option was first introduced in the `v0.10.14` release. + The support for the options table argument was first introduced in the `v0.5.7` release. This method was first introduced in the `v0.5.0rc1` release. [Back to TOC](#nginx-api-for-lua) + +tcpsock:getfd +-------------------- + +**syntax:** *fd, err = tcpsock:getfd()* + +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** + +Get the file descriptor of the current tcp socket. + +This method was first introduced in the `v0.10.29` release. + +[Back to TOC](#nginx-api-for-lua) + + +tcpsock:setclientcert +--------------------- + +**syntax:** *ok, err = tcpsock:setclientcert(cert, pkey)* + +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** + +Set client certificate chain and corresponding private key to the TCP socket object. +The certificate chain and private key provided will be used later by the [tcpsock:sslhandshake](#tcpsocksslhandshake) method. + +* `cert` specify a client certificate chain cdata object that will be used while handshaking with +remote server. These objects can be created using [ngx.ssl.parse\_pem\_cert](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md#parse_pem_cert) or [ngx.ssl.parse\_der\_cert](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md#parse_der_cert) +function provided by lua-resty-core. Note that specifying the `cert` option requires +corresponding `pkey` be provided too. See below. +* `pkey` specify a private key corresponds to the `cert` option above. +These objects can be created using [ngx.ssl.parse\_pem\_priv\_key](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md#parse_pem_priv_key) or [ngx.ssl.parse\_der\_priv\_key](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md#parse_der_priv_key) +function provided by lua-resty-core. + +If both of `cert` and `pkey` are `nil`, this method will clear any existing client certificate and private key +that was previously set on the cosocket object. + +This method was first introduced in the `v0.10.22` release. + +[Back to TOC](#nginx-api-for-lua) + tcpsock:sslhandshake -------------------- + **syntax:** *session, err = tcpsock:sslhandshake(reused_session?, server_name?, ssl_verify?, send_status_req?)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Does SSL/TLS handshake on the currently established connection. @@ -7100,9 +8237,10 @@ This method was first introduced in the `v0.9.11` release. tcpsock:send ------------ + **syntax:** *bytes, err = tcpsock:send(data)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Sends data without blocking on the current TCP or Unix Domain Socket connection. @@ -7130,11 +8268,12 @@ This feature was first introduced in the `v0.5.0rc1` release. tcpsock:receive --------------- + **syntax:** *data, err, partial = tcpsock:receive(size)* **syntax:** *data, err, partial = tcpsock:receive(pattern?)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Receives data from the connected socket according to the reading pattern or size. @@ -7172,11 +8311,47 @@ This feature was first introduced in the `v0.5.0rc1` release. [Back to TOC](#nginx-api-for-lua) +tcpsock:receiveany +------------------ + +**syntax:** *data, err = tcpsock:receiveany(max)* + +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** + +Returns any data received by the connected socket, at most `max` bytes. + +This method is a synchronous operation just like the [send](#tcpsocksend) method and is 100% nonblocking. + +In case of success, it returns the data received; in case of error, it returns `nil` with a string describing the error. + +If the received data is more than this size, this method will return with exactly this size of data. +The remaining data in the underlying receive buffer could be returned in the next reading operation. + +Timeout for the reading operation is controlled by the [lua_socket_read_timeout](#lua_socket_read_timeout) config directive and the [settimeouts](#tcpsocksettimeouts) method. And the latter takes priority. For example: + +```lua + + sock:settimeouts(1000, 1000, 1000) -- one second timeout for connect/read/write + local data, err = sock:receiveany(10 * 1024) -- read any data, at most 10K + if not data then + ngx.say("failed to read any data: ", err) + return + end + ngx.say("successfully read: ", data) +``` + +This method doesn't automatically close the current connection when the read timeout error occurs. For other connection errors, this method always automatically closes the connection. + +This feature was first introduced in the `v0.10.14` release. + +[Back to TOC](#nginx-api-for-lua) + tcpsock:receiveuntil -------------------- + **syntax:** *iterator = tcpsock:receiveuntil(pattern, options?)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** This method returns an iterator Lua function that can be called to read the data stream until it sees the specified pattern or an error occurs. @@ -7273,9 +8448,10 @@ This method was first introduced in the `v0.5.0rc1` release. tcpsock:close ------------- + **syntax:** *ok, err = tcpsock:close()* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Closes the current TCP or stream unix domain socket. It returns the `1` in case of success and returns `nil` with a string describing the error otherwise. @@ -7289,13 +8465,14 @@ This feature was first introduced in the `v0.5.0rc1` release. tcpsock:settimeout ------------------ + **syntax:** *tcpsock:settimeout(time)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Set the timeout value in milliseconds for subsequent socket operations ([connect](#tcpsockconnect), [receive](#tcpsockreceive), and iterators returned from [receiveuntil](#tcpsockreceiveuntil)). -Settings done by this method takes priority over those config directives, i.e., [lua_socket_connect_timeout](#lua_socket_connect_timeout), [lua_socket_send_timeout](#lua_socket_send_timeout), and [lua_socket_read_timeout](#lua_socket_read_timeout). +Settings done by this method take priority over those specified via config directives (i.e. [lua_socket_connect_timeout](#lua_socket_connect_timeout), [lua_socket_send_timeout](#lua_socket_send_timeout), and [lua_socket_read_timeout](#lua_socket_read_timeout)). Note that this method does *not* affect the [lua_socket_keepalive_timeout](#lua_socket_keepalive_timeout) setting; the `timeout` argument to the [setkeepalive](#tcpsocksetkeepalive) method should be used for this purpose instead. @@ -7305,16 +8482,17 @@ This feature was first introduced in the `v0.5.0rc1` release. tcpsock:settimeouts ------------------- + **syntax:** *tcpsock:settimeouts(connect_timeout, send_timeout, read_timeout)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** -Sets the connect timeout thresold, send timeout threshold, and read timeout threshold, respetively, in milliseconds, for subsequent socket +Respectively sets the connect, send, and read timeout thresholds (in milliseconds) for subsequent socket operations ([connect](#tcpsockconnect), [send](#tcpsocksend), [receive](#tcpsockreceive), and iterators returned from [receiveuntil](#tcpsockreceiveuntil)). -Settings done by this method takes priority over those config directives, i.e., [lua_socket_connect_timeout](#lua_socket_connect_timeout), [lua_socket_send_timeout](#lua_socket_send_timeout), and [lua_socket_read_timeout](#lua_socket_read_timeout). +Settings done by this method take priority over those specified via config directives (i.e. [lua_socket_connect_timeout](#lua_socket_connect_timeout), [lua_socket_send_timeout](#lua_socket_send_timeout), and [lua_socket_read_timeout](#lua_socket_read_timeout)). -You are recommended to use [settimeouts](#tcpsocksettimeouts) instead of [settimeout](#tcpsocksettimeout). +It is recommended to use [settimeouts](#tcpsocksettimeouts) instead of [settimeout](#tcpsocksettimeout). Note that this method does *not* affect the [lua_socket_keepalive_timeout](#lua_socket_keepalive_timeout) setting; the `timeout` argument to the [setkeepalive](#tcpsocksetkeepalive) method should be used for this purpose instead. @@ -7324,33 +8502,143 @@ This feature was first introduced in the `v0.10.7` release. tcpsock:setoption ----------------- -**syntax:** *tcpsock:setoption(option, value?)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**syntax:** *ok, err = tcpsock:setoption(option, value?)* + +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** -This function is added for [LuaSocket](http://w3.impa.br/~diego/software/luasocket/tcp.html) API compatibility and does nothing for now. Its functionality will be implemented in future. +This function is added for [LuaSocket](http://w3.impa.br/~diego/software/luasocket/tcp.html) API compatibility, its functionality is implemented `v0.10.18`. This feature was first introduced in the `v0.5.0rc1` release. +In case of success, it returns `true`. Otherwise, it returns nil and a string describing the error. + +The `option` is a string with the option name, and the value depends on the option being set: + +* `keepalive` + + Setting this option to true enables sending of keep-alive messages on + connection-oriented sockets. Make sure the `connect` function + had been called before, for example, + + ```lua + + local ok, err = tcpsock:setoption("keepalive", true) + if not ok then + ngx.say("setoption keepalive failed: ", err) + end + ``` +* `reuseaddr` + + Enabling this option indicates that the rules used in validating addresses + supplied in a call to bind should allow reuse of local addresses. Make sure + the `connect` function had been called before, for example, + + ```lua + + local ok, err = tcpsock:setoption("reuseaddr", 0) + if not ok then + ngx.say("setoption reuseaddr failed: ", err) + end + ``` +* `tcp-nodelay` + + Setting this option to true disables the Nagle's algorithm for the connection. + Make sure the `connect` function had been called before, for example, + + ```lua + + local ok, err = tcpsock:setoption("tcp-nodelay", true) + if not ok then + ngx.say("setoption tcp-nodelay failed: ", err) + end + ``` +* `sndbuf` + + Sets the maximum socket send buffer in bytes. The kernel doubles this value + (to allow space for bookkeeping overhead) when it is set using setsockopt(). + Make sure the `connect` function had been called before, for example, + + ```lua + + local ok, err = tcpsock:setoption("sndbuf", 1024 * 10) + if not ok then + ngx.say("setoption sndbuf failed: ", err) + end + ``` +* `rcvbuf` + + Sets the maximum socket receive buffer in bytes. The kernel doubles this value + (to allow space for bookkeeping overhead) when it is set using setsockopt. Make + sure the `connect` function had been called before, for example, + + ```lua + + local ok, err = tcpsock:setoption("rcvbuf", 1024 * 10) + if not ok then + ngx.say("setoption rcvbuf failed: ", err) + end + ``` + +NOTE: Once the option is set, it will become effective until the connection is closed. If you know the connection is from the connection pool and all the in-pool connections already have called the setoption() method with the desired socket option state, then you can just skip calling setoption() again to avoid the overhead of repeated calls, for example, + +```lua + + local count, err = tcpsock:getreusedtimes() + if not count then + ngx.say("getreusedtimes failed: ", err) + return + end + + if count == 0 then + local ok, err = tcpsock:setoption("rcvbuf", 1024 * 10) + if not ok then + ngx.say("setoption rcvbuf failed: ", err) + return + end + end +``` + +These options described above are supported in `v0.10.18`, and more options will be implemented in future. + [Back to TOC](#nginx-api-for-lua) tcpsock:setkeepalive -------------------- + **syntax:** *ok, err = tcpsock:setkeepalive(timeout?, size?)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Puts the current socket's connection immediately into the cosocket built-in connection pool and keep it alive until other [connect](#tcpsockconnect) method calls request it or the associated maximal idle timeout is expired. The first optional argument, `timeout`, can be used to specify the maximal idle timeout (in milliseconds) for the current connection. If omitted, the default setting in the [lua_socket_keepalive_timeout](#lua_socket_keepalive_timeout) config directive will be used. If the `0` value is given, then the timeout interval is unlimited. -The second optional argument, `size`, can be used to specify the maximal number of connections allowed in the connection pool for the current server (i.e., the current host-port pair or the unix domain socket file path). Note that the size of the connection pool cannot be changed once the pool is created. When this argument is omitted, the default setting in the [lua_socket_pool_size](#lua_socket_pool_size) config directive will be used. - -When the connection pool exceeds the available size limit, the least recently used (idle) connection already in the pool will be closed to make room for the current connection. - -Note that the cosocket connection pool is per Nginx worker process rather than per Nginx server instance, so the size limit specified here also applies to every single Nginx worker process. - -Idle connections in the pool will be monitored for any exceptional events like connection abortion or unexpected incoming data on the line, in which cases the connection in question will be closed and removed from the pool. +The second optional argument `size` is considered deprecated since +the `v0.10.14` release of this module, in favor of the +`pool_size` option of the [connect](#tcpsockconnect) method. +Since the `v0.10.14` release, this option will only take effect if +the call to [connect](#tcpsockconnect) did not already create a connection +pool. +When this option takes effect (no connection pool was previously created by +[connect](#tcpsockconnect)), it will specify the size of the connection pool, +and create it. +If omitted (and no pool was previously created), the default size is the value +of the [lua_socket_pool_size](#lua_socket_pool_size) directive. +The connection pool holds up to `size` alive connections ready to be +reused by subsequent calls to [connect](#tcpsockconnect), but note that there +is no upper limit to the total number of opened connections outside of the +pool. +When the connection pool would exceed its size limit, the least recently used +(kept-alive) connection already in the pool will be closed to make room for +the current connection. +Note that the cosocket connection pool is per Nginx worker process rather +than per Nginx server instance, so the size limit specified here also applies +to every single Nginx worker process. Also note that the size of the connection +pool cannot be changed once it has been created. +If you need to restrict the total number of opened connections, specify both +the `pool_size` and `backlog` option in the call to +[connect](#tcpsockconnect). In case of success, this method returns `1`; otherwise, it returns `nil` and a string describing the error. @@ -7364,9 +8652,10 @@ This feature was first introduced in the `v0.5.0rc1` release. tcpsock:getreusedtimes ---------------------- + **syntax:** *count, err = tcpsock:getreusedtimes()* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** This method returns the (successfully) reused times for the current connection. In case of error, it returns `nil` and a string describing the error. @@ -7378,6 +8667,7 @@ This feature was first introduced in the `v0.5.0rc1` release. ngx.socket.connect ------------------ + **syntax:** *tcpsock, err = ngx.socket.connect(host, port)* **syntax:** *tcpsock, err = ngx.socket.connect("unix:/path/to/unix-domain.socket")* @@ -7404,9 +8694,10 @@ This feature was first introduced in the `v0.5.0rc1` release. ngx.get_phase ------------- + **syntax:** *str = ngx.get_phase()* -**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** Retrieves the current running phase name. Possible return values are @@ -7420,6 +8711,8 @@ Retrieves the current running phase name. Possible return values are for the context of [ssl_session_fetch_by_lua*](#ssl_session_fetch_by_lua_block). * `ssl_session_store` for the context of [ssl_session_store_by_lua*](#ssl_session_store_by_lua_block). +* `ssl_client_hello` + for the context of [ssl_client_hello_by_lua*](#ssl_client_hello_by_lua_block). * `set` for the context of [set_by_lua*](#set_by_lua). * `rewrite` @@ -7438,6 +8731,8 @@ Retrieves the current running phase name. Possible return values are for the context of [log_by_lua*](#log_by_lua). * `timer` for the context of user callback functions for [ngx.timer.*](#ngxtimerat). +* `exit_worker` + for the context of [exit_worker_by_lua*](#exit_worker_by_lua). This API was first introduced in the `v0.5.10` release. @@ -7445,9 +8740,10 @@ This API was first introduced in the `v0.5.10` release. ngx.thread.spawn ---------------- + **syntax:** *co = ngx.thread.spawn(func, arg1, arg2, ...)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Spawns a new user "light thread" with the Lua function `func` as well as those optional arguments `arg1`, `arg2`, and etc. Returns a Lua thread (or Lua coroutine) object represents this "light thread". @@ -7462,14 +8758,14 @@ All the Lua code chunks running by [rewrite_by_lua](#rewrite_by_lua), [access_by By default, the corresponding Nginx handler (e.g., [rewrite_by_lua](#rewrite_by_lua) handler) will not terminate until 1. both the "entry thread" and all the user "light threads" terminates, -1. a "light thread" (either the "entry thread" or a user "light thread" aborts by calling [ngx.exit](#ngxexit), [ngx.exec](#ngxexec), [ngx.redirect](#ngxredirect), or [ngx.req.set_uri(uri, true)](#ngxreqset_uri), or +1. a "light thread" (either the "entry thread" or a user "light thread") aborts by calling [ngx.exit](#ngxexit), [ngx.exec](#ngxexec), [ngx.redirect](#ngxredirect), or [ngx.req.set_uri(uri, true)](#ngxreqset_uri), or 1. the "entry thread" terminates with a Lua error. When the user "light thread" terminates with a Lua error, however, it will not abort other running "light threads" like the "entry thread" does. -Due to the limitation in the Nginx subrequest model, it is not allowed to abort a running Nginx subrequest in general. So it is also prohibited to abort a running "light thread" that is pending on one ore more Nginx subrequests. You must call [ngx.thread.wait](#ngxthreadwait) to wait for those "light thread" to terminate before quitting the "world". A notable exception here is that you can abort pending subrequests by calling [ngx.exit](#ngxexit) with and only with the status code `ngx.ERROR` (-1), `408`, `444`, or `499`. +Due to the limitation in the Nginx subrequest model, it is not allowed to abort a running Nginx subrequest in general. So it is also prohibited to abort a running "light thread" that is pending on one or more Nginx subrequests. You must call [ngx.thread.wait](#ngxthreadwait) to wait for those "light thread" to terminate before quitting the "world". A notable exception here is that you can abort pending subrequests by calling [ngx.exit](#ngxexit) with and only with the status code `ngx.ERROR` (-1), `408`, `444`, or `499`. -The "light threads" are not scheduled in a pre-emptive way. In other words, no time-slicing is performed automatically. A "light thread" will keep running exclusively on the CPU until +The "light threads" are not scheduled in a preemptive way. In other words, no time-slicing is performed automatically. A "light thread" will keep running exclusively on the CPU until 1. a (nonblocking) I/O operation cannot be completed in a single run, 1. it calls [coroutine.yield](#coroutineyield) to actively give up execution, or @@ -7583,9 +8879,10 @@ This API was first enabled in the `v0.7.0` release. ngx.thread.wait --------------- + **syntax:** *ok, res1, res2, ... = ngx.thread.wait(thread1, thread2, ...)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Waits on one or more child "light threads" and returns the results of the first "light thread" that terminates (either successfully or with an error). @@ -7686,13 +8983,14 @@ This API was first enabled in the `v0.7.0` release. ngx.thread.kill --------------- + **syntax:** *ok, err = ngx.thread.kill(thread)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua** Kills a running "light thread" created by [ngx.thread.spawn](#ngxthreadspawn). Returns a true value when successful or `nil` and a string describing the error otherwise. -According to the current implementation, only the parent coroutine (or "light thread") can kill a thread. Also, a running "light thread" with pending NGINX subrequests (initiated by [ngx.location.capture](#ngxlocationcapture) for example) cannot be killed due to a limitation in the NGINX core. +According to the current implementation, only the parent coroutine (or "light thread") can kill a thread. Also, a running "light thread" with pending Nginx subrequests (initiated by [ngx.location.capture](#ngxlocationcapture) for example) cannot be killed due to a limitation in the Nginx core. This API was first enabled in the `v0.9.9` release. @@ -7700,6 +8998,7 @@ This API was first enabled in the `v0.9.9` release. ngx.on_abort ------------ + **syntax:** *ok, err = ngx.on_abort(callback)* **context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** @@ -7740,9 +9039,10 @@ See also [lua_check_client_abort](#lua_check_client_abort). ngx.timer.at ------------ + **syntax:** *hdl, err = ngx.timer.at(delay, callback, user_arg1, user_arg2, ...)* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Creates an Nginx timer with a user callback function as well as optional user arguments. @@ -7758,7 +9058,7 @@ be any Lua function, which will be invoked later in a background called automatically by the Nginx core with the arguments `premature`, `user_arg1`, `user_arg2`, and etc, where the `premature` argument takes a boolean value indicating whether it is a premature timer -expiration or not, and `user_arg1`, `user_arg2`, and etc, are +expiration or not(for the `0` delay timer it is always `false`), and `user_arg1`, `user_arg2`, and etc, are those (extra) user arguments specified when calling `ngx.timer.at` as the remaining arguments. @@ -7796,6 +9096,8 @@ Here is a simple example: ngx.log(ngx.ERR, "failed to create timer: ", err) return end + + -- other job in log_by_lua_block } } ``` @@ -7816,6 +9118,8 @@ One can also create infinite re-occurring timers, for instance, a timer getting ngx.log(ngx.ERR, "failed to create the timer: ", err) return end + + -- do something in timer end local ok, err = ngx.timer.at(delay, handler) @@ -7823,6 +9127,8 @@ One can also create infinite re-occurring timers, for instance, a timer getting ngx.log(ngx.ERR, "failed to create the timer: ", err) return end + + -- do other jobs ``` It is recommended, however, to use the [ngx.timer.every](#ngxtimerevery) API function @@ -7861,7 +9167,14 @@ user "light threads" ([ngx.thread.*](#ngxthreadspawn)), [ngx.exit](#ngxexit), [n (like [ngx.say](#ngxsay), [ngx.print](#ngxprint), and [ngx.flush](#ngxflush)) are explicitly disabled in this context. -You can pass most of the standard Lua values (nils, booleans, numbers, strings, tables, closures, file handles, and etc) into the timer callback, either explicitly as user arguments or implicitly as upvalues for the callback closure. There are several exceptions, however: you *cannot* pass any thread objects returned by [coroutine.create](#coroutinecreate) and [ngx.thread.spawn](#ngxthreadspawn) or any cosocket objects returned by [ngx.socket.tcp](#ngxsockettcp), [ngx.socket.udp](#ngxsocketudp), and [ngx.req.socket](#ngxreqsocket) because these objects' lifetime is bound to the request context creating them while the timer callback is detached from the creating request's context (by design) and runs in its own (fake) request context. If you try to share the thread or cosocket objects across the boundary of the creating request, then you will get the "no co ctx found" error (for threads) or "bad request" (for cosockets). It is fine, however, to create all these objects inside your timer callback. +You must notice that each timer will be based on a fake request (this fake request is also based on a fake connection). Because Nginx's memory release is based on the connection closure, if you run a lot of APIs that apply for memory resources in a timer, such as [tcpsock:connect](#tcpsockconnect), will cause the accumulation of memory resources. So it is recommended to create a new timer after running several times to release memory resources. + +You can pass most of the standard Lua values (nils, booleans, numbers, strings, tables, closures, file handles, etc.) into the timer callback, either explicitly as user arguments or implicitly as upvalues for the callback closure. There are several exceptions, however: you *cannot* pass any thread objects returned by [coroutine.create](#coroutinecreate) and [ngx.thread.spawn](#ngxthreadspawn) or any cosocket objects returned by [ngx.socket.tcp](#ngxsockettcp), [ngx.socket.udp](#ngxsocketudp), and [ngx.req.socket](#ngxreqsocket) because these objects' lifetime is bound to the request context creating them while the timer callback is detached from the creating request's context (by design) and runs in its own (fake) request context. If you try to share the thread or cosocket objects across the boundary of the creating request, then you will get the "no co ctx found" error (for threads) or "bad request" (for cosockets). It is fine, however, to create all these objects inside your timer callback. + +Please note that the timer Lua handler has its own copy of the `ngx.ctx` magic +table. It won't share the same `ngx.ctx` with the Lua handler creating the timer. +If you need to pass data from the timer creator to the timer handler, please +use the extra parameters of `ngx.timer.at()`. This API was first introduced in the `v0.8.0` release. @@ -7869,15 +9182,19 @@ This API was first introduced in the `v0.8.0` release. ngx.timer.every --------------- + **syntax:** *hdl, err = ngx.timer.every(delay, callback, user_arg1, user_arg2, ...)* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Similar to the [ngx.timer.at](#ngxtimerat) API function, but 1. `delay` *cannot* be zero, 1. timer will be created every `delay` seconds until the current Nginx worker process starts exiting. +Like [ngx.timer.at](#ngxtimerat), the `callback` argument will be called +automatically with the arguments `premature`, `user_arg1`, `user_arg2`, etc. + When success, returns a "conditional true" value (but not a `true`). Otherwise, returns a "conditional false" value and a string describing the error. This API also respect the [lua_max_pending_timers](#lua_max_pending_timers) and [lua_max_running_timers](#lua_max_running_timers). @@ -7888,9 +9205,10 @@ This API was first introduced in the `v0.10.9` release. ngx.timer.running_count ----------------------- + **syntax:** *count = ngx.timer.running_count()* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** Returns the number of timers currently running. @@ -7900,9 +9218,10 @@ This directive was first introduced in the `v0.9.20` release. ngx.timer.pending_count ----------------------- + **syntax:** *count = ngx.timer.pending_count()* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** Returns the number of pending timers. @@ -7912,11 +9231,12 @@ This directive was first introduced in the `v0.9.20` release. ngx.config.subsystem -------------------- + **syntax:** *subsystem = ngx.config.subsystem* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua** -This string field indicates the current NGINX subsystem the current Lua environment is based on. For this module, this field always takes the string value `"http"`. For +This string field indicates the Nginx subsystem the current Lua environment is based on. For this module, this field always takes the string value `"http"`. For [ngx_stream_lua_module](https://github.com/openresty/stream-lua-nginx-module#readme), however, this field takes the value `"stream"`. This field was first introduced in the `0.10.1`. @@ -7925,9 +9245,10 @@ This field was first introduced in the `0.10.1`. ngx.config.debug ---------------- + **syntax:** *debug = ngx.config.debug* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua** This boolean field indicates whether the current Nginx is a debug build, i.e., being built by the `./configure` option `--with-debug`. @@ -7940,9 +9261,9 @@ ngx.config.prefix **syntax:** *prefix = ngx.config.prefix()* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua** -Returns the Nginx server "prefix" path, as determined by the `-p` command-line option when running the nginx executable, or the path specified by the `--prefix` command-line option when building Nginx with the `./configure` script. +Returns the Nginx server "prefix" path, as determined by the `-p` command-line option when running the Nginx executable, or the path specified by the `--prefix` command-line option when building Nginx with the `./configure` script. This function was first introduced in the `0.9.2`. @@ -7953,7 +9274,7 @@ ngx.config.nginx_version **syntax:** *ver = ngx.config.nginx_version* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua** This field take an integral value indicating the version number of the current Nginx core being used. For example, the version number `1.4.3` results in the Lua number 1004003. @@ -7968,7 +9289,7 @@ ngx.config.nginx_configure **context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua** -This function returns a string for the NGINX `./configure` command's arguments string. +This function returns a string for the Nginx `./configure` command's arguments string. This API was first introduced in the `0.9.5` release. @@ -7992,7 +9313,7 @@ ngx.worker.exiting **syntax:** *exiting = ngx.worker.exiting()* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua** This function returns a boolean value indicating whether the current Nginx worker process already starts exiting. Nginx worker process exiting happens on Nginx server quit or configuration reload (aka HUP reload). @@ -8005,7 +9326,7 @@ ngx.worker.pid **syntax:** *pid = ngx.worker.pid()* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua** This function returns a Lua number for the process ID (PID) of the current Nginx worker process. This API is more efficient than `ngx.var.pid` and can be used in contexts where the [ngx.var.VARIABLE](#ngxvarvariable) API cannot be used (like [init_worker_by_lua](#init_worker_by_lua)). @@ -8013,15 +9334,28 @@ This API was first introduced in the `0.9.5` release. [Back to TOC](#nginx-api-for-lua) +ngx.worker.pids +-------------- + +**syntax:** *pids = ngx.worker.pids()* + +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, exit_worker_by_lua** + +This function returns a Lua table for all Nginx worker process IDs (PIDs). Nginx uses channel to send the current worker PID to another worker in the worker process start or restart. So this API can get all current worker PIDs. Windows does not have this API. + +This API was first introduced in the `0.10.23` release. + +[Back to TOC](#nginx-api-for-lua) + ngx.worker.count ---------------- **syntax:** *count = ngx.worker.count()* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua** Returns the total number of the Nginx worker processes (i.e., the value configured -by the [worker_processes](http://nginx.org/en/docs/ngx_core_module.html#worker_processes) +by the [worker_processes](https://nginx.org/en/docs/ngx_core_module.html#worker_processes) directive in `nginx.conf`). This API was first introduced in the `0.9.20` release. @@ -8031,16 +9365,16 @@ This API was first introduced in the `0.9.20` release. ngx.worker.id ------------- -**syntax:** *count = ngx.worker.id()* +**syntax:** *id = ngx.worker.id()* -**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_worker_by_lua** +**context:** *set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_worker_by_lua*, exit_worker_by_lua** Returns the ordinal number of the current Nginx worker processes (starting from number 0). So if the total number of workers is `N`, then this method may return a number between 0 and `N - 1` (inclusive). -This function returns meaningful values only for NGINX 1.9.1+. With earlier versions of NGINX, it +This function returns meaningful values only for Nginx 1.9.1+. With earlier versions of Nginx, it always returns `nil`. See also [ngx.worker.count](#ngxworkercount). @@ -8051,11 +9385,12 @@ This API was first introduced in the `0.9.20` release. ngx.semaphore ------------- + **syntax:** *local semaphore = require "ngx.semaphore"* This is a Lua module that implements a classic-style semaphore API for efficient synchronizations among different "light threads". Sharing the same semaphore among different "light threads" created in different (request) -contexts are also supported as long as the "light threads" reside in the same NGINX worker process +contexts are also supported as long as the "light threads" reside in the same Nginx worker process and the [lua_code_cache](#lua_code_cache) directive is turned on (which is the default). This Lua module does not ship with this ngx_lua module itself rather it is shipped with @@ -8072,6 +9407,7 @@ This feature requires at least ngx_lua `v0.10.0`. ngx.balancer ------------ + **syntax:** *local balancer = require "ngx.balancer"* This is a Lua module that provides a Lua API to allow defining completely dynamic load balancers @@ -8091,6 +9427,7 @@ This feature requires at least ngx_lua `v0.10.0`. ngx.ssl ------- + **syntax:** *local ssl = require "ngx.ssl"* This Lua module provides API functions to control the SSL handshake process in contexts like @@ -8109,6 +9446,7 @@ This feature requires at least ngx_lua `v0.10.0`. ngx.ocsp -------- + **syntax:** *local ocsp = require "ngx.ocsp"* This Lua module provides API to perform OCSP queries, OCSP response validations, and @@ -8122,7 +9460,7 @@ This Lua module does not ship with this ngx_lua module itself rather it is shipp the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. -Please refer to the [documentation](https://github.com/openresty/lua-resty-core/blob/ocsp-cert-by-lua-2/lib/ngx/ocsp.md) +Please refer to the [documentation](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ocsp.md) for this `ngx.ocsp` Lua module for more details. This feature requires at least ngx_lua `v0.10.0`. @@ -8131,11 +9469,12 @@ This feature requires at least ngx_lua `v0.10.0`. ndk.set_var.DIRECTIVE --------------------- + **syntax:** *res = ndk.set_var.DIRECTIVE_NAME* -**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua** -This mechanism allows calling other nginx C modules' directives that are implemented by [Nginx Devel Kit](https://github.com/simpl/ngx_devel_kit) (NDK)'s set_var submodule's `ndk_set_var_value`. +This mechanism allows calling other Nginx C modules' directives that are implemented by [Nginx Devel Kit](https://github.com/simplresty/ngx_devel_kit) (NDK)'s set_var submodule's `ndk_set_var_value`. For example, the following [set-misc-nginx-module](http://github.com/openresty/set-misc-nginx-module) directives can be invoked this way: @@ -8157,7 +9496,7 @@ For instance, ```lua - local res = ndk.set_var.set_escape_uri('a/b'); + local res = ndk.set_var.set_escape_uri('a/b') -- now res == 'a%2fb' ``` @@ -8166,19 +9505,20 @@ Similarly, the following directives provided by [encrypted-session-nginx-module] * [set_encrypt_session](http://github.com/openresty/encrypted-session-nginx-module#set_encrypt_session) * [set_decrypt_session](http://github.com/openresty/encrypted-session-nginx-module#set_decrypt_session) -This feature requires the [ngx_devel_kit](https://github.com/simpl/ngx_devel_kit) module. +This feature requires the [ngx_devel_kit](https://github.com/simplresty/ngx_devel_kit) module. [Back to TOC](#nginx-api-for-lua) coroutine.create ---------------- + **syntax:** *co = coroutine.create(f)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Creates a user Lua coroutines with a Lua function, and returns a coroutine object. -Similar to the standard Lua [coroutine.create](http://www.lua.org/manual/5.1/manual.html#pdf-coroutine.create) API, but works in the context of the Lua coroutines created by ngx_lua. +Similar to the standard Lua [coroutine.create](https://www.lua.org/manual/5.1/manual.html#pdf-coroutine.create) API, but works in the context of the Lua coroutines created by ngx_lua. This API was first usable in the context of [init_by_lua*](#init_by_lua) since the `0.9.2`. @@ -8188,13 +9528,14 @@ This API was first introduced in the `v0.6.0` release. coroutine.resume ---------------- + **syntax:** *ok, ... = coroutine.resume(co, ...)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** -Resumes the executation of a user Lua coroutine object previously yielded or just created. +Resumes the execution of a user Lua coroutine object previously yielded or just created. -Similar to the standard Lua [coroutine.resume](http://www.lua.org/manual/5.1/manual.html#pdf-coroutine.resume) API, but works in the context of the Lua coroutines created by ngx_lua. +Similar to the standard Lua [coroutine.resume](https://www.lua.org/manual/5.1/manual.html#pdf-coroutine.resume) API, but works in the context of the Lua coroutines created by ngx_lua. This API was first usable in the context of [init_by_lua*](#init_by_lua) since the `0.9.2`. @@ -8204,13 +9545,14 @@ This API was first introduced in the `v0.6.0` release. coroutine.yield --------------- + **syntax:** *... = coroutine.yield(...)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** Yields the execution of the current user Lua coroutine. -Similar to the standard Lua [coroutine.yield](http://www.lua.org/manual/5.1/manual.html#pdf-coroutine.yield) API, but works in the context of the Lua coroutines created by ngx_lua. +Similar to the standard Lua [coroutine.yield](https://www.lua.org/manual/5.1/manual.html#pdf-coroutine.yield) API, but works in the context of the Lua coroutines created by ngx_lua. This API was first usable in the context of [init_by_lua*](#init_by_lua) since the `0.9.2`. @@ -8220,11 +9562,12 @@ This API was first introduced in the `v0.6.0` release. coroutine.wrap -------------- + **syntax:** *co = coroutine.wrap(f)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** -Similar to the standard Lua [coroutine.wrap](http://www.lua.org/manual/5.1/manual.html#pdf-coroutine.wrap) API, but works in the context of the Lua coroutines created by ngx_lua. +Similar to the standard Lua [coroutine.wrap](https://www.lua.org/manual/5.1/manual.html#pdf-coroutine.wrap) API, but works in the context of the Lua coroutines created by ngx_lua. This API was first usable in the context of [init_by_lua*](#init_by_lua) since the `0.9.2`. @@ -8234,11 +9577,12 @@ This API was first introduced in the `v0.6.0` release. coroutine.running ----------------- + **syntax:** *co = coroutine.running()* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** -Identical to the standard Lua [coroutine.running](http://www.lua.org/manual/5.1/manual.html#pdf-coroutine.running) API. +Identical to the standard Lua [coroutine.running](https://www.lua.org/manual/5.1/manual.html#pdf-coroutine.running) API. This API was first usable in the context of [init_by_lua*](#init_by_lua) since the `0.9.2`. @@ -8248,11 +9592,12 @@ This API was first enabled in the `v0.6.0` release. coroutine.status ---------------- + **syntax:** *status = coroutine.status(co)* -**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua** +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua** -Identical to the standard Lua [coroutine.status](http://www.lua.org/manual/5.1/manual.html#pdf-coroutine.status) API. +Identical to the standard Lua [coroutine.status](https://www.lua.org/manual/5.1/manual.html#pdf-coroutine.status) API. This API was first usable in the context of [init_by_lua*](#init_by_lua) since the `0.9.2`. @@ -8260,6 +9605,135 @@ This API was first enabled in the `v0.6.0` release. [Back to TOC](#nginx-api-for-lua) +ngx.run_worker_thread +--------------------- + +**syntax:** *ok, res1, res2, ... = ngx.run_worker_thread(threadpool, module_name, func_name, arg1, arg2, ...)* + +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua** + +**This API is still experimental and may change in the future without notice.** + +**This API is available only for Linux.** + +Wrap the [nginx worker thread](http://nginx.org/en/docs/dev/development_guide.html#threads) to execute lua function. The caller coroutine would yield until the function returns. + +Only the following ngx_lua APIs could be used in `function_name` function of the `module` module: + +* `ngx.encode_base64` +* `ngx.decode_base64` + +* `ngx.hmac_sha1` +* `ngx.encode_args` +* `ngx.decode_args` +* `ngx.quote_sql_str` + +* `ngx.crc32_short` +* `ngx.crc32_long` +* `ngx.hmac_sha1` +* `ngx.md5_bin` +* `ngx.md5` + +* `ngx.config.subsystem` +* `ngx.config.debug` +* `ngx.config.prefix` +* `ngx.config.nginx_version` +* `ngx.config.nginx_configure` +* `ngx.config.ngx_lua_version` + +* `ngx.shared.DICT` + +The first argument `threadpool` specifies the Nginx thread pool name defined by [thread_pool](https://nginx.org/en/docs/ngx_core_module.html#thread_pool). + +The second argument `module_name` specifies the lua module name to execute in the worker thread, which would return a lua table. The module must be inside the package path, e.g. + +```nginx + + lua_package_path '/opt/openresty/?.lua;;'; +``` + +The third argument `func_name` specifies the function field in the module table as the second argument. + +The type of `args` must be one of type below: + +* boolean +* number +* string +* nil +* table (the table may be recursive, and contains members of types above.) + +The `ok` is in boolean type, which indicate the C land error (failed to get thread from thread pool, pcall the module function failed, etc.). If `ok` is `false`, the `res1` is the error string. + +The return values (res1, ...) are returned by invocation of the module function. Normally, the `res1` should be in boolean type, so that the caller could inspect the error. + +This API is useful when you need to execute the below types of tasks: + +* CPU bound task, e.g. do md5 calculation +* File I/O task +* Call `os.execute()` or blocking C API via `ffi` +* Call external Lua library not based on cosocket or nginx + +Example1: do md5 calculation. + +```nginx + + location /calc_md5 { + default_type 'text/plain'; + + content_by_lua_block { + local ok, md5_or_err = ngx.run_worker_thread("testpool", "md5", "md5") + ngx.say(ok, " : ", md5_or_err) + } + } +``` + +`md5.lua` + +```lua +local function md5() + return ngx.md5("hello") +end + +return { md5=md5, } +``` + +Example2: write logs into the log file. + +```nginx + + location /write_log_file { + default_type 'text/plain'; + + content_by_lua_block { + local ok, err = ngx.run_worker_thread("testpool", "write_log_file", "log", ngx.var.arg_str) + if not ok then + ngx.say(ok, " : ", err) + return + end + ngx.say(ok) + } + } +``` + +`write_log_file.lua` + +```lua + + local function log(str) + local file, err = io.open("/tmp/tmp.log", "a") + if not file then + return false, err + end + file:write(str) + file:flush() + file:close() + return true + end + return {log=log} +``` + +[Back to TOC](#nginx-api-for-lua) + Obsolete Sections ================= @@ -8272,3 +9746,12 @@ Special PCRE Sequences This section has been renamed to [Special Escaping Sequences](#special-escaping-sequences). +[Back to TOC](#table-of-contents) + +Lua/LuaJIT bytecode support +--------------------------- + +This section has been renamed to +[LuaJIT bytecode support](#luajit-bytecode-support). As of version +`v0.10.16` of this module, the standard Lua interpreter (also known +as "PUC-Rio Lua") is not supported anymore. diff --git a/config b/config index 044deb974e..7b1b061362 100644 --- a/config +++ b/config @@ -1,15 +1,11 @@ -ngx_feature="Lua library" -ngx_feature_libs="-llua -lm" -ngx_feature_name= -ngx_feature_run=no -ngx_feature_incs="#include " -ngx_feature_path= -ngx_feature_test="#if LUA_VERSION_NUM != 501 -# error unsupported Lua language version -#endif -(void) luaL_newstate();" ngx_lua_opt_I= ngx_lua_opt_L= +luajit_ld_opt= + +ngx_feature_name= +ngx_feature_run=no +ngx_feature_incs= +ngx_feature_test= if [ -n "$LUAJIT_INC" -o -n "$LUAJIT_LIB" ]; then # explicitly set LuaJIT paths @@ -20,7 +16,7 @@ if [ -n "$LUAJIT_INC" -o -n "$LUAJIT_LIB" ]; then ngx_lua_opt_I="-I$LUAJIT_INC" ngx_lua_opt_L="-L$LUAJIT_LIB" - # ensure that our -I$LUAJIT_INC and -L$LUAJIT_LIB is at the first. + # ensure that -I$LUAJIT_INC and -L$LUAJIT_LIB come first SAVED_CC_TEST_FLAGS="$CC_TEST_FLAGS" CC_TEST_FLAGS="$ngx_lua_opt_I $CC_TEST_FLAGS" SAVED_NGX_TEST_LD_OPT="$NGX_TEST_LD_OPT" @@ -35,23 +31,23 @@ if [ -n "$LUAJIT_INC" -o -n "$LUAJIT_LIB" ]; then CC_TEST_FLAGS="$SAVED_CC_TEST_FLAGS" NGX_TEST_LD_OPT="$SAVED_NGX_TEST_LD_OPT" else - # attempt to link with -ldl, static linking on Linux requires it. ngx_feature="LuaJIT library in $LUAJIT_LIB and $LUAJIT_INC (specified by the LUAJIT_LIB and LUAJIT_INC env, with -ldl)" ngx_feature_path="$LUAJIT_INC" ngx_lua_opt_I="-I$LUAJIT_INC" ngx_lua_opt_L="-L$LUAJIT_LIB" + luajit_ld_opt="-lm -ldl" - # ensure that our -I$LUAJIT_INC and -L$LUAJIT_LIB is at the first. + # ensure that -I$LUAJIT_INC and -L$LUAJIT_LIB come first SAVED_CC_TEST_FLAGS="$CC_TEST_FLAGS" CC_TEST_FLAGS="$ngx_lua_opt_I $CC_TEST_FLAGS" SAVED_NGX_TEST_LD_OPT="$NGX_TEST_LD_OPT" NGX_TEST_LD_OPT="$ngx_lua_opt_L $NGX_TEST_LD_OPT" if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R$LUAJIT_LIB $ngx_lua_opt_L -lluajit-5.1 -lm -ldl" + ngx_feature_libs="-R$LUAJIT_LIB $ngx_lua_opt_L -lluajit-5.1 $luajit_ld_opt" else - ngx_feature_libs="$ngx_lua_opt_L -lluajit-5.1 -lm -ldl" + ngx_feature_libs="$ngx_lua_opt_L -lluajit-5.1 $luajit_ld_opt" fi . auto/feature @@ -66,17 +62,18 @@ if [ -n "$LUAJIT_INC" -o -n "$LUAJIT_LIB" ]; then ngx_feature_path="$LUAJIT_INC" ngx_lua_opt_I="-I$LUAJIT_INC" ngx_lua_opt_L="-L$LUAJIT_LIB" + luajit_ld_opt="-lm" - # ensure that our -I$LUAJIT_INC and -L$LUAJIT_LIB is at the first. + # ensure that -I$LUAJIT_INC and -L$LUAJIT_LIB come first SAVED_CC_TEST_FLAGS="$CC_TEST_FLAGS" CC_TEST_FLAGS="$ngx_lua_opt_I $CC_TEST_FLAGS" SAVED_NGX_TEST_LD_OPT="$NGX_TEST_LD_OPT" NGX_TEST_LD_OPT="$ngx_lua_opt_L $NGX_TEST_LD_OPT" if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R$LUAJIT_LIB $ngx_lua_opt_L -lluajit-5.1 -lm" + ngx_feature_libs="-R$LUAJIT_LIB $ngx_lua_opt_L -lluajit-5.1 $luajit_ld_opt" else - ngx_feature_libs="$ngx_lua_opt_L -lluajit-5.1 -lm" + ngx_feature_libs="$ngx_lua_opt_L -lluajit-5.1 $luajit_ld_opt" fi . auto/feature @@ -89,7 +86,7 @@ if [ -n "$LUAJIT_INC" -o -n "$LUAJIT_LIB" ]; then if [ $ngx_found = no ]; then cat << END - $0: error: ngx_http_lua_module requires the Lua or LuaJIT library and LUAJIT_LIB is defined as $LUAJIT_LIB and LUAJIT_INC (path for lua.h) $LUAJIT_INC, but we cannot find LuaJIT there. + $0: error: ngx_http_lua_module requires the LuaJIT library, but it could not be found where specified (LUAJIT_LIB=$LUAJIT_LIB, LUAJIT_INC=$LUAJIT_INC). END exit 1 fi @@ -98,7 +95,8 @@ END Darwin:*) case "$NGX_MACHINE" in amd64 | x86_64 | i386) - echo "adding extra linking options needed by LuaJIT" + echo "adding extra linking options needed by LuaJIT on $NGX_MACHINE" + luajit_ld_opt="$luajit_ld_opt -pagezero_size 10000 -image_base 100000000" ngx_feature_libs="$ngx_feature_libs -pagezero_size 10000 -image_base 100000000" ;; @@ -111,176 +109,47 @@ END ;; esac else - if [ -n "$LUA_INC" -o -n "$LUA_LIB" ]; then - # explicitly set Lua paths - ngx_feature="Lua library in $LUA_LIB and $LUA_INC (specified by the LUA_LIB and LUA_INC env)" - ngx_feature_path="$LUA_INC" - ngx_lua_opt_I="-I$LUA_INC" - ngx_lua_opt_L="-L$LUA_LIB" - - # ensure that our -I$LUA_INC and -L$LUA_LIB is at the first. - SAVED_CC_TEST_FLAGS="$CC_TEST_FLAGS" - CC_TEST_FLAGS="$ngx_lua_opt_I $CC_TEST_FLAGS" - SAVED_NGX_TEST_LD_OPT="$NGX_TEST_LD_OPT" - NGX_TEST_LD_OPT="$ngx_lua_opt_L $NGX_TEST_LD_OPT" - + # auto-discovery + if [ $ngx_found = no ]; then + # FreeBSD with luajit-2.0 from ports collection + ngx_feature="LuaJIT library in /usr/local/" + ngx_feature_path="/usr/local/include/luajit-2.0" + luajit_ld_opt="-lm" + LUAJIT_LIB="/usr/local/lib" if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R$LUA_LIB $ngx_lua_opt_L -llua -lm -ldl" + ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lluajit-5.1 -lm" else - ngx_feature_libs="$ngx_lua_opt_L -llua -lm -ldl" + ngx_feature_libs="-L/usr/local/lib -lluajit-5.1 -lm" fi - . auto/feature + fi - # clean up - CC_TEST_FLAGS="$SAVED_CC_TEST_FLAGS" - NGX_TEST_LD_OPT="$SAVED_NGX_TEST_LD_OPT" - - if [ $ngx_found = no ]; then - # retry without -ldl - - ngx_feature_path="$LUA_INC" - ngx_lua_opt_I="-I$LUA_INC" - ngx_lua_opt_L="-L$LUA_LIB" - - # ensure that our -I$LUA_INC and -L$LUA_LIB is at the first. - SAVED_CC_TEST_FLAGS="$CC_TEST_FLAGS" - CC_TEST_FLAGS="$ngx_lua_opt_I $CC_TEST_FLAGS" - SAVED_NGX_TEST_LD_OPT="$NGX_TEST_LD_OPT" - NGX_TEST_LD_OPT="$ngx_lua_opt_L $NGX_TEST_LD_OPT" - - if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R$LUA_LIB $ngx_lua_opt_L -llua -lm" - else - ngx_feature_libs="$ngx_lua_opt_L -llua -lm" - fi - - . auto/feature - - # clean up - CC_TEST_FLAGS="$SAVED_CC_TEST_FLAGS" - NGX_TEST_LD_OPT="$SAVED_NGX_TEST_LD_OPT" - fi - - if [ $ngx_found = no ]; then - cat << END - $0: error: ngx_http_lua_module requires the Lua or LuaJIT library and LUA_LIB is defined as $LUA_LIB and LUA_INC (path for lua.h) is $LUA_INC, but we cannot find standard Lua there. -END - exit 1 + if [ $ngx_found = no ]; then + # Gentoo with LuaJIT-2.0, try with -ldl + ngx_feature="LuaJIT library in /usr/" + ngx_feature_path="/usr/include/luajit-2.0" + luajit_ld_opt="-lm -ldl" + LUAJIT_LIB="/usr/lib" + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/usr/lib -L/usr/lib -lm -lluajit-5.1 -ldl" + else + ngx_feature_libs="-L/usr/lib -lm -lluajit-5.1 -ldl" fi - else - # auto-discovery - ngx_feature="Lua library" - ngx_feature_libs="-llua -lm" . auto/feature + fi - if [ $ngx_found = no ]; then - # OpenBSD-5.2 - ngx_feature="Lua library in /usr/local/" - ngx_feature_path="/usr/local/include/lua-5.1" - if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -llua -lm" - else - ngx_feature_libs="-L/usr/local/lib -llua5.1 -lm" - fi - . auto/feature - fi - - if [ $ngx_found = no ]; then - # OpenBSD < 5.2 - ngx_feature="Lua library in /usr/local/" - ngx_feature_path="/usr/local/include" - if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -llua -lm" - else - ngx_feature_libs="-L/usr/local/lib -llua -lm" - fi - . auto/feature - fi - - if [ $ngx_found = no ]; then - # NetBSD - ngx_feature="Lua library in /usr/pkg/" - ngx_feature_path="/usr/pkg/include/" - if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lm -llua" - else - ngx_feature_libs="-L/usr/pkg/lib -lm -llua" - fi - . auto/feature - fi - - if [ $ngx_found = no ]; then - # MacPorts - ngx_feature="Lua library in /opt/local/" - ngx_feature_path="/opt/local/include" - if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lm -llua" - else - ngx_feature_libs="-L/opt/local/lib -lm -llua" - fi - . auto/feature - fi - - if [ $ngx_found = no ]; then - # FreeBSD - ngx_feature="Lua library in /usr/local/*/lua51/" - ngx_feature_path="/usr/local/include/lua51" - if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R/usr/local/lib/lua51 -L/usr/local/lib/lua51 -llua -lm" - else - ngx_feature_libs="-L/usr/local/lib/lua51 -llua -lm" - fi - . auto/feature - fi - - if [ $ngx_found = no ]; then - # Debian - ngx_feature="Lua library in /usr/" - ngx_feature_path="/usr/include/lua5.1" - if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R/usr/lib -L/usr/lib -lm -llua5.1" - else - ngx_feature_libs="-L/usr/lib -lm -llua5.1" - fi - . auto/feature - fi - - if [ $ngx_found = no ]; then - # FreeBSD with luajit-2.0 from ports collection - ngx_feature="LuaJIT library in /usr/local/" - ngx_feature_path="/usr/local/include/luajit-2.0" - if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lluajit-5.1 -lm" - else - ngx_feature_libs="-L/usr/local/lib -lluajit-5.1 -lm" - fi - . auto/feature - fi - - if [ $ngx_found = no ]; then - # Gentoo with LuaJIT-2.0, try with -ldl - ngx_feature="LuaJIT library in /usr/" - ngx_feature_path="/usr/include/luajit-2.0" - if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R/usr/lib -L/usr/lib -lm -lluajit-5.1 -ldl" - else - ngx_feature_libs="-L/usr/lib -lm -lluajit-5.1 -ldl" - fi - . auto/feature - fi - - if [ $ngx_found = no ]; then - # Gentoo with LuaJIT 2.0 - ngx_feature="LuaJIT library in /usr/" - ngx_feature_path="/usr/include/luajit-2.0" - if [ $NGX_RPATH = YES ]; then - ngx_feature_libs="-R/usr/lib -L/usr/lib -lm -lluajit-5.1" - else - ngx_feature_libs="-L/usr/lib -lm -lluajit-5.1" - fi - . auto/feature + if [ $ngx_found = no ]; then + # Gentoo with LuaJIT 2.0 + ngx_feature="LuaJIT library in /usr/" + ngx_feature_path="/usr/include/luajit-2.0" + luajit_ld_opt="-lm" + LUAJIT_LIB="/usr/lib" + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/usr/lib -L/usr/lib -lm -lluajit-5.1" + else + ngx_feature_libs="-L/usr/lib -lm -lluajit-5.1" fi + . auto/feature fi fi @@ -289,19 +158,82 @@ ngx_module_libs= if [ $ngx_found = yes ]; then # this is a hack to persuade nginx's build system to favor - # the paths set by our user environments: + # the paths set by our user environment CFLAGS="$ngx_lua_opt_I $CFLAGS" NGX_LD_OPT="$ngx_lua_opt_L $NGX_LD_OPT" ngx_module_incs="$ngx_module_incs $ngx_feature_path" ngx_module_libs="$ngx_module_libs $ngx_feature_libs" else - cat << END - $0: error: ngx_http_lua_module requires the Lua library. + cat << END + $0: error: ngx_http_lua_module requires the LuaJIT library. +END + exit 1 +fi + +# ---------------------------------------- + +ngx_feature="LuaJIT 2.x" +ngx_feature_run=no +ngx_feature_incs="#include " +ngx_feature_test="#if !defined(LUAJIT_VERSION_NUM) || LUAJIT_VERSION_NUM < 20000 + # error unsupported LuaJIT version + #endif + " + +. auto/feature + +if [ $ngx_found = no ]; then + cat << END + $0: error: unsupported LuaJIT version; ngx_http_lua_module requires LuaJIT 2.x. +END + exit 1 +fi + +# ---------------------------------------- + +ngx_feature="Lua language 5.1" +ngx_feature_run=no +ngx_feature_incs="#include " +ngx_feature_test="#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM != 501 + # error unsupported Lua language version + #endif + " + +. auto/feature + +if [ $ngx_found = no ]; then + cat << END + $0: error: unsupported Lua language version; ngx_http_lua_module requires Lua 5.1. END - exit 1 + exit 1 fi +# ---------------------------------------- + +ngx_feature="LuaJIT has FFI" +ngx_feature_libs="$ngx_module_libs" +ngx_feature_run=no +ngx_feature_incs="#include + #include + #include + " +ngx_feature_test="lua_State *L = luaL_newstate(); + assert(L != NULL); + luaopen_ffi(L); + " + +. auto/feature + +if [ $ngx_found = no ]; then + cat << END + $0: error: unsupported LuaJIT build; ngx_http_lua_module requires LuaJIT with FFI enabled. +END + exit 1 +fi + +# ---------------------------------------- + ngx_addon_name=ngx_http_lua_module HTTP_LUA_SRCS=" \ $ngx_addon_dir/src/ngx_http_lua_script.c \ @@ -329,6 +261,7 @@ HTTP_LUA_SRCS=" \ $ngx_addon_dir/src/ngx_http_lua_util.c \ $ngx_addon_dir/src/ngx_http_lua_cache.c \ $ngx_addon_dir/src/ngx_http_lua_contentby.c \ + $ngx_addon_dir/src/ngx_http_lua_server_rewriteby.c \ $ngx_addon_dir/src/ngx_http_lua_rewriteby.c \ $ngx_addon_dir/src/ngx_http_lua_accessby.c \ $ngx_addon_dir/src/ngx_http_lua_setby.c \ @@ -346,6 +279,7 @@ HTTP_LUA_SRCS=" \ $ngx_addon_dir/src/ngx_http_lua_bodyfilterby.c \ $ngx_addon_dir/src/ngx_http_lua_initby.c \ $ngx_addon_dir/src/ngx_http_lua_initworkerby.c \ + $ngx_addon_dir/src/ngx_http_lua_exitworkerby.c \ $ngx_addon_dir/src/ngx_http_lua_socket_udp.c \ $ngx_addon_dir/src/ngx_http_lua_req_method.c \ $ngx_addon_dir/src/ngx_http_lua_phase.c \ @@ -353,34 +287,38 @@ HTTP_LUA_SRCS=" \ $ngx_addon_dir/src/ngx_http_lua_timer.c \ $ngx_addon_dir/src/ngx_http_lua_config.c \ $ngx_addon_dir/src/ngx_http_lua_worker.c \ + $ngx_addon_dir/src/ngx_http_lua_ssl_client_helloby.c \ $ngx_addon_dir/src/ngx_http_lua_ssl_certby.c \ + $ngx_addon_dir/src/ngx_http_lua_ssl_export_keying_material.c \ $ngx_addon_dir/src/ngx_http_lua_ssl_ocsp.c \ $ngx_addon_dir/src/ngx_http_lua_lex.c \ $ngx_addon_dir/src/ngx_http_lua_balancer.c \ $ngx_addon_dir/src/ngx_http_lua_ssl_session_storeby.c \ $ngx_addon_dir/src/ngx_http_lua_ssl_session_fetchby.c \ $ngx_addon_dir/src/ngx_http_lua_ssl.c \ + $ngx_addon_dir/src/ngx_http_lua_proxy_ssl_verifyby.c \ $ngx_addon_dir/src/ngx_http_lua_log_ringbuf.c \ + $ngx_addon_dir/src/ngx_http_lua_input_filters.c \ + $ngx_addon_dir/src/ngx_http_lua_pipe.c \ + $ngx_addon_dir/src/ngx_http_lua_worker_thread.c \ " HTTP_LUA_DEPS=" \ $ngx_addon_dir/src/ddebug.h \ + $ngx_addon_dir/src/ngx_http_lua_autoconf.h \ $ngx_addon_dir/src/ngx_http_lua_script.h \ $ngx_addon_dir/src/ngx_http_lua_log.h \ $ngx_addon_dir/src/ngx_http_lua_subrequest.h \ $ngx_addon_dir/src/ngx_http_lua_ndk.h \ $ngx_addon_dir/src/ngx_http_lua_control.h \ - $ngx_addon_dir/src/ngx_http_lua_time.h \ $ngx_addon_dir/src/ngx_http_lua_string.h \ $ngx_addon_dir/src/ngx_http_lua_misc.h \ - $ngx_addon_dir/src/ngx_http_lua_variable.h \ $ngx_addon_dir/src/ngx_http_lua_output.h \ $ngx_addon_dir/src/ngx_http_lua_headers.h \ $ngx_addon_dir/src/ngx_http_lua_uri.h \ $ngx_addon_dir/src/ngx_http_lua_req_body.h \ $ngx_addon_dir/src/ngx_http_lua_args.h \ $ngx_addon_dir/src/ngx_http_lua_ctx.h \ - $ngx_addon_dir/src/ngx_http_lua_regex.h \ $ngx_addon_dir/src/ngx_http_lua_common.h \ $ngx_addon_dir/src/ngx_http_lua_directive.h \ $ngx_addon_dir/src/ngx_http_lua_headers_out.h \ @@ -390,6 +328,7 @@ HTTP_LUA_DEPS=" \ $ngx_addon_dir/src/ngx_http_lua_util.h \ $ngx_addon_dir/src/ngx_http_lua_cache.h \ $ngx_addon_dir/src/ngx_http_lua_contentby.h \ + $ngx_addon_dir/src/ngx_http_lua_server_rewriteby.c \ $ngx_addon_dir/src/ngx_http_lua_rewriteby.h \ $ngx_addon_dir/src/ngx_http_lua_accessby.h \ $ngx_addon_dir/src/ngx_http_lua_setby.h \ @@ -407,24 +346,28 @@ HTTP_LUA_DEPS=" \ $ngx_addon_dir/src/ngx_http_lua_bodyfilterby.h \ $ngx_addon_dir/src/ngx_http_lua_initby.h \ $ngx_addon_dir/src/ngx_http_lua_initworkerby.h \ + $ngx_addon_dir/src/ngx_http_lua_exitworkerby.h \ $ngx_addon_dir/src/ngx_http_lua_socket_udp.h \ - $ngx_addon_dir/src/ngx_http_lua_req_method.h \ - $ngx_addon_dir/src/ngx_http_lua_phase.h \ $ngx_addon_dir/src/ngx_http_lua_probe.h \ $ngx_addon_dir/src/ngx_http_lua_uthread.h \ $ngx_addon_dir/src/ngx_http_lua_timer.h \ $ngx_addon_dir/src/ngx_http_lua_config.h \ - $ngx_addon_dir/src/ngx_http_lua_worker.h \ + $ngx_addon_dir/src/ngx_http_lua_ssl_client_helloby.h \ $ngx_addon_dir/src/ngx_http_lua_ssl_certby.h \ $ngx_addon_dir/src/ngx_http_lua_lex.h \ $ngx_addon_dir/src/ngx_http_lua_balancer.h \ + $ngx_addon_dir/src/ngx_http_lua_ssl_export_keying_material.h \ $ngx_addon_dir/src/ngx_http_lua_ssl_session_storeby.h \ $ngx_addon_dir/src/ngx_http_lua_ssl_session_fetchby.h \ $ngx_addon_dir/src/ngx_http_lua_ssl.h \ + $ngx_addon_dir/src/ngx_http_lua_proxy_ssl_verifyby.h \ $ngx_addon_dir/src/ngx_http_lua_log_ringbuf.h \ + $ngx_addon_dir/src/ngx_http_lua_input_filters.h \ + $ngx_addon_dir/src/ngx_http_lua_pipe.h \ + $ngx_addon_dir/src/ngx_http_lua_worker_thread.h \ " -CFLAGS="$CFLAGS -DNDK_SET_VAR" +# ---------------------------------------- ngx_feature="export symbols by default (-E)" ngx_feature_libs="-Wl,-E" @@ -440,6 +383,8 @@ if [ $ngx_found = yes ]; then CORE_LIBS="-Wl,-E $CORE_LIBS" fi +# ---------------------------------------- + # for Cygwin ngx_feature="export symbols by default (--export-all-symbols)" ngx_feature_libs="-Wl,--export-all-symbols" @@ -455,13 +400,7 @@ if [ $ngx_found = yes ]; then CORE_LIBS="-Wl,--export-all-symbols $CORE_LIBS" fi -NGX_DTRACE_PROVIDERS="$NGX_DTRACE_PROVIDERS $ngx_addon_dir/dtrace/ngx_lua_provider.d" -NGX_TAPSET_SRCS="$NGX_TAPSET_SRCS $ngx_addon_dir/tapset/ngx_lua.stp" - -USE_MD5=YES -USE_SHA1=YES - -CORE_INCS="$CORE_INCS $ngx_addon_dir/src/api" +# ---------------------------------------- ngx_feature="SO_PASSCRED" ngx_feature_libs= @@ -474,26 +413,19 @@ ngx_feature_test='setsockopt(1, SOL_SOCKET, SO_PASSCRED, NULL, 0);' . auto/feature -ngx_feature="__attribute__(constructor)" +# ---------------------------------------- + +ngx_feature="SA_RESTART" ngx_feature_libs= -ngx_feature_name="NGX_HTTP_LUA_HAVE_CONSTRUCTOR" -ngx_feature_run=yes -ngx_feature_incs="#include -int a = 2; -__attribute__((constructor)) -static void foo(void) -{ - a = 0; -} -" -ngx_feature_test="exit(a);" -SAVED_CC_TEST_FLAGS="$CC_TEST_FLAGS" -CC_TEST_FLAGS="-Werror -Wall $CC_TEST_FLAGS" +ngx_feature_name="NGX_HTTP_LUA_HAVE_SA_RESTART" +ngx_feature_run=no +ngx_feature_incs="#include " +ngx_feature_path= +ngx_feature_test='struct sigaction act; + act.sa_flags |= SA_RESTART;' . auto/feature -CC_TEST_FLAGS="$SAVED_CC_TEST_FLAGS" - # ---------------------------------------- ngx_feature="malloc_trim" @@ -512,7 +444,53 @@ CC_TEST_FLAGS="$SAVED_CC_TEST_FLAGS" # ---------------------------------------- -if test -n "$ngx_module_link"; then +ngx_feature="pipe2" +ngx_feature_libs= +ngx_feature_name="NGX_HTTP_LUA_HAVE_PIPE2" +ngx_feature_run=no +ngx_feature_incs="#include " +ngx_feature_test="int fd[2]; pipe2(fd, O_CLOEXEC|O_NONBLOCK);" +SAVED_CC_TEST_FLAGS="$CC_TEST_FLAGS" +CC_TEST_FLAGS="-Werror -Wall $CC_TEST_FLAGS" + +. auto/feature + +CC_TEST_FLAGS="$SAVED_CC_TEST_FLAGS" + +# ---------------------------------------- + +ngx_feature="signalfd" +ngx_feature_libs= +ngx_feature_name="NGX_HTTP_LUA_HAVE_SIGNALFD" +ngx_feature_run=no +ngx_feature_incs="#include " +ngx_feature_test="sigset_t set = { 0 }; + signalfd(-1, &set, SFD_NONBLOCK|SFD_CLOEXEC);" +SAVED_CC_TEST_FLAGS="$CC_TEST_FLAGS" +CC_TEST_FLAGS="-Werror -Wall $CC_TEST_FLAGS" + +. auto/feature + +CC_TEST_FLAGS="$SAVED_CC_TEST_FLAGS" + +# ---------------------------------------- + +ngx_feature="execvpe" +ngx_feature_libs= +ngx_feature_name="NGX_HTTP_LUA_HAVE_EXECVPE" +ngx_feature_run=no +ngx_feature_incs= +ngx_feature_test='char* argv[] = {"/bin/sh"};execvpe("/bin/sh", argv, NULL);' +SAVED_CC_TEST_FLAGS="$CC_TEST_FLAGS" +CC_TEST_FLAGS="-Werror -Wall $CC_TEST_FLAGS" + +. auto/feature + +CC_TEST_FLAGS="$SAVED_CC_TEST_FLAGS" + +# ---------------------------------------- + +if [ -n "$ngx_module_link" ]; then ngx_module_type=HTTP_AUX_FILTER ngx_module_name=$ngx_addon_name ngx_module_deps="$HTTP_LUA_DEPS" @@ -528,5 +506,16 @@ else CORE_LIBS="$CORE_LIBS $ngx_module_libs" fi -#CFLAGS=$"$CFLAGS -DLUA_DEFAULT_PATH='\"/usr/local/openresty/lualib/?.lua\"'" -#CFLAGS=$"$CFLAGS -DLUA_DEFAULT_CPATH='\"/usr/local/openresty/lualib/?.so\"'" +# ---------------------------------------- + +USE_MD5=YES +USE_SHA1=YES + +NGX_DTRACE_PROVIDERS="$NGX_DTRACE_PROVIDERS $ngx_addon_dir/dtrace/ngx_lua_provider.d" +NGX_TAPSET_SRCS="$NGX_TAPSET_SRCS $ngx_addon_dir/tapset/ngx_lua.stp" + +CORE_INCS="$CORE_INCS $ngx_addon_dir/src/api" + +CFLAGS="$CFLAGS -DNDK_SET_VAR" + +echo "/* DO NOT EDIT! This file was automatically generated by config */" > "$ngx_addon_dir/src/ngx_http_lua_autoconf.h" diff --git a/doc/HttpLuaModule.wiki b/doc/HttpLuaModule.wiki index f2b477752b..09af86f771 100644 --- a/doc/HttpLuaModule.wiki +++ b/doc/HttpLuaModule.wiki @@ -2,7 +2,13 @@ ngx_http_lua_module - Embed the power of Lua into Nginx HTTP Servers. -''This module is not distributed with the Nginx source.'' See [[#Installation|the installation instructions]]. +This module is a core component of [https://openresty.org OpenResty]. If you are using this module, +then you are essentially using OpenResty. + +''This module is not distributed with the Nginx source.'' See +[[#Installation|the installation instructions]]. + +This is a core component of OpenResty. If you are using this module, then you are essentially using OpenResty :) = Status = @@ -10,7 +16,33 @@ Production ready. = Version = -This document describes ngx_lua [https://github.com/openresty/lua-nginx-module/tags v0.10.11] released on 3 November 2017. +This document describes ngx_lua +[https://github.com/openresty/lua-nginx-module/tags v0.10.25], which was released +on 19 June 2023. + += Videos = + +* YouTube video "[Hello World HTTP Example with OpenResty/Lua](https://youtu.be/eSfYLvVQMxw)" + + [![Hello World HTTP Example with OpenResty/Lua](https://img.youtube.com/vi/eSfYLvVQMxw/0.jpg)](https://youtu.be/eSfYLvVQMxw) + +* YouTube video "[Write Your Own Lua Modules in OpenResty/Nginx Applications](https://youtu.be/vfYxOMl5LVY)" + + [![Write Your Own Lua Modules in OpenResty/Nginx Applications](https://img.youtube.com/vi/vfYxOMl5LVY/0.jpg)](https://youtu.be/vfYxOMl5LVY) + +* YouTube video "[OpenResty's resty Command-Line Utility Demo](https://youtu.be/L1c7aw4mSOo)" + + [![OpenResty's resty Command-Line Utility Demo](https://img.youtube.com/vi/L1c7aw4mSOo/0.jpg)](https://youtu.be/L1c7aw4mSOo) + +* YouTube video "[Measure Execution Time of Lua Code Correctly in OpenResty](https://youtu.be/VkRYW_qLoME)" + + [![Measure Execution Time of Lua Code Correctly in OpenResty](https://img.youtube.com/vi/VkRYW_qLoME/0.jpg)](https://youtu.be/VkRYW_qLoME) + +* YouTube video "[Precompile Lua Modules into LuaJIT Bytecode to Speedup OpenResty Startup](https://youtu.be/EP7c0BM2yNo)" + + [![Precompile Lua Modules into LuaJIT Bytecode to Speedup OpenResty Startup](https://img.youtube.com/vi/EP7c0BM2yNo/0.jpg)](https://youtu.be/EP7c0BM2yNo) + +You are welcome to subscribe to our [official YouTube channel, OpenResty](https://www.youtube.com/channel/UCXVmwF-UCScv2ftsGoMqxhw). = Synopsis = @@ -130,12 +162,27 @@ This document describes ngx_lua [https://github.com/openresty/lua-nginx-module/t = Description = -This module embeds Lua, via the standard Lua 5.1 interpreter or [http://luajit.org/luajit.html LuaJIT 2.0/2.1], into Nginx and by leveraging Nginx's subrequests, allows the integration of the powerful Lua threads (Lua coroutines) into the Nginx event model. +This module embeds [https://luajit.org/luajit.html LuaJIT 2.0/2.1] into Nginx. +It is a core component of [https://openresty.org OpenResty]. If you are using +this module, then you are essentially using OpenResty. + +Since version v0.10.16 of this module, the standard Lua +interpreter (also known as "PUC-Rio Lua") is not supported anymore. This +document interchangeably uses the terms "Lua" and "LuaJIT" to refer to the +LuaJIT interpreter. + +By leveraging Nginx's subrequests, this module allows the integration of the +powerful Lua threads (known as Lua "coroutines") into the Nginx event model. -Unlike [https://httpd.apache.org/docs/trunk/mod/mod_lua.html Apache's mod_lua] and [http://redmine.lighttpd.net/wiki/1/Docs:ModMagnet Lighttpd's mod_magnet], Lua code executed using this module can be ''100% non-blocking'' on network traffic as long as the [[#Nginx API for Lua|Nginx API for Lua]] provided by this module is used to handle -requests to upstream services such as MySQL, PostgreSQL, Memcached, Redis, or upstream HTTP web services. +Unlike [https://httpd.apache.org/docs/trunk/mod/mod_lua.html Apache's mod_lua] +and [http://redmine.lighttpd.net/wiki/1/Docs:ModMagnet Lighttpd's mod_magnet], +Lua code executed using this module can be ''100% non-blocking'' on network +traffic as long as the [[#Nginx API for Lua|Nginx API for Lua]] provided by +this module is used to handle requests to upstream services such as MySQL, +PostgreSQL, Memcached, Redis, or upstream HTTP web services. -At least the following Lua libraries and Nginx modules can be used with this ngx_lua module: +At least the following Lua libraries and Nginx modules can be used with this +module: * [https://github.com/openresty/lua-resty-memcached lua-resty-memcached] * [https://github.com/openresty/lua-resty-mysql lua-resty-mysql] @@ -154,21 +201,32 @@ At least the following Lua libraries and Nginx modules can be used with this ngx * [[HttpProxyModule|ngx_proxy]] * [[HttpFastcgiModule|ngx_fastcgi]] -Almost all the Nginx modules can be used with this ngx_lua module by means of [[#ngx.location.capture|ngx.location.capture]] or [[#ngx.location.capture_multi|ngx.location.capture_multi]] but it is recommended to use those lua-resty-* libraries instead of creating subrequests to access the Nginx upstream modules because the former is usually much more flexible and memory-efficient. +Almost any Nginx modules can be used with this ngx_lua module by means of +[[#ngx.location.capture|ngx.location.capture]] or +[[#ngx.location.capture_multi|ngx.location.capture_multi]] but it is +recommended to use those lua-resty-* libraries instead of creating +subrequests to access the Nginx upstream modules because the former is usually +much more flexible and memory-efficient. -The Lua interpreter or LuaJIT instance is shared across all the requests in a single nginx worker process but request contexts are segregated using lightweight Lua coroutines. +The Lua interpreter (also known as "Lua State" or "LuaJIT VM instance") is +shared across all the requests in a single Nginx worker process to minimize +memory use. Request contexts are segregated using lightweight Lua coroutines. -Loaded Lua modules persist in the nginx worker process level resulting in a small memory footprint in Lua even when under heavy loads. +Loaded Lua modules persist in the Nginx worker process level resulting in a +small memory footprint in Lua even when under heavy loads. -This module is plugged into NGINX's "http" subsystem so it can only speaks downstream communication protocols in the HTTP family (HTTP 0.9/1.0/1.1/2.0, WebSockets, and etc). -If you want to do generic TCP communications with the downstream clients, then you should use the [https://github.com/openresty/stream-lua-nginx-module#readme ngx_stream_lua] module instead -which has a compatible Lua API. +This module is plugged into Nginx's "http" subsystem so it can only speaks +downstream communication protocols in the HTTP family (HTTP 0.9/1.0/1.1/2.0, +WebSockets, etc...). If you want to do generic TCP communications with the +downstream clients, then you should use the +[https://github.com/openresty/stream-lua-nginx-module#readme ngx_stream_lua] +module instead, which offers a compatible Lua API. = Typical Uses = Just to name a few: -* Mashup'ing and processing outputs of various nginx upstream outputs (proxy, drizzle, postgres, redis, memcached, and etc) in Lua, +* Mashup'ing and processing outputs of various Nginx upstream outputs (proxy, drizzle, postgres, redis, memcached, and etc) in Lua, * doing arbitrarily complex access control and security checks in Lua before requests actually reach the upstream backends, * manipulating response headers in an arbitrary way (by Lua) * fetching backend information from external storage backends (like redis, memcached, mysql, postgresql) and use that information to choose which upstream backend to access on-the-fly, @@ -176,16 +234,23 @@ Just to name a few: * doing very complex URL dispatch in Lua at rewrite phase, * using Lua to implement advanced caching mechanism for Nginx's subrequests and arbitrary locations. -The possibilities are unlimited as the module allows bringing together various elements within Nginx as well as exposing the power of the Lua language to the user. The module provides the full flexibility of scripting while offering performance levels comparable with native C language programs both in terms of CPU time as well as memory footprint. This is particularly the case when LuaJIT 2.x is enabled. +The possibilities are unlimited as the module allows bringing together various +elements within Nginx as well as exposing the power of the Lua language to the +user. The module provides the full flexibility of scripting while offering +performance levels comparable with native C language programs both in terms of +CPU time as well as memory footprint thanks to LuaJIT 2.x. -Other scripting language implementations typically struggle to match this performance level. - -The Lua state (Lua VM instance) is shared across all the requests handled by a single nginx worker process to minimize memory use. +Other scripting language implementations typically struggle to match this +performance level. = Nginx Compatibility = The latest version of this module is compatible with the following versions of Nginx: +* 1.19.x (last tested: 1.19.3) +* 1.17.x (last tested: 1.17.8) +* 1.15.x (last tested: 1.15.8) +* 1.14.x * 1.13.x (last tested: 1.13.6) * 1.12.x * 1.11.x (last tested: 1.11.2) @@ -199,21 +264,36 @@ Nginx cores older than 1.6.0 (exclusive) are *not* supported. = Installation = -It is *highly* recommended to use [http://openresty.org OpenResty releases] which integrate Nginx, ngx_lua, LuaJIT 2.1, as well as other powerful companion Nginx modules and Lua libraries. It is discouraged to build this module with nginx yourself since it is tricky to set up exactly right. Also, the stock nginx cores have various limitations and long standing bugs that can make some of this modules' features become disabled, not work properly, or run slower. +It is *highly* recommended to use [https://openresty.org OpenResty releases] +which bundle Nginx, ngx_lua (this module), LuaJIT, as well as other powerful +companion Nginx modules and Lua libraries. + +It is discouraged to build this module with Nginx yourself since it is tricky +to set up exactly right. + +Note that Nginx, LuaJIT, and OpenSSL official releases have various limitations +and long standing bugs that can cause some of this module's features to be +disabled, not work properly, or run slower. Official OpenResty releases are +recommended because they bundle [https://github.com/openresty/luajit2 +OpenResty's optimized LuaJIT 2.1 fork] and +[https://github.com/openresty/openresty/tree/master/patches Nginx/OpenSSL +patches]. Alternatively, ngx_lua can be manually compiled into Nginx: -# Install LuaJIT 2.0 or 2.1 (recommended) or Lua 5.1 (Lua 5.2 is ''not'' supported yet). LuaJIT can be downloaded from the [http://luajit.org/download.html LuaJIT project website] and Lua 5.1, from the [http://www.lua.org/ Lua project website]. Some distribution package managers also distribute LuaJIT and/or Lua. -# Download the latest version of the ngx_devel_kit (NDK) module [https://github.com/simpl/ngx_devel_kit/tags HERE]. -# Download the latest version of ngx_lua [https://github.com/openresty/lua-nginx-module/tags HERE]. -# Download the latest version of Nginx [http://nginx.org/ HERE] (See [[#Nginx Compatibility|Nginx Compatibility]]) +# LuaJIT can be downloaded from the [https://github.com/openresty/luajit2/releases latest release of OpenResty's LuaJIT fork]. The official LuaJIT 2.x releases are also supported, although performance will be significantly lower for reasons elaborated above +# Download the latest version of the ngx_devel_kit (NDK) module [https://github.com/simplresty/ngx_devel_kit/tags HERE] +# Download the latest version of ngx_lua [https://github.com/openresty/lua-nginx-module/tags HERE] +# Download the latest supported version of Nginx [https://nginx.org/ HERE] (See [[#Nginx Compatibility|Nginx Compatibility]]) +# Download the latest version of the lua-resty-core [HERE](https://github.com/openresty/lua-resty-core) +# Download the latest version of the lua-resty-lrucache [HERE](https://github.com/openresty/lua-resty-lrucache) Build the source with this module: - wget 'http://nginx.org/download/nginx-1.13.6.tar.gz' - tar -xzvf nginx-1.13.6.tar.gz - cd nginx-1.13.6/ + wget 'https://openresty.org/download/nginx-1.19.3.tar.gz' + tar -xzvf nginx-1.19.3.tar.gz + cd nginx-1.19.3/ # tell nginx's build system where to find LuaJIT 2.0: export LUAJIT_LIB=/path/to/luajit/lib @@ -223,13 +303,9 @@ Build the source with this module: export LUAJIT_LIB=/path/to/luajit/lib export LUAJIT_INC=/path/to/luajit/include/luajit-2.1 - # or tell where to find Lua if using Lua instead: - #export LUA_LIB=/path/to/lua/lib - #export LUA_INC=/path/to/lua/include - # Here we assume Nginx is to be installed under /opt/nginx/. ./configure --prefix=/opt/nginx \ - --with-ld-opt="-Wl,-rpath,/path/to/luajit-or-lua/lib" \ + --with-ld-opt="-Wl,-rpath,/path/to/luajit/lib" \ --add-module=/path/to/ngx_devel_kit \ --add-module=/path/to/lua-nginx-module @@ -237,16 +313,28 @@ Build the source with this module: # current nginx build. # You can get usually those options using command nginx -V - # you can change the parallism number 2 below to fit the number of spare CPU cores in your + # you can change the parallelism number 2 below to fit the number of spare CPU cores in your # machine. make -j2 make install + + # Note that this version of lug-nginx-module not allow to set `lua_load_resty_core off;` any more. + # So, you have to install `lua-resty-core` and `lua-resty-lrucache` manually as below. + + cd lua-resty-core + make install PREFIX=/opt/nginx + cd lua-resty-lrucache + make install PREFIX=/opt/nginx + + # add necessary `lua_package_path` directive to `nginx.conf`, in the http context + + lua_package_path "/opt/nginx/lib/lua/?.lua;;"; == Building as a dynamic module == Starting from NGINX 1.9.11, you can also compile this module as a dynamic module, by using the --add-dynamic-module=PATH option instead of --add-module=PATH on the -./configure command line above. And then you can explicitly load the module in your nginx.conf via the [load_module](http://nginx.org/en/docs/ngx_core_module.html#load_module) +./configure command line above. And then you can explicitly load the module in your nginx.conf via the [load_module](https://nginx.org/en/docs/ngx_core_module.html#load_module) directive, for example, @@ -256,39 +344,19 @@ load_module /path/to/modules/ngx_http_lua_module.so; == C Macro Configurations == -While building this module either via OpenResty or with the NGINX core, you can define the following C macros via the C compiler options: +While building this module either via OpenResty or with the Nginx core, you can define the following C macros via the C compiler options: * NGX_LUA_USE_ASSERT : When defined, will enable assertions in the ngx_lua C code base. Recommended for debugging or testing builds. It can introduce some (small) runtime overhead when enabled. This macro was first introduced in the v0.9.10 release. * NGX_LUA_ABORT_AT_PANIC -: When the Lua/LuaJIT VM panics, ngx_lua will instruct the current nginx worker process to quit gracefully by default. By specifying this C macro, ngx_lua will abort the current nginx worker process (which usually result in a core dump file) immediately. This option is useful for debugging VM panics. This option was first introduced in the v0.9.8 release. -* NGX_LUA_NO_FFI_API -: Excludes pure C API functions for FFI-based Lua API for NGINX (as required by [https://github.com/openresty/lua-resty-core#readme lua-resty-core], for example). Enabling this macro can make the resulting binary code size smaller. +: When the LuaJIT VM panics, ngx_lua will instruct the current nginx worker process to quit gracefully by default. By specifying this C macro, ngx_lua will abort the current nginx worker process (which usually result in a core dump file) immediately. This option is useful for debugging VM panics. This option was first introduced in the v0.9.8 release. -To enable one or more of these macros, just pass extra C compiler options to the ./configure script of either NGINX or OpenResty. For instance, +To enable one or more of these macros, just pass extra C compiler options to the ./configure script of either Nginx or OpenResty. For instance, ./configure --with-cc-opt="-DNGX_LUA_USE_ASSERT -DNGX_LUA_ABORT_AT_PANIC" -== Installation on Ubuntu 11.10 == - -Note that it is recommended to use LuaJIT 2.0 or LuaJIT 2.1 instead of the standard Lua 5.1 interpreter wherever possible. - -If the standard Lua 5.1 interpreter is required however, run the following command to install it from the Ubuntu repository: - - -apt-get install -y lua5.1 liblua5.1-0 liblua5.1-0-dev - - -Everything should be installed correctly, except for one small tweak. - -Library name liblua.so has been changed in liblua5.1 package, it only comes with liblua5.1.so, which needs to be symlinked to /usr/lib so it could be found during the configuration process. - - -ln -s /usr/lib/x86_64-linux-gnu/liblua5.1.so /usr/lib/liblua.so - - = Community = == English Mailing List == @@ -301,7 +369,8 @@ The [https://groups.google.com/group/openresty openresty] mailing list is for Ch = Code Repository = -The code repository of this project is hosted on github at [https://github.com/openresty/lua-nginx-module openresty/lua-nginx-module]. +The code repository of this project is hosted on GitHub at +[https://github.com/openresty/lua-nginx-module openresty/lua-nginx-module]. = Bugs and Patches = @@ -310,11 +379,13 @@ Please submit bug reports, wishlists, or patches by # creating a ticket on the [https://github.com/openresty/lua-nginx-module/issues GitHub Issue Tracker], # or posting to the [[#Community|OpenResty community]]. -= Lua/LuaJIT bytecode support = += LuaJIT bytecode support = + +Watch YouTube video "[Measure Execution Time of Lua Code Correctly in OpenResty](https://youtu.be/VkRYW_qLoME)" -As from the v0.5.0rc32 release, all *_by_lua_file configure directives (such as [[#content_by_lua_file|content_by_lua_file]]) support loading Lua 5.1 and LuaJIT 2.0/2.1 raw bytecode files directly. +[![Precompile Lua Modules into LuaJIT Bytecode to Speedup OpenResty Startup](https://img.youtube.com/vi/EP7c0BM2yNo/0.jpg)](https://youtu.be/EP7c0BM2yNo) -Please note that the bytecode format used by LuaJIT 2.0/2.1 is not compatible with that used by the standard Lua 5.1 interpreter. So if using LuaJIT 2.0/2.1 with ngx_lua, LuaJIT compatible bytecode files must be generated as shown: +As from the v0.5.0rc32 release, all *_by_lua_file configure directives (such as [[#content_by_lua_file|content_by_lua_file]]) support loading LuaJIT 2.0/2.1 raw bytecode files directly: /path/to/luajit/bin/luajit -b /path/to/input_file.lua /path/to/output_file.ljbc @@ -328,33 +399,26 @@ The -bg option can be used to include debug information in the LuaJ Please refer to the official LuaJIT documentation on the -b option for more details: -http://luajit.org/running.html#opt_b - -Also, the bytecode files generated by LuaJIT 2.1 is ''not'' compatible with LuaJIT 2.0, and vice versa. The support for LuaJIT 2.1 bytecode was first added in ngx_lua v0.9.3. - -Similarly, if using the standard Lua 5.1 interpreter with ngx_lua, Lua compatible bytecode files must be generated using the luac commandline utility as shown: - - - luac -o /path/to/output_file.luac /path/to/input_file.lua - - -Unlike as with LuaJIT, debug information is included in standard Lua 5.1 bytecode files by default. This can be striped out by specifying the -s option as shown: +https://luajit.org/running.html#opt_b - - luac -s -o /path/to/output_file.luac /path/to/input_file.lua - +Note that the bytecode files generated by LuaJIT 2.1 is ''not'' compatible with +LuaJIT 2.0, and vice versa. The support for LuaJIT 2.1 bytecode was first added +in ngx_lua v0.9.3. -Attempts to load standard Lua 5.1 bytecode files into ngx_lua instances linked to LuaJIT 2.0/2.1 or vice versa, will result in an error message, such as that below, being logged into the Nginx error.log file: +Attempts to load standard Lua 5.1 bytecode files into ngx_lua instances linked +to LuaJIT 2.0/2.1 (or vice versa) will result in an Nginx error message such as +the one below: [error] 13909#0: *1 failed to load Lua inlined code: bad byte-code header in /path/to/test_file.luac -Loading bytecode files via the Lua primitives like require and dofile should always work as expected. +Loading bytecode files via the Lua primitives like require and +dofile should always work as expected. = System Environment Variable Support = -If you want to access the system environment variable, say, foo, in Lua via the standard Lua API [http://www.lua.org/manual/5.1/manual.html#pdf-os.getenv os.getenv], then you should also list this environment variable name in your nginx.conf file via the [http://nginx.org/en/docs/ngx_core_module.html#env env directive]. For example, +If you want to access the system environment variable, say, foo, in Lua via the standard Lua API [https://www.lua.org/manual/5.1/manual.html#pdf-os.getenv os.getenv], then you should also list this environment variable name in your nginx.conf file via the [https://nginx.org/en/docs/ngx_core_module.html#env env directive]. For example, env foo; @@ -375,9 +439,12 @@ To force curl to send HTTP 1.0 requests, use the -0 op = Statically Linking Pure Lua Modules = -When LuaJIT 2.x is used, it is possible to statically link the bytecode of pure Lua modules into the Nginx executable. +With LuaJIT 2.x, it is possible to statically link the bytecode of pure Lua +modules into the Nginx executable. -Basically you use the luajit executable to compile .lua Lua module files to .o object files containing the exported bytecode data, and then link the .o files directly in your Nginx build. +You can use the luajit executable to compile .lua Lua +module files to .o object files containing the exported bytecode +data, and then link the .o files directly in your Nginx build. Below is a trivial example to demonstrate this. Consider that we have the following .lua file named foo.lua: @@ -394,7 +461,9 @@ Below is a trivial example to demonstrate this. Consider that we have the follow And then we compile this .lua file to foo.o file: + /path/to/luajit/bin/luajit -bg foo.lua foo.o + What matters here is the name of the .lua file, which determines how you use this module later on the Lua land. The file name foo.o does not matter at all except the .o file extension (which tells luajit what output format is used). If you want to strip the Lua debug information from the resulting bytecode, you can just specify the -b option above instead of -bg. @@ -429,7 +498,7 @@ When you have multiple .lua files to compile and link, then just sp ./configure --with-ld-opt="/path/to/foo.o /path/to/bar.o" ... -If you have just too many .o files, then it might not be feasible to name them all in a single command. In this case, you can build a static library (or archive) for your .o files, as in +If you have too many .o files, then it might not be feasible to name them all in a single command. In this case, you can build a static library (or archive) for your .o files, as in ar rcus libmyluafiles.a *.o @@ -446,9 +515,17 @@ where /path/to/lib is the path of the directory containing the require builtin to import the module, and then manipulate the shared data in Lua. This works because required Lua modules are loaded only once and all coroutines will share the same copy of the module (both its code and data). Note however that Lua global variables (note, not module-level variables) WILL NOT persist between requests because of the one-coroutine-per-request isolation design. +To globally share data among all the requests handled by the same Nginx worker +process, encapsulate the shared data into a Lua module, use the Lua +require builtin to import the module, and then manipulate the +shared data in Lua. This works because required Lua modules are loaded only +once and all coroutines will share the same copy of the module (both its code +and data). -Here is a complete small example: +Note that the use of global Lua variables is *strongly discouraged*, as it may +lead to unexpected race conditions between concurrent requests. + +Here is a small example on sharing data within an Nginx worker via a Lua module: -- mydata.lua @@ -479,16 +556,16 @@ and then accessing it from nginx.conf: The mydata module in this example will only be loaded and run on the first request to the location /lua, -and all subsequent requests to the same nginx worker process will use the reloaded instance of the +and all subsequent requests to the same Nginx worker process will use the reloaded instance of the module as well as the same copy of the data in it, until a HUP signal is sent to the Nginx master process to force a reload. This data sharing technique is essential for high performance Lua applications based on this module. -Note that this data sharing is on a ''per-worker'' basis and not on a ''per-server'' basis. That is, when there are multiple nginx worker processes under an Nginx master, data sharing cannot cross the process boundary between these workers. +Note that this data sharing is on a ''per-worker'' basis and not on a ''per-server'' basis. That is, when there are multiple Nginx worker processes under an Nginx master, data sharing cannot cross the process boundary between these workers. -It is usually recommended to share read-only data this way. You can also share changeable data among all the concurrent requests of each nginx worker process as +It is usually recommended to share read-only data this way. You can also share changeable data among all the concurrent requests of each Nginx worker process as long as there is ''no'' nonblocking I/O operations (including [[#ngx.sleep|ngx.sleep]]) in the middle of your calculations. As long as you do not give the -control back to the nginx event loop and ngx_lua's light thread +control back to the Nginx event loop and ngx_lua's light thread scheduler (even implicitly), there can never be any race conditions in between. For this reason, always be very careful when you want to share changeable data on the worker level. Buggy optimizations can easily lead to hard-to-debug @@ -497,12 +574,13 @@ race conditions under load. If server-wide data sharing is required, then use one or more of the following approaches: # Use the [[#ngx.shared.DICT|ngx.shared.DICT]] API provided by this module. -# Use only a single nginx worker and a single server (this is however not recommended when there is a multi core CPU or multiple CPUs in a single machine). -# Use data storage mechanisms such as memcached, redis, MySQL or PostgreSQL. [http://openresty.org The OpenResty bundle] associated with this module comes with a set of companion Nginx modules and Lua libraries that provide interfaces with these data storage mechanisms. +# Use only a single Nginx worker and a single server (this is however not recommended when there is a multi core CPU or multiple CPUs in a single machine). +# Use data storage mechanisms such as memcached, redis, MySQL or PostgreSQL. [https://openresty.org The OpenResty official releases] come with a set of companion Nginx modules and Lua libraries that provide interfaces with these data storage mechanisms. = Known Issues = == TCP socket connect operation issues == + The [[#tcpsock:connect|tcpsock:connect]] method may indicate success despite connection failures such as with Connection Refused errors. However, later attempts to manipulate the cosocket object will fail and return the actual error status message generated by the failed connect operation. @@ -510,11 +588,12 @@ However, later attempts to manipulate the cosocket object will fail and return t This issue is due to limitations in the Nginx event model and only appears to affect Mac OS X. == Lua Coroutine Yielding/Resuming == -* Because Lua's dofile and require builtins are currently implemented as C functions in both Lua 5.1 and LuaJIT 2.0/2.1, if the Lua file being loaded by dofile or require invokes [[#ngx.location.capture|ngx.location.capture*]], [[#ngx.exec|ngx.exec]], [[#ngx.exit|ngx.exit]], or other API functions requiring yielding in the *top-level* scope of the Lua file, then the Lua error "attempt to yield across C-call boundary" will be raised. To avoid this, put these calls requiring yielding into your own Lua functions in the Lua file instead of the top-level scope of the file. -* As the standard Lua 5.1 interpreter's VM is not fully resumable, the methods [[#ngx.location.capture|ngx.location.capture]], [[#ngx.location.capture_multi|ngx.location.capture_multi]], [[#ngx.redirect|ngx.redirect]], [[#ngx.exec|ngx.exec]], and [[#ngx.exit|ngx.exit]] cannot be used within the context of a Lua [http://www.lua.org/manual/5.1/manual.html#pdf-pcall pcall()] or [http://www.lua.org/manual/5.1/manual.html#pdf-xpcall xpcall()] or even the first line of the for ... in ... statement when the standard Lua 5.1 interpreter is used and the attempt to yield across metamethod/C-call boundary error will be produced. Please use LuaJIT 2.x, which supports a fully resumable VM, to avoid this. + +* Because Lua's dofile and require builtins are currently implemented as C functions in LuaJIT 2.0/2.1, if the Lua file being loaded by dofile or require invokes [[#ngx.location.capture|ngx.location.capture*]], [[#ngx.exec|ngx.exec]], [[#ngx.exit|ngx.exit]], or other API functions requiring yielding in the *top-level* scope of the Lua file, then the Lua error "attempt to yield across C-call boundary" will be raised. To avoid this, put these calls requiring yielding into your own Lua functions in the Lua file instead of the top-level scope of the file. == Lua Variable Scope == -Care must be taken when importing modules and this form should be used: + +Care must be taken when importing modules, and this form should be used: local xxx = require('xxx') @@ -548,7 +627,6 @@ It is therefore *highly* recommended to always declare such within an appropriat local function foo() return 123 end - To find all instances of Lua global variables in your Lua code, run the [https://github.com/openresty/nginx-devel-utils/blob/master/lua-releng lua-releng tool] across all .lua source files: $ lua-releng @@ -562,7 +640,8 @@ The output says that the line 1489 of file lib/foo/bar.lua writes t This tool will guarantee that local variables in the Lua module functions are all declared with the local keyword, otherwise a runtime exception will be thrown. It prevents undesirable race conditions while accessing such variables. See [[#Data_Sharing_within_an_Nginx_Worker|Data Sharing within an Nginx Worker]] for the reasons behind this. == Locations Configured by Subrequest Directives of Other Modules == -The [[#ngx.location.capture|ngx.location.capture]] and [[#ngx.location.capture_multi|ngx.location.capture_multi]] directives cannot capture locations that include the [[HttpAdditionModule#add_before_body|add_before_body]], [[HttpAdditionModule#add_after_body|add_after_body]], [http://nginx.org/en/docs/http/ngx_http_auth_request_module.html#auth_request auth_request], [[HttpEchoModule#echo_location|echo_location]], [[HttpEchoModule#echo_location_async|echo_location_async]], [[HttpEchoModule#echo_subrequest|echo_subrequest]], or [[HttpEchoModule#echo_subrequest_async|echo_subrequest_async]] directives. + +The [[#ngx.location.capture|ngx.location.capture]] and [[#ngx.location.capture_multi|ngx.location.capture_multi]] directives cannot capture locations that include the [[HttpAdditionModule#add_before_body|add_before_body]], [[HttpAdditionModule#add_after_body|add_after_body]], [https://nginx.org/en/docs/http/ngx_http_auth_request_module.html#auth_request auth_request], [[HttpEchoModule#echo_location|echo_location]], [[HttpEchoModule#echo_location_async|echo_location_async]], [[HttpEchoModule#echo_subrequest|echo_subrequest]], or [[HttpEchoModule#echo_subrequest_async|echo_subrequest_async]] directives. location /foo { @@ -586,17 +665,17 @@ will not work as expected. == Cosockets Not Available Everywhere == -Due to internal limitations in the nginx core, the cosocket API is disabled in the following contexts: [[#set_by_lua|set_by_lua*]], [[#log_by_lua|log_by_lua*]], [[#header_filter_by_lua|header_filter_by_lua*]], and [[#body_filter_by_lua|body_filter_by_lua]]. +Due to internal limitations in the Nginx core, the cosocket API is disabled in the following contexts: [[#set_by_lua|set_by_lua*]], [[#log_by_lua|log_by_lua*]], [[#header_filter_by_lua|header_filter_by_lua*]], and [[#body_filter_by_lua|body_filter_by_lua]]. -The cosockets are currently also disabled in the [[#init_by_lua|init_by_lua*]] and [[#init_worker_by_lua|init_worker_by_lua*]] directive contexts but we may add support for these contexts in the future because there is no limitation in the nginx core (or the limitation might be worked around). +The cosockets are currently also disabled in the [[#init_by_lua|init_by_lua*]] and [[#init_worker_by_lua|init_worker_by_lua*]] directive contexts but we may add support for these contexts in the future because there is no limitation in the Nginx core (or the limitation might be worked around). -There exists a work-around, however, when the original context does *not* need to wait for the cosocket results. That is, creating a zero-delay timer via the [[#ngx.timer.at|ngx.timer.at]] API and do the cosocket results in the timer handler, which runs asynchronously as to the original context creating the timer. +There exists a workaround, however, when the original context does *not* need to wait for the cosocket results. That is, creating a zero-delay timer via the [[#ngx.timer.at|ngx.timer.at]] API and do the cosocket results in the timer handler, which runs asynchronously as to the original context creating the timer. == Special Escaping Sequences == '''NOTE''' Following the v0.9.17 release, this pitfall can be avoided by using the *_by_lua_block {} configuration directives. -PCRE sequences such as \d, \s, or \w, require special attention because in string literals, the backslash character, \, is stripped out by both the Lua language parser and by the nginx config file parser before processing if not within a *_by_lua_block {} directive. So the following snippet will not work as expected: +PCRE sequences such as \d, \s, or \w, require special attention because in string literals, the backslash character, \, is stripped out by both the Lua language parser and by the Nginx config file parser before processing if not within a *_by_lua_block {} directive. So the following snippet will not work as expected: # nginx.conf @@ -684,7 +763,7 @@ As noted earlier, PCRE sequences presented within *_by_lua_block {} # nginx.conf location /test { content_by_lua_block { - local regex = "\d+" + local regex = [[\d+]] local m = ngx.re.match("hello, 1234", regex) if m then ngx.say(m[0]) else ngx.say("not matched!") end } @@ -692,6 +771,7 @@ As noted earlier, PCRE sequences presented within *_by_lua_block {} # evaluates to "1234" +'''NOTE''' You are recommended to use `by_lua_file` when the Lua code is very long. == Mixing with SSI Not Supported == @@ -723,38 +803,21 @@ phases. = TODO = * cosocket: implement LuaSocket's unconnected UDP API. -* port this module to the "datagram" subsystem of NGINX for implementing general UDP servers instead of HTTP -servers in Lua. For example, - - datagram { - server { - listen 1953; - handler_by_lua_block { - -- custom Lua code implementing the special UDP server... - } - } - } - -* shm: implement a "shared queue API" to complement the existing [[#lua_shared_dict|shared dict]] API. * cosocket: add support in the context of [[#init_by_lua|init_by_lua*]]. * cosocket: implement the bind() method for stream-typed cosockets. -* cosocket: pool-based backend concurrency level control: implement automatic connect queueing when the backend concurrency exceeds its connection pool limit. * cosocket: review and merge aviramc's [https://github.com/openresty/lua-nginx-module/pull/290 patch] for adding the bsdrecv method. -* add new API function ngx.resp.add_header to emulate the standard add_header config directive. +* cosocket: add configure options for different strategies of handling the cosocket connection exceeding in the pools. * review and apply vadim-pavlov's patch for [[#ngx.location.capture|ngx.location.capture]]'s extra_headers option -* use ngx_hash_t to optimize the built-in header look-up process for [[#ngx.req.set_header|ngx.req.set_header]], [[#ngx.header.HEADER|ngx.header.HEADER]], and etc. -* add configure options for different strategies of handling the cosocket connection exceeding in the pools. -* add directives to run Lua codes when nginx stops. +* use ngx_hash_t to optimize the built-in header look-up process for [[#ngx.req.set_header|ngx.req.set_header]], and etc. * add ignore_resp_headers, ignore_resp_body, and ignore_resp options to [[#ngx.location.capture|ngx.location.capture]] and [[#ngx.location.capture_multi|ngx.location.capture_multi]] methods, to allow micro performance tuning on the user side. * add automatic Lua code time slicing support by yielding and resuming the Lua VM actively via Lua's debug hooks. * add stat mode similar to [https://httpd.apache.org/docs/trunk/mod/mod_lua.html mod_lua]. -* cosocket: add client SSL certificate support. = Changes = The changes made in every release of this module are listed in the change logs of the OpenResty bundle: -http://openresty.org/#Changes +https://openresty.org/#Changes = Test Suite = @@ -766,7 +829,7 @@ The following dependencies are required to run the test suite: ** Test::Nginx: https://github.com/openresty/test-nginx * Nginx modules: -** [https://github.com/simpl/ngx_devel_kit ngx_devel_kit] +** [https://github.com/simplresty/ngx_devel_kit ngx_devel_kit] ** [https://github.com/openresty/set-misc-nginx-module ngx_set_misc] ** [http://mdounin.ru/files/ngx_http_auth_request_module-0.2.tar.gz ngx_auth_request] (this is not needed if you're using Nginx 1.5.4+. ** [https://github.com/openresty/echo-nginx-module ngx_echo] @@ -809,7 +872,7 @@ To run specific test files: To run a specific test block in a particular test file, add the line --- ONLY to the test block you want to run, and then use the prove utility to run that .t file. -There are also various testing modes based on mockeagain, valgrind, and etc. Refer to the [http://search.cpan.org/perldoc?Test::Nginx Test::Nginx documentation] for more details for various advanced testing modes. See also the test reports for the Nginx test cluster running on Amazon EC2: http://qa.openresty.org. +There are also various testing modes based on mockeagain, valgrind, and etc. Refer to the [https://search.cpan.org/perldoc?Test::Nginx Test::Nginx documentation] for more details for various advanced testing modes. See also the test reports for the Nginx test cluster running on Amazon EC2: https://qa.openresty.org. = Copyright and License = @@ -817,7 +880,7 @@ This module is licensed under the BSD license. Copyright (C) 2009-2017, by Xiaozhe Wang (chaoslawful) . -Copyright (C) 2009-2017, by Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. +Copyright (C) 2009-2019, by Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. All rights reserved. @@ -831,26 +894,35 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND = See Also = -* [https://github.com/openresty/stream-lua-nginx-module#readme ngx_stream_lua_module] for an official port of this module for the NGINX "stream" subsystem (doing generic downstream TCP communications). +Blog posts: + +* [Introduction to Lua-Land CPU Flame Graphs](https://blog.openresty.com/en/lua-cpu-flame-graph/?src=gh_ngxlua) +* [How OpenResty and Nginx Allocate and Manage Memory](https://blog.openresty.com/en//how-or-alloc-mem?src=gh_ngxlua) +* [How OpenResty and Nginx Shared Memory Zones Consume RAM](https://blog.openresty.com/en/how-nginx-shm-consume-ram/?src=gh_ngxlua) +* [Memory Fragmentation in OpenResty and Nginx's Shared Memory Zones](https://blog.openresty.com/en/nginx-shm-frag/?src=gh_ngxlua) + +Other related modules and libraries: + +* [https://github.com/openresty/stream-lua-nginx-module#readme ngx_stream_lua_module] for an official port of this module for the Nginx "stream" subsystem (doing generic downstream TCP communications). * [https://github.com/openresty/lua-resty-memcached lua-resty-memcached] library based on ngx_lua cosocket. * [https://github.com/openresty/lua-resty-redis lua-resty-redis] library based on ngx_lua cosocket. * [https://github.com/openresty/lua-resty-mysql lua-resty-mysql] library based on ngx_lua cosocket. * [https://github.com/openresty/lua-resty-upload lua-resty-upload] library based on ngx_lua cosocket. * [https://github.com/openresty/lua-resty-dns lua-resty-dns] library based on ngx_lua cosocket. * [https://github.com/openresty/lua-resty-websocket lua-resty-websocket] library for both WebSocket server and client, based on ngx_lua cosocket. -* [https://github.com/openresty/lua-resty-string lua-resty-string] library based on [http://luajit.org/ext_ffi.html LuaJIT FFI]. +* [https://github.com/openresty/lua-resty-string lua-resty-string] library based on [https://luajit.org/ext_ffi.html LuaJIT FFI]. * [https://github.com/openresty/lua-resty-lock lua-resty-lock] library for a nonblocking simple lock API. * [https://github.com/cloudflare/lua-resty-cookie lua-resty-cookie] library for HTTP cookie manipulation. -* [http://openresty.org/#RoutingMySQLQueriesBasedOnURIArgs Routing requests to different MySQL queries based on URI arguments] -* [http://openresty.org/#DynamicRoutingBasedOnRedis Dynamic Routing Based on Redis and Lua] -* [http://openresty.org/#UsingLuaRocks Using LuaRocks with ngx_lua] +* [https://openresty.org/#RoutingMySQLQueriesBasedOnURIArgs Routing requests to different MySQL queries based on URI arguments] +* [https://openresty.org/#DynamicRoutingBasedOnRedis Dynamic Routing Based on Redis and Lua] +* [https://openresty.org/#UsingLuaRocks Using LuaRocks with ngx_lua] * [https://github.com/openresty/lua-nginx-module/wiki/Introduction Introduction to ngx_lua] -* [https://github.com/simpl/ngx_devel_kit ngx_devel_kit] +* [https://github.com/simplresty/ngx_devel_kit ngx_devel_kit] * [[HttpEchoModule]] * [[HttpDrizzleModule]] * [https://github.com/FRiCKLE/ngx_postgres postgres-nginx-module] * [[HttpMemcModule]] -* [http://openresty.org The OpenResty bundle] +* [https://openresty.org The OpenResty bundle] * [https://github.com/openresty/nginx-systemtap-toolkit Nginx Systemtap Toolkit] = Directives = @@ -862,15 +934,33 @@ how the result will be used. Below is a diagram showing the order in which direc ![Lua Nginx Modules Directives](https://cloud.githubusercontent.com/assets/2137369/15272097/77d1c09e-1a37-11e6-97ef-d9767035fc3e.png) +== lua_load_resty_core == + +'''syntax:''' ''lua_load_resty_core on|off'' + +'''default:''' ''lua_load_resty_core on'' + +'''context:''' ''http'' + +This directive is deprecated since the v0.10.16 release of this +module. The resty.core module from +[https://github.com/openresty/lua-resty-core lua-resty-core] is now mandatorily +loaded during the Lua VM initialization. Specifying this directive will have no +effect. + +This directive was first introduced in the v0.10.15 release and +used to optionally load the resty.core module. + == lua_capture_error_log == + '''syntax:''' ''lua_capture_error_log size'' '''default:''' ''none'' '''context:''' ''http'' -Enables a buffer of the specified size for capturing all the nginx error log message data (not just those produced -by this module or the nginx http subsystem, but everything) without touching files or disks. +Enables a buffer of the specified size for capturing all the Nginx error log message data (not just those produced +by this module or the Nginx http subsystem, but everything) without touching files or disks. You can use units like `k` and `m` in the size value, as in @@ -894,34 +984,36 @@ also remove these already read from the global capturing buffer, making room for any new error log data. For this reason, the user should not configure this buffer to be too big if the user read the buffered error log data fast enough. -Note that the log level specified in the standard [http://nginx.org/r/error_log error_log] directive +Note that the log level specified in the standard [https://nginx.org/r/error_log error_log] directive ''does'' have effect on this capturing facility. It only captures log -messages of a level no lower than the specified log level in the [http://nginx.org/r/error_log error_log] directive. +messages of a level no lower than the specified log level in the [https://nginx.org/r/error_log error_log] directive. The user can still choose to set an even higher filtering log level on the fly via the Lua API function [https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/errlog.md#set_filter_level errlog.set_filter_level]. -So it is more flexible than the static [http://nginx.org/r/error_log error_log] directive. +So it is more flexible than the static [https://nginx.org/r/error_log error_log] directive. It is worth noting that there is no way to capture the debugging logs -without building OpenResty or NGINX with the ./configure +without building OpenResty or Nginx with the ./configure option --with-debug. And enabling debugging logs is strongly discouraged in production builds due to high overhead. This directive was first introduced in the v0.10.9 release. == lua_use_default_type == + '''syntax:''' ''lua_use_default_type on | off'' '''default:''' ''lua_use_default_type on'' '''context:''' ''http, server, location, location if'' -Specifies whether to use the MIME type specified by the [http://nginx.org/en/docs/http/ngx_http_core_module.html#default_type default_type] directive for the default value of the Content-Type response header. Deactivate this directive if a default Content-Type response header for Lua request handlers is not desired. +Specifies whether to use the MIME type specified by the [https://nginx.org/en/docs/http/ngx_http_core_module.html#default_type default_type] directive for the default value of the Content-Type response header. Deactivate this directive if a default Content-Type response header for Lua request handlers is not desired. This directive is turned on by default. This directive was first introduced in the v0.9.1 release. == lua_malloc_trim == + '''syntax:''' ''lua_malloc_trim '' '''default:''' ''lua_malloc_trim 1000'' @@ -929,7 +1021,7 @@ This directive was first introduced in the v0.9.1 release. '''context:''' ''http'' Asks the underlying libc runtime library to release its cached free memory back to the operating system every -N requests processed by the NGINX core. By default, N is 1000. You can configure the request count +N requests processed by the Nginx core. By default, N is 1000. You can configure the request count by using your own numbers. Smaller numbers mean more frequent releases, which may introduce higher CPU time consumption and smaller memory footprint while larger numbers usually lead to less CPU time overhead and relatively larger memory footprint. Just tune the number for your own use cases. @@ -940,8 +1032,8 @@ Configuring the argument to 0 essentially turns off the periodical lua_malloc_trim 0; # turn off trimming completely -The current implementation uses an NGINX log phase handler to do the request counting. So the appearance of the -[http://nginx.org/en/docs/http/ngx_http_core_module.html#log_subrequest log_subrequest on] directives in nginx.conf +The current implementation uses an Nginx log phase handler to do the request counting. So the appearance of the +[https://nginx.org/en/docs/http/ngx_http_core_module.html#log_subrequest log_subrequest on] directives in nginx.conf may make the counting faster when subrequests are involved. By default, only "main requests" count. Note that this directive does *not* affect the memory allocated by LuaJIT's own allocator based on the mmap @@ -982,7 +1074,26 @@ Disabling the Lua code cache is strongly discouraged for production use and should only be used during development as it has a significant negative impact on overall performance. For example, the performance of a "hello world" Lua example can drop by an order of magnitude after disabling the Lua code cache. +== lua_thread_cache_max_entries == + +'''syntax:''' ''lua_thread_cache_max_entries '' + +'''default:''' ''lua_thread_cache_max_entries 1024'' + +'''context:''' ''http'' + +Specifies the maximum number of entries allowed in the worker process level lua thread object cache. + +This cache recycles the lua thread GC objects among all our "light threads". + +A zero value of `` disables the cache. + +Note that this feature requires OpenResty's LuaJIT with the new C API `lua_resetthread`. + +This feature was first introduced in verson `v0.10.9`. + == lua_regex_cache_max_entries == + '''syntax:''' ''lua_regex_cache_max_entries '' '''default:''' ''lua_regex_cache_max_entries 1024'' @@ -1004,6 +1115,7 @@ If you are using the ngx.re.* implementation of [lua-resty-core](ht Do not activate the o option for regular expressions (and/or replace string arguments for [[#ngx.re.sub|ngx.re.sub]] and [[#ngx.re.gsub|ngx.re.gsub]]) that are generated ''on the fly'' and give rise to infinite variations to avoid hitting the specified limit. == lua_regex_match_limit == + '''syntax:''' ''lua_regex_match_limit '' '''default:''' ''lua_regex_match_limit 0'' @@ -1056,9 +1168,29 @@ As from the v0.5.0rc29 release, the special notation $prefix< '''NOTE''' Use of this directive is ''discouraged'' following the v0.9.17 release. Use the [[#init_by_lua_block|init_by_lua_block]] directive instead. -Runs the Lua code specified by the argument on the global Lua VM level when the Nginx master process (if any) is loading the Nginx config file. +Similar to the [[#init_by_lua_block|init_by_lua_block]] directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). + +For instance, + + + init_by_lua ' + print("I need no extra escaping here, for example: \r\nblah") + ' + + +This directive was first introduced in the v0.5.5 release. + +== init_by_lua_block == -When Nginx receives the HUP signal and starts reloading the config file, the Lua VM will also be re-created and init_by_lua will run again on the new Lua VM. In case that the [[#lua_code_cache|lua_code_cache]] directive is turned off (default on), the init_by_lua handler will run upon every request because in this special mode a standalone Lua VM is always created for each request. +'''syntax:''' ''init_by_lua_block { lua-script }'' + +'''context:''' ''http'' + +'''phase:''' ''loading-config'' + + +When Nginx receives the HUP signal and starts reloading the config file, the Lua VM will also be re-created and init_by_lua_block will run again on the new Lua VM. In case that the [[#lua_code_cache|lua_code_cache]] directive is turned off (default on), the init_by_lua_block handler will run upon every request because in this special mode a standalone Lua VM is always created for each request. Usually you can pre-load Lua modules at server start-up by means of this hook and take advantage of modern operating systems' copy-on-write (COW) optimization. Here is an example for pre-loading Lua modules: @@ -1070,7 +1202,7 @@ Usually you can pre-load Lua modules at server start-up by means of this hook an location = /api { content_by_lua_block { -- the following require() will just return - -- the alrady loaded module from package.loaded: + -- the already loaded module from package.loaded: ngx.say(require "cjson".encode{dog = 5, cat = 6}) } } @@ -1083,25 +1215,25 @@ You can also initialize the [[#lua_shared_dict|lua_shared_dict]] shm storage at lua_shared_dict dogs 1m; init_by_lua_block { - local dogs = ngx.shared.dogs; + local dogs = ngx.shared.dogs dogs:set("Tom", 56) } server { location = /api { content_by_lua_block { - local dogs = ngx.shared.dogs; + local dogs = ngx.shared.dogs ngx.say(dogs:get("Tom")) } } } -But note that, the [[#lua_shared_dict|lua_shared_dict]]'s shm storage will not be cleared through a config reload (via the HUP signal, for example). So if you do ''not'' want to re-initialize the shm storage in your init_by_lua code in this case, then you just need to set a custom flag in the shm storage and always check the flag in your init_by_lua code. +But note that, the [[#lua_shared_dict|lua_shared_dict]]'s shm storage will not be cleared through a config reload (via the HUP signal, for example). So if you do ''not'' want to re-initialize the shm storage in your init_by_lua_block code in this case, then you just need to set a custom flag in the shm storage and always check the flag in your init_by_lua_block code. -Because the Lua code in this context runs before Nginx forks its worker processes (if any), data or code loaded here will enjoy the [http://en.wikipedia.org/wiki/Copy-on-write Copy-on-write (COW)] feature provided by many operating systems among all the worker processes, thus saving a lot of memory. +Because the Lua code in this context runs before Nginx forks its worker processes (if any), data or code loaded here will enjoy the [https://en.wikipedia.org/wiki/Copy-on-write Copy-on-write (COW)] feature provided by many operating systems among all the worker processes, thus saving a lot of memory. -Do *not* initialize your own Lua global variables in this context because use of Lua global variables have performance penalties and can lead to global namespace pollution (see the [[#Lua_Variable_Scope|Lua Variable Scope]] section for more details). The recommended way is to use proper [http://www.lua.org/manual/5.1/manual.html#5.3 Lua module] files (but do not use the standard Lua function [http://www.lua.org/manual/5.1/manual.html#pdf-module module()] to define Lua modules because it pollutes the global namespace as well) and call [http://www.lua.org/manual/5.1/manual.html#pdf-require require()] to load your own module files in init_by_lua or other contexts ([http://www.lua.org/manual/5.1/manual.html#pdf-require require()] does cache the loaded Lua modules in the global package.loaded table in the Lua registry so your modules will only loaded once for the whole Lua VM instance). +Do *not* initialize your own Lua global variables in this context because use of Lua global variables have performance penalties and can lead to global namespace pollution (see the [[#Lua_Variable_Scope|Lua Variable Scope]] section for more details). The recommended way is to use proper [https://www.lua.org/manual/5.1/manual.html#5.3 Lua module] files (but do not use the standard Lua function [https://www.lua.org/manual/5.1/manual.html#pdf-module module()] to define Lua modules because it pollutes the global namespace as well) and call [https://www.lua.org/manual/5.1/manual.html#pdf-require require()] to load your own module files in init_by_lua_block or other contexts ([https://www.lua.org/manual/5.1/manual.html#pdf-require require()] does cache the loaded Lua modules in the global package.loaded table in the Lua registry so your modules will only loaded once for the whole Lua VM instance). Only a small set of the [[#Nginx API for Lua|Nginx API for Lua]] is supported in this context: @@ -1114,30 +1246,12 @@ Basically you can safely use Lua libraries that do blocking I/O in this very con You should be very careful about potential security vulnerabilities in your Lua code registered in this context because the Nginx master process is often run under the root account. -This directive was first introduced in the v0.5.5 release. - -== init_by_lua_block == - -'''syntax:''' ''init_by_lua_block { lua-script }'' - -'''context:''' ''http'' - -'''phase:''' ''loading-config'' - -Similar to the [[#init_by_lua|init_by_lua]] directive except that this directive inlines -the Lua source directly -inside a pair of curly braces ({}) instead of in an NGINX string literal (which requires -special character escaping). +This directive was first introduced in the v0.9.17 release. -For instance, +See also the following blog posts for more details on OpenResty and Nginx's shared memory zones: - - init_by_lua_block { - print("I need no extra escaping here, for example: \r\nblah") - } - - -This directive was first introduced in the v0.9.17 release. +* [How OpenResty and Nginx Shared Memory Zones Consume RAM](https://blog.openresty.com/en/how-nginx-shm-consume-ram/?src=gh_ngxlua) +* [Memory Fragmentation in OpenResty and Nginx's Shared Memory Zones](https://blog.openresty.com/en/nginx-shm-frag/?src=gh_ngxlua) == init_by_lua_file == @@ -1147,7 +1261,7 @@ This directive was first introduced in the v0.9.17 release. '''phase:''' ''loading-config'' -Equivalent to [[#init_by_lua|init_by_lua]], except that the file specified by contains the Lua code or [[#Lua/LuaJIT bytecode support|Lua/LuaJIT bytecode]] to be executed. +Equivalent to [[#init_by_lua_block|init_by_lua_block]], except that the file specified by contains the Lua code or [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. When a relative path like foo/bar.lua is given, they will be turned into the absolute path relative to the server prefix path determined by the -p PATH command-line option while starting the Nginx server. @@ -1163,12 +1277,35 @@ This directive was first introduced in the v0.5.5 release. '''NOTE''' Use of this directive is ''discouraged'' following the v0.9.17 release. Use the [[#init_worker_by_lua_block|init_worker_by_lua_block]] directive instead. -Runs the specified Lua code upon every Nginx worker process's startup when the master process is enabled. When the master process is disabled, this hook will just run after [[#init_by_lua|init_by_lua*]]. +Similar to the [[#init_worker_by_lua_block|init_worker_by_lua_block]] directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). -This hook is often used to create per-worker reoccurring timers (via the [[#ngx.timer.at|ngx.timer.at]] Lua API), either for backend health-check or other timed routine work. Below is an example, +For instance, init_worker_by_lua ' + print("I need no extra escaping here, for example: \r\nblah") + '; + + +This directive was first introduced in the v0.9.5 release. + +This hook no longer runs in the cache manager and cache loader processes since the v0.10.12 release. + +== init_worker_by_lua_block == + +'''syntax:''' ''init_worker_by_lua_block { lua-script }'' + +'''context:''' ''http'' + +'''phase:''' ''starting-worker'' + +Runs the specified Lua code upon every Nginx worker process's startup when the master process is enabled. When the master process is disabled, this hook will just run after [[#init_by_lua_block|init_by_lua*]]. + +This hook is often used to create per-worker reoccurring timers (via the [[#ngx.timer.at|ngx.timer.at]] Lua API), either for backend health-check or other timed routine work. Below is an example, + + + init_worker_by_lua_block { local delay = 3 -- in seconds local new_timer = ngx.timer.at local log = ngx.log @@ -1184,6 +1321,8 @@ This hook is often used to create per-worker reoccurring timers (via the [[#ngx. return end end + + -- do something in timer end local hdl, err = new_timer(delay, check) @@ -1191,51 +1330,64 @@ This hook is often used to create per-worker reoccurring timers (via the [[#ngx. log(ERR, "failed to create timer: ", err) return end - '; + + -- other job in init_worker_by_lua + } -This directive was first introduced in the v0.9.5 release. +This directive was first introduced in the v0.9.17 release. This hook no longer runs in the cache manager and cache loader processes since the v0.10.12 release. -== init_worker_by_lua_block == +== init_worker_by_lua_file == -'''syntax:''' ''init_worker_by_lua_block { lua-script }'' +'''syntax:''' ''init_worker_by_lua_file '' '''context:''' ''http'' '''phase:''' ''starting-worker'' -Similar to the [[#init_worker_by_lua|init_worker_by_lua]] directive except that this directive inlines -the Lua source directly -inside a pair of curly braces ({}) instead of in an NGINX string literal (which requires -special character escaping). +Similar to [[#init_worker_by_lua_block|init_worker_by_lua_block]], but accepts the file path to a Lua source file or Lua bytecode file. -For instance, +This directive was first introduced in the v0.9.5 release. + +This hook no longer runs in the cache manager and cache loader processes since the v0.10.12 release. + +== exit_worker_by_lua_block == + +'''syntax:''' ''exit_worker_by_lua_block { lua-script }'' + +'''context:''' ''http'' + +'''phase:''' ''exiting-worker'' + +Runs the specified Lua code upon every Nginx worker process's exit when the master process is enabled. When the master process is disabled, this hook will run before the Nginx process exits. + +This hook is often used to release resources allocated by each worker (e.g. resources allocated by [[#init_worker_by_lua_block|init_worker_by_lua*]]), or to prevent workers from exiting abnormally. + +For example, - init_worker_by_lua_block { - print("I need no extra escaping here, for example: \r\nblah") + exit_worker_by_lua_block { + print("log from exit_worker_by_lua_block") } -This directive was first introduced in the v0.9.17 release. +It's not allowed to create a timer (even a 0-delay timer) here since it runs after all timers have been processed. -This hook no longer runs in the cache manager and cache loader processes since the v0.10.12 release. +This directive was first introduced in the v0.10.18 release. -== init_worker_by_lua_file == +== exit_worker_by_lua_file == -'''syntax:''' ''init_worker_by_lua_file '' +'''syntax:''' ''exit_worker_by_lua_file '' '''context:''' ''http'' -'''phase:''' ''starting-worker'' - -Similar to [[#init_worker_by_lua|init_worker_by_lua]], but accepts the file path to a Lua source file or Lua bytecode file. +'''phase:''' ''exiting-worker'' -This directive was first introduced in the v0.9.5 release. +Similar to [[#exit_worker_by_lua_block|exit_worker_by_lua_block]], but accepts the file path to a Lua source file or Lua bytecode file. -This hook no longer runs in the cache manager and cache loader processes since the v0.10.12 release. +This directive was first introduced in the v0.10.18 release. == set_by_lua == @@ -1247,14 +1399,37 @@ This hook no longer runs in the cache manager and cache loader processes since t '''NOTE''' Use of this directive is ''discouraged'' following the v0.9.17 release. Use the [[#set_by_lua_block|set_by_lua_block]] directive instead. -Executes code specified in with optional input arguments $arg1 $arg2 ..., and returns string output to $res. -The code in can make [[#Nginx API for Lua|API calls]] and can retrieve input arguments from the ngx.arg table (index starts from 1 and increases sequentially). +Similar to the [[#set_by_lua_block|set_by_lua_block]] directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping), and +# this directive support extra arguments after the Lua script. + +For example, + + + set_by_lua $res ' return 32 + math.cos(32) '; + # $res now has the value "32.834223360507" or alike. + + +As from the v0.5.0rc29 release, Nginx variable interpolation is disabled in the argument of this directive and therefore, the dollar sign character ($) can be used directly. + +This directive requires the [https://github.com/simplresty/ngx_devel_kit ngx_devel_kit] module. + +== set_by_lua_block == + +'''syntax:''' ''set_by_lua_block $res { lua-script }'' + +'''context:''' ''server, server if, location, location if'' + +'''phase:''' ''rewrite'' + +Executes code specified inside a pair of curly braces ({}), and returns string output to $res. +The code inside a pair of curly braces ({}) can make [[#Nginx API for Lua|API calls]] and can retrieve input arguments from the ngx.arg table (index starts from 1 and increases sequentially). This directive is designed to execute short, fast running code blocks as the Nginx event loop is blocked during code execution. Time consuming code sequences should therefore be avoided. This directive is implemented by injecting custom commands into the standard [[HttpRewriteModule]]'s command list. Because [[HttpRewriteModule]] does not support nonblocking I/O in its commands, Lua APIs requiring yielding the current Lua "light thread" cannot work in this directive. -At least the following API functions are currently disabled within the context of set_by_lua: +At least the following API functions are currently disabled within the context of set_by_lua_block: * Output API functions (e.g., [[#ngx.say|ngx.say]] and [[#ngx.send_headers|ngx.send_headers]]) * Control API functions (e.g., [[#ngx.exit|ngx.exit]]) @@ -1269,13 +1444,13 @@ a time. However, a workaround is possible using the [[#ngx.var.VARIABLE|ngx.var. location /foo { set $diff ''; # we have to predefine the $diff variable here - set_by_lua $sum ' + set_by_lua_block $sum { local a = 32 local b = 56 - ngx.var.diff = a - b; -- write to $diff directly - return a + b; -- return the $sum value normally - '; + ngx.var.diff = a - b -- write to $diff directly + return a + b -- return the $sum value normally + } echo "sum = $sum, diff = $diff"; } @@ -1285,48 +1460,25 @@ This directive can be freely mixed with all directives of the [[HttpRewriteModul set $foo 32; - set_by_lua $bar 'return tonumber(ngx.var.foo) + 1'; + set_by_lua_block $bar { return tonumber(ngx.var.foo) + 1 } set $baz "bar: $bar"; # $baz == "bar: 33" -As from the v0.5.0rc29 release, Nginx variable interpolation is disabled in the argument of this directive and therefore, the dollar sign character ($) can be used directly. - -This directive requires the [https://github.com/simpl/ngx_devel_kit ngx_devel_kit] module. - -== set_by_lua_block == - -'''syntax:''' ''set_by_lua_block $res { lua-script }'' - -'''context:''' ''server, server if, location, location if'' - -'''phase:''' ''rewrite'' - -Similar to the [[#set_by_lua|set_by_lua]] directive except that - -# this directive inlines the Lua source directly -inside a pair of curly braces ({}) instead of in an NGINX string literal (which requires -special character escaping), and -# this directive does not support extra arguments after the Lua script as in [[#set_by_lua|set_by_lua]]. - -For example, - - - set_by_lua_block $res { return 32 + math.cos(32) } - # $res now has the value "32.834223360507" or alike. - - No special escaping is required in the Lua code block. +This directive requires the [https://github.com/simplresty/ngx_devel_kit ngx_devel_kit] module. + This directive was first introduced in the v0.9.17 release. == set_by_lua_file == + '''syntax:''' ''set_by_lua_file $res [$arg1 $arg2 ...]'' '''context:''' ''server, server if, location, location if'' '''phase:''' ''rewrite'' -Equivalent to [[#set_by_lua|set_by_lua]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#Lua/LuaJIT bytecode support|Lua/LuaJIT bytecode]] to be executed. +Equivalent to [[#set_by_lua_block|set_by_lua_block]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. Nginx variable interpolation is supported in the argument string of this directive. But special care must be taken for injection attacks. @@ -1337,7 +1489,7 @@ and the Nginx config must be reloaded each time the Lua source file is modified. The Lua code cache can be temporarily disabled during development by switching [[#lua_code_cache|lua_code_cache]] off in nginx.conf to avoid reloading Nginx. -This directive requires the [https://github.com/simpl/ngx_devel_kit ngx_devel_kit] module. +This directive requires the [https://github.com/simplresty/ngx_devel_kit ngx_devel_kit] module. == content_by_lua == @@ -1349,10 +1501,16 @@ This directive requires the [https://github.com/simpl/ngx_devel_kit ngx_devel_ki '''NOTE''' Use of this directive is ''discouraged'' following the v0.9.17 release. Use the [[#content_by_lua_block|content_by_lua_block]] directive instead. -Acts as a "content handler" and executes Lua code string specified in for every request. -The Lua code may make [[#Nginx API for Lua|API calls]] and is executed as a new spawned coroutine in an independent global environment (i.e. a sandbox). +Similar to the [[#content_by_lua_block|content_by_lua_block]] directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). -Do not use this directive and other content handler directives in the same location. For example, this directive and the [[HttpProxyModule#proxy_pass|proxy_pass]] directive should not be used in the same location. +For instance, + + + content_by_lua ' + ngx.say("I need no extra escaping here, for example: \r\nblah") + '; + == content_by_lua_block == @@ -1362,11 +1520,6 @@ Do not use this directive and other content handler directives in the same locat '''phase:''' ''content'' -Similar to the [[#content_by_lua|content_by_lua]] directive except that this directive inlines -the Lua source directly -inside a pair of curly braces ({}) instead of in an NGINX string literal (which requires -special character escaping). - For instance, @@ -1375,6 +1528,11 @@ For instance, } +Acts as a "content handler" and executes Lua code string specified in { lua-script } for every request. +The Lua code may make [[#Nginx API for Lua|API calls]] and is executed as a new spawned coroutine in an independent global environment (i.e. a sandbox). + +Do not use this directive and other content handler directives in the same location. For example, this directive and the [[HttpProxyModule#proxy_pass|proxy_pass]] directive should not be used in the same location. + This directive was first introduced in the v0.9.17 release. == content_by_lua_file == @@ -1385,7 +1543,7 @@ This directive was first introduced in the v0.9.17 release. '''phase:''' ''content'' -Equivalent to [[#content_by_lua|content_by_lua]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#Lua/LuaJIT bytecode support|Lua/LuaJIT bytecode]] to be executed. +Equivalent to [[#content_by_lua_block|content_by_lua_block]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. Nginx variables can be used in the string to provide flexibility. This however carries some risks and is not ordinarily recommended. @@ -1409,6 +1567,88 @@ Nginx variables are supported in the file path for dynamic dispatch, for example But be very careful about malicious user inputs and always carefully validate or filter out the user-supplied path components. +== server_rewrite_by_lua_block == + +'''syntax:''' ''server_rewrite_by_lua_block { lua-script }'' + +'''context:''' ''http, server'' + +'''phase:''' ''server rewrite'' + +Acts as a server rewrite phase handler and executes Lua code string specified in { lua-script } for every request. +The Lua code may make [[#Nginx API for Lua|API calls]] and is executed as a new spawned coroutine in an independent global environment (i.e. a sandbox). + + +server { + ... + + server_rewrite_by_lua_block { + ngx.ctx.a = "server_rewrite_by_lua_block in http" + } + + location /lua { + content_by_lua_block { + ngx.say(ngx.ctx.a) + ngx.log(ngx.INFO, ngx.ctx.a) + } + } +} + + +Just as any other rewrite phase handlers, [[#server_rewrite_by_lua_block|server_rewrite_by_lua_block]] also runs in subrequests. + + +server { + server_rewrite_by_lua_block { + ngx.log(ngx.INFO, "is_subrequest:", ngx.is_subrequest) + } + + location /lua { + content_by_lua_block { + local res = ngx.location.capture("/sub") + ngx.print(res.body) + } + } + + location /sub { + content_by_lua_block { + ngx.say("OK") + } + } +} + + +Note that when calling ngx.exit(ngx.OK) within a [[#server_rewrite_by_lua_block|server_rewrite_by_lua_block]] handler, the Nginx request processing control flow will still continue to the content handler. To terminate the current request from within a [[#server_rewrite_by_lua_block|server_rewrite_by_lua_block]] handler, call [[#ngx.exit|ngx.exit]] with status >= 200 (ngx.HTTP_OK) and status < 300 (ngx.HTTP_SPECIAL_RESPONSE) for successful quits and ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) (or its friends) for failures. + + + + server_rewrite_by_lua_block { + ngx.exit(503) + } + + location /bar { + ... + # never exec + } + + + +== server_rewrite_by_lua_file == + +'''syntax:''' ''server_rewrite_by_lua_file '' + +'''context:''' ''http, server'' + +'''phase:''' ''server rewrite'' + +Equivalent to [[#server_rewrite_by_lua_block|server_rewrite_by_lua_block]], except that the file specified by contains the Lua code, or, as from the v0.10.22 release, the [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. + +Nginx variables can be used in the string to provide flexibility. This however carries some risks and is not ordinarily recommended. + +When a relative path like foo/bar.lua is given, they will be turned into the absolute path relative to the server prefix path determined by the -p PATH command-line option while starting the Nginx server. + +When the Lua code cache is turned on (by default), the user code is loaded once at the first request and cached and the Nginx config must be reloaded each time the Lua source file is modified. The Lua code cache can be temporarily disabled during development by switching [[#lua_code_cache|lua_code_cache]] off in nginx.conf to avoid reloading Nginx. + == rewrite_by_lua == '''syntax:''' ''rewrite_by_lua '' @@ -1419,7 +1659,26 @@ But be very careful about malicious user inputs and always carefully validate or '''NOTE''' Use of this directive is ''discouraged'' following the v0.9.17 release. Use the [[#rewrite_by_lua_block|rewrite_by_lua_block]] directive instead. -Acts as a rewrite phase handler and executes Lua code string specified in for every request. +Similar to the [[#rewrite_by_lua_block|rewrite_by_lua_block]] directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). + +For instance, + + + rewrite_by_lua ' + do_something("hello, world!\nhiya\n") + '; + + +== rewrite_by_lua_block == + +'''syntax:''' ''rewrite_by_lua_block { lua-script }'' + +'''context:''' ''http, server, location, location if'' + +'''phase:''' ''rewrite tail'' + +Acts as a rewrite phase handler and executes Lua code string specified in { lua-script } for every request. The Lua code may make [[#Nginx API for Lua|API calls]] and is executed as a new spawned coroutine in an independent global environment (i.e. a sandbox). Note that this handler always runs ''after'' the standard [[HttpRewriteModule]]. So the following will work as expected: @@ -1428,12 +1687,14 @@ Note that this handler always runs ''after'' the standard [[HttpRewriteModule]]. location /foo { set $a 12; # create and initialize $a set $b ""; # create and initialize $b - rewrite_by_lua 'ngx.var.b = tonumber(ngx.var.a) + 1'; + rewrite_by_lua_block { + ngx.var.b = tonumber(ngx.var.a) + 1 + } echo "res = $b"; } -because set $a 12 and set $b "" run ''before'' [[#rewrite_by_lua|rewrite_by_lua]]. +because set $a 12 and set $b "" run ''before'' [[#rewrite_by_lua_block|rewrite_by_lua_block]]. On the other hand, the following will not work as expected: @@ -1441,7 +1702,9 @@ On the other hand, the following will not work as expected: ? location /foo { ? set $a 12; # create and initialize $a ? set $b ''; # create and initialize $b - ? rewrite_by_lua 'ngx.var.b = tonumber(ngx.var.a) + 1'; + ? rewrite_by_lua_block { + ? ngx.var.b = tonumber(ngx.var.a) + 1 + ? } ? if ($b = '13') { ? rewrite ^ /bar redirect; ? break; @@ -1451,7 +1714,7 @@ On the other hand, the following will not work as expected: ? } -because if runs ''before'' [[#rewrite_by_lua|rewrite_by_lua]] even if it is placed after [[#rewrite_by_lua|rewrite_by_lua]] in the config. +because if runs ''before'' [[#rewrite_by_lua_block|rewrite_by_lua_block]] even if it is placed after [[#rewrite_by_lua_block|rewrite_by_lua_block]] in the config. The right way of doing this is as follows: @@ -1459,18 +1722,18 @@ The right way of doing this is as follows: location /foo { set $a 12; # create and initialize $a set $b ''; # create and initialize $b - rewrite_by_lua ' + rewrite_by_lua_block { ngx.var.b = tonumber(ngx.var.a) + 1 if tonumber(ngx.var.b) == 13 then - return ngx.redirect("/bar"); + return ngx.redirect("/bar") end - '; + } echo "res = $b"; } -Note that the [http://www.grid.net.ru/nginx/eval.en.html ngx_eval] module can be approximated by using [[#rewrite_by_lua|rewrite_by_lua]]. For example, +Note that the [http://www.grid.net.ru/nginx/eval.en.html ngx_eval] module can be approximated by using [[#rewrite_by_lua_block|rewrite_by_lua_block]]. For example, location / { @@ -1495,57 +1758,38 @@ can be implemented in ngx_lua as: } location / { - rewrite_by_lua ' + rewrite_by_lua_block { local res = ngx.location.capture("/check-spam") if res.body == "spam" then return ngx.redirect("/terms-of-use.html") end - '; + } fastcgi_pass ...; } -Just as any other rewrite phase handlers, [[#rewrite_by_lua|rewrite_by_lua]] also runs in subrequests. +Just as any other rewrite phase handlers, [[#rewrite_by_lua_block|rewrite_by_lua_block]] also runs in subrequests. -Note that when calling ngx.exit(ngx.OK) within a [[#rewrite_by_lua|rewrite_by_lua]] handler, the nginx request processing control flow will still continue to the content handler. To terminate the current request from within a [[#rewrite_by_lua|rewrite_by_lua]] handler, calling [[#ngx.exit|ngx.exit]] with status >= 200 (ngx.HTTP_OK) and status < 300 (ngx.HTTP_SPECIAL_RESPONSE) for successful quits and ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) (or its friends) for failures. +Note that when calling ngx.exit(ngx.OK) within a [[#rewrite_by_lua_block|rewrite_by_lua_block]] handler, the Nginx request processing control flow will still continue to the content handler. To terminate the current request from within a [[#rewrite_by_lua_block|rewrite_by_lua_block]] handler, call [[#ngx.exit|ngx.exit]] with status >= 200 (ngx.HTTP_OK) and status < 300 (ngx.HTTP_SPECIAL_RESPONSE) for successful quits and ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) (or its friends) for failures. -If the [[HttpRewriteModule]]'s [[HttpRewriteModule#rewrite|rewrite]] directive is used to change the URI and initiate location re-lookups (internal redirections), then any [[#rewrite_by_lua|rewrite_by_lua]] or [[#rewrite_by_lua_file|rewrite_by_lua_file]] code sequences within the current location will not be executed. For example, +If the [[HttpRewriteModule]]'s [[HttpRewriteModule#rewrite|rewrite]] directive is used to change the URI and initiate location re-lookups (internal redirections), then any [[#rewrite_by_lua_block|rewrite_by_lua_block]] or [[#rewrite_by_lua_file_block|rewrite_by_lua_file_block]] code sequences within the current location will not be executed. For example, location /foo { rewrite ^ /bar; - rewrite_by_lua 'ngx.exit(503)'; + rewrite_by_lua_block { + ngx.exit(503) + } } location /bar { ... } -Here the Lua code ngx.exit(503) will never run. This will be the case if rewrite ^ /bar last is used as this will similarly initiate an internal redirection. If the break modifier is used instead, there will be no internal redirection and the rewrite_by_lua code will be executed. - -The rewrite_by_lua code will always run at the end of the rewrite request-processing phase unless [[#rewrite_by_lua_no_postpone|rewrite_by_lua_no_postpone]] is turned on. +Here the Lua code ngx.exit(503) will never run. This will be the case if rewrite ^ /bar last is used as this will similarly initiate an internal redirection. If the break modifier is used instead, there will be no internal redirection and the rewrite_by_lua_block code will be executed. -== rewrite_by_lua_block == - -'''syntax:''' ''rewrite_by_lua_block { lua-script }'' - -'''context:''' ''http, server, location, location if'' - -'''phase:''' ''rewrite tail'' - -Similar to the [[#rewrite_by_lua|rewrite_by_lua]] directive except that this directive inlines -the Lua source directly -inside a pair of curly braces ({}) instead of in an NGINX string literal (which requires -special character escaping). - -For instance, - - - rewrite_by_lua_block { - do_something("hello, world!\nhiya\n") - } - +The rewrite_by_lua_block code will always run at the end of the rewrite request-processing phase unless [[#rewrite_by_lua_no_postpone|rewrite_by_lua_no_postpone]] is turned on. This directive was first introduced in the v0.9.17 release. @@ -1557,7 +1801,7 @@ This directive was first introduced in the v0.9.17 release. '''phase:''' ''rewrite tail'' -Equivalent to [[#rewrite_by_lua|rewrite_by_lua]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#Lua/LuaJIT bytecode support|Lua/LuaJIT bytecode]] to be executed. +Equivalent to [[#rewrite_by_lua_block|rewrite_by_lua_block]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. Nginx variables can be used in the string to provide flexibility. This however carries some risks and is not ordinarily recommended. @@ -1579,7 +1823,26 @@ Nginx variables are supported in the file path for dynamic dispatch just as in [ '''NOTE''' Use of this directive is ''discouraged'' following the v0.9.17 release. Use the [[#access_by_lua_block|access_by_lua_block]] directive instead. -Acts as an access phase handler and executes Lua code string specified in for every request. +Similar to the [[#access_by_lua_block|access_by_lua_block]] directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). + +For instance, + + + access_by_lua ' + do_something("hello, world!\nhiya\n") + '; + + +== access_by_lua_block == + +'''syntax:''' ''access_by_lua_block { lua-script }'' + +'''context:''' ''http, server, location, location if'' + +'''phase:''' ''access tail'' + +Acts as an access phase handler and executes Lua code string specified in { for every request. The Lua code may make [[#Nginx API for Lua|API calls]] and is executed as a new spawned coroutine in an independent global environment (i.e. a sandbox). Note that this handler always runs ''after'' the standard [[HttpAccessModule]]. So the following will work as expected: @@ -1591,18 +1854,18 @@ Note that this handler always runs ''after'' the standard [[HttpAccessModule]]. allow 10.1.1.0/16; deny all; - access_by_lua ' + access_by_lua_block { local res = ngx.location.capture("/mysql", { ... }) ... - '; + } # proxy_pass/fastcgi_pass/... } -That is, if a client IP address is in the blacklist, it will be denied before the MySQL query for more complex authentication is executed by [[#access_by_lua|access_by_lua]]. +That is, if a client IP address is in the blacklist, it will be denied before the MySQL query for more complex authentication is executed by [[#access_by_lua_block|access_by_lua_block]]. -Note that the [http://mdounin.ru/hg/ngx_http_auth_request_module/ ngx_auth_request] module can be approximated by using [[#access_by_lua|access_by_lua]]: +Note that the [http://mdounin.ru/hg/ngx_http_auth_request_module/ ngx_auth_request] module can be approximated by using [[#access_by_lua_block|access_by_lua_block]]: location / { @@ -1616,7 +1879,7 @@ can be implemented in ngx_lua as: location / { - access_by_lua ' + access_by_lua_block { local res = ngx.location.capture("/auth") if res.status == ngx.HTTP_OK then @@ -1628,40 +1891,19 @@ can be implemented in ngx_lua as: end ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) - '; + } # proxy_pass/fastcgi_pass/postgres_pass/... } -As with other access phase handlers, [[#access_by_lua|access_by_lua]] will ''not'' run in subrequests. +As with other access phase handlers, [[#access_by_lua_block|access_by_lua_block]] will ''not'' run in subrequests. -Note that when calling ngx.exit(ngx.OK) within a [[#access_by_lua|access_by_lua]] handler, the nginx request processing control flow will still continue to the content handler. To terminate the current request from within a [[#access_by_lua|access_by_lua]] handler, calling [[#ngx.exit|ngx.exit]] with status >= 200 (ngx.HTTP_OK) and status < 300 (ngx.HTTP_SPECIAL_RESPONSE) for successful quits and ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) (or its friends) for failures. +Note that when calling ngx.exit(ngx.OK) within a [[#access_by_lua_block|access_by_lua_block]] handler, the Nginx request processing control flow will still continue to the content handler. To terminate the current request from within a [[#access_by_lua_block|access_by_lua_block]] handler, call [[#ngx.exit|ngx.exit]] with status >= 200 (ngx.HTTP_OK) and status < 300 (ngx.HTTP_SPECIAL_RESPONSE) for successful quits and ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) (or its friends) for failures. Starting from the v0.9.20 release, you can use the [[#access_by_lua_no_postpone|access_by_lua_no_postpone]] directive to control when to run this handler inside the "access" request-processing phase -of NGINX. - -== access_by_lua_block == - -'''syntax:''' ''access_by_lua_block { lua-script }'' - -'''context:''' ''http, server, location, location if'' - -'''phase:''' ''access tail'' - -Similar to the [[#access_by_lua|access_by_lua]] directive except that this directive inlines -the Lua source directly -inside a pair of curly braces ({}) instead of in an NGINX string literal (which requires -special character escaping). - -For instance, - - - access_by_lua_block { - do_something("hello, world!\nhiya\n") - } - +of Nginx. This directive was first introduced in the v0.9.17 release. @@ -1673,7 +1915,7 @@ This directive was first introduced in the v0.9.17 release. '''phase:''' ''access tail'' -Equivalent to [[#access_by_lua|access_by_lua]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#Lua/LuaJIT bytecode support|Lua/LuaJIT bytecode]] to be executed. +Equivalent to [[#access_by_lua_block|access_by_lua_block]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. Nginx variables can be used in the string to provide flexibility. This however carries some risks and is not ordinarily recommended. @@ -1695,22 +1937,15 @@ Nginx variables are supported in the file path for dynamic dispatch just as in [ '''NOTE''' Use of this directive is ''discouraged'' following the v0.9.17 release. Use the [[#header_filter_by_lua_block|header_filter_by_lua_block]] directive instead. -Uses Lua code specified in to define an output header filter. - -Note that the following API functions are currently disabled within this context: - -* Output API functions (e.g., [[#ngx.say|ngx.say]] and [[#ngx.send_headers|ngx.send_headers]]) -* Control API functions (e.g., [[#ngx.redirect|ngx.redirect]] and [[#ngx.exec|ngx.exec]]) -* Subrequest API functions (e.g., [[#ngx.location.capture|ngx.location.capture]] and [[#ngx.location.capture_multi|ngx.location.capture_multi]]) -* Cosocket API functions (e.g., [[#ngx.socket.tcp|ngx.socket.tcp]] and [[#ngx.req.socket|ngx.req.socket]]). +Similar to the [[#header_filter_by_lua_block|header_filter_by_lua_block]] directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). -Here is an example of overriding a response header (or adding one if absent) in our Lua header filter: +For instance, - location / { - proxy_pass http://mybackend; - header_filter_by_lua 'ngx.header.Foo = "blah"'; - } + header_filter_by_lua ' + ngx.header["content-length"] = nil + '; This directive was first introduced in the v0.2.1rc20 release. @@ -1723,16 +1958,23 @@ This directive was first introduced in the v0.2.1rc20 release. '''phase:''' ''output-header-filter'' -Similar to the [[#header_filter_by_lua|header_filter_by_lua]] directive except that this directive inlines -the Lua source directly -inside a pair of curly braces ({}) instead of in an NGINX string literal (which requires -special character escaping). +Uses Lua code specified in { lua-script } to define an output header filter. -For instance, +Note that the following API functions are currently disabled within this context: + +* Output API functions (e.g., [[#ngx.say|ngx.say]] and [[#ngx.send_headers|ngx.send_headers]]) +* Control API functions (e.g., [[#ngx.redirect|ngx.redirect]] and [[#ngx.exec|ngx.exec]]) +* Subrequest API functions (e.g., [[#ngx.location.capture|ngx.location.capture]] and [[#ngx.location.capture_multi|ngx.location.capture_multi]]) +* Cosocket API functions (e.g., [[#ngx.socket.tcp|ngx.socket.tcp]] and [[#ngx.req.socket|ngx.req.socket]]). + +Here is an example of overriding a response header (or adding one if absent) in our Lua header filter: - header_filter_by_lua_block { - ngx.header["content-length"] = nil + location / { + proxy_pass http://mybackend; + header_filter_by_lua_block { + ngx.header.Foo = "blah" + } } @@ -1746,7 +1988,7 @@ This directive was first introduced in the v0.9.17 release. '''phase:''' ''output-header-filter'' -Equivalent to [[#header_filter_by_lua|header_filter_by_lua]], except that the file specified by contains the Lua code, or as from the v0.5.0rc32 release, the [[#Lua/LuaJIT bytecode support|Lua/LuaJIT bytecode]] to be executed. +Equivalent to [[#header_filter_by_lua_block|header_filter_by_lua_block]], except that the file specified by contains the Lua code, or as from the v0.5.0rc32 release, the [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. When a relative path like foo/bar.lua is given, they will be turned into the absolute path relative to the server prefix path determined by the -p PATH command-line option while starting the Nginx server. @@ -1762,7 +2004,28 @@ This directive was first introduced in the v0.2.1rc20 release. '''NOTE''' Use of this directive is ''discouraged'' following the v0.9.17 release. Use the [[#body_filter_by_lua_block|body_filter_by_lua_block]] directive instead. -Uses Lua code specified in to define an output body filter. +Similar to the [[#body_filter_by_lua_block|body_filter_by_lua_block]] directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). + +For instance, + + + body_filter_by_lua ' + local data, eof = ngx.arg[1], ngx.arg[2] + '; + + +This directive was first introduced in the v0.5.0rc32 release. + +== body_filter_by_lua_block == + +'''syntax:''' ''body_filter_by_lua_block { lua-script-str }'' + +'''context:''' ''http, server, location, location if'' + +'''phase:''' ''output-body-filter'' + +Uses Lua code specified in { lua-script } to define an output body filter. The input data chunk is passed via [[#ngx.arg|ngx.arg]][1] (as a Lua string value) and the "eof" flag indicating the end of the response body data stream is passed via [[#ngx.arg|ngx.arg]][2] (as a Lua boolean value). @@ -1781,7 +2044,9 @@ The Lua code can pass its own modified version of the input data chunk to the do location / { proxy_pass http://mybackend; - body_filter_by_lua 'ngx.arg[1] = string.upper(ngx.arg[1])'; + body_filter_by_lua_block { + ngx.arg[1] = string.upper(ngx.arg[1]) + } } @@ -1794,7 +2059,7 @@ Likewise, new "eof" flag can also be specified by setting a boolean value to [[# echo hello world; echo hiya globe; - body_filter_by_lua ' + body_filter_by_lua_block { local chunk = ngx.arg[1] if string.match(chunk, "hello") then ngx.arg[2] = true -- new eof @@ -1803,7 +2068,7 @@ Likewise, new "eof" flag can also be specified by setting a boolean value to [[# -- just throw away any remaining chunk data ngx.arg[1] = nil - '; + } } @@ -1821,12 +2086,16 @@ When the Lua code may change the length of the response body, then it is require location /foo { # fastcgi_pass/proxy_pass/... - header_filter_by_lua_block { ngx.header.content_length = nil } - body_filter_by_lua 'ngx.arg[1] = string.len(ngx.arg[1]) .. "\\n"'; + header_filter_by_lua_block { + ngx.header.content_length = nil + } + body_filter_by_lua_block { + ngx.arg[1] = string.len(ngx.arg[1]) .. "\n" + } } -Note that the following API functions are currently disabled within this context due to the limitations in NGINX output filter's current implementation: +Note that the following API functions are currently disabled within this context due to the limitations in Nginx output filter's current implementation: * Output API functions (e.g., [[#ngx.say|ngx.say]] and [[#ngx.send_headers|ngx.send_headers]]) * Control API functions (e.g., [[#ngx.exit|ngx.exit]] and [[#ngx.exec|ngx.exec]]) @@ -1835,29 +2104,6 @@ Note that the following API functions are currently disabled within this context Nginx output filters may be called multiple times for a single request because response body may be delivered in chunks. Thus, the Lua code specified by in this directive may also run multiple times in the lifetime of a single HTTP request. -This directive was first introduced in the v0.5.0rc32 release. - -== body_filter_by_lua_block == - -'''syntax:''' ''body_filter_by_lua_block { lua-script-str }'' - -'''context:''' ''http, server, location, location if'' - -'''phase:''' ''output-body-filter'' - -Similar to the [[#body_filter_by_lua|body_filter_by_lua]] directive except that this directive inlines -the Lua source directly -inside a pair of curly braces ({}) instead of in an NGINX string literal (which requires -special character escaping). - -For instance, - - - body_filter_by_lua_block { - local data, eof = ngx.arg[1], ngx.arg[2] - } - - This directive was first introduced in the v0.9.17 release. == body_filter_by_lua_file == @@ -1868,7 +2114,7 @@ This directive was first introduced in the v0.9.17 release. '''phase:''' ''output-body-filter'' -Equivalent to [[#body_filter_by_lua|body_filter_by_lua]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#Lua/LuaJIT bytecode support|Lua/LuaJIT bytecode]] to be executed. +Equivalent to [[#body_filter_by_lua_block|body_filter_by_lua_block]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. When a relative path like foo/bar.lua is given, they will be turned into the absolute path relative to the server prefix path determined by the -p PATH command-line option while starting the Nginx server. @@ -1884,7 +2130,28 @@ This directive was first introduced in the v0.5.0rc32 release. '''NOTE''' Use of this directive is ''discouraged'' following the v0.9.17 release. Use the [[#log_by_lua_block|log_by_lua_block]] directive instead. -Runs the Lua source code inlined as the at the log request processing phase. This does not replace the current access logs, but runs before. +Similar to the [[#log_by_lua_block|log_by_lua_block]] directive, but accepts the Lua source directly in an Nginx string literal (which requires +special character escaping). + +For instance, + + + log_by_lua ' + print("I need no extra escaping here, for example: \r\nblah") + '; + + +This directive was first introduced in the v0.5.0rc31 release. + +== log_by_lua_block == + +'''syntax:''' ''log_by_lua_block { lua-script }'' + +'''context:''' ''http, server, location, location if'' + +'''phase:''' ''log'' + +Runs the Lua source code inlined as the { lua-script } at the log request processing phase. This does not replace the current access logs, but runs before. Note that the following API functions are currently disabled within this context: @@ -1902,7 +2169,7 @@ Here is an example of gathering average data for [[HttpUpstreamModule#$upstream_ location / { proxy_pass http://mybackend; - log_by_lua ' + log_by_lua_block { local log_dict = ngx.shared.log_dict local upstream_time = tonumber(ngx.var.upstream_response_time) @@ -1915,7 +2182,7 @@ Here is an example of gathering average data for [[HttpUpstreamModule#$upstream_ log_dict:add("upstream_time-nb", 0) log_dict:incr("upstream_time-nb", 1) end - '; + } } location = /status { @@ -1935,29 +2202,6 @@ Here is an example of gathering average data for [[HttpUpstreamModule#$upstream_ } -This directive was first introduced in the v0.5.0rc31 release. - -== log_by_lua_block == - -'''syntax:''' ''log_by_lua_block { lua-script }'' - -'''context:''' ''http, server, location, location if'' - -'''phase:''' ''log'' - -Similar to the [[#log_by_lua|log_by_lua]] directive except that this directive inlines -the Lua source directly -inside a pair of curly braces ({}) instead of in an NGINX string literal (which requires -special character escaping). - -For instance, - - - log_by_lua_block { - print("I need no extra escaping here, for example: \r\nblah") - } - - This directive was first introduced in the v0.9.17 release. == log_by_lua_file == @@ -1968,7 +2212,7 @@ This directive was first introduced in the v0.9.17 release. '''phase:''' ''log'' -Equivalent to [[#log_by_lua|log_by_lua]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#Lua/LuaJIT bytecode support|Lua/LuaJIT bytecode]] to be executed. +Equivalent to [[#log_by_lua_block|log_by_lua_block]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. When a relative path like foo/bar.lua is given, they will be turned into the absolute path relative to the server prefix path determined by the -p PATH command-line option while starting the Nginx server. @@ -2003,13 +2247,13 @@ For instance, } -The resulting Lua load balancer can work with any existing nginx upstream modules -like [http://nginx.org/en/docs/http/ngx_http_proxy_module.html ngx_proxy] and -[http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html ngx_fastcgi]. +The resulting Lua load balancer can work with any existing Nginx upstream modules +like [https://nginx.org/en/docs/http/ngx_http_proxy_module.html ngx_proxy] and +[https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html ngx_fastcgi]. Also, the Lua load balancer can work with the standard upstream connection pool mechanism, -i.e., the standard [http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive keepalive] directive. -Just ensure that the [http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive keepalive] directive +i.e., the standard [https://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive keepalive] directive. +Just ensure that the [https://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive keepalive] directive is used *after* this balancer_by_lua_block directive in a single upstream {} configuration block. The Lua load balancer can totally ignore the list of servers defined in the upstream {} block @@ -2018,8 +2262,8 @@ and select peer from a completely dynamic server list (even changing per request from the [https://github.com/openresty/lua-resty-core lua-resty-core] library. The Lua code handler registered by this directive might get called more than once in a single -downstream request when the nginx upstream mechanism retries the request on conditions -specified by directives like the [http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream proxy_next_upstream] +downstream request when the Nginx upstream mechanism retries the request on conditions +specified by directives like the [https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream proxy_next_upstream] directive. This Lua code execution context does not support yielding, so Lua APIs that may yield @@ -2038,11 +2282,31 @@ This directive was first introduced in the v0.10.0 release. '''phase:''' ''content'' -Equivalent to [[#balancer_by_lua_block|balancer_by_lua_block]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#Lua/LuaJIT bytecode support|Lua/LuaJIT bytecode]] to be executed. +Equivalent to [[#balancer_by_lua_block|balancer_by_lua_block]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. When a relative path like foo/bar.lua is given, they will be turned into the absolute path relative to the server prefix path determined by the -p PATH command-line option while starting the Nginx server. -This directive was first introduced in the v0.10.0 release. +This directive was first introduced in the v0.10.0 release. + +== balancer_keepalive == + +'''syntax:''' ''balancer_keepalive '' + +'''context:''' ''upstream'' + +'''phase:''' ''loading-config'' + +The total-connections parameter sets the maximum number of idle +keepalive connections to upstream servers that are preserved in the cache of +each worker process. When this number is exceeded, the least recently used +connections are closed. + +It should be particularly noted that the keepalive directive does not limit the +total number of connections to upstream servers that an nginx worker process +can open. The connections parameter should be set to a number small enough to +let upstream servers process new incoming connections as well. + +This directive was first introduced in the v0.10.21 release. == lua_need_request_body == @@ -2054,7 +2318,7 @@ This directive was first introduced in the v0.10.0 release. '''phase:''' ''depends on usage'' -Determines whether to force the request body data to be read before running rewrite/access/access_by_lua* or not. The Nginx core does not read the client request body by default and if request body data is required, then this directive should be turned on or the [[#ngx.req.read_body|ngx.req.read_body]] function should be called within the Lua code. +Determines whether to force the request body data to be read before running rewrite/access/content_by_lua* or not. The Nginx core does not read the client request body by default and if request body data is required, then this directive should be turned on or the [[#ngx.req.read_body|ngx.req.read_body]] function should be called within the Lua code. To read the request body data within the [[HttpCoreModule#$request_body|$request_body]] variable, [[HttpCoreModule#client_body_buffer_size|client_body_buffer_size]] must have the same value as [[HttpCoreModule#client_max_body_size|client_max_body_size]]. Because when the content length exceeds [[HttpCoreModule#client_body_buffer_size|client_body_buffer_size]] but less than [[HttpCoreModule#client_max_body_size|client_max_body_size]], Nginx will buffer the data into a temporary file on the disk, which will lead to empty value in the [[HttpCoreModule#$request_body|$request_body]] variable. @@ -2069,6 +2333,118 @@ It is recommended however, to use the [[#ngx.req.read_body|ngx.req.read_body]] a This also applies to [[#access_by_lua|access_by_lua*]]. +== ssl_client_hello_by_lua_block == + +'''syntax:''' ''ssl_client_hello_by_lua_block { lua-script }'' + +'''context:''' ''http, server'' + +'''phase:''' ''right-after-client-hello-message-was-processed'' + +This directive runs user Lua code when Nginx is about to post-process the SSL client hello message for the downstream +SSL (https) connections. + +It is particularly useful for dynamically setting the SSL protocols according to the SNI. + +It is also useful to do some custom operations according to the per-connection information in the client hello message. + +For example, one can parse custom client hello extension and do the corresponding handling in pure Lua. + +This Lua handler will always run whether the SSL session is resumed (via SSL session IDs or TLS session tickets) or not. +While the ssl_certificate_by_lua* Lua handler will only runs when initiating a full SSL handshake. + +The [https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/clienthello.md ngx.ssl.clienthello] Lua modules +provided by the [https://github.com/openresty/lua-resty-core/#readme lua-resty-core] +library are particularly useful in this context. + +Note that this handler runs in extremely early stage of SSL handshake, before the SSL client hello extensions are parsed. +So you can not use some Lua API like ssl.server_name() which is dependent on the later stage's processing. + +Also note that only the directive in default server is valid for several virtual servers with the same IP address and port. + +Below is a trivial example using the +[https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/clienthello.md ngx.ssl.clienthello] module +at the same time: + + +server { + listen 443 ssl; + server_name test.com; + ssl_certificate /path/to/cert.crt; + ssl_certificate_key /path/to/key.key; + ssl_client_hello_by_lua_block { + local ssl_clt = require "ngx.ssl.clienthello" + local host, err = ssl_clt.get_client_hello_server_name() + if host == "test.com" then + ssl_clt.set_protocols({"TLSv1", "TLSv1.1"}) + elseif host == "test2.com" then + ssl_clt.set_protocols({"TLSv1.2", "TLSv1.3"}) + elseif not host then + ngx.log(ngx.ERR, "failed to get the SNI name: ", err) + ngx.exit(ngx.ERROR) + else + ngx.log(ngx.ERR, "unknown SNI name: ", host) + ngx.exit(ngx.ERROR) + end + } + ... +} +server { + listen 443 ssl; + server_name test2.com; + ssl_certificate /path/to/cert.crt; + ssl_certificate_key /path/to/key.key; + ... +} + + +See more information in the [https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/clienthello.md ngx.ssl.clienthello] +Lua modules' official documentation. + +Uncaught Lua exceptions in the user Lua code immediately abort the current SSL session, so does the +[[#ngx.exit|ngx.exit]] call with an error code like ngx.ERROR. + +This Lua code execution context *does* support yielding, so Lua APIs that may yield +(like cosockets, sleeping, and "light threads") +are enabled in this context + +Note, you need to configure the [https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate ssl_certificate] +and [https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_key ssl_certificate_key] +to avoid the following error while starting NGINX: + + + nginx: [emerg] no ssl configured for the server + + +This directive requires OpenSSL 1.1.1 or greater. + +If you are using the [official pre-built +packages](https://openresty.org/en/linux-packages.html) for +[OpenResty](https://openresty.org/) 1.21.4.1 or later, then everything should +work out of the box. + +If you are not using the Nginx core shipped with +[OpenResty](https://openresty.org) 1.21.4.1 or later, you will need to apply +patches to the standard Nginx core: + +https://openresty.org/en/nginx-ssl-patches.html + +This directive was first introduced in the v0.10.21 release. + +== ssl_client_hello_by_lua_file == + +'''syntax:''' ''ssl_client_hello_by_lua_file '' + +'''context:''' ''http, server'' + +'''phase:''' ''right-after-client-hello-message-was-processed'' + +Equivalent to [[#ssl_client_hello_by_lua_block|ssl_client_hello_by_lua_block]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. + +When a relative path like foo/bar.lua is given, they will be turned into the absolute path relative to the server prefix path determined by the -p PATH command-line option while starting the Nginx server. + +This directive was first introduced in the v0.10.21 release. + == ssl_certificate_by_lua_block == '''syntax:''' ''ssl_certificate_by_lua_block { lua-script }'' @@ -2077,7 +2453,7 @@ This also applies to [[#access_by_lua|access_by_lua*]]. '''phase:''' ''right-before-SSL-handshake'' -This directive runs user Lua code when NGINX is about to start the SSL handshake for the downstream +This directive runs user Lua code when Nginx is about to start the SSL handshake for the downstream SSL (https) connections. It is particularly useful for setting the SSL certificate chain and the corresponding private key on a per-request @@ -2099,9 +2475,9 @@ library are particularly useful in this context. You can use the Lua API offered to manipulate the SSL certificate chain and private key for the current SSL connection being initiated. -This Lua handler does not run at all, however, when NGINX/OpenSSL successfully resumes +This Lua handler does not run at all, however, when Nginx/OpenSSL successfully resumes the SSL session via SSL session IDs or TLS session tickets for the current SSL connection. In -other words, this Lua handler only runs when NGINX has to initiate a full SSL handshake. +other words, this Lua handler only runs when Nginx has to initiate a full SSL handshake. Below is a trivial example using the [https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md ngx.ssl] module @@ -2133,8 +2509,8 @@ This Lua code execution context *does* support yielding, so Lua APIs that may yi (like cosockets, sleeping, and "light threads") are enabled in this context. -Note, however, you still need to configure the [http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate ssl_certificate] and -[http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_key ssl_certificate_key] +Note, however, you still need to configure the [https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate ssl_certificate] and +[https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_key ssl_certificate_key] directives even though you will not use this static certificate and private key at all. This is because the NGINX core requires their appearance otherwise you are seeing the following error while starting NGINX: @@ -2143,14 +2519,18 @@ while starting NGINX: nginx: [emerg] no ssl configured for the server -This directive currently requires the following NGINX core patch to work correctly: +This directive requires OpenSSL 1.0.2e or greater. -http://mailman.nginx.org/pipermail/nginx-devel/2016-January/007748.html +If you are using the [official pre-built +packages](https://openresty.org/en/linux-packages.html) for +[OpenResty](https://openresty.org/) 1.9.7.2 or later, then everything should +work out of the box. -The bundled version of the NGINX core in OpenResty 1.9.7.2 (or above) already has this -patch applied. +If you are not using the Nginx core shipped with +[OpenResty](https://openresty.org) 1.9.7.2 or later, you will need to apply +patches to the standard Nginx core: -Furthermore, one needs at least OpenSSL 1.0.2e for this directive to work. +https://openresty.org/en/nginx-ssl-patches.html This directive was first introduced in the v0.10.0 release. @@ -2162,7 +2542,7 @@ This directive was first introduced in the v0.10.0 release. '''phase:''' ''right-before-SSL-handshake'' -Equivalent to [[#ssl_certificate_by_lua_block|ssl_certificate_by_lua_block]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#Lua/LuaJIT bytecode support|Lua/LuaJIT bytecode]] to be executed. +Equivalent to [[#ssl_certificate_by_lua_block|ssl_certificate_by_lua_block]], except that the file specified by contains the Lua code, or, as from the v0.5.0rc32 release, the [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. When a relative path like foo/bar.lua is given, they will be turned into the absolute path relative to the server prefix path determined by the -p PATH command-line option while starting the Nginx server. @@ -2196,14 +2576,14 @@ SSL session resumption can then get immediately initiated and bypass the full SS Please note that TLS session tickets are very different and it is the clients' responsibility to cache the SSL session state when session tickets are used. SSL session resumptions based on TLS session tickets would happen automatically without going through this hook (nor the -[[#ssl_session_store_by_lua*|ssl_session_store_by_lua_block]] hook). This hook is mainly +[[#ssl_session_store_by_lua_block|ssl_session_store_by_lua*]] hook). This hook is mainly for older or less capable SSL clients that can only do SSL sessions by session IDs. When [[#ssl_certificate_by_lua_block|ssl_certificate_by_lua*]] is specified at the same time, this hook usually runs before [[#ssl_certificate_by_lua_block|ssl_certificate_by_lua*]]. When the SSL session is found and successfully loaded for the current SSL connection, SSL session resumption will happen and thus bypass the [[#ssl_certificate_by_lua_block|ssl_certificate_by_lua*]] -hook completely. In this case, NGINX also bypasses the [[#ssl_session_store_by_lua*|ssl_session_store_by_lua_block]] +hook completely. In this case, Nginx also bypasses the [[#ssl_session_store_by_lua_block|ssl_session_store_by_lua*]] hook, for obvious reasons. To easily test this hook locally with a modern web browser, you can temporarily put the following line @@ -2213,23 +2593,27 @@ in your https server block to disable the TLS session ticket support: But do not forget to comment this line out before publishing your site to the world. -If you are using the [official pre-built packages](http://openresty.org/en/linux-packages.html) for [OpenResty](https://openresty.org/) +If you are using the [official pre-built packages](https://openresty.org/en/linux-packages.html) for [OpenResty](https://openresty.org/) 1.11.2.1 or later, then everything should work out of the box. -If you are using OpenSSL libraries not provided by [OpenResty](https://openresty.org), -then you need to apply the following patch for OpenSSL 1.0.2h or later: +If you are not using one of the [OpenSSL +packages](https://openresty.org/en/linux-packages.html) provided by +[OpenResty](https://openresty.org), you will need to apply patches to OpenSSL +in order to use this directive: -https://github.com/openresty/openresty/blob/master/patches/openssl-1.0.2h-sess_set_get_cb_yield.patch +https://openresty.org/en/openssl-patches.html -If you are not using the NGINX core shipped with [OpenResty](https://openresty.org) 1.11.2.1 or later, then you need to -apply the following patch to the standard NGINX core 1.11.2 or later: +Similarly, if you are not using the Nginx core shipped with +[OpenResty](https://openresty.org) 1.11.2.1 or later, you will need to apply +patches to the standard Nginx core: -http://openresty.org/download/nginx-1.11.2-nonblocking_ssl_handshake_hooks.patch +https://openresty.org/en/nginx-ssl-patches.html This directive was first introduced in the v0.10.6 release. -Note that: this directive is only allowed to used in '''http context''' from the v0.10.7 release -(because SSL session resumption happens before server name dispatch). +Note that this directive can only be used in the '''http context''' starting +with the v0.10.7 release since SSL session resumption happens +before server name dispatch. == ssl_session_fetch_by_lua_file == @@ -2239,7 +2623,7 @@ Note that: this directive is only allowed to used in '''http context''' from the '''phase:''' ''right-before-SSL-handshake'' -Equivalent to [[#ssl_session_fetch_by_lua_block|ssl_session_fetch_by_lua_block]], except that the file specified by contains the Lua code, or rather, the [[#Lua/LuaJIT bytecode support|Lua/LuaJIT bytecode]] to be executed. +Equivalent to [[#ssl_session_fetch_by_lua_block|ssl_session_fetch_by_lua_block]], except that the file specified by contains the Lua code, or rather, the [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. When a relative path like foo/bar.lua is given, they will be turned into the absolute path relative to the server prefix path determined by the -p PATH command-line option while starting the Nginx server. @@ -2290,7 +2674,7 @@ Note that: this directive is only allowed to used in '''http context''' from the '''phase:''' ''right-after-SSL-handshake'' -Equivalent to [[#ssl_session_store_by_lua_block|ssl_session_store_by_lua_block]], except that the file specified by contains the Lua code, or rather, the [[#Lua/LuaJIT bytecode support|Lua/LuaJIT bytecode]] to be executed. +Equivalent to [[#ssl_session_store_by_lua_block|ssl_session_store_by_lua_block]], except that the file specified by contains the Lua code, or rather, the [[#LuaJIT bytecode support|LuaJIT bytecode]] to be executed. When a relative path like foo/bar.lua is given, they will be turned into the absolute path relative to the server prefix path determined by the -p PATH command-line option while starting the Nginx server. @@ -2311,7 +2695,7 @@ Note that: this directive is only allowed to used in '''http context''' from the Declares a shared memory zone, , to serve as storage for the shm based Lua dictionary ngx.shared.. -Shared memory zones are always shared by all the nginx worker processes in the current nginx server instance. +Shared memory zones are always shared by all the Nginx worker processes in the current Nginx server instance. The argument accepts size units such as k and m: @@ -2411,7 +2795,7 @@ Default to 30 connections for every pool. When the connection pool exceeds the available size limit, the least recently used (idle) connection already in the pool will be closed to make room for the current connection. -Note that the cosocket connection pool is per nginx worker process rather than per nginx server instance, so size limit specified here also applies to every single nginx worker process. +Note that the cosocket connection pool is per Nginx worker process rather than per Nginx server instance, so size limit specified here also applies to every single Nginx worker process. This directive was first introduced in the v0.5.0rc1 release. @@ -2437,7 +2821,7 @@ This directive was first introduced in the v0.5.0rc1 release. '''context:''' ''http, server, location'' -This directive can be used to toggle error logging when a failure occurs for the TCP or UDP cosockets. If you are already doing proper error handling and logging in your Lua code, then it is recommended to turn this directive off to prevent data flushing in your nginx error log files (which is usually rather expensive). +This directive can be used to toggle error logging when a failure occurs for the TCP or UDP cosockets. If you are already doing proper error handling and logging in your Lua code, then it is recommended to turn this directive off to prevent data flushing in your Nginx error log files (which is usually rather expensive). This directive was first introduced in the v0.5.13 release. @@ -2469,7 +2853,7 @@ This directive was first introduced in the v0.9.11 release. == lua_ssl_protocols == -'''syntax:''' ''lua_ssl_protocols [SSLv2] [SSLv3] [TLSv1] [TLSv1.1] [TLSv1.2]'' +'''syntax:''' ''lua_ssl_protocols [SSLv2] [SSLv3] [TLSv1] [TLSv1.1] [TLSv1.2] [TLSv1.3]'' '''default:''' ''lua_ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2'' @@ -2477,13 +2861,47 @@ This directive was first introduced in the v0.9.11 release. Enables the specified protocols for requests to a SSL/TLS server in the [[#tcpsock:sslhandshake|tcpsock:sslhandshake]] method. +The support for the TLSv1.3 parameter requires version v0.10.12 *and* OpenSSL 1.1.1. + This directive was first introduced in the v0.9.11 release. +== lua_ssl_certificate == + +'''syntax:''' ''lua_ssl_certificate '' + +'''default:''' ''none'' + +'''context:''' ''http, server, location'' + +Specifies the file path to the SSL/TLS certificate in PEM format used for the [[#tcpsock:sslhandshake|tcpsock:sslhandshake]] method. + +This directive allows you to specify the SSL/TLS certificate that will be presented to server during the SSL/TLS handshake process. + +This directive was first introduced in the v0.10.26 release. + +See also [[#lua_ssl_certificate_key|lua_ssl_certificate_key]] and [[#lua_ssl_verify_depth|lua_ssl_verify_depth]]. + +== lua_ssl_certificate_key == + +'''syntax:''' ''lua_ssl_certificate_key '' + +'''default:''' ''none'' + +'''context:''' ''http, server, location'' + +Specifies the file path to the private key associated with the SSL/TLS certificate used in the [[#tcpsock:sslhandshake|tcpsock:sslhandshake]] method. + +This directive allows you to specify the private key file corresponding to the SSL/TLS certificate specified by lua_ssl_certificate. The private key should be in PEM format and must match the certificate. + +This directive was first introduced in the v0.10.26 release. + +See also [[#lua_ssl_certificate|lua_ssl_certificate]] and [[#lua_ssl_verify_depth|lua_ssl_verify_depth]]. + == lua_ssl_trusted_certificate == '''syntax:''' ''lua_ssl_trusted_certificate '' -'''default:''' ''no'' +'''default:''' ''none'' '''context:''' ''http, server, location'' @@ -2505,7 +2923,57 @@ Sets the verification depth in the server certificates chain. This directive was first introduced in the v0.9.11 release. -See also [[#lua_ssl_trusted_certificate|lua_ssl_trusted_certificate]]. +See also [[#lua_ssl_certificate|lua_ssl_certificate]], [[#lua_ssl_certificate_key|lua_ssl_certificate_key]] and [[#lua_ssl_trusted_certificate|lua_ssl_trusted_certificate]]. + +== lua_ssl_key_log == + +'''syntax:''' ''lua_ssl_key_log '' + +'''default:''' ''none'' + +'''context:''' ''http, server, location'' + +Enables logging of client connection SSL keys in the [[#tcpsock:sslhandshake|tcpsock:sslhandshake]] method and specifies the path to the key log file. Keys are logged in the SSLKEYLOGFILE format compatible with Wireshark. + +== lua_ssl_conf_command == + +'''syntax:''' ''lua_ssl_conf_command '' + +'''default:''' ''no'' + +'''context:''' ''http, server, location'' + +Sets arbitrary OpenSSL configuration [https://www.openssl.org/docs/man1.1.1/man3/SSL_CONF_cmd.html commands]. + +The directive is supported when using OpenSSL 1.0.2 or higher and nginx 1.19.4 or higher. According to the specify command, higher OpenSSL version may be needed. + +Several lua_ssl_conf_command directives can be specified on the same level: + + +lua_ssl_conf_command Options PrioritizeChaCha; +lua_ssl_conf_command Ciphersuites TLS_CHACHA20_POLY1305_SHA256; + + +Configuration commands are applied after OpenResty own configuration for SSL, so they can be used to override anything set by OpenResty. + +Note though that configuring OpenSSL directly with lua_ssl_conf_command might result in a behaviour OpenResty does not expect, and should be done with care. + +This directive was first introduced in the v0.10.21 release. + +== lua_upstream_skip_openssl_default_verify == + +'''syntax:''' ''lua_upstream_skip_openssl_default_verify on|off'' + +'''default:''' ''lua_upstream_skip_openssl_default_verify off'' + +'''context:''' ''location, location-if'' + +When using proxy_ssl_verify_by_lua directive, `lua_upstream_skip_openssl_default_verify` controls whether to skip default openssl's verify function, that means using pure Lua code to verify upstream server certificate. + +This directive is turned off by default. + +[Back to TOC](#directives) + == lua_http10_buffering == @@ -2557,7 +3025,7 @@ This directive was first introduced in the v0.9.20 release. '''context:''' ''http, server, location, location-if'' -Controls whether to transform underscores (_) in the response header names specified in the [[#ngx.header.HEADER|ngx.header.HEADER]] API to hypens (-). +Controls whether to transform underscores (_) in the response header names specified in the [[#ngx.header.HEADER|ngx.header.HEADER]] API to hyphens (-). This directive was first introduced in the v0.5.0rc32 release. @@ -2624,11 +3092,42 @@ When exceeding this limit, Nginx will stop running the callbacks of newly expire This directive was first introduced in the v0.8.0 release. +== lua_sa_restart == + +'''syntax:''' ''lua_sa_restart on|off'' + +'''default:''' ''lua_sa_restart on'' + +'''context:''' ''http'' + +When enabled, this module will set the `SA_RESTART` flag on Nginx workers signal dispositions. + +This allows Lua I/O primitives to not be interrupted by Nginx's handling of various signals. + +This directive was first introduced in the v0.10.14 release. + +== lua_worker_thread_vm_pool_size == + +'''syntax:''' ''lua_worker_thread_vm_pool_size '' + +'''default:''' ''lua_worker_thread_vm_pool_size 100'' + +'''context:''' ''http'' + +Specifies the size limit of the Lua VM pool (default 100) that will be used in the [ngx.run_worker_thread](#ngxrun_worker_thread) API. + +Also, it is not allowed to create Lua VMs that exceeds the pool size limit. + +The Lua VM in the VM pool is used to execute Lua code in separate thread. + +The pool is global at Nginx worker level. And it is used to reuse Lua VMs between requests. + = Nginx API for Lua = == Introduction == + The various *_by_lua, *_by_lua_block and *_by_lua_file configuration directives serve as gateways to the Lua API within the nginx.conf file. The Nginx Lua API described below can only be called within the user Lua code run in the context of these configuration directives. The API is exposed to Lua in the form of two standard packages ngx and ndk. These packages are in the default global scope within ngx_lua and are always available within ngx_lua directives. @@ -2647,7 +3146,7 @@ The packages can be introduced into external Lua modules like this: return _M -Use of the [http://www.lua.org/manual/5.1/manual.html#pdf-package.seeall package.seeall] flag is strongly discouraged due to its various bad side-effects. +Use of the [https://www.lua.org/manual/5.1/manual.html#pdf-package.seeall package.seeall] flag is strongly discouraged due to its various bad side-effects. It is also possible to directly require the packages in external Lua modules: @@ -2661,6 +3160,7 @@ The ability to require these packages was introduced in the v0.2.1rc19io library but huge file reading and writing should be avoided wherever possible as they may block the Nginx process significantly. Delegating all network and disk I/O operations to Nginx's subrequests (via the [[#ngx.location.capture|ngx.location.capture]] method and similar) is strongly recommended for maximum performance. == ngx.arg == + '''syntax:''' ''val = ngx.arg[index]'' '''context:''' ''set_by_lua*, body_filter_by_lua*'' @@ -2693,9 +3193,10 @@ When this table is used in the context of [[#body_filter_by_lua|body_filter_by_l The data chunk and "eof" flag passed to the downstream Nginx output filters can also be overridden by assigning values directly to the corresponding table elements. When setting nil or an empty Lua string value to ngx.arg[1], no data chunk will be passed to the downstream Nginx output filters at all. == ngx.var.VARIABLE == + '''syntax:''' ''ngx.var.VAR_NAME'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, balancer_by_lua*'' Read and write Nginx variable values. @@ -2704,22 +3205,22 @@ Read and write Nginx variable values. ngx.var.some_nginx_variable_name = value -Note that only already defined nginx variables can be written to. +Note that only already defined Nginx variables can be written to. For example: location /foo { set $my_var ''; # this line is required to create $my_var at config time content_by_lua_block { - ngx.var.my_var = 123; + ngx.var.my_var = 123 ... } } -That is, nginx variables cannot be created on-the-fly. +That is, Nginx variables cannot be created on-the-fly. -Some special nginx variables like $args and $limit_rate can be assigned a value, +Some special Nginx variables like $args and $limit_rate can be assigned a value, many others are not, like $query_string, $arg_PARAMETER, and $http_NAME. Nginx regex group capturing variables $1, $2, $3, and etc, can be read by this @@ -2740,12 +3241,13 @@ Setting ngx.var.Foo to a nil value will unset the nil while uninitialized (but defined) NGINX variables are evaluated to an empty Lua string. +Undefined Nginx variables are evaluated to nil while uninitialized (but defined) Nginx variables are evaluated to an empty Lua string. This API requires a relatively expensive metamethod call and it is recommended to avoid using it on hot code paths. == Core constants == -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, *log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' + +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, *log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' ngx.OK (0) @@ -2755,7 +3257,7 @@ This API requires a relatively expensive metamethod call and it is recommended t ngx.DECLINED (-5) -Note that only three of these constants are utilized by the [[#Nginx API for Lua|Nginx API for Lua]] (i.e., [[#ngx.exit|ngx.exit]] accepts NGX_OK, NGX_ERROR, and NGX_DECLINED as input). +Note that only three of these constants are utilized by the [[#Nginx API for Lua|Nginx API for Lua]] (i.e., [[#ngx.exit|ngx.exit]] accepts ngx.OK, ngx.ERROR, and ngx.DECLINED as input). ngx.null @@ -2766,7 +3268,8 @@ The ngx.null constant is a NULL light userdata usually The ngx.DECLINED constant was first introduced in the v0.5.0rc19 release. == HTTP method constants == -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' + +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' ngx.HTTP_GET @@ -2789,7 +3292,8 @@ The ngx.DECLINED constant was first introduced in the v0.5.0r These constants are usually used in [[#ngx.location.capture|ngx.location.capture]] and [[#ngx.location.capture_multi|ngx.location.capture_multi]] method calls. == HTTP status constants == -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' + +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' value = ngx.HTTP_CONTINUE (100) (first added in the v0.9.20 release) @@ -2805,6 +3309,7 @@ These constants are usually used in [[#ngx.location.capture|ngx.location.capture value = ngx.HTTP_SEE_OTHER (303) value = ngx.HTTP_NOT_MODIFIED (304) value = ngx.HTTP_TEMPORARY_REDIRECT (307) (first added in the v0.9.20 release) + value = ngx.HTTP_PERMANENT_REDIRECT (308) value = ngx.HTTP_BAD_REQUEST (400) value = ngx.HTTP_UNAUTHORIZED (401) value = ngx.HTTP_PAYMENT_REQUIRED (402) (first added in the v0.9.20 release) @@ -2820,7 +3325,8 @@ These constants are usually used in [[#ngx.location.capture|ngx.location.capture value = ngx.HTTP_CLOSE (444) (first added in the v0.9.20 release) value = ngx.HTTP_ILLEGAL (451) (first added in the v0.9.20 release) value = ngx.HTTP_INTERNAL_SERVER_ERROR (500) - value = ngx.HTTP_METHOD_NOT_IMPLEMENTED (501) + value = ngx.HTTP_NOT_IMPLEMENTED (501) + value = ngx.HTTP_METHOD_NOT_IMPLEMENTED (501) (kept for compatibility) value = ngx.HTTP_BAD_GATEWAY (502) (first added in the v0.9.20 release) value = ngx.HTTP_SERVICE_UNAVAILABLE (503) value = ngx.HTTP_GATEWAY_TIMEOUT (504) (first added in the v0.3.1rc38 release) @@ -2829,7 +3335,8 @@ These constants are usually used in [[#ngx.location.capture|ngx.location.capture == Nginx log level constants == -'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' + +'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' ngx.STDERR @@ -2846,11 +3353,12 @@ These constants are usually used in [[#ngx.location.capture|ngx.location.capture These constants are usually used by the [[#ngx.log|ngx.log]] method. == print == + '''syntax:''' ''print(...)'' -'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' -Writes argument values into the nginx error.log file with the ngx.NOTICE log level. +Writes argument values into the Nginx error.log file with the ngx.NOTICE log level. It is equivalent to @@ -2863,7 +3371,8 @@ Lua nil arguments are accepted and result in literal "nil"2048 byte limitation on error message lengths in the Nginx core. This limit includes trailing newlines and leading time stamps. If the message size exceeds this limit, Nginx will truncate the message text accordingly. This limit can be manually modified by editing the NGX_MAX_ERROR_STR macro definition in the src/core/ngx_log.h file in the Nginx source tree. == ngx.ctx == -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*'' + +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, exit_worker_by_lua*'' This table can be used to store per-request Lua context data and has a life time identical to the current request (as with the Nginx variables). @@ -2924,7 +3433,7 @@ Then GET /main will give the output Here, modification of the ngx.ctx.blah entry in the subrequest does not affect the one in the parent request. This is because they have two separate versions of ngx.ctx.blah. -Internal redirection will destroy the original request ngx.ctx data (if any) and the new request will have an empty ngx.ctx table. For instance, +Internal redirects (triggered by nginx configuration directives like `error_page`, `try_files`, `index` and etc) will destroy the original request ngx.ctx data (if any) and the new request will have an empty ngx.ctx table. For instance, location /new { @@ -2949,6 +3458,14 @@ Then GET /orig will give rather than the original "hello" value. +Because HTTP request is created after SSL handshake, the ngx.ctx created +in [[#ssl_certificate_by_lua|ssl_certificate_by_lua*]], [[#ssl_session_store_by_lua|ssl_session_store_by_lua*]], [[#ssl_session_fetch_by_lua|ssl_session_fetch_by_lua*]] and [[#ssl_client_hello_by_lua|ssl_client_hello_by_lua*]] +is not available in the following phases like [[#rewrite_by_lua|rewrite_by_lua*]]. + +Since v0.10.18, the ngx.ctx created during a SSL handshake +will be inherited by the requests which share the same TCP connection established by the handshake. +Note that overwrite values in ngx.ctx in the http request phases (like `rewrite_by_lua*`) will only take affect in the current http request. + Arbitrary data values, including Lua closures and nested tables, can be inserted into this "magic" table. It also allows the registration of custom meta methods. Overriding ngx.ctx with a new Lua table is also supported, for example, @@ -2995,13 +3512,14 @@ return _M That is, let the caller pass the ctx table explicitly via a function argument. == ngx.location.capture == + '''syntax:''' ''res = ngx.location.capture(uri, options?)'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' Issues a synchronous but still non-blocking ''Nginx Subrequest'' using uri. -Nginx's subrequests provide a powerful way to make non-blocking internal requests to other locations configured with disk file directory or ''any'' other nginx C modules like ngx_proxy, ngx_fastcgi, ngx_memc, +Nginx's subrequests provide a powerful way to make non-blocking internal requests to other locations configured with disk file directory or ''any'' other Nginx C modules like ngx_proxy, ngx_fastcgi, ngx_memc, ngx_postgres, ngx_drizzle, and even ngx_lua itself and etc etc etc. Also note that subrequests just mimic the HTTP interface but there is ''no'' extra HTTP/TCP traffic ''nor'' IPC involved. Everything works internally, efficiently, on the C level. @@ -3047,7 +3565,7 @@ URI query strings can be concatenated to URI itself, for instance, Named locations like @foo are not allowed due to a limitation in -the nginx core. Use normal locations combined with the internal directive to +the Nginx core. Use normal locations combined with the internal directive to prepare internal-only locations. An optional option table can be fed as the second @@ -3064,7 +3582,7 @@ argument, which supports the options: * vars : take a Lua table which holds the values to set the specified Nginx variables in the subrequest as this option's value. This option was first introduced in the v0.3.1rc31 release. * copy_all_vars -: specify whether to copy over all the Nginx variable values of the current request to the subrequest in question. modifications of the nginx variables in the subrequest will not affect the current (parent) request. This option was first introduced in the v0.3.1rc31 release. +: specify whether to copy over all the Nginx variable values of the current request to the subrequest in question. modifications of the Nginx variables in the subrequest will not affect the current (parent) request. This option was first introduced in the v0.3.1rc31 release. * share_all_vars : specify whether to share all the Nginx variables of the subrequest with the current (parent) request. modifications of the Nginx variables in the subrequest will affect the current (parent) request. Enabling this option may lead to hard-to-debug issues due to bad side-effects and is considered bad and harmful. Only enable this option when you completely know what you are doing. * always_forward_body @@ -3103,13 +3621,13 @@ The args option can also take plain query strings: ngx.location.capture('/foo?a=1', - { args = 'b=3&c=%3a' } } + { args = 'b=3&c=%3a' } ) This is functionally identical to the previous examples. -The share_all_vars option controls whether to share nginx variables among the current request and its subrequests. +The share_all_vars option controls whether to share Nginx variables among the current request and its subrequests. If this option is set to true, then the current request and associated subrequests will share the same Nginx variable scope. Hence, changes to Nginx variables made by a subrequest will affect the current request. Care should be taken in using this option as variable scope sharing can have unexpected side effects. The args, vars, or copy_all_vars options are generally preferable instead. @@ -3126,7 +3644,7 @@ This option is set to false by default set $dog 'hello'; content_by_lua_block { res = ngx.location.capture("/other", - { share_all_vars = true }); + { share_all_vars = true }) ngx.print(res.body) ngx.say(ngx.var.uri, ": ", ngx.var.dog) @@ -3153,7 +3671,7 @@ The copy_all_vars option provides a copy of the parent request's Ng set $dog 'hello'; content_by_lua_block { res = ngx.location.capture("/other", - { copy_all_vars = true }); + { copy_all_vars = true }) ngx.print(res.body) ngx.say(ngx.var.uri, ": ", ngx.var.dog) @@ -3190,7 +3708,7 @@ unescaping them in the Nginx config file. set $cat ''; content_by_lua_block { res = ngx.location.capture("/other", - { vars = { dog = "hello", cat = 32 }}); + { vars = { dog = "hello", cat = 32 }}) ngx.print(res.body) } @@ -3217,8 +3735,8 @@ The ctx option can be used to specify a custom Lua table to serve a local ctx = {} res = ngx.location.capture("/sub", { ctx = ctx }) - ngx.say(ctx.foo); - ngx.say(ngx.ctx.foo); + ngx.say(ctx.foo) + ngx.say(ngx.ctx.foo) } } @@ -3235,13 +3753,13 @@ It is also possible to use this ctx option to share the same [[#ngx location /sub { content_by_lua_block { - ngx.ctx.foo = "bar"; + ngx.ctx.foo = "bar" } } location /lua { content_by_lua_block { res = ngx.location.capture("/sub", { ctx = ngx.ctx }) - ngx.say(ngx.ctx.foo); + ngx.say(ngx.ctx.foo) } } @@ -3261,7 +3779,7 @@ in gzipped responses that cannot be handled properly in Lua code. Original reque When the body option is not specified and the always_forward_body option is false (the default value), the POST and PUT subrequests will inherit the request bodies of the parent request (if any). -There is a hard-coded upper limit on the number of concurrent subrequests possible for every main request. In older versions of Nginx, the limit was 50 concurrent subrequests and in more recent versions, Nginx 1.1.x onwards, this was increased to 200 concurrent subrequests. When this limit is exceeded, the following error message is added to the error.log file: +There is a hard-coded upper limit on the number of subrequests possible for every main request. In older versions of Nginx, the limit was 50 concurrent subrequests and in more recent versions, Nginx 1.9.5 onwards, the same limit is changed to limit the depth of recursive subrequests. When this limit is exceeded, the following error message is added to the error.log file: [error] 13983#0: *1 subrequests cycle while processing "/uri" @@ -3272,6 +3790,7 @@ The limit can be manually modified if required by editing the definition of the Please also refer to restrictions on capturing locations configured by [[#Locations_Configured_by_Subrequest_Directives_of_Other_Modules|subrequest directives of other modules]]. == ngx.location.capture_multi == + '''syntax:''' ''res1, res2, ... = ngx.location.capture_multi({ {uri, options?}, {uri, options?}, ... })'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -3310,7 +3829,9 @@ Lua tables can be used for both requests and responses when the number of subreq table.insert(reqs, { "/memcached" }) -- issue all the requests at once and wait until they all return - local resps = { ngx.location.capture_multi(reqs) } + local resps = { + ngx.location.capture_multi(reqs) + } -- loop over the responses table for i, resp in ipairs(resps) do @@ -3331,6 +3852,7 @@ of this function. Logically speaking, the [[#ngx.location.capture|ngx.location.c Please also refer to restrictions on capturing locations configured by [[#Locations_Configured_by_Subrequest_Directives_of_Other_Modules|subrequest directives of other modules]]. == ngx.status == + '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*'' Read and write the current request's response status. This should be called @@ -3341,13 +3863,14 @@ before sending out the response headers. status = ngx.status -Setting ngx.status after the response header is sent out has no effect but leaving an error message in your nginx's error log file: +Setting ngx.status after the response header is sent out has no effect but leaving an error message in your Nginx's error log file: attempt to set ngx.status after sending out response headers == ngx.header.HEADER == + '''syntax:''' ''ngx.header.HEADER = VALUE'' '''syntax:''' ''value = ngx.header.HEADER'' @@ -3362,9 +3885,9 @@ The header names are matched case-insensitively. -- equivalent to ngx.header["Content-Type"] = 'text/plain' - ngx.header.content_type = 'text/plain'; + ngx.header.content_type = 'text/plain' - ngx.header["X-My-Header"] = 'blah blah'; + ngx.header["X-My-Header"] = 'blah blah' Multi-value headers can be set this way: @@ -3397,16 +3920,16 @@ is equivalent to Setting a slot to nil effectively removes it from the response headers: - ngx.header["X-My-Header"] = nil; + ngx.header["X-My-Header"] = nil The same applies to assigning an empty table: - ngx.header["X-My-Header"] = {}; + ngx.header["X-My-Header"] = {} -Setting ngx.header.HEADER after sending out response headers (either explicitly with [[#ngx.send_headers|ngx.send_headers]] or implicitly with [[#ngx.print|ngx.print]] and similar) will throw out a Lua exception. +Setting ngx.header.HEADER after sending out response headers (either explicitly with [[#ngx.send_headers|ngx.send_headers]] or implicitly with [[#ngx.print|ngx.print]] and similar) will log an error message. Reading ngx.header.HEADER will return the value of the response header named HEADER. @@ -3447,17 +3970,26 @@ to be returned when reading ngx.header.Foo. Note that ngx.header is not a normal Lua table and as such, it is not possible to iterate through it using the Lua ipairs function. +Note: this function throws a Lua error if HEADER or +VALUE contain unsafe characters (control characters). + For reading ''request'' headers, use the [[#ngx.req.get_headers|ngx.req.get_headers]] function instead. == ngx.resp.get_headers == -'''syntax:''' ''headers = ngx.resp.get_headers(max_headers?, raw?)'' + +'''syntax:''' ''headers, err = ngx.resp.get_headers(max_headers?, raw?)'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, balancer_by_lua*'' Returns a Lua table holding all the current response headers for the current request. -local h = ngx.resp.get_headers() +local h, err = ngx.resp.get_headers() + +if err == "truncated" then + -- one can choose to ignore or reject the current response here +end + for k, v in pairs(h) do ... end @@ -3465,21 +3997,25 @@ end This function has the same signature as [[#ngx.req.get_headers|ngx.req.get_headers]] except getting response headers instead of request headers. +Note that a maximum of 100 response headers are parsed by default (including those with the same name) and that additional response headers are silently discarded to guard against potential denial of service attacks. Since v0.10.13, when the limit is exceeded, it will return a second value which is the string `"truncated"`. + This API was first introduced in the v0.9.5 release. == ngx.req.is_internal == + '''syntax:''' ''is_internal = ngx.req.is_internal()'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*'' Returns a boolean indicating whether the current request is an "internal request", i.e., -a request initiated from inside the current nginx server instead of from the client side. +a request initiated from inside the current Nginx server instead of from the client side. Subrequests are all internal requests and so are requests after internal redirects. This API was first introduced in the v0.9.20 release. == ngx.req.start_time == + '''syntax:''' ''secs = ngx.req.start_time()'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*'' @@ -3497,6 +4033,7 @@ This function was first introduced in the v0.7.7 release. See also [[#ngx.now|ngx.now]] and [[#ngx.update_time|ngx.update_time]]. == ngx.req.http_version == + '''syntax:''' ''num = ngx.req.http_version()'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*'' @@ -3508,6 +4045,7 @@ Current possible values are 2.0, 1.0, 1.1, and 0.9. Returns nil for This method was first introduced in the v0.7.17 release. == ngx.req.raw_header == + '''syntax:''' ''str = ngx.req.raw_header(no_request_line?)'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*'' @@ -3551,9 +4089,10 @@ This method was first introduced in the v0.7.17 release. This method does not work in HTTP/2 requests yet. == ngx.req.get_method == + '''syntax:''' ''method_name = ngx.req.get_method()'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, balancer_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, balancer_by_lua*, log_by_lua*'' Retrieves the current request's request method name. Strings like "GET" and "POST" are returned instead of numerical [[#HTTP method constants|method constants]]. @@ -3564,6 +4103,7 @@ This method was first introduced in the v0.5.6 release. See also [[#ngx.req.set_method|ngx.req.set_method]]. == ngx.req.set_method == + '''syntax:''' ''ngx.req.set_method(method_id)'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*'' @@ -3577,7 +4117,8 @@ This method was first introduced in the v0.5.6 release. See also [[#ngx.req.get_method|ngx.req.get_method]]. == ngx.req.set_uri == -'''syntax:''' ''ngx.req.set_uri(uri, jump?)'' + +'''syntax:''' ''ngx.req.set_uri(uri, jump?, binary?)'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*'' @@ -3587,7 +4128,7 @@ The optional boolean jump argument can trigger location rematch (or Location jump will not be triggered otherwise, and only the current request's URI will be modified, which is also the default behavior. This function will return but with no returned values when the jump argument is false or absent altogether. -For example, the following nginx config snippet +For example, the following Nginx config snippet rewrite ^ /foo last; @@ -3640,6 +4181,9 @@ which is functionally equivalent to } +Note: this function throws a Lua error if the uri argument +contains unsafe characters (control characters). + Note that it is not possible to use this interface to rewrite URI arguments and that [[#ngx.req.set_uri_args|ngx.req.set_uri_args]] should be used for this instead. For instance, Nginx config @@ -3660,9 +4204,22 @@ or ngx.req.set_uri("/foo", true) +Starting from 0.10.16 of this module, this function accepts an +optional boolean binary argument to allow arbitrary binary URI +data. By default, this binary argument is false and this function +will throw out a Lua error such as the one below when the uri +argument contains any control characters (ASCII Code 0 ~ 0x08, 0x0A ~ 0x1F and 0x7F). + + + [error] 23430#23430: *1 lua entry thread aborted: runtime error: + content_by_lua(nginx.conf:44):3: ngx.req.set_uri unsafe byte "0x00" + in "\x00foo" (maybe you want to set the 'binary' argument?) + + This interface was first introduced in the v0.3.1rc14 release. == ngx.req.set_uri_args == + '''syntax:''' ''ngx.req.set_uri_args(args)'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*'' @@ -3679,7 +4236,12 @@ or a Lua table holding the query arguments' key-value pairs, as in ngx.req.set_uri_args({ a = 3, b = "hello world" }) -where in the latter case, this method will escape argument keys and values according to the URI escaping rule. +In the former case, i.e., when the whole query-string is provided directly, +the input Lua string should already be well-formed with the URI encoding. +For security considerations, this method will automatically escape any control and +whitespace characters (ASCII code 0x00 ~ 0x20 and 0x7F) in the Lua string. + +In the latter case, this method will escape argument keys and values according to the URI escaping rule. Multi-value arguments are also supported: @@ -3694,16 +4256,23 @@ This interface was first introduced in the v0.3.1rc13 release. See also [[#ngx.req.set_uri|ngx.req.set_uri]]. == ngx.req.get_uri_args == -'''syntax:''' ''args = ngx.req.get_uri_args(max_args?)'' + +'''syntax:''' ''args, err = ngx.req.get_uri_args(max_args?, tab?)'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, balancer_by_lua*'' -Returns a Lua table holding all the current request URL query arguments. +Returns a Lua table holding all the current request URL query arguments. An optional tab argument +can be used to reuse the table returned by this method. location = /test { content_by_lua_block { - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err == "truncated" then + -- one can choose to ignore or reject the current request here + end + for key, val in pairs(args) do if type(val) == "table" then ngx.say(key, ": ", table.concat(val, ", ")) @@ -3746,11 +4315,11 @@ That is, they will take Lua boolean values true. However, they are Empty key arguments are discarded. GET /test?=hello&=world will yield an empty output for instance. -Updating query arguments via the nginx variable $args (or ngx.var.args in Lua) at runtime is also supported: +Updating query arguments via the Nginx variable $args (or ngx.var.args in Lua) at runtime is also supported: ngx.var.args = "a=3&b=42" - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() Here the args table will always look like @@ -3761,23 +4330,27 @@ Here the args table will always look like regardless of the actual request query string. -Note that a maximum of 100 request arguments are parsed by default (including those with the same name) and that additional request arguments are silently discarded to guard against potential denial of service attacks. +Note that a maximum of 100 request arguments are parsed by default (including those with the same name) and that additional request arguments are silently discarded to guard against potential denial of service attacks. Since v0.10.13, when the limit is exceeded, it will return a second value which is the string `"truncated"`. However, the optional max_args function argument can be used to override this limit: - local args = ngx.req.get_uri_args(10) + local args, err = ngx.req.get_uri_args(10) + if err == "truncated" then + -- one can choose to ignore or reject the current request here + end This argument can be set to zero to remove the limit and to process all request arguments received: - local args = ngx.req.get_uri_args(0) + local args, err = ngx.req.get_uri_args(0) Removing the max_args cap is strongly discouraged. == ngx.req.get_post_args == + '''syntax:''' ''args, err = ngx.req.get_post_args(max_args?)'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*'' @@ -3789,6 +4362,11 @@ Returns a Lua table holding all the current request POST query arguments (of the content_by_lua_block { ngx.req.read_body() local args, err = ngx.req.get_post_args() + + if err == "truncated" then + -- one can choose to ignore or reject the current request here + end + if not args then ngx.say("failed to get post args: ", err) return @@ -3851,31 +4429,40 @@ That is, they will take Lua boolean values true. However, they are Empty key arguments are discarded. POST /test with body =hello&=world will yield empty outputs for instance. -Note that a maximum of 100 request arguments are parsed by default (including those with the same name) and that additional request arguments are silently discarded to guard against potential denial of service attacks. +Note that a maximum of 100 request arguments are parsed by default (including those with the same name) and that additional request arguments are silently discarded to guard against potential denial of service attacks. Since v0.10.13, when the limit is exceeded, it will return a second value which is the string `"truncated"`. However, the optional max_args function argument can be used to override this limit: - local args = ngx.req.get_post_args(10) + local args, err = ngx.req.get_post_args(10) + if err == "truncated" then + -- one can choose to ignore or reject the current request here + end This argument can be set to zero to remove the limit and to process all request arguments received: - local args = ngx.req.get_post_args(0) + local args, err = ngx.req.get_post_args(0) Removing the max_args cap is strongly discouraged. == ngx.req.get_headers == -'''syntax:''' ''headers = ngx.req.get_headers(max_headers?, raw?)'' + +'''syntax:''' ''headers, err = ngx.req.get_headers(max_headers?, raw?)'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*'' Returns a Lua table holding all the current request headers. - local h = ngx.req.get_headers() + local h, err = ngx.req.get_headers() + + if err == "truncated" then + -- one can choose to ignore or reject the current request here + end + for k, v in pairs(h) do ... end @@ -3903,18 +4490,22 @@ the value of ngx.req.get_headers()["Foo"] will be a Lua (array) tab {"foo", "bar", "baz"} -Note that a maximum of 100 request headers are parsed by default (including those with the same name) and that additional request headers are silently discarded to guard against potential denial of service attacks. +Note that a maximum of 100 request headers are parsed by default (including those with the same name) and that additional request headers are silently discarded to guard against potential denial of service attacks. Since v0.10.13, when the limit is exceeded, it will return a second value which is the string `"truncated"`. However, the optional max_headers function argument can be used to override this limit: - local headers = ngx.req.get_headers(10) + local headers, err = ngx.req.get_headers(10) + + if err == "truncated" then + -- one can choose to ignore or reject the current request here + end This argument can be set to zero to remove the limit and to process all request headers received: - local headers = ngx.req.get_headers(0) + local headers, err = ngx.req.get_headers(0) Removing the max_headers cap is strongly discouraged. @@ -3932,14 +4523,22 @@ Also, by default, an __index metamethod is added to the resulting L The __index metamethod will not be added when the raw argument is set to true. == ngx.req.set_header == + '''syntax:''' ''ngx.req.set_header(header_name, header_value)'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*'' Set the current request's request header named header_name to value header_value, overriding any existing ones. +The input Lua string `header_name` and `header_value` should already be well-formed with the URI encoding. +For security considerations, this method will automatically escape " ", """, "(", ")", ",", "/", ":", ";", "?", +"<", "=", ">", "?", "@", "[", "]", "\", "{", "}", 0x00-0x1F, 0x7F-0xFF in `header_name` and automatically escape +"0x00-0x08, 0x0A-0x0F, 0x7F in `header_value`. + By default, all the subrequests subsequently initiated by [[#ngx.location.capture|ngx.location.capture]] and [[#ngx.location.capture_multi|ngx.location.capture_multi]] will inherit the new header. +It is not a Lua's equivalent of nginx `proxy_set_header` directive (same is true about [ngx.req.clear_header](#ngxreqclear_header)). `proxy_set_header` only affects the upstream request while `ngx.req.set_header` change the incoming request. Record the http headers in the access log file will show the difference. But you still can use it as an alternative of nginx `proxy_set_header` directive as long as you know the difference. + Here is an example of setting the Content-Type header: @@ -3974,7 +4573,11 @@ is equivalent to ngx.req.clear_header("X-Foo") +Note: this function throws a Lua error if header_name or +header_value contain unsafe characters (control characters). + == ngx.req.clear_header == + '''syntax:''' ''ngx.req.clear_header(header_name)'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*'' @@ -3982,6 +4585,7 @@ is equivalent to Clears the current request's request header named header_name. None of the current request's existing subrequests will be affected but subsequently initiated subrequests will inherit the change by default. == ngx.req.read_body == + '''syntax:''' ''ngx.req.read_body()'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -4009,6 +4613,7 @@ In cases where current request may have a request body and the request body data This function was first introduced in the v0.3.1rc17 release. == ngx.req.discard_body == + '''syntax:''' ''ngx.req.discard_body()'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -4024,19 +4629,22 @@ This function was first introduced in the v0.3.1rc17 release. See also [[#ngx.req.read_body|ngx.req.read_body]]. == ngx.req.get_body_data == -'''syntax:''' ''data = ngx.req.get_body_data()'' + +'''syntax:''' ''data = ngx.req.get_body_data(max_bytes?)'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, log_by_lua*'' Retrieves in-memory request body data. It returns a Lua string rather than a Lua table holding all the parsed query arguments. Use the [[#ngx.req.get_post_args|ngx.req.get_post_args]] function instead if a Lua table is required. +The optional max_bytes function argument can be used when you don't need the entire body. + This function returns nil if # the request body has not been read, # the request body has been read into disk temporary files, # or the request body has zero size. -If the request body has not been read yet, call [[#ngx.req.read_body|ngx.req.read_body]] first (or turned on [[#lua_need_request_body|lua_need_request_body]] to force this module to read the request body. This is not recommended however). +If the request body has not been read yet, call [[#ngx.req.read_body|ngx.req.read_body]] first (or turn on [[#lua_need_request_body|lua_need_request_body]] to force this module to read the request body. This is not recommended however). If the request body has been read into disk files, try calling the [[#ngx.req.get_body_file|ngx.req.get_body_file]] function instead. @@ -4049,6 +4657,7 @@ This function was first introduced in the v0.3.1rc17 release. See also [[#ngx.req.get_body_file|ngx.req.get_body_file]]. == ngx.req.get_body_file == + '''syntax:''' ''file_name = ngx.req.get_body_file()'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -4057,7 +4666,7 @@ Retrieves the file name for the in-file request body data. Returns nilv0.3.1rc17 release. See also [[#ngx.req.get_body_data|ngx.req.get_body_data]]. == ngx.req.set_body_data == + '''syntax:''' ''ngx.req.set_body_data(data)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, balancer_by_lua*'' Set the current request's request body using the in-memory data specified by the data argument. -If the current request's request body has not been read, then it will be properly discarded. When the current request's request body has been read into memory or buffered into a disk file, then the old request body's memory will be freed or the disk file will be cleaned up immediately, respectively. +If the request body has not been read yet, call [[#ngx.req.read_body|ngx.req.read_body]] first (or turn on [[#lua_need_request_body|lua_need_request_body]] to force this module to read the request body. This is not recommended however). Additionally, the request body must not have been previously discarded by [[#ngx.req.discard_body|ngx.req.discard_body]]. + +Whether the previous request body has been read into memory or buffered into a disk file, it will be freed or the disk file will be cleaned up immediately, respectively. + +Note that this function is also work for balancer phase but it needs to call [https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/balancer.md#recreate_request balancer.recreate_request] to make the change take effect after set the request body data or headers. This function was first introduced in the v0.3.1rc18 release. See also [[#ngx.req.set_body_file|ngx.req.set_body_file]]. == ngx.req.set_body_file == + '''syntax:''' ''ngx.req.set_body_file(file_name, auto_clean?)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, balancer_by_lua*'' Set the current request's request body using the in-file data specified by the file_name argument. +If the request body has not been read yet, call [[#ngx.req.read_body|ngx.req.read_body]] first (or turn on [[#lua_need_request_body|lua_need_request_body]] to force this module to read the request body. This is not recommended however). Additionally, the request body must not have been previously discarded by [[#ngx.req.discard_body|ngx.req.discard_body]]. + If the optional auto_clean argument is given a true value, then this file will be removed at request completion or the next time this function or [[#ngx.req.set_body_data|ngx.req.set_body_data]] are called in the same request. The auto_clean is default to false. Please ensure that the file specified by the file_name argument exists and is readable by an Nginx worker process by setting its permission properly to avoid Lua exception errors. -If the current request's request body has not been read, then it will be properly discarded. When the current request's request body has been read into memory or buffered into a disk file, then the old request body's memory will be freed or the disk file will be cleaned up immediately, respectively. +Whether the previous request body has been read into memory or buffered into a disk file, it will be freed or the disk file will be cleaned up immediately, respectively. + +Note that this function is also work for balancer phase but it needs to call [https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/balancer.md#recreate_request balancer.recreate_request] to make the change take effect after set the request body data or headers. This function was first introduced in the v0.3.1rc18 release. See also [[#ngx.req.set_body_data|ngx.req.set_body_data]]. == ngx.req.init_body == + '''syntax:''' ''ngx.req.init_body(buffer_size?)'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*'' -Creates a new blank request body for the current request and inializes the buffer for later request body data writing via the [[#ngx.req.append_body|ngx.req.append_body]] and [[#ngx.req.finish_body|ngx.req.finish_body]] APIs. +Creates a new blank request body for the current request and initializes the buffer for later request body data writing via the [[#ngx.req.append_body|ngx.req.append_body]] and [[#ngx.req.finish_body|ngx.req.finish_body]] APIs. If the buffer_size argument is specified, then its value will be used for the size of the memory buffer for body writing with [[#ngx.req.append_body|ngx.req.append_body]]. If the argument is omitted, then the value specified by the standard [[HttpCoreModule#client_body_buffer_size|client_body_buffer_size]] directive will be used instead. @@ -4125,6 +4745,7 @@ This function can be used with [[#ngx.req.append_body|ngx.req.append_body]], [[# This function was first introduced in the v0.5.11 release. == ngx.req.append_body == + '''syntax:''' ''ngx.req.append_body(data_chunk)'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -4142,6 +4763,7 @@ This function was first introduced in the v0.5.11 release. See also [[#ngx.req.init_body|ngx.req.init_body]]. == ngx.req.finish_body == + '''syntax:''' ''ngx.req.finish_body()'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -4155,30 +4777,33 @@ This function was first introduced in the v0.5.11 release. See also [[#ngx.req.init_body|ngx.req.init_body]]. == ngx.req.socket == + '''syntax:''' ''tcpsock, err = ngx.req.socket()'' '''syntax:''' ''tcpsock, err = ngx.req.socket(raw)'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' -Returns a read-only cosocket object that wraps the downstream connection. Only [[#tcpsock:receive|receive]] and [[#tcpsock:receiveuntil|receiveuntil]] methods are supported on this object. +Returns a read-only cosocket object that wraps the downstream connection. Only [[#tcpsock:receive|receive]], [[#tcpsock:receiveany|receiveany]] and [[#tcpsock:receiveuntil|receiveuntil]] methods are supported on this object. In case of error, nil will be returned as well as a string describing the error. +'''Note:''' This method will block while waiting for client request body to be fully received. Block time depends on the [http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_timeout client_body_timeout] directive and maximum body size specified by the [http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size client_max_body_size] directive. If read timeout occurs or client body size exceeds the defined limit, this function will not return and 408 Request Time-out or 413 Request Entity Too Large response will be returned to the client instead. The socket object returned by this method is usually used to read the current request's body in a streaming fashion. Do not turn on the [[#lua_need_request_body|lua_need_request_body]] directive, and do not mix this call with [[#ngx.req.read_body|ngx.req.read_body]] and [[#ngx.req.discard_body|ngx.req.discard_body]]. If any request body data has been pre-read into the Nginx core request header buffer, the resulting cosocket object will take care of this to avoid potential data loss resulting from such pre-reading. Chunked request bodies are not yet supported in this API. -Since the v0.9.0 release, this function accepts an optional boolean raw argument. When this argument is true, this function returns a full-duplex cosocket object wrapping around the raw downstream connection socket, upon which you can call the [[#tcpsock:receive|receive]], [[#tcpsock:receiveuntil|receiveuntil]], and [[#tcpsock:send|send]] methods. +Since the v0.9.0 release, this function accepts an optional boolean raw argument. When this argument is true, this function returns a full-duplex cosocket object wrapping around the raw downstream connection socket, upon which you can call the [[#tcpsock:receive|receive]], [[#tcpsock:receiveany|receiveany]], [[#tcpsock:receiveuntil|receiveuntil]], and [[#tcpsock:send|send]] methods. When the raw argument is true, it is required that no pending data from any previous [[#ngx.say|ngx.say]], [[#ngx.print|ngx.print]], or [[#ngx.send_headers|ngx.send_headers]] calls exists. So if you have these downstream output calls previously, you should call [[#ngx.flush|ngx.flush(true)]] before calling ngx.req.socket(true) to ensure that there is no pending output data. If the request body has not been read yet, then this "raw socket" can also be used to read the request body. -You can use the "raw request socket" returned by ngx.req.socket(true) to implement fancy protocols like [http://en.wikipedia.org/wiki/WebSocket WebSocket], or just emit your own raw HTTP response header or body data. You can refer to the [https://github.com/openresty/lua-resty-websocket lua-resty-websocket library] for a real world example. +You can use the "raw request socket" returned by ngx.req.socket(true) to implement fancy protocols like [https://en.wikipedia.org/wiki/WebSocket WebSocket], or just emit your own raw HTTP response header or body data. You can refer to the [https://github.com/openresty/lua-resty-websocket lua-resty-websocket library] for a real world example. This function was first introduced in the v0.5.0rc1 release. == ngx.exec == + '''syntax:''' ''ngx.exec(uri, args?)'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -4186,9 +4811,9 @@ This function was first introduced in the v0.5.0rc1 release. Does an internal redirect to uri with args and is similar to the [[HttpEchoModule#echo_exec|echo_exec]] directive of the [[HttpEchoModule]]. - ngx.exec('/some-location'); - ngx.exec('/some-location', 'a=3&b=5&c=6'); - ngx.exec('/some-location?a=3&b=5', 'c=6'); + ngx.exec('/some-location') + ngx.exec('/some-location', 'a=3&b=5&c=6') + ngx.exec('/some-location?a=3&b=5', 'c=6') The optional second args can be used to specify extra URI query arguments, for example: @@ -4214,7 +4839,7 @@ Named locations are also supported but the second args argument wil location /foo { content_by_lua_block { - ngx.exec("@bar", "a=goodbye"); + ngx.exec("@bar", "a=goodbye") } } @@ -4239,18 +4864,23 @@ outputs by either [[#ngx.print|ngx.print]] or [[#ngx.say|ngx.say]]. It is recommended that a coding style that combines this method call with the return statement, i.e., return ngx.exec(...) be adopted when this method call is used in contexts other than [[#header_filter_by_lua|header_filter_by_lua*]] to reinforce the fact that the request processing is being terminated. == ngx.redirect == + '''syntax:''' ''ngx.redirect(uri, status?)'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' Issue an HTTP 301 or 302 redirection to uri. +Note: this function throws a Lua error if the uri argument +contains unsafe characters (control characters). + The optional status parameter specifies the HTTP status code to be used. The following status codes are supported right now: * 301 * 302 (default) * 303 * 307 +* 308 It is 302 (ngx.HTTP_MOVED_TEMPORARILY) by default. @@ -4288,7 +4918,7 @@ This method is similar to the [[HttpRewriteModule#rewrite|rewrite]] directive wi is equivalent to the following Lua code - return ngx.redirect('/foo'); -- Lua code + return ngx.redirect('/foo') -- Lua code while @@ -4315,6 +4945,7 @@ outputs by either [[#ngx.print|ngx.print]] or [[#ngx.say|ngx.say]]. It is recommended that a coding style that combines this method call with the return statement, i.e., return ngx.redirect(...) be adopted when this method call is used in contexts other than [[#header_filter_by_lua|header_filter_by_lua*]] to reinforce the fact that the request processing is being terminated. == ngx.send_headers == + '''syntax:''' ''ok, err = ngx.send_headers()'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -4327,6 +4958,7 @@ Note that there is normally no need to manually send out response headers as ngx before content is output with [[#ngx.say|ngx.say]] or [[#ngx.print|ngx.print]] or when [[#content_by_lua|content_by_lua*]] exits normally. == ngx.headers_sent == + '''syntax:''' ''value = ngx.headers_sent'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -4336,6 +4968,7 @@ Returns true if the response headers have been sent (by ngx_lua), a This API was first introduced in ngx_lua v0.3.1rc6. == ngx.print == + '''syntax:''' ''ok, err = ngx.print(...)'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -4372,6 +5005,7 @@ This is an asynchronous call and will return immediately without waiting for all Please note that both ngx.print and [[#ngx.say|ngx.say]] will always invoke the whole Nginx output body filter chain, which is an expensive operation. So be careful when calling either of these two in a tight loop; buffer the data yourself in Lua and save the calls. == ngx.say == + '''syntax:''' ''ok, err = ngx.say(...)'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -4379,9 +5013,10 @@ Please note that both ngx.print and [[#ngx.say|ngx.say]] will alway Just as [[#ngx.print|ngx.print]] but also emit a trailing newline. == ngx.log == + '''syntax:''' ''ngx.log(log_level, ...)'' -'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Log arguments concatenated to error.log with the given logging level. @@ -4392,6 +5027,7 @@ The log_level argument can take constants like ngx.ERR There is a hard coded 2048 byte limitation on error message lengths in the Nginx core. This limit includes trailing newlines and leading time stamps. If the message size exceeds this limit, Nginx will truncate the message text accordingly. This limit can be manually modified by editing the NGX_MAX_ERROR_STR macro definition in the src/core/ngx_log.h file in the Nginx source tree. == ngx.flush == + '''syntax:''' ''ok, err = ngx.flush(wait?)'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -4409,11 +5045,12 @@ Note that ngx.flush is not functional when in the HTTP 1.0 output b Since v0.8.3 this function returns 1 on success, or returns nil and a string describing the error otherwise. == ngx.exit == + '''syntax:''' ''ngx.exit(status)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' -When status >= 200 (i.e., ngx.HTTP_OK and above), it will interrupt the execution of the current request and return status code to nginx. +When status >= 200 (i.e., ngx.HTTP_OK and above), it will interrupt the execution of the current request and return status code to Nginx. When status == 0 (i.e., ngx.OK), it will only quit the current phase handler (or the content handler if the [[#content_by_lua|content_by_lua*]] directive is used) and continue to run later phases (if any) for the current request. @@ -4449,7 +5086,7 @@ Number literals can be used directly as the argument, for instance, ngx.exit(501) -Note that while this method accepts all [[#HTTP status constants|HTTP status constants]] as input, it only accepts NGX_OK and NGX_ERROR of the [[#core constants|core constants]]. +Note that while this method accepts all [[#HTTP status constants|HTTP status constants]] as input, it only accepts ngx.OK and ngx.ERROR of the [[#core constants|core constants]]. Also note that this method call terminates the processing of the current request and that it is recommended that a coding style that combines this method call with the return statement, i.e., return ngx.exit(...) be used to reinforce the fact that the request processing is being terminated. @@ -4458,6 +5095,7 @@ When being used in the contexts of [[#header_filter_by_lua|header_filter_by_lua* an asynchronous operation and will return immediately. This behavior may change in future and it is recommended that users always use return in combination as suggested above. == ngx.eof == + '''syntax:''' ''ok, err = ngx.eof()'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -4488,11 +5126,12 @@ A better way to do background jobs is to use the [[#ngx.timer.at|ngx.timer.at]] Since v0.8.3 this function returns 1 on success, or returns nil and a string describing the error otherwise. == ngx.sleep == + '''syntax:''' ''ngx.sleep(seconds)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' -Sleeps for the specified seconds without blocking. One can specify time resolution up to 0.001 seconds (i.e., one milliseconds). +Sleeps for the specified seconds without blocking. One can specify time resolution up to 0.001 seconds (i.e., one millisecond). Behind the scene, this method makes use of the Nginx timers. @@ -4501,16 +5140,26 @@ Since the 0.7.20 release, The 0 time argument can also This method was introduced in the 0.5.0rc30 release. == ngx.escape_uri == -'''syntax:''' ''newstr = ngx.escape_uri(str)'' -'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''syntax:''' ''newstr = ngx.escape_uri(str, type?)'' -Escape str as a URI component. +'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' + +Since `v0.10.16`, this function accepts an optional type argument. +It accepts the following values (defaults to `2`): + +* `0`: escapes str as a full URI. And the characters + (space), #, %, +`?`, 0x00 ~ 0x1F, 0x7F ~ 0xFF will be escaped. +* `2`: escape str as a URI component. All characters except +alphabetic characters, digits, -, ., _, +~ will be encoded as `%XX`. == ngx.unescape_uri == + '''syntax:''' ''newstr = ngx.unescape_uri(str)'' -'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*'' +'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Unescape str as an escaped URI component. @@ -4526,10 +5175,27 @@ gives the output b r56 7 +Invalid escaping sequences are handled in a conventional way: `%`s are left unchanged. Also, characters that should not appear in escaped string are simply left unchanged. + +For example, + + + ngx.say(ngx.unescape_uri("try %search%%20%again%")) + + +gives the output + + + try %search% %again% + + +(Note that `%20` following `%` got unescaped, even it can be considered a part of invalid sequence.) + == ngx.encode_args == + '''syntax:''' ''str = ngx.encode_args(table)'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_client_hello_by_lua*'' Encode the Lua table to a query args string according to the URI encoded rules. @@ -4578,13 +5244,14 @@ If the argument value is false, then the effect is equivalent to th This method was first introduced in the v0.3.1rc27 release. == ngx.decode_args == -'''syntax:''' ''table = ngx.decode_args(str, max_args?)'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''syntax:''' ''table, err = ngx.decode_args(str, max_args?)'' + +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Decodes a URI encoded query-string into a Lua table. This is the inverse function of [[#ngx.encode_args|ngx.encode_args]]. -The optional max_args argument can be used to specify the maximum number of arguments parsed from the str argument. By default, a maximum of 100 request arguments are parsed (including those with the same name) and that additional URI arguments are silently discarded to guard against potential denial of service attacks. +The optional max_args argument can be used to specify the maximum number of arguments parsed from the str argument. By default, a maximum of 100 request arguments are parsed (including those with the same name) and that additional URI arguments are silently discarded to guard against potential denial of service attacks. Since v0.10.13, when the limit is exceeded, it will return a second value which is the string `"truncated"`. This argument can be set to zero to remove the limit and to process all request arguments received: @@ -4597,25 +5264,42 @@ Removing the max_args cap is strongly discouraged. This method was introduced in the v0.5.0rc29. == ngx.encode_base64 == + '''syntax:''' ''newstr = ngx.encode_base64(str, no_padding?)'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Encodes str to a base64 digest. Since the 0.9.16 release, an optional boolean-typed no_padding argument can be specified to control whether the base64 padding should be appended to the resulting digest (default to false, i.e., with padding enabled). == ngx.decode_base64 == + '''syntax:''' ''newstr = ngx.decode_base64(str)'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' + +Decodes the str argument as a base64 digest to the raw form. +The str should be standard 'base64' encoding for RFC 3548 or RFC 4648, and will returns nil if is not well formed or any characters not in the base encoding alphabet. + +== ngx.decode_base64mime == +'''syntax:''' ''newstr = ngx.decode_base64mime(str)'' + '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' -Decodes the str argument as a base64 digest to the raw form. Returns nil if str is not well formed. +'''requires:''' resty.core.base64 or resty.core + +Decodes the str argument as a base64 digest to the raw form. +The str follows base64 transfer encoding for MIME (RFC 2045), and will discard characters outside the base encoding alphabet. +Returns nil if str is not well formed. + + '''Note:''' This method requires the resty.core.base64 or resty.core modules from the [https://github.com/openresty/lua-resty-core lua-resty-core] library. == ngx.crc32_short == + '''syntax:''' ''intval = ngx.crc32_short(str)'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Calculates the CRC-32 (Cyclic Redundancy Code) digest for the str argument. @@ -4626,9 +5310,10 @@ Behind the scene, it is just a thin wrapper around the ngx_crc32_shortv0.3.1rc8 release. == ngx.crc32_long == + '''syntax:''' ''intval = ngx.crc32_long(str)'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Calculates the CRC-32 (Cyclic Redundancy Code) digest for the str argument. @@ -4639,11 +5324,12 @@ Behind the scene, it is just a thin wrapper around the ngx_crc32_longv0.3.1rc8 release. == ngx.hmac_sha1 == + '''syntax:''' ''digest = ngx.hmac_sha1(secret_key, str)'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' -Computes the [http://en.wikipedia.org/wiki/HMAC HMAC-SHA1] digest of the argument str and turns the result using the secret key . +Computes the [https://en.wikipedia.org/wiki/HMAC HMAC-SHA1] digest of the argument str and turns the result using the secret key . The raw binary form of the HMAC-SHA1 digest will be generated, use [[#ngx.encode_base64|ngx.encode_base64]], for example, to encode the result to a textual representation if desired. @@ -4667,9 +5353,10 @@ This API requires the OpenSSL library enabled in the Nginx build (usually by pas This function was first introduced in the v0.3.1rc29 release. == ngx.md5 == + '''syntax:''' ''digest = ngx.md5(str)'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Returns the hexadecimal representation of the MD5 digest of the str argument. @@ -4677,7 +5364,9 @@ For example, location = /md5 { - content_by_lua_block { ngx.say(ngx.md5("hello")) } + content_by_lua_block { + ngx.say(ngx.md5("hello")) + } } @@ -4690,18 +5379,20 @@ yields the output See [[#ngx.md5_bin|ngx.md5_bin]] if the raw binary MD5 digest is required. == ngx.md5_bin == + '''syntax:''' ''digest = ngx.md5_bin(str)'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Returns the binary form of the MD5 digest of the str argument. See [[#ngx.md5|ngx.md5]] if the hexadecimal form of the MD5 digest is required. == ngx.sha1_bin == + '''syntax:''' ''digest = ngx.sha1_bin(str)'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Returns the binary form of the SHA-1 digest of the str argument. @@ -4710,72 +5401,80 @@ This function requires SHA-1 support in the Nginx build. (This usually just mean This function was first introduced in the v0.5.0rc6. == ngx.quote_sql_str == + '''syntax:''' ''quoted_value = ngx.quote_sql_str(raw_value)'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Returns a quoted SQL string literal according to the MySQL quoting rules. == ngx.today == + '''syntax:''' ''str = ngx.today()'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' -Returns current date (in the format yyyy-mm-dd) from the nginx cached time (no syscall involved unlike Lua's date library). +Returns current date (in the format yyyy-mm-dd) from the Nginx cached time (no syscall involved unlike Lua's date library). This is the local time. == ngx.time == + '''syntax:''' ''secs = ngx.time()'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' -Returns the elapsed seconds from the epoch for the current time stamp from the nginx cached time (no syscall involved unlike Lua's date library). +Returns the elapsed seconds from the epoch for the current time stamp from the Nginx cached time (no syscall involved unlike Lua's date library). Updates of the Nginx time cache can be forced by calling [[#ngx.update_time|ngx.update_time]] first. == ngx.now == + '''syntax:''' ''secs = ngx.now()'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' -Returns a floating-point number for the elapsed time in seconds (including milliseconds as the decimal part) from the epoch for the current time stamp from the nginx cached time (no syscall involved unlike Lua's date library). +Returns a floating-point number for the elapsed time in seconds (including milliseconds as the decimal part) from the epoch for the current time stamp from the Nginx cached time (no syscall involved unlike Lua's date library). You can forcibly update the Nginx time cache by calling [[#ngx.update_time|ngx.update_time]] first. This API was first introduced in v0.3.1rc32. == ngx.update_time == + '''syntax:''' ''ngx.update_time()'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Forcibly updates the Nginx current time cache. This call involves a syscall and thus has some overhead, so do not abuse it. This API was first introduced in v0.3.1rc32. == ngx.localtime == + '''syntax:''' ''str = ngx.localtime()'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' -Returns the current time stamp (in the format yyyy-mm-dd hh:mm:ss) of the nginx cached time (no syscall involved unlike Lua's [http://www.lua.org/manual/5.1/manual.html#pdf-os.date os.date] function). +Returns the current time stamp (in the format yyyy-mm-dd hh:mm:ss) of the Nginx cached time (no syscall involved unlike Lua's [https://www.lua.org/manual/5.1/manual.html#pdf-os.date os.date] function). This is the local time. == ngx.utctime == + '''syntax:''' ''str = ngx.utctime()'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' -Returns the current time stamp (in the format yyyy-mm-dd hh:mm:ss) of the nginx cached time (no syscall involved unlike Lua's [http://www.lua.org/manual/5.1/manual.html#pdf-os.date os.date] function). +Returns the current time stamp (in the format yyyy-mm-dd hh:mm:ss) of the Nginx cached time (no syscall involved unlike Lua's [https://www.lua.org/manual/5.1/manual.html#pdf-os.date os.date] function). This is the UTC time. == ngx.cookie_time == + '''syntax:''' ''str = ngx.cookie_time(sec)'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Returns a formatted string can be used as the cookie expiration time. The parameter sec is the time stamp in seconds (like those returned from [[#ngx.time|ngx.time]]). @@ -4785,9 +5484,10 @@ Returns a formatted string can be used as the cookie expiration time. The parame == ngx.http_time == + '''syntax:''' ''str = ngx.http_time(sec)'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Returns a formated string can be used as the http header time (for example, being used in Last-Modified header). The parameter sec is the time stamp in seconds (like those returned from [[#ngx.time|ngx.time]]). @@ -4797,9 +5497,10 @@ Returns a formated string can be used as the http header time (for example, bein == ngx.parse_http_time == + '''syntax:''' ''sec = ngx.parse_http_time(str)'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Parse the http time string (as returned by [[#ngx.http_time|ngx.http_time]]) into seconds. Returns the seconds or nil if the input string is in bad forms. @@ -4811,16 +5512,18 @@ Parse the http time string (as returned by [[#ngx.http_time|ngx.http_time]]) int == ngx.is_subrequest == + '''syntax:''' ''value = ngx.is_subrequest'' '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*'' -Returns true if the current request is an nginx subrequest, or false otherwise. +Returns true if the current request is an Nginx subrequest, or false otherwise. == ngx.re.match == + '''syntax:''' ''captures, err = ngx.re.match(subject, regex, options?, ctx?, res_table?)'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Matches the subject string using the Perl compatible regular expression regex with the optional options. @@ -4952,7 +5655,7 @@ The ctx table argument combined with the a regex modif Note that, the options argument is not optional when the ctx argument is specified and that the empty Lua string ("") must be used as placeholder for options if no meaningful regex options are required. -This method requires the PCRE library enabled in Nginx. ([[#Special Escaping Sequences|Known Issue With Special Escaping Sequences]]). +This method requires the PCRE library enabled in Nginx ([[#Special Escaping Sequences|Known Issue With Special Escaping Sequences]]). To confirm that PCRE JIT is enabled, activate the Nginx debug log by adding the --with-debug option to Nginx or OpenResty's ./configure script. Then, enable the "debug" error log level in error_log directive. The following message will be generated if PCRE JIT is enabled: @@ -4965,11 +5668,12 @@ Starting from the 0.9.4 release, this function also accepts a 5th a This feature was introduced in the v0.2.1rc11 release. == ngx.re.find == + '''syntax:''' ''from, to, err = ngx.re.find(subject, regex, options?, ctx?, nth?)'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' -Similar to [[#ngx.re.match|ngx.re.match]] but only returns the beginning index (from) and end index (to) of the matched substring. The returned indexes are 1-based and can be fed directly into the [http://www.lua.org/manual/5.1/manual.html#pdf-string.sub string.sub] API function to obtain the matched substring. +Similar to [[#ngx.re.match|ngx.re.match]] but only returns the beginning index (from) and end index (to) of the matched substring. The returned indexes are 1-based and can be fed directly into the [https://www.lua.org/manual/5.1/manual.html#pdf-string.sub string.sub] API function to obtain the matched substring. In case of errors (like bad regexes or any PCRE runtime errors), this API function returns two nil values followed by a string describing the error. @@ -5014,9 +5718,10 @@ Since the 0.9.3 release, an optional 5th argument, nth This API function was first introduced in the v0.9.2 release. == ngx.re.gmatch == + '''syntax:''' ''iterator, err = ngx.re.gmatch(subject, regex, options?)'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Similar to [[#ngx.re.match|ngx.re.match]], but returns a Lua iterator instead, so as to let the user programmer iterate all the matches over the string argument with the PCRE regex. @@ -5082,14 +5787,15 @@ The optional options argument takes exactly the same semantics as t The current implementation requires that the iterator returned should only be used in a single request. That is, one should ''not'' assign it to a variable belonging to persistent namespace like a Lua package. -This method requires the PCRE library enabled in Nginx. ([[#Special Escaping Sequences|Known Issue With Special Escaping Sequences]]). +This method requires the PCRE library enabled in Nginx ([[#Special Escaping Sequences|Known Issue With Special Escaping Sequences]]). This feature was first introduced in the v0.2.1rc12 release. == ngx.re.sub == + '''syntax:''' ''newstr, n, err = ngx.re.sub(subject, regex, replace, options?)'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Substitutes the first match of the Perl compatible regular expression regex on the subject argument string with the string or function argument replace. The optional options argument has exactly the same meaning as in [[#ngx.re.match|ngx.re.match]]. @@ -5099,13 +5805,13 @@ When the replace is a string, then it is treated as a special templ local newstr, n, err = ngx.re.sub("hello, 1234", "([0-9])[0-9]", "[$0][$1]") - if newstr then - -- newstr == "hello, [12][1]34" - -- n == 1 - else + if not newstr then ngx.log(ngx.ERR, "error: ", err) return end + + -- newstr == "hello, [12][1]34" + -- n == 1 where $0 referring to the whole substring matched by the pattern and $1 referring to the first parenthesized capturing substring. @@ -5114,16 +5820,16 @@ Curly braces can also be used to disambiguate variable names from the background local newstr, n, err = ngx.re.sub("hello, 1234", "[0-9]", "${0}00") - -- newstr == "hello, 100234" - -- n == 1 + -- newstr == "hello, 100234" + -- n == 1 Literal dollar sign characters ($) in the replace string argument can be escaped by another dollar sign, for instance, local newstr, n, err = ngx.re.sub("hello, 1234", "[0-9]", "$$") - -- newstr == "hello, $234" - -- n == 1 + -- newstr == "hello, $234" + -- n == 1 Do not use backlashes to escape dollar signs; it will not work as expected. @@ -5134,21 +5840,23 @@ When the replace argument is of type "function", then it will be in local func = function (m) return "[" .. m[0] .. "][" .. m[1] .. "]" end + local newstr, n, err = ngx.re.sub("hello, 1234", "( [0-9] ) [0-9]", func, "x") - -- newstr == "hello, [12][1]34" - -- n == 1 + -- newstr == "hello, [12][1]34" + -- n == 1 The dollar sign characters in the return value of the replace function argument are not special at all. -This method requires the PCRE library enabled in Nginx. ([[#Special Escaping Sequences|Known Issue With Special Escaping Sequences]]). +This method requires the PCRE library enabled in Nginx ([[#Special Escaping Sequences|Known Issue With Special Escaping Sequences]]). This feature was first introduced in the v0.2.1rc13 release. == ngx.re.gsub == + '''syntax:''' ''newstr, n, err = ngx.re.gsub(subject, regex, replace, options?)'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Just like [[#ngx.re.sub|ngx.re.sub]], but does global substitution. @@ -5156,13 +5864,13 @@ Here is some examples: local newstr, n, err = ngx.re.gsub("hello, world", "([a-z])[a-z]+", "[$0,$1]", "i") - if newstr then - -- newstr == "[hello,h], [world,w]" - -- n == 2 - else + if not newstr then ngx.log(ngx.ERR, "error: ", err) return end + + -- newstr == "[hello,h], [world,w]" + -- n == 2 @@ -5170,24 +5878,25 @@ Here is some examples: return "[" .. m[0] .. "," .. m[1] .. "]" end local newstr, n, err = ngx.re.gsub("hello, world", "([a-z])[a-z]+", func, "i") - -- newstr == "[hello,h], [world,w]" - -- n == 2 + -- newstr == "[hello,h], [world,w]" + -- n == 2 -This method requires the PCRE library enabled in Nginx. ([[#Special Escaping Sequences|Known Issue With Special Escaping Sequences]]). +This method requires the PCRE library enabled in Nginx ([[#Special Escaping Sequences|Known Issue With Special Escaping Sequences]]). This feature was first introduced in the v0.2.1rc15 release. == ngx.shared.DICT == + '''syntax:''' ''dict = ngx.shared.DICT'' '''syntax:''' ''dict = ngx.shared[name_var]'' -'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Fetching the shm-based Lua dictionary object for the shared memory zone named DICT defined by the [[#lua_shared_dict|lua_shared_dict]] directive. -Shared memory zones are always shared by all the nginx worker processes in the current nginx server instance. +Shared memory zones are always shared by all the Nginx worker processes in the current Nginx server instance. The resulting object dict has the following methods: @@ -5213,7 +5922,7 @@ The resulting object dict has the following methods: * [[#ngx.shared.DICT.capacity|capacity]] * [[#ngx.shared.DICT.free_space|free_space]] -All these methods are ''atomic'' operations, that is, safe from concurrent accesses from multiple nginx worker processes for the same lua_shared_dict zone. +All these methods are ''atomic'' operations, that is, safe from concurrent accesses from multiple Nginx worker processes for the same lua_shared_dict zone. Here is an example: @@ -5260,9 +5969,10 @@ The contents in the dictionary storage will be lost, however, when the Nginx ser This feature was first introduced in the v0.3.1rc22 release. == ngx.shared.DICT.get == + '''syntax:''' ''value, flags = ngx.shared.DICT:get(key)'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Retrieving the value in the dictionary [[#ngx.shared.DICT|ngx.shared.DICT]] for the key key. If the key does not exist or has expired, then nil will be returned. @@ -5293,9 +6003,10 @@ This feature was first introduced in the v0.3.1rc22 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.get_stale == + '''syntax:''' ''value, flags, stale = ngx.shared.DICT:get_stale(key)'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Similar to the [[#ngx.shared.DICT.get|get]] method but returns the value even if the key has already expired. @@ -5308,9 +6019,10 @@ This method was first introduced in the 0.8.6 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.set == + '''syntax:''' ''success, err, forcible = ngx.shared.DICT:set(key, value, exptime?, flags?)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Unconditionally sets a key-value pair into the shm-based dictionary [[#ngx.shared.DICT|ngx.shared.DICT]]. Returns three values: @@ -5326,6 +6038,10 @@ The optional flags argument specifies a user flags value associated When it fails to allocate memory for the current key-value item, then set will try removing existing items in the storage according to the Least-Recently Used (LRU) algorithm. Note that, LRU takes priority over expiration time here. If up to tens of existing items have been removed and the storage left is still insufficient (either due to the total capacity limit specified by [[#lua_shared_dict|lua_shared_dict]] or memory segmentation), then the err return value will be no memory and success will be false. +If the sizes of items in the dictionary are not multiples or even powers of a certain value (like 2), it is easier to encounter no memory error because of memory fragmentation. It is recommended to use different dictionaries for different sizes of items. + +When you encounter no memory error, you can also evict more least-recently-used items by retrying this method call more times to to make room for the current item. + If this method succeeds in storing the current item by forcibly removing other not-yet-expired items in the dictionary via LRU, the forcible return value will be true. If it stores the item without forcibly removing other valid items, then the return value forcible will be false. The first argument to this method must be the dictionary object itself, for example, @@ -5351,9 +6067,10 @@ Please note that while internally the key-value pair is set atomically, the atom See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.safe_set == + '''syntax:''' ''ok, err = ngx.shared.DICT:safe_set(key, value, exptime?, flags?)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Similar to the [[#ngx.shared.DICT.set|set]] method, but never overrides the (least recently used) unexpired items in the store when running out of storage in the shared memory zone. In this case, it will immediately return nil and the string "no memory". @@ -5362,9 +6079,10 @@ This feature was first introduced in the v0.7.18 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.add == + '''syntax:''' ''success, err, forcible = ngx.shared.DICT:add(key, value, exptime?, flags?)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Just like the [[#ngx.shared.DICT.set|set]] method, but only stores the key-value pair into the dictionary [[#ngx.shared.DICT|ngx.shared.DICT]] if the key does ''not'' exist. @@ -5375,9 +6093,10 @@ This feature was first introduced in the v0.3.1rc22 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.safe_add == + '''syntax:''' ''ok, err = ngx.shared.DICT:safe_add(key, value, exptime?, flags?)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Similar to the [[#ngx.shared.DICT.add|add]] method, but never overrides the (least recently used) unexpired items in the store when running out of storage in the shared memory zone. In this case, it will immediately return nil and the string "no memory". @@ -5386,9 +6105,10 @@ This feature was first introduced in the v0.7.18 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.replace == + '''syntax:''' ''success, err, forcible = ngx.shared.DICT:replace(key, value, exptime?, flags?)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Just like the [[#ngx.shared.DICT.set|set]] method, but only stores the key-value pair into the dictionary [[#ngx.shared.DICT|ngx.shared.DICT]] if the key ''does'' exist. @@ -5399,9 +6119,10 @@ This feature was first introduced in the v0.3.1rc22 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.delete == + '''syntax:''' ''ngx.shared.DICT:delete(key)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Unconditionally removes the key-value pair from the shm-based dictionary [[#ngx.shared.DICT|ngx.shared.DICT]]. @@ -5412,9 +6133,12 @@ This feature was first introduced in the v0.3.1rc22 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.incr == -'''syntax:''' ''newval, err, forcible? = ngx.shared.DICT:incr(key, value, init?)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''syntax:''' ''newval, err, forcible? = ngx.shared.DICT:incr(key, value, init?, init_ttl?)'' + +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' + +'''optional requirement:''' resty.core.shdict or resty.core Increments the (numerical) value for key in the shm-based dictionary [[#ngx.shared.DICT|ngx.shared.DICT]] by the step value value. Returns the new resulting number if the operation is successfully completed or nil and an error message otherwise. @@ -5425,6 +6149,24 @@ When the key does not exist or has already expired in the shared dictionary, Like the [[#ngx.shared.DICT.add|add]] method, it also overrides the (least recently used) unexpired items in the store when running out of storage in the shared memory zone. +The optional init_ttl argument specifies expiration time (in seconds) of the value when it is initialized via the init argument. The time resolution is 0.001 seconds. If init_ttl takes the value 0 (which is the default), then the item will never expire. This argument cannot be provided without providing the init argument as well, and has no effect if the value already exists (e.g., if it was previously inserted via [[#ngx.shared.DICT.set|set]] or the likes). + +'''Note:''' Usage of the init_ttl argument requires the resty.core.shdict or resty.core modules from the [https://github.com/openresty/lua-resty-core lua-resty-core] library. Example: + + + require "resty.core" + + local cats = ngx.shared.cats + local newval, err = cats:incr("black_cats", 1, 0, 0.1) + + print(newval) -- 1 + + ngx.sleep(0.2) + + local val, err = cats:get("black_cats") + print(val) -- nil + + The forcible return value will always be nil when the init argument is not specified. If this method succeeds in storing the current item by forcibly removing other not-yet-expired items in the dictionary via LRU, the forcible return value will be true. If it stores the item without forcibly removing other valid items, then the return value forcible will be false. @@ -5437,12 +6179,15 @@ This method was first introduced in the v0.3.1rc22 release. The optional init parameter was first added in the v0.10.6 release. +The optional init_ttl parameter was introduced in the v0.10.12rc2 release. + See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.lpush == + '''syntax:''' ''length, err = ngx.shared.DICT:lpush(key, value)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Inserts the specified (numerical or string) value at the head of the list named key in the shm-based dictionary [[#ngx.shared.DICT|ngx.shared.DICT]]. Returns the number of elements in the list after the push operation. @@ -5455,9 +6200,10 @@ This feature was first introduced in the v0.10.6 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.rpush == + '''syntax:''' ''length, err = ngx.shared.DICT:rpush(key, value)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Similar to the [[#ngx.shared.DICT.lpush|lpush]] method, but inserts the specified (numerical or string) value at the tail of the list named key. @@ -5466,9 +6212,10 @@ This feature was first introduced in the v0.10.6 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.lpop == + '''syntax:''' ''val, err = ngx.shared.DICT:lpop(key)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Removes and returns the first element of the list named key in the shm-based dictionary [[#ngx.shared.DICT|ngx.shared.DICT]]. @@ -5479,9 +6226,10 @@ This feature was first introduced in the v0.10.6 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.rpop == + '''syntax:''' ''val, err = ngx.shared.DICT:rpop(key)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Removes and returns the last element of the list named key in the shm-based dictionary [[#ngx.shared.DICT|ngx.shared.DICT]]. @@ -5492,9 +6240,10 @@ This feature was first introduced in the v0.10.6 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.llen == + '''syntax:''' ''len, err = ngx.shared.DICT:llen(key)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Returns the number of elements in the list named key in the shm-based dictionary [[#ngx.shared.DICT|ngx.shared.DICT]]. @@ -5505,9 +6254,10 @@ This feature was first introduced in the v0.10.6 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.ttl == + '''syntax:''' ''ttl, err = ngx.shared.DICT:ttl(key)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' '''requires:''' resty.core.shdict or resty.core @@ -5538,9 +6288,10 @@ This feature was first introduced in the v0.10.11 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.expire == + '''syntax:''' ''success, err = ngx.shared.DICT:expire(key, exptime)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' '''requires:''' resty.core.shdict or resty.core @@ -5573,33 +6324,36 @@ This feature was first introduced in the v0.10.11 release. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.flush_all == + '''syntax:''' ''ngx.shared.DICT:flush_all()'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' -Flushes out all the items in the dictionary. This method does not actuall free up all the memory blocks in the dictionary but just marks all the existing items as expired. +Flushes out all the items in the dictionary. This method does not actually free up all the memory blocks in the dictionary but just marks all the existing items as expired. This feature was first introduced in the v0.5.0rc17 release. See also [[#ngx.shared.DICT.flush_expired|ngx.shared.DICT.flush_expired]] and [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.flush_expired == + '''syntax:''' ''flushed = ngx.shared.DICT:flush_expired(max_count?)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Flushes out the expired items in the dictionary, up to the maximal number specified by the optional max_count argument. When the max_count argument is given 0 or not given at all, then it means unlimited. Returns the number of items that have actually been flushed. -Unlike the [[#ngx.shared.DICT.flush_all|flush_all]] method, this method actually free up the memory used by the expired items. +Unlike the [[#ngx.shared.DICT.flush_all|flush_all]] method, this method actually frees up the memory used by the expired items. This feature was first introduced in the v0.6.3 release. See also [[#ngx.shared.DICT.flush_all|ngx.shared.DICT.flush_all]] and [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.get_keys == + '''syntax:''' ''keys = ngx.shared.DICT:get_keys(max_count?)'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Fetch a list of the keys from the dictionary, up to . @@ -5610,9 +6364,10 @@ By default, only the first 1024 keys (if any) are returned. When the v0.7.3 release. == ngx.shared.DICT.capacity == + '''syntax:''' ''capacity_bytes = ngx.shared.DICT:capacity()'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' '''requires:''' resty.core.shdict or resty.core @@ -5632,20 +6387,21 @@ This feature was first introduced in the v0.10.11 release. '''Note:''' This method requires the resty.core.shdict or resty.core modules from the [https://github.com/openresty/lua-resty-core lua-resty-core] library. -This feature requires at least nginx core version 0.7.3. +This feature requires at least Nginx core version 0.7.3. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.shared.DICT.free_space == + '''syntax:''' ''free_page_bytes = ngx.shared.DICT:free_space()'' -'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' '''requires:''' resty.core.shdict or resty.core Retrieves the free page size in bytes for the shm-based dictionary [[#ngx.shared.DICT|ngx.shared.DICT]]. -'''Note:''' The memory for ngx.shared.DICT is allocated via the nginx slab allocator which has each slot for +'''Note:''' The memory for ngx.shared.DICT is allocated via the Nginx slab allocator which has each slot for data size ranges like \~8, 9\~16, 17\~32, ..., 1025\~2048, 2048\~ bytes. And pages are assigned to a slot if there is no room in already assigned pages for the slot. @@ -5670,14 +6426,15 @@ This feature was first introduced in the v0.10.11 release. '''Note:''' This method requires the resty.core.shdict or resty.core modules from the [https://github.com/openresty/lua-resty-core lua-resty-core] library. -This feature requires at least nginx core version 1.11.7. +This feature requires at least Nginx core version 1.11.7. See also [[#ngx.shared.DICT|ngx.shared.DICT]]. == ngx.socket.udp == + '''syntax:''' ''udpsock = ngx.socket.udp()'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Creates and returns a UDP or datagram-oriented unix domain socket object (also known as one type of the "cosocket" objects). The following methods are supported on this object: @@ -5694,11 +6451,12 @@ This feature was first introduced in the v0.5.7 release. See also [[#ngx.socket.tcp|ngx.socket.tcp]]. == udpsock:setpeername == + '''syntax:''' ''ok, err = udpsock:setpeername(host, port)'' '''syntax:''' ''ok, err = udpsock:setpeername("unix:/path/to/unix-domain.socket")'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Attempts to connect a UDP socket object to a remote server or to a datagram unix domain socket file. Because the datagram protocol is actually connection-less, this method does not really establish a "connection", but only just set the name of the remote peer for subsequent read/write operations. @@ -5740,6 +6498,9 @@ Since the v0.7.18 release, connecting to a datagram unix domain soc ngx.say("failed to connect to the datagram unix domain socket: ", err) return end + + -- do something after connect + -- such as sock:send or sock:receive assuming the datagram service is listening on the unix domain socket file /tmp/some-datagram-service.sock and the client socket will use the "autobind" feature on Linux. @@ -5749,9 +6510,10 @@ Calling this method on an already connected socket object will cause the origina This method was first introduced in the v0.5.7 release. == udpsock:send == + '''syntax:''' ''ok, err = udpsock:send(data)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Sends data on the current UDP or datagram unix domain socket object. @@ -5762,9 +6524,10 @@ The input argument data can either be a Lua string or a (nested) Lu This feature was first introduced in the v0.5.7 release. == udpsock:receive == + '''syntax:''' ''data, err = udpsock:receive(size?)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Receives data from the UDP or datagram unix domain socket object with an optional receive buffer size argument, size. @@ -5793,9 +6556,10 @@ It is important here to call the [[#udpsock:settimeout|settimeout]] method ''bef This feature was first introduced in the v0.5.7 release. == udpsock:close == + '''syntax:''' ''ok, err = udpsock:close()'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Closes the current UDP or datagram unix domain socket. It returns the 1 in case of success and returns nil with a string describing the error otherwise. @@ -5804,9 +6568,10 @@ Socket objects that have not invoked this method (and associated connections) wi This feature was first introduced in the v0.5.7 release. == udpsock:settimeout == + '''syntax:''' ''udpsock:settimeout(time)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Set the timeout value in milliseconds for subsequent socket operations (like [[#udpsock:receive|receive]]). @@ -5822,13 +6587,16 @@ socket, then this API name is preferred. This API function was first added to the v0.10.1 release. == ngx.socket.tcp == + '''syntax:''' ''tcpsock = ngx.socket.tcp()'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Creates and returns a TCP or stream-oriented unix domain socket object (also known as one type of the "cosocket" objects). The following methods are supported on this object: +* [[#tcpsock:bind|bind]] * [[#tcpsock:connect|connect]] +* [[#tcpsock:setclientcert|setclientcert]] * [[#tcpsock:sslhandshake|sslhandshake]] * [[#tcpsock:send|send]] * [[#tcpsock:receive|receive]] @@ -5836,13 +6604,14 @@ Creates and returns a TCP or stream-oriented unix domain socket object (also kno * [[#tcpsock:settimeout|settimeout]] * [[#tcpsock:settimeouts|settimeouts]] * [[#tcpsock:setoption|setoption]] +* [[#tcpsock:receiveany|receiveany]] * [[#tcpsock:receiveuntil|receiveuntil]] * [[#tcpsock:setkeepalive|setkeepalive]] * [[#tcpsock:getreusedtimes|getreusedtimes]] It is intended to be compatible with the TCP API of the [http://w3.impa.br/~diego/software/luasocket/tcp.html LuaSocket] library but is 100% nonblocking out of the box. Also, we introduce some new APIs to provide more functionalities. -The cosocket object created by this API function has exactly the same lifetime as the Lua handler creating it. So never pass the cosocket object to any other Lua handler (including ngx.timer callback functions) and never share the cosocket object between different NGINX requests. +The cosocket object created by this API function has exactly the same lifetime as the Lua handler creating it. So never pass the cosocket object to any other Lua handler (including ngx.timer callback functions) and never share the cosocket object between different Nginx requests. For every cosocket object's underlying connection, if you do not explicitly close it (via [[#tcpsock:close|close]]) or put it back to the connection @@ -5863,12 +6632,45 @@ This feature was first introduced in the v0.5.0rc1 release. See also [[#ngx.socket.udp|ngx.socket.udp]]. +== tcpsock:bind == +'''syntax:''' ''ok, err = tcpsock:bind(address)'' + +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*'' + +Just like the standard [[HttpProxyModule#proxy_bind|proxy_bind]] directive, this api makes the outgoing connection to a upstream server originate from the specified local IP address. + +Only IP addresses can be specified as the address argument. + +Here is an example for connecting to a TCP server from the specified local IP address: + + + location /test { + content_by_lua_block { + local sock = ngx.socket.tcp() + -- assume "192.168.1.10" is the local ip address + local ok, err = sock:bind("192.168.1.10") + if not ok then + ngx.say("failed to bind") + return + end + local ok, err = sock:connect("192.168.1.67", 80) + if not ok then + ngx.say("failed to connect server: ", err) + return + end + ngx.say("successfully connected!") + sock:close() + } + } + + == tcpsock:connect == + '''syntax:''' ''ok, err = tcpsock:connect(host, port, options_table?)'' '''syntax:''' ''ok, err = tcpsock:connect("unix:/path/to/unix-domain.socket", options_table?)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Attempts to connect a TCP socket object to a remote server or to a stream unix domain socket file without blocking. @@ -5912,6 +6714,9 @@ Connecting to a Unix Domain Socket file is also possible: ngx.say("failed to connect to the memcached unix domain socket: ", err) return end + + -- do something after connect + -- such as sock:send or sock:receive assuming memcached (or something else) is listening on the unix domain socket file /tmp/memcached.sock. @@ -5933,14 +6738,74 @@ An optional Lua table can be specified as the last argument to this method to sp * pool : specify a custom name for the connection pool being used. If omitted, then the connection pool name will be generated from the string template ":" or "". +* pool_size +: specify the size of the connection pool. If omitted and no +: backlog option was provided, no pool will be created. If omitted +: but backlog was provided, the pool will be created with a default +: size equal to the value of the [[#lua_socket_pool_size|lua_socket_pool_size]] +: directive. +: The connection pool holds up to pool_size alive connections +: ready to be reused by subsequent calls to [[#tcpsock:connect|connect]], but +: note that there is no upper limit to the total number of opened connections +: outside of the pool. If you need to restrict the total number of opened +: connections, specify the backlog option. +: When the connection pool would exceed its size limit, the least recently used +: (kept-alive) connection already in the pool will be closed to make room for +: the current connection. +: Note that the cosocket connection pool is per Nginx worker process rather +: than per Nginx server instance, so the size limit specified here also applies +: to every single Nginx worker process. Also note that the size of the connection +: pool cannot be changed once it has been created. +: This option was first introduced in the v0.10.14 release. + +* backlog +: if specified, this module will limit the total number of opened connections +: for this pool. No more connections than pool_size can be opened +: for this pool at any time. If the connection pool is full, subsequent +: connect operations will be queued into a queue equal to this option's +: value (the "backlog" queue). +: If the number of queued connect operations is equal to backlog, +: subsequent connect operations will fail and return nil plus the +: error string "too many waiting connect operations". +: The queued connect operations will be resumed once the number of connections +: in the pool is less than pool_size. +: The queued connect operation will abort once they have been queued for more +: than connect_timeout, controlled by +: [[#tcpsock:settimeouts|settimeouts]], and will return nil plus +: the error string "timeout". +: This option was first introduced in the v0.10.14 release. + The support for the options table argument was first introduced in the v0.5.7 release. This method was first introduced in the v0.5.0rc1 release. +== tcpsock:setclientcert == + +'''syntax:''' ''ok, err = tcpsock:setclientcert(cert, pkey)'' + +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' + +Set client certificate chain and corresponding private key to the TCP socket object. +The certificate chain and private key provided will be used later by the [tcpsock:sslhandshake](#tcpsocksslhandshake) method. + +* cert specify a client certificate chain cdata object that will be used while handshaking with +remote server. These objects can be created using [ngx.ssl.parse\_pem\_cert](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md#parse_pem_cert) +function provided by lua-resty-core. Note that specifying the cert option requires +corresponding pkey be provided too. See below. +* pkey specify a private key corresponds to the cert option above. +These objects can be created using [ngx.ssl.parse\_pem\_priv\_key](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md#parse_pem_priv_key) +function provided by lua-resty-core. + +If both of cert and pkey are nil, this method will clear any existing client certificate and private key +that was previously set on the cosocket object. + +This method was first introduced in the `v0.10.22` release. + == tcpsock:sslhandshake == + '''syntax:''' ''session, err = tcpsock:sslhandshake(reused_session?, server_name?, ssl_verify?, send_status_req?)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Does SSL/TLS handshake on the currently established connection. @@ -5980,9 +6845,10 @@ immediately. This method was first introduced in the v0.9.11 release. == tcpsock:send == + '''syntax:''' ''bytes, err = tcpsock:send(data)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Sends data without blocking on the current TCP or Unix Domain Socket connection. @@ -6006,11 +6872,12 @@ In case of any connection errors, this method always automatically closes the cu This feature was first introduced in the v0.5.0rc1 release. == tcpsock:receive == + '''syntax:''' ''data, err, partial = tcpsock:receive(size)'' '''syntax:''' ''data, err, partial = tcpsock:receive(pattern?)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Receives data from the connected socket according to the reading pattern or size. @@ -6045,10 +6912,42 @@ Since the v0.8.8 release, this method no longer automatically close This feature was first introduced in the v0.5.0rc1 release. +== tcpsock:receiveany == + +'''syntax:''' ''data, err = tcpsock:receiveany(max)'' + +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' + +Returns any data received by the connected socket, at most max bytes. + +This method is a synchronous operation just like the [[#tcpsock:send|send]] method and is 100% nonblocking. + +In case of success, it returns the data received; in case of error, it returns nil with a string describing the error. + +If the received data is more than this size, this method will return with exactly this size of data. +The remaining data in the underlying receive buffer could be returned in the next reading operation. + +Timeout for the reading operation is controlled by the [[#lua_socket_read_timeout|lua_socket_read_timeout]] config directive and the [[#tcpsock:settimeouts|settimeouts]] method. And the latter takes priority. For example: + + + sock:settimeouts(1000, 1000, 1000) -- one second timeout for connect/read/write + local data, err = sock:receiveany(10 * 1024) -- read any data, at most 10K + if not data then + ngx.say("failed to read any data: ", err) + return + end + ngx.say("successfully read: ", data) + + +This method doesn't automatically close the current connection when the read timeout error occurs. For other connection errors, this method always automatically closes the connection. + +This feature was first introduced in the v0.10.14 release. + == tcpsock:receiveuntil == + '''syntax:''' ''iterator = tcpsock:receiveuntil(pattern, options?)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' This method returns an iterator Lua function that can be called to read the data stream until it sees the specified pattern or an error occurs. @@ -6138,9 +7037,10 @@ Since the v0.8.8 release, this method no longer automatically close This method was first introduced in the v0.5.0rc1 release. == tcpsock:close == + '''syntax:''' ''ok, err = tcpsock:close()'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Closes the current TCP or stream unix domain socket. It returns the 1 in case of success and returns nil with a string describing the error otherwise. @@ -6151,59 +7051,165 @@ Socket objects that have not invoked this method (and associated connections) wi This feature was first introduced in the v0.5.0rc1 release. == tcpsock:settimeout == + '''syntax:''' ''tcpsock:settimeout(time)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Set the timeout value in milliseconds for subsequent socket operations ([[#tcpsock:connect|connect]], [[#tcpsock:receive|receive]], and iterators returned from [[#tcpsock:receiveuntil|receiveuntil]]). -Settings done by this method takes priority over those config directives, i.e., [[#lua_socket_connect_timeout|lua_socket_connect_timeout]], [[#lua_socket_send_timeout|lua_socket_send_timeout]], and [[#lua_socket_read_timeout|lua_socket_read_timeout]]. +Settings done by this method take priority over those specified via config directives (i.e. [[#lua_socket_connect_timeout|lua_socket_connect_timeout]], [[#lua_socket_send_timeout|lua_socket_send_timeout]], and [[#lua_socket_read_timeout|lua_socket_read_timeout]]). Note that this method does ''not'' affect the [[#lua_socket_keepalive_timeout|lua_socket_keepalive_timeout]] setting; the timeout argument to the [[#tcpsock:setkeepalive|setkeepalive]] method should be used for this purpose instead. This feature was first introduced in the v0.5.0rc1 release. == tcpsock:settimeouts == + '''syntax:''' ''tcpsock:settimeouts(connect_timeout, send_timeout, read_timeout)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' -Sets the connect timeout thresold, send timeout threshold, and read timeout threshold, respetively, in milliseconds, for subsequent socket +Respectively sets the connect, send, and read timeout thresholds (in milliseconds) for subsequent socket operations ([[#tcpsock:connect|connect]], [[#tcpsock:send|send]], [[#tcpsock:receive|receive]], and iterators returned from [[#tcpsock:receiveuntil|receiveuntil]]). -Settings done by this method takes priority over those config directives, i.e., [[#lua_socket_connect_timeout|lua_socket_connect_timeout]], [[#lua_socket_send_timeout|lua_socket_send_timeout]], and [[#lua_socket_read_timeout|lua_socket_read_timeout]]. +Settings done by this method take priority over those specified via config directives (i.e. [[#lua_socket_connect_timeout|lua_socket_connect_timeout]], [[#lua_socket_send_timeout|lua_socket_send_timeout]], and [[#lua_socket_read_timeout|lua_socket_read_timeout]]). -You are recommended to use [[#tcpsock:settimeouts|settimeouts]] instead of [[#tcpsock:settimeout|settimeout]]. +It is recommended to use [[#tcpsock:settimeouts|settimeouts]] instead of [[#tcpsock:settimeout|settimeout]]. Note that this method does ''not'' affect the [[#lua_socket_keepalive_timeout|lua_socket_keepalive_timeout]] setting; the timeout argument to the [[#tcpsock:setkeepalive|setkeepalive]] method should be used for this purpose instead. This feature was first introduced in the v0.10.7 release. == tcpsock:setoption == -'''syntax:''' ''tcpsock:setoption(option, value?)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''syntax:''' ''ok, err = tcpsock:setoption(option, value?)'' + +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' -This function is added for [http://w3.impa.br/~diego/software/luasocket/tcp.html LuaSocket] API compatibility and does nothing for now. Its functionality will be implemented in future. +This function is added for [http://w3.impa.br/~diego/software/luasocket/tcp.html LuaSocket] API compatibility and does nothing for now. Its functionality is implemented v0.10.18. This feature was first introduced in the v0.5.0rc1 release. +In case of success, it returns true. Otherwise, it returns nil and a string describing the error. + +The option is a string with the option name, and the value depends on the option being set: + +* keepalive + +: Setting this option to true enables sending of keep-alive messages on +: connection-oriented sockets. Make sure the connect function +: had been called before, for example, + + + local ok, err = tcpsock:setoption("keepalive", true) + if not ok then + ngx.say("setoption keepalive failed: ", err) + end + +* reuseaddr + +: Enabling this option indicates that the rules used in validating addresses +: supplied in a call to bind should allow reuse of local addresses. Make sure +: the connect function had been called before, for example, + + + local ok, err = tcpsock:setoption("reuseaddr", 0) + if not ok then + ngx.say("setoption reuseaddr failed: ", err) + end + +* tcp-nodelay + +: Setting this option to true disables the Nagle's algorithm for the connection. +: Make sure the connect function had been called before, for example, + + + local ok, err = tcpsock:setoption("tcp-nodelay", true) + if not ok then + ngx.say("setoption tcp-nodelay failed: ", err) + end + +* sndbuf + +: Sets the maximum socket send buffer in bytes. The kernel doubles this value +: (to allow space for bookkeeping overhead) when it is set using setsockopt(). +: Make sure the connect function had been called before, for example, + + + local ok, err = tcpsock:setoption("sndbuf", 1024 * 10) + if not ok then + ngx.say("setoption sndbuf failed: ", err) + end + +* rcvbuf + +: Sets the maximum socket receive buffer in bytes. The kernel doubles this value +: (to allow space for bookkeeping overhead) when it is set using setsockopt. Make +: sure the connect function had been called before, for example, + + + local ok, err = tcpsock:setoption("rcvbuf", 1024 * 10) + if not ok then + ngx.say("setoption rcvbuf failed: ", err) + end + + +NOTE: Once the option is set, it will become effective until the connection is closed. If you know the connection is from the connection pool and all the in-pool connections already have called the setoption() method with the desired socket option state, then you can just skip calling setoption() again to avoid the overhead of repeated calls, for example, + + + local count, err = tcpsock:getreusedtimes() + if not count then + ngx.say("getreusedtimes failed: ", err) + return + end + + if count == 0 then + local ok, err = tcpsock:setoption("rcvbuf", 1024 * 10) + if not ok then + ngx.say("setoption rcvbuf failed: ", err) + return + end + end + + +These options described above are supported in v0.10.18, and more options will be implemented in future. + == tcpsock:setkeepalive == + '''syntax:''' ''ok, err = tcpsock:setkeepalive(timeout?, size?)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Puts the current socket's connection immediately into the cosocket built-in connection pool and keep it alive until other [[#tcpsock:connect|connect]] method calls request it or the associated maximal idle timeout is expired. The first optional argument, timeout, can be used to specify the maximal idle timeout (in milliseconds) for the current connection. If omitted, the default setting in the [[#lua_socket_keepalive_timeout|lua_socket_keepalive_timeout]] config directive will be used. If the 0 value is given, then the timeout interval is unlimited. -The second optional argument, size, can be used to specify the maximal number of connections allowed in the connection pool for the current server (i.e., the current host-port pair or the unix domain socket file path). Note that the size of the connection pool cannot be changed once the pool is created. When this argument is omitted, the default setting in the [[#lua_socket_pool_size|lua_socket_pool_size]] config directive will be used. - -When the connection pool exceeds the available size limit, the least recently used (idle) connection already in the pool will be closed to make room for the current connection. - -Note that the cosocket connection pool is per Nginx worker process rather than per Nginx server instance, so the size limit specified here also applies to every single Nginx worker process. - -Idle connections in the pool will be monitored for any exceptional events like connection abortion or unexpected incoming data on the line, in which cases the connection in question will be closed and removed from the pool. +The second optional argument size is considered deprecated since +the v0.10.14 release of this module, in favor of the +pool_size option of the [[#tcpsock:connect|connect]] method. +Since the v0.10.14 release, this option will only take effect if +the call to [[#tcpsock:connect|connect]] did not already create a connection +pool. +When this option takes effect (no connection pool was previously created by +[[#tcpsock:connect|connect]]), it will specify the size of the connection pool, +and create it. +If omitted (and no pool was previously created), the default size is the value +of the [[#lua_socket_pool_size|lua_socket_pool_size]] directive. +The connection pool holds up to size alive connections ready to be +reused by subsequent calls to [[#tcpsock:connect|connect]], but note that there +is no upper limit to the total number of opened connections outside of the +pool. +When the connection pool would exceed its size limit, the least recently used +(kept-alive) connection already in the pool will be closed to make room for +the current connection. +Note that the cosocket connection pool is per Nginx worker process rather +than per Nginx server instance, so the size limit specified here also applies +to every single Nginx worker process. Also note that the size of the connection +pool cannot be changed once it has been created. +If you need to restrict the total number of opened connections, specify both +the pool_size and backlog option in the call to +[[#tcpsock:connect|connect]]. In case of success, this method returns 1; otherwise, it returns nil and a string describing the error. @@ -6214,9 +7220,10 @@ This method also makes the current cosocket object enter the "closed" state, so This feature was first introduced in the v0.5.0rc1 release. == tcpsock:getreusedtimes == + '''syntax:''' ''count, err = tcpsock:getreusedtimes()'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' This method returns the (successfully) reused times for the current connection. In case of error, it returns nil and a string describing the error. @@ -6225,6 +7232,7 @@ If the current connection does not come from the built-in connection pool, then This feature was first introduced in the v0.5.0rc1 release. == ngx.socket.connect == + '''syntax:''' ''tcpsock, err = ngx.socket.connect(host, port)'' '''syntax:''' ''tcpsock, err = ngx.socket.connect("unix:/path/to/unix-domain.socket")'' @@ -6247,9 +7255,10 @@ There is no way to use the [[#tcpsock:settimeout|settimeout]] method to specify This feature was first introduced in the v0.5.0rc1 release. == ngx.get_phase == + '''syntax:''' ''str = ngx.get_phase()'' -'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_by_lua*, init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Retrieves the current running phase name. Possible return values are @@ -6263,6 +7272,8 @@ Retrieves the current running phase name. Possible return values are : for the context of [[#ssl_session_fetch_by_lua_block|ssl_session_fetch_by_lua*]]. * ssl_session_store : for the context of [[#ssl_session_store_by_lua_block|ssl_session_store_by_lua*]]. +* ssl_client_hello +: for the context of [[#ssl_client_hello_by_lua_block|ssl_client_hello_by_lua*]]. * set : for the context of [[#set_by_lua|set_by_lua*]]. * rewrite @@ -6281,13 +7292,16 @@ Retrieves the current running phase name. Possible return values are : for the context of [[#log_by_lua|log_by_lua*]]. * timer : for the context of user callback functions for [[#ngx.timer.at|ngx.timer.*]]. +* exit_worker +: for the context of [[#exit_worker_by_lua|exit_worker_by_lua*]]. This API was first introduced in the v0.5.10 release. == ngx.thread.spawn == + '''syntax:''' ''co = ngx.thread.spawn(func, arg1, arg2, ...)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Spawns a new user "light thread" with the Lua function func as well as those optional arguments arg1, arg2, and etc. Returns a Lua thread (or Lua coroutine) object represents this "light thread". @@ -6302,7 +7316,7 @@ All the Lua code chunks running by [[#rewrite_by_lua|rewrite_by_lua]], [[#access By default, the corresponding Nginx handler (e.g., [[#rewrite_by_lua|rewrite_by_lua]] handler) will not terminate until # both the "entry thread" and all the user "light threads" terminates, -# a "light thread" (either the "entry thread" or a user "light thread" aborts by calling [[#ngx.exit|ngx.exit]], [[#ngx.exec|ngx.exec]], [[#ngx.redirect|ngx.redirect]], or [[#ngx.req.set_uri|ngx.req.set_uri(uri, true)]], or +# a "light thread" (either the "entry thread" or a user "light thread") aborts by calling [[#ngx.exit|ngx.exit]], [[#ngx.exec|ngx.exec]], [[#ngx.redirect|ngx.redirect]], or [[#ngx.req.set_uri|ngx.req.set_uri(uri, true)]], or # the "entry thread" terminates with a Lua error. When the user "light thread" terminates with a Lua error, however, it will not abort other running "light threads" like the "entry thread" does. @@ -6418,9 +7432,10 @@ Then it will generate the output This API was first enabled in the v0.7.0 release. == ngx.thread.wait == + '''syntax:''' ''ok, res1, res2, ... = ngx.thread.wait(thread1, thread2, ...)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Waits on one or more child "light threads" and returns the results of the first "light thread" that terminates (either successfully or with an error). @@ -6516,17 +7531,19 @@ And it will generate the following output: This API was first enabled in the v0.7.0 release. == ngx.thread.kill == + '''syntax:''' ''ok, err = ngx.thread.kill(thread)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_client_hello_by_lua*'' Kills a running "light thread" created by [[#ngx.thread.spawn|ngx.thread.spawn]]. Returns a true value when successful or nil and a string describing the error otherwise. -According to the current implementation, only the parent coroutine (or "light thread") can kill a thread. Also, a running "light thread" with pending NGINX subrequests (initiated by [[#ngx.location.capture|ngx.location.capture]] for example) cannot be killed due to a limitation in the NGINX core. +According to the current implementation, only the parent coroutine (or "light thread") can kill a thread. Also, a running "light thread" with pending Nginx subrequests (initiated by [[#ngx.location.capture|ngx.location.capture]] for example) cannot be killed due to a limitation in the Nginx core. This API was first enabled in the v0.9.9 release. == ngx.on_abort == + '''syntax:''' ''ok, err = ngx.on_abort(callback)'' '''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' @@ -6563,9 +7580,10 @@ This API was first introduced in the v0.7.4 release. See also [[#lua_check_client_abort|lua_check_client_abort]]. == ngx.timer.at == + '''syntax:''' ''hdl, err = ngx.timer.at(delay, callback, user_arg1, user_arg2, ...)'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Creates an Nginx timer with a user callback function as well as optional user arguments. @@ -6618,6 +7636,8 @@ Here is a simple example: ngx.log(ngx.ERR, "failed to create timer: ", err) return end + + -- other job in log_by_lua_block } } @@ -6637,6 +7657,8 @@ One can also create infinite re-occurring timers, for instance, a timer getting ngx.log(ngx.ERR, "failed to create the timer: ", err) return end + + -- do something in timer end local ok, err = ngx.timer.at(delay, handler) @@ -6644,6 +7666,8 @@ One can also create infinite re-occurring timers, for instance, a timer getting ngx.log(ngx.ERR, "failed to create the timer: ", err) return end + + -- do other jobs It is recommended, however, to use the [[#ngx.timer.every|ngx.timer.every]] API function @@ -6682,20 +7706,31 @@ user "light threads" ([[#ngx.thread.spawn|ngx.thread.*]]), [[#ngx.exit|ngx.exit] (like [[#ngx.say|ngx.say]], [[#ngx.print|ngx.print]], and [[#ngx.flush|ngx.flush]]) are explicitly disabled in this context. +You must notice that each timer will be based on a fake request (this fake request is also based on a fake connection). Because Nginx's memory release is based on the connection closure, if you run a lot of APIs that apply for memory resources in a timer, such as [[#tcpsock:connect|tcpsock:connect]], will cause the accumulation of memory resources. So it is recommended to create a new timer after running several times to release memory resources. + You can pass most of the standard Lua values (nils, booleans, numbers, strings, tables, closures, file handles, and etc) into the timer callback, either explicitly as user arguments or implicitly as upvalues for the callback closure. There are several exceptions, however: you ''cannot'' pass any thread objects returned by [[#coroutine.create|coroutine.create]] and [[#ngx.thread.spawn|ngx.thread.spawn]] or any cosocket objects returned by [[#ngx.socket.tcp|ngx.socket.tcp]], [[#ngx.socket.udp|ngx.socket.udp]], and [[#ngx.req.socket|ngx.req.socket]] because these objects' lifetime is bound to the request context creating them while the timer callback is detached from the creating request's context (by design) and runs in its own (fake) request context. If you try to share the thread or cosocket objects across the boundary of the creating request, then you will get the "no co ctx found" error (for threads) or "bad request" (for cosockets). It is fine, however, to create all these objects inside your timer callback. +Please note that the timer Lua handler has its own copy of the `ngx.ctx` magic +table. It won't share the same `ngx.ctx` with the Lua handler creating the timer. +If you need to pass data from the timer creator to the timer handler, please +use the extra parameters of `ngx.timer.at()`. + This API was first introduced in the v0.8.0 release. == ngx.timer.every == + '''syntax:''' ''hdl, err = ngx.timer.every(delay, callback, user_arg1, user_arg2, ...)'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Similar to the [[#ngx.timer.at|ngx.timer.at]] API function, but # delay ''cannot'' be zero, # timer will be created every delay seconds until the current Nginx worker process starts exiting. +Like [[#ngx.timer.at|ngx.timer.at]], the callback argument will be called +automatically with the arguments premature, user_arg1, user_arg2, etc. + When success, returns a "conditional true" value (but not a true). Otherwise, returns a "conditional false" value and a string describing the error. This API also respect the [[#lua_max_pending_timers|lua_max_pending_timers]] and [[#lua_max_running_timers|lua_max_running_timers]]. @@ -6703,37 +7738,41 @@ This API also respect the [[#lua_max_pending_timers|lua_max_pending_timers]] and This API was first introduced in the v0.10.9 release. == ngx.timer.running_count == + '''syntax:''' ''count = ngx.timer.running_count()'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Returns the number of timers currently running. This directive was first introduced in the v0.9.20 release. == ngx.timer.pending_count == + '''syntax:''' ''count = ngx.timer.pending_count()'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' Returns the number of pending timers. This directive was first introduced in the v0.9.20 release. == ngx.config.subsystem == + '''syntax:''' ''subsystem = ngx.config.subsystem'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua*'' -This string field indicates the current NGINX subsystem the current Lua environment is based on. For this module, this field always takes the string value "http". For +This string field indicates the Nginx subsystem the current Lua environment is based on. For this module, this field always takes the string value "http". For [https://github.com/openresty/stream-lua-nginx-module#readme ngx_stream_lua_module], however, this field takes the value "stream". This field was first introduced in the 0.10.1. == ngx.config.debug == + '''syntax:''' ''debug = ngx.config.debug'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua*'' This boolean field indicates whether the current Nginx is a debug build, i.e., being built by the ./configure option --with-debug. @@ -6743,9 +7782,9 @@ This field was first introduced in the 0.8.7. '''syntax:''' ''prefix = ngx.config.prefix()'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua*'' -Returns the Nginx server "prefix" path, as determined by the -p command-line option when running the nginx executable, or the path specified by the --prefix command-line option when building Nginx with the ./configure script. +Returns the Nginx server "prefix" path, as determined by the -p command-line option when running the Nginx executable, or the path specified by the --prefix command-line option when building Nginx with the ./configure script. This function was first introduced in the 0.9.2. @@ -6753,7 +7792,7 @@ This function was first introduced in the 0.9.2. '''syntax:''' ''ver = ngx.config.nginx_version'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua*'' This field take an integral value indicating the version number of the current Nginx core being used. For example, the version number 1.4.3 results in the Lua number 1004003. @@ -6765,7 +7804,7 @@ This API was first introduced in the 0.9.3 release. '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*'' -This function returns a string for the NGINX ./configure command's arguments string. +This function returns a string for the Nginx ./configure command's arguments string. This API was first introduced in the 0.9.5 release. @@ -6783,7 +7822,7 @@ This API was first introduced in the 0.9.3 release. '''syntax:''' ''exiting = ngx.worker.exiting()'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua*'' This function returns a boolean value indicating whether the current Nginx worker process already starts exiting. Nginx worker process exiting happens on Nginx server quit or configuration reload (aka HUP reload). @@ -6793,36 +7832,43 @@ This API was first introduced in the 0.9.3 release. '''syntax:''' ''pid = ngx.worker.pid()'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua*'' This function returns a Lua number for the process ID (PID) of the current Nginx worker process. This API is more efficient than ngx.var.pid and can be used in contexts where the [[#ngx.var.VARIABLE|ngx.var.VARIABLE]] API cannot be used (like [[#init_worker_by_lua|init_worker_by_lua]]). This API was first introduced in the 0.9.5 release. +== ngx.worker.pids == + +'''syntax:''' ''pid = ngx.worker.pids()'' + +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, exit_worker_by_lua*'' + +This function returns a Lua table for all Nginx worker process ID (PID). Nginx uses channel to send the current worker PID to another worker in the worker process start or restart. So this API can get all current worker PID. == ngx.worker.count == '''syntax:''' ''count = ngx.worker.count()'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_by_lua*, init_worker_by_lua*, exit_worker_by_lua*'' Returns the total number of the Nginx worker processes (i.e., the value configured -by the [http://nginx.org/en/docs/ngx_core_module.html#worker_processes worker_processes] +by the [https://nginx.org/en/docs/ngx_core_module.html#worker_processes worker_processes] directive in nginx.conf). This API was first introduced in the 0.9.20 release. == ngx.worker.id == -'''syntax:''' ''count = ngx.worker.id()'' +'''syntax:''' ''id = ngx.worker.id()'' -'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_worker_by_lua*'' +'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, init_worker_by_lua*, exit_worker_by_lua*'' Returns the ordinal number of the current Nginx worker processes (starting from number 0). So if the total number of workers is N, then this method may return a number between 0 and N - 1 (inclusive). -This function returns meaningful values only for NGINX 1.9.1+. With earlier versions of NGINX, it +This function returns meaningful values only for Nginx 1.9.1+. With earlier versions of Nginx, it always returns nil. See also [[#ngx.worker.count|ngx.worker.count]]. @@ -6830,11 +7876,12 @@ See also [[#ngx.worker.count|ngx.worker.count]]. This API was first introduced in the 0.9.20 release. == ngx.semaphore == + '''syntax:''' ''local semaphore = require "ngx.semaphore"'' This is a Lua module that implements a classic-style semaphore API for efficient synchronizations among different "light threads". Sharing the same semaphore among different "light threads" created in different (request) -contexts are also supported as long as the "light threads" reside in the same NGINX worker process +contexts are also supported as long as the "light threads" reside in the same Nginx worker process and the [[#lua_code_cache|lua_code_cache]] directive is turned on (which is the default). This Lua module does not ship with this ngx_lua module itself rather it is shipped with @@ -6848,6 +7895,7 @@ for more details. This feature requires at least ngx_lua v0.10.0. == ngx.balancer == + '''syntax:''' ''local balancer = require "ngx.balancer"'' This is a Lua module that provides a Lua API to allow defining completely dynamic load balancers @@ -6864,6 +7912,7 @@ for more details. This feature requires at least ngx_lua v0.10.0. == ngx.ssl == + '''syntax:''' ''local ssl = require "ngx.ssl"'' This Lua module provides API functions to control the SSL handshake process in contexts like @@ -6879,6 +7928,7 @@ for this ngx.ssl Lua module for more details. This feature requires at least ngx_lua v0.10.0. == ngx.ocsp == + '''syntax:''' ''local ocsp = require "ngx.ocsp"'' This Lua module provides API to perform OCSP queries, OCSP response validations, and @@ -6892,17 +7942,18 @@ This Lua module does not ship with this ngx_lua module itself rather it is shipp the [https://github.com/openresty/lua-resty-core lua-resty-core] library. -Please refer to the [https://github.com/openresty/lua-resty-core/blob/ocsp-cert-by-lua-2/lib/ngx/ocsp.md documentation] +Please refer to the [https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ocsp.md documentation] for this ngx.ocsp Lua module for more details. This feature requires at least ngx_lua v0.10.0. == ndk.set_var.DIRECTIVE == + '''syntax:''' ''res = ndk.set_var.DIRECTIVE_NAME'' -'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''init_worker_by_lua*, set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, exit_worker_by_lua*, ssl_client_hello_by_lua*'' -This mechanism allows calling other nginx C modules' directives that are implemented by [https://github.com/simpl/ngx_devel_kit Nginx Devel Kit] (NDK)'s set_var submodule's ndk_set_var_value. +This mechanism allows calling other Nginx C modules' directives that are implemented by [https://github.com/simplresty/ngx_devel_kit Nginx Devel Kit] (NDK)'s set_var submodule's ndk_set_var_value. For example, the following [[HttpSetMiscModule]] directives can be invoked this way: @@ -6923,7 +7974,7 @@ For example, the following [[HttpSetMiscModule]] directives can be invoked this For instance, - local res = ndk.set_var.set_escape_uri('a/b'); + local res = ndk.set_var.set_escape_uri('a/b') -- now res == 'a%2fb' @@ -6932,80 +7983,213 @@ Similarly, the following directives provided by [[HttpEncryptedSessionModule]] c * [[HttpEncryptedSessionModule#set_encrypt_session|set_encrypt_session]] * [[HttpEncryptedSessionModule#set_decrypt_session|set_decrypt_session]] -This feature requires the [https://github.com/simpl/ngx_devel_kit ngx_devel_kit] module. +This feature requires the [https://github.com/simplresty/ngx_devel_kit ngx_devel_kit] module. == coroutine.create == + '''syntax:''' ''co = coroutine.create(f)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Creates a user Lua coroutines with a Lua function, and returns a coroutine object. -Similar to the standard Lua [http://www.lua.org/manual/5.1/manual.html#pdf-coroutine.create coroutine.create] API, but works in the context of the Lua coroutines created by ngx_lua. +Similar to the standard Lua [https://www.lua.org/manual/5.1/manual.html#pdf-coroutine.create coroutine.create] API, but works in the context of the Lua coroutines created by ngx_lua. This API was first usable in the context of [[#init_by_lua|init_by_lua*]] since the 0.9.2. This API was first introduced in the v0.6.0 release. == coroutine.resume == + '''syntax:''' ''ok, ... = coroutine.resume(co, ...)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' -Resumes the executation of a user Lua coroutine object previously yielded or just created. +Resumes the execution of a user Lua coroutine object previously yielded or just created. -Similar to the standard Lua [http://www.lua.org/manual/5.1/manual.html#pdf-coroutine.resume coroutine.resume] API, but works in the context of the Lua coroutines created by ngx_lua. +Similar to the standard Lua [https://www.lua.org/manual/5.1/manual.html#pdf-coroutine.resume coroutine.resume] API, but works in the context of the Lua coroutines created by ngx_lua. This API was first usable in the context of [[#init_by_lua|init_by_lua*]] since the 0.9.2. This API was first introduced in the v0.6.0 release. == coroutine.yield == + '''syntax:''' ''... = coroutine.yield(...)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' Yields the execution of the current user Lua coroutine. -Similar to the standard Lua [http://www.lua.org/manual/5.1/manual.html#pdf-coroutine.yield coroutine.yield] API, but works in the context of the Lua coroutines created by ngx_lua. +Similar to the standard Lua [https://www.lua.org/manual/5.1/manual.html#pdf-coroutine.yield coroutine.yield] API, but works in the context of the Lua coroutines created by ngx_lua. This API was first usable in the context of [[#init_by_lua|init_by_lua*]] since the 0.9.2. This API was first introduced in the v0.6.0 release. == coroutine.wrap == + '''syntax:''' ''co = coroutine.wrap(f)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' -Similar to the standard Lua [http://www.lua.org/manual/5.1/manual.html#pdf-coroutine.wrap coroutine.wrap] API, but works in the context of the Lua coroutines created by ngx_lua. +Similar to the standard Lua [https://www.lua.org/manual/5.1/manual.html#pdf-coroutine.wrap coroutine.wrap] API, but works in the context of the Lua coroutines created by ngx_lua. This API was first usable in the context of [[#init_by_lua|init_by_lua*]] since the 0.9.2. This API was first introduced in the v0.6.0 release. == coroutine.running == + '''syntax:''' ''co = coroutine.running()'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' -Identical to the standard Lua [http://www.lua.org/manual/5.1/manual.html#pdf-coroutine.running coroutine.running] API. +Identical to the standard Lua [https://www.lua.org/manual/5.1/manual.html#pdf-coroutine.running coroutine.running] API. This API was first usable in the context of [[#init_by_lua|init_by_lua*]] since the 0.9.2. This API was first enabled in the v0.6.0 release. == coroutine.status == + '''syntax:''' ''status = coroutine.status(co)'' -'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*'' +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*'' -Identical to the standard Lua [http://www.lua.org/manual/5.1/manual.html#pdf-coroutine.status coroutine.status] API. +Identical to the standard Lua [https://www.lua.org/manual/5.1/manual.html#pdf-coroutine.status coroutine.status] API. This API was first usable in the context of [[#init_by_lua|init_by_lua*]] since the 0.9.2. This API was first enabled in the v0.6.0 release. +== ngx.run_worker_thread == + +'''syntax:''' ''ok, res1, res2, ... = ngx.run_worker_thread(threadpool, module_name, func_name, arg1, arg2, ...)'' + +'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*'' + +'''This API is still experimental and may change in the future without notice.''' + +'''This API is available only for Linux.''' + +Wrap the [http://nginx.org/en/docs/dev/development_guide.html#threads nginx worker thread] to execute lua function. The caller coroutine would yield until the function returns. + +Only the following ngx_lua APIs could be used in `function_name` function of the `module` module: + +* `ngx.encode_base64` +* `ngx.decode_base64` + +* `ngx.hmac_sha1` +* `ngx.encode_args` +* `ngx.decode_args` +* `ngx.quote_sql_str` + +* `ngx.re.match` +* `ngx.re.find` +* `ngx.re.gmatch` +* `ngx.re.sub` +* `ngx.re.gsub` + +* `ngx.crc32_short` +* `ngx.crc32_long` +* `ngx.hmac_sha1` +* `ngx.md5_bin` +* `ngx.md5` + +* `ngx.config.subsystem` +* `ngx.config.debug` +* `ngx.config.prefix` +* `ngx.config.nginx_version` +* `ngx.config.nginx_configure` +* `ngx.config.ngx_lua_version` + + +The first argument `threadpool` specifies the Nginx thread pool name defined by [thread_pool](https://nginx.org/en/docs/ngx_core_module.html#thread_pool). + +The second argument module_name specifies the lua module name to execute in the worker thread, which would return a lua table. The module must be inside the package path, e.g. + + +lua_package_path '/opt/openresty/?.lua;;'; + + +The third argument func_name specifies the function field in the module table as the second argument. + +The type of args must be one of type below: + +* boolean +* number +* string +* nil +* table (the table may be recursive, and contains members of types above.) + +The ok is in boolean type, which indicate the C land error (failed to get thread from thread pool, pcall the module function failed, .etc). If ok is false, the res1 is the error string. + +The return values (res1, ...) are returned by invocation of the module function. Normally, the res1 should be in boolean type, so that the caller could inspect the error. + +This API is useful when you need to execute the below types of tasks: + +* CPU bound task, e.g. do md5 calculation +* File I/O task +* Call os.execute() or blocking C API via ffi +* Call external Lua library not based on cosocket or nginx + +Example1: do md5 calculation. + + +location /calc_md5 { + default_type 'text/plain'; + + content_by_lua_block { + local ok, md5_or_err = ngx.run_worker_thread("testpool", "md5", "md5") + ngx.say(ok, " : ", md5_or_err) + } +} + + + +md5.lua + + +local function md5() + return ngx.md5("hello") +end +return {md5=md5} + + +Example2: write logs into the log file. + + +location /write_log_file { + default_type 'text/plain'; + + content_by_lua_block { + local ok, err = ngx.run_worker_thread("testpool", "write_log_file", "log", ngx.var.arg_str) + if not ok then + ngx.say(ok, " : ", err) + return + end + ngx.say(ok) + } +} + + +write_log_file.lua + + +local function log(str) + local file, err = io.open("/tmp/tmp.log", "a") + if not file then + return false, err + end + file:write(str) + file:flush() + file:close() + return true +end +return {log=log} + + = Obsolete Sections = This section is just holding obsolete documentation sections that have been either renamed or removed so that existing links over the web are still valid. @@ -7014,3 +8198,9 @@ This section is just holding obsolete documentation sections that have been eith This section has been renamed to [[#Special Escaping Sequences|Special Escaping Sequences]]. +== Lua/LuaJIT bytecode support == + +This section has been renamed to +[[#LuaJIT bytecode support|LuaJIT bytecode support]]. As of version +v0.10.16 of this module, the standard Lua interpreter (also known +as "PUC-Rio Lua") is not supported anymore. diff --git a/doc/images/lua_nginx_modules_directives.drawio.png b/doc/images/lua_nginx_modules_directives.drawio.png new file mode 100644 index 0000000000..41112147f2 Binary files /dev/null and b/doc/images/lua_nginx_modules_directives.drawio.png differ diff --git a/src/api/ngx_http_lua_api.h b/src/api/ngx_http_lua_api.h index a725a51413..51a9e7b714 100644 --- a/src/api/ngx_http_lua_api.h +++ b/src/api/ngx_http_lua_api.h @@ -19,7 +19,11 @@ /* Public API for other Nginx modules */ -#define ngx_http_lua_version 10012 +#define ngx_http_lua_version 10029 +#define NGX_HTTP_LUA_EXPORT_CO_CTX_CLEANUP 1 + + +typedef struct ngx_http_lua_co_ctx_s ngx_http_lua_co_ctx_t; typedef struct { @@ -34,6 +38,13 @@ typedef struct { } ngx_http_lua_value_t; +typedef struct { + int len; + /* this padding hole on 64-bit systems is expected */ + u_char *data; +} ngx_http_lua_ffi_str_t; + + lua_State *ngx_http_lua_get_global_state(ngx_conf_t *cf); ngx_http_request_t *ngx_http_lua_get_request(lua_State *L); @@ -49,6 +60,24 @@ ngx_shm_zone_t *ngx_http_lua_find_zone(u_char *name_data, size_t name_len); ngx_shm_zone_t *ngx_http_lua_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name, size_t size, void *tag); +ngx_http_lua_co_ctx_t *ngx_http_lua_get_cur_co_ctx(ngx_http_request_t *r); + +void ngx_http_lua_set_cur_co_ctx(ngx_http_request_t *r, + ngx_http_lua_co_ctx_t *coctx); + +lua_State *ngx_http_lua_get_co_ctx_vm(ngx_http_lua_co_ctx_t *coctx); + + +void *ngx_http_lua_get_co_ctx_data(ngx_http_lua_co_ctx_t *coctx); +void ngx_http_lua_set_co_ctx_cleanup(ngx_http_lua_co_ctx_t *coctx, + ngx_http_cleanup_pt cleanup, void *data); +void ngx_http_lua_cleanup_co_ctx_pending_operation( + ngx_http_lua_co_ctx_t *coctx); + +void ngx_http_lua_co_ctx_resume_helper(ngx_http_lua_co_ctx_t *coctx, int nrets); + +int ngx_http_lua_get_lua_http10_buffering(ngx_http_request_t *r); + #endif /* _NGX_HTTP_LUA_API_H_INCLUDED_ */ diff --git a/src/ddebug.h b/src/ddebug.h index f2d4b738f9..7bf22e9bc1 100644 --- a/src/ddebug.h +++ b/src/ddebug.h @@ -17,8 +17,8 @@ # if (NGX_HAVE_VARIADIC_MACROS) -# define dd(...) fprintf(stderr, "lua *** %s: ", __func__); \ - fprintf(stderr, __VA_ARGS__); \ +# define dd(...) fprintf(stderr, "lua *** %s: ", __func__); \ + fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, " at %s line %d.\n", __FILE__, __LINE__) # else @@ -54,22 +54,22 @@ dd(const char *fmt, ...) { #if defined(DDEBUG) && (DDEBUG) -#define dd_check_read_event_handler(r) \ - dd("r->read_event_handler = %s", \ - r->read_event_handler == ngx_http_block_reading ? \ - "ngx_http_block_reading" : \ - r->read_event_handler == ngx_http_test_reading ? \ - "ngx_http_test_reading" : \ - r->read_event_handler == ngx_http_request_empty_handler ? \ +#define dd_check_read_event_handler(r) \ + dd("r->read_event_handler = %s", \ + r->read_event_handler == ngx_http_block_reading ? \ + "ngx_http_block_reading" : \ + r->read_event_handler == ngx_http_test_reading ? \ + "ngx_http_test_reading" : \ + r->read_event_handler == ngx_http_request_empty_handler ? \ "ngx_http_request_empty_handler" : "UNKNOWN") -#define dd_check_write_event_handler(r) \ - dd("r->write_event_handler = %s", \ - r->write_event_handler == ngx_http_handler ? \ - "ngx_http_handler" : \ - r->write_event_handler == ngx_http_core_run_phases ? \ - "ngx_http_core_run_phases" : \ - r->write_event_handler == ngx_http_request_empty_handler ? \ +#define dd_check_write_event_handler(r) \ + dd("r->write_event_handler = %s", \ + r->write_event_handler == ngx_http_handler ? \ + "ngx_http_handler" : \ + r->write_event_handler == ngx_http_core_run_phases ? \ + "ngx_http_core_run_phases" : \ + r->write_event_handler == ngx_http_request_empty_handler ? \ "ngx_http_request_empty_handler" : "UNKNOWN") #else diff --git a/src/ngx_http_lua_accessby.c b/src/ngx_http_lua_accessby.c index 56bf0fa0ad..39bdcda2bf 100644 --- a/src/ngx_http_lua_accessby.c +++ b/src/ngx_http_lua_accessby.c @@ -60,7 +60,7 @@ ngx_http_lua_access_handler(ngx_http_request_t *r) #endif if (cur_ph < last_ph) { - dd("swaping the contents of cur_ph and last_ph..."); + dd("swapping the contents of cur_ph and last_ph..."); tmp = *cur_ph; @@ -95,6 +95,7 @@ ngx_http_lua_access_handler(ngx_http_request_t *r) dd("entered? %d", (int) ctx->entered_access_phase); + if (ctx->entered_access_phase) { dd("calling wev handler"); rc = ctx->resume_handler(r); @@ -105,7 +106,9 @@ ngx_http_lua_access_handler(ngx_http_request_t *r) } if (rc == NGX_OK) { - if (r->header_sent) { + if (r->header_sent + || (r->headers_out.status != 0 && ctx->out != NULL)) + { dd("header already sent"); /* response header was already generated in access_by_lua*, @@ -145,11 +148,6 @@ ngx_http_lua_access_handler(ngx_http_request_t *r) ngx_http_lua_generic_phase_post_read); if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { -#if (nginx_version < 1002006) || \ - (nginx_version >= 1003000 && nginx_version < 1003009) - r->main->count--; -#endif - return rc; } @@ -175,10 +173,15 @@ ngx_http_lua_access_handler_inline(ngx_http_request_t *r) L = ngx_http_lua_get_lua_vm(r, NULL); + if (!llcf->enable_code_cache) { + llcf->access_src_ref = LUA_REFNIL; + } + /* load Lua inline script (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, llcf->access_src.value.data, llcf->access_src.value.len, + &llcf->access_src_ref, llcf->access_src_key, (const char *) llcf->access_chunkname); @@ -215,8 +218,13 @@ ngx_http_lua_access_handler_file(ngx_http_request_t *r) L = ngx_http_lua_get_lua_vm(r, NULL); + if (!llcf->enable_code_cache) { + llcf->access_src_ref = LUA_REFNIL; + } + /* load Lua script file (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadfile(r->connection->log, L, script_path, + &llcf->access_src_ref, llcf->access_src_key); if (rc != NGX_OK) { if (rc < NGX_HTTP_SPECIAL_RESPONSE) { @@ -243,7 +251,7 @@ ngx_http_lua_access_by_chunk(lua_State *L, ngx_http_request_t *r) ngx_event_t *rev; ngx_connection_t *c; ngx_http_lua_ctx_t *ctx; - ngx_http_cleanup_t *cln; + ngx_pool_cleanup_t *cln; ngx_http_lua_loc_conf_t *llcf; @@ -261,9 +269,11 @@ ngx_http_lua_access_by_chunk(lua_State *L, ngx_http_request_t *r) /* move code closure to new coroutine */ lua_xmove(L, co, 1); +#ifndef OPENRESTY_LUAJIT /* set closure's env table to new coroutine's globals table */ ngx_http_lua_get_globals_table(co); lua_setfenv(co, -2); +#endif /* save nginx request in coroutine globals table */ ngx_http_lua_set_req(co, r); @@ -288,11 +298,13 @@ ngx_http_lua_access_by_chunk(lua_State *L, ngx_http_request_t *r) ctx->cur_co_ctx->co_top = 1; #endif + ngx_http_lua_attach_co_ctx_to_L(co, ctx->cur_co_ctx); + /* }}} */ - /* {{{ register request cleanup hooks */ + /* {{{ register nginx pool cleanup hooks */ if (ctx->cleanup == NULL) { - cln = ngx_http_cleanup_add(r, 0); + cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -368,7 +380,8 @@ ngx_http_lua_access_by_chunk(lua_State *L, ngx_http_request_t *r) #if 1 if (rc == NGX_OK) { - if (r->header_sent) { + if (r->header_sent || (r->headers_out.status != 0 && ctx->out != NULL)) + { dd("header already sent"); /* response header was already generated in access_by_lua*, diff --git a/src/ngx_http_lua_api.c b/src/ngx_http_lua_api.c index 7b590e7a9f..2478f0da80 100644 --- a/src/ngx_http_lua_api.c +++ b/src/ngx_http_lua_api.c @@ -175,7 +175,7 @@ ngx_http_lua_shared_memory_init(ngx_shm_zone_t *shm_zone, void *data) } zone->shm = shm_zone->shm; -#if defined(nginx_version) && nginx_version >= 1009000 +#if (nginx_version >= 1009000) zone->noreuse = shm_zone->noreuse; #endif @@ -195,7 +195,7 @@ ngx_http_lua_shared_memory_init(ngx_shm_zone_t *shm_zone, void *data) lmcf->shm_zones_inited++; if (lmcf->shm_zones_inited == lmcf->shm_zones->nelts - && lmcf->init_handler) + && lmcf->init_handler && !ngx_test_config) { saved_cycle = ngx_cycle; ngx_cycle = ctx->cycle; @@ -213,4 +213,155 @@ ngx_http_lua_shared_memory_init(ngx_shm_zone_t *shm_zone, void *data) return NGX_OK; } + +ngx_http_lua_co_ctx_t * +ngx_http_lua_get_cur_co_ctx(ngx_http_request_t *r) +{ + ngx_http_lua_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + + return ctx == NULL ? NULL : ctx->cur_co_ctx; +} + + +void +ngx_http_lua_set_cur_co_ctx(ngx_http_request_t *r, ngx_http_lua_co_ctx_t *coctx) +{ + ngx_http_lua_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + + coctx->data = r; + + ctx->cur_co_ctx = coctx; +} + + +lua_State * +ngx_http_lua_get_co_ctx_vm(ngx_http_lua_co_ctx_t *coctx) +{ + return coctx->co; +} + + +void * +ngx_http_lua_get_co_ctx_data(ngx_http_lua_co_ctx_t *coctx) +{ + return coctx ? coctx->data : NULL; +} + + +void +ngx_http_lua_set_co_ctx_cleanup(ngx_http_lua_co_ctx_t *coctx, + ngx_http_cleanup_pt cleanup, void *data) +{ + coctx->cleanup = cleanup; + coctx->data = data; +} + + +void +ngx_http_lua_cleanup_co_ctx_pending_operation(ngx_http_lua_co_ctx_t *coctx) +{ + ngx_http_lua_cleanup_pending_operation(coctx); +} + + +static ngx_int_t +ngx_http_lua_co_ctx_resume(ngx_http_request_t *r) +{ + lua_State *vm; + ngx_connection_t *c; + ngx_int_t rc; + ngx_uint_t nreqs; + ngx_http_lua_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + return NGX_ERROR; + } + + ctx->resume_handler = ngx_http_lua_wev_handler; + + c = r->connection; + vm = ngx_http_lua_get_lua_vm(r, ctx); + nreqs = c->requests; + + rc = ngx_http_lua_run_thread(vm, r, ctx, ctx->cur_co_ctx->nrets); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua run thread returned %d", rc); + + if (rc == NGX_AGAIN) { + return ngx_http_lua_run_posted_threads(c, vm, r, ctx, nreqs); + } + + if (rc == NGX_DONE) { + ngx_http_lua_finalize_request(r, NGX_DONE); + return ngx_http_lua_run_posted_threads(c, vm, r, ctx, nreqs); + } + + if (ctx->entered_content_phase) { + ngx_http_lua_finalize_request(r, rc); + return NGX_DONE; + } + + return rc; +} + + +void +ngx_http_lua_co_ctx_resume_helper(ngx_http_lua_co_ctx_t *coctx, int nrets) +{ + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_lua_ctx_t *ctx; + ngx_http_log_ctx_t *log_ctx; + + r = coctx->data; + c = r->connection; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + + if (ctx == NULL) { + return; + } + + if (c->fd != (ngx_socket_t) -1) { /* not a fake connection */ + log_ctx = c->log->data; + log_ctx->current_request = r; + } + + coctx->nrets = nrets; + coctx->cleanup = NULL; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "lua coctx resume handler: \"%V?%V\"", &r->uri, &r->args); + + ctx->cur_co_ctx = coctx; + + if (ctx->entered_content_phase) { + (void) ngx_http_lua_co_ctx_resume(r); + + } else { + ctx->resume_handler = ngx_http_lua_co_ctx_resume; + ngx_http_core_run_phases(r); + } + + ngx_http_run_posted_requests(c); +} + + +int +ngx_http_lua_get_lua_http10_buffering(ngx_http_request_t *r) +{ + ngx_http_lua_loc_conf_t *llcf; + + llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); + + return llcf->http10_buffering; +} + + /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_args.c b/src/ngx_http_lua_args.c index b43697c7a1..1280846545 100644 --- a/src/ngx_http_lua_args.c +++ b/src/ngx_http_lua_args.c @@ -16,10 +16,71 @@ static int ngx_http_lua_ngx_req_set_uri_args(lua_State *L); -static int ngx_http_lua_ngx_req_get_uri_args(lua_State *L); static int ngx_http_lua_ngx_req_get_post_args(lua_State *L); +uintptr_t +ngx_http_lua_escape_args(u_char *dst, u_char *src, size_t size) +{ + ngx_uint_t n; + static u_char hex[] = "0123456789ABCDEF"; + + /* %00-%20 %7F*/ + + static uint32_t escape[] = { + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + + /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ + 0x00000001, /* 0000 0000 0000 0000 0000 0000 0000 0001 */ + + /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + + /* ~}| {zyx wvut srqp onml kjih gfed cba` */ + 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ + + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + }; + + if (dst == NULL) { + + /* find the number of the characters to be escaped */ + + n = 0; + + while (size) { + if (escape[*src >> 5] & (1 << (*src & 0x1f))) { + n++; + } + + src++; + size--; + } + + return (uintptr_t) n; + } + + while (size) { + if (escape[*src >> 5] & (1 << (*src & 0x1f))) { + *dst++ = '%'; + *dst++ = hex[*src >> 4]; + *dst++ = hex[*src & 0xf]; + src++; + + } else { + *dst++ = *src++; + } + + size--; + } + + return (uintptr_t) dst; +} + + static int ngx_http_lua_ngx_req_set_uri_args(lua_State *L) { @@ -28,6 +89,7 @@ ngx_http_lua_ngx_req_set_uri_args(lua_State *L) const char *msg; size_t len; u_char *p; + uintptr_t escape; if (lua_gettop(L) != 1) { return luaL_error(L, "expecting 1 argument but seen %d", @@ -43,7 +105,6 @@ ngx_http_lua_ngx_req_set_uri_args(lua_State *L) switch (lua_type(L, 1)) { case LUA_TNUMBER: - case LUA_TSTRING: p = (u_char *) lua_tolstring(L, 1, &len); args.data = ngx_palloc(r->pool, len); @@ -56,6 +117,32 @@ ngx_http_lua_ngx_req_set_uri_args(lua_State *L) args.len = len; break; + case LUA_TSTRING: + p = (u_char *) lua_tolstring(L, 1, &len); + + escape = ngx_http_lua_escape_args(NULL, p, len); + if (escape > 0) { + args.len = len + 2 * escape; + args.data = ngx_palloc(r->pool, args.len); + if (args.data == NULL) { + return NGX_ERROR; + } + + ngx_http_lua_escape_args(args.data, p, len); + + } else { + args.data = ngx_palloc(r->pool, len); + if (args.data == NULL) { + return luaL_error(L, "no memory"); + } + + ngx_memcpy(args.data, p, len); + + args.len = len; + } + + break; + case LUA_TTABLE: ngx_http_lua_process_args_option(r, L, 1, &args); @@ -65,7 +152,7 @@ ngx_http_lua_ngx_req_set_uri_args(lua_State *L) default: msg = lua_pushfstring(L, "string, number, or table expected, " - "but got %s", luaL_typename(L, 2)); + "but got %s", luaL_typename(L, 1)); return luaL_argerror(L, 1, msg); } @@ -80,64 +167,6 @@ ngx_http_lua_ngx_req_set_uri_args(lua_State *L) } -static int -ngx_http_lua_ngx_req_get_uri_args(lua_State *L) -{ - ngx_http_request_t *r; - u_char *buf; - u_char *last; - int retval; - int n; - int max; - - n = lua_gettop(L); - - if (n != 0 && n != 1) { - return luaL_error(L, "expecting 0 or 1 arguments but seen %d", n); - } - - if (n == 1) { - max = luaL_checkinteger(L, 1); - lua_pop(L, 1); - - } else { - max = NGX_HTTP_LUA_MAX_ARGS; - } - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } - - ngx_http_lua_check_fake_request(L, r); - - if (r->args.len == 0) { - lua_createtable(L, 0, 0); - return 1; - } - - /* we copy r->args over to buf to simplify - * unescaping query arg keys and values */ - - buf = ngx_palloc(r->pool, r->args.len); - if (buf == NULL) { - return luaL_error(L, "no memory"); - } - - lua_createtable(L, 0, 4); - - ngx_memcpy(buf, r->args.data, r->args.len); - - last = buf + r->args.len; - - retval = ngx_http_lua_parse_args(L, buf, last, max); - - ngx_pfree(r->pool, buf); - - return retval; -} - - static int ngx_http_lua_ngx_req_get_post_args(lua_State *L) { @@ -311,10 +340,11 @@ ngx_http_lua_parse_args(lua_State *L, u_char *buf, u_char *last, int max) } if (max > 0 && ++count == max) { + lua_pushliteral(L, "truncated"); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "lua hit query args limit %d", max); - - return 1; + return 2; } } else { @@ -367,18 +397,11 @@ ngx_http_lua_inject_req_args_api(lua_State *L) lua_pushcfunction(L, ngx_http_lua_ngx_req_set_uri_args); lua_setfield(L, -2, "set_uri_args"); - lua_pushcfunction(L, ngx_http_lua_ngx_req_get_uri_args); - lua_setfield(L, -2, "get_uri_args"); - - lua_pushcfunction(L, ngx_http_lua_ngx_req_get_uri_args); - lua_setfield(L, -2, "get_query_args"); /* deprecated */ - lua_pushcfunction(L, ngx_http_lua_ngx_req_get_post_args); lua_setfield(L, -2, "get_post_args"); } -#ifndef NGX_LUA_NO_FFI_API size_t ngx_http_lua_ffi_req_get_querystring_len(ngx_http_request_t *r) { @@ -387,7 +410,8 @@ ngx_http_lua_ffi_req_get_querystring_len(ngx_http_request_t *r) int -ngx_http_lua_ffi_req_get_uri_args_count(ngx_http_request_t *r, int max) +ngx_http_lua_ffi_req_get_uri_args_count(ngx_http_request_t *r, int max, + int *truncated) { int count; u_char *p, *last; @@ -396,6 +420,8 @@ ngx_http_lua_ffi_req_get_uri_args_count(ngx_http_request_t *r, int max) return NGX_HTTP_LUA_FFI_BAD_CONTEXT; } + *truncated = 0; + if (max < 0) { max = NGX_HTTP_LUA_MAX_ARGS; } @@ -417,6 +443,7 @@ ngx_http_lua_ffi_req_get_uri_args_count(ngx_http_request_t *r, int max) if (count) { if (max > 0 && count > max) { count = max; + *truncated = 1; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "lua hit query args limit %d", max); } @@ -544,7 +571,6 @@ ngx_http_lua_ffi_req_get_uri_args(ngx_http_request_t *r, u_char *buf, return i; } -#endif /* NGX_LUA_NO_FFI_API */ /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_balancer.c b/src/ngx_http_lua_balancer.c index 2fa634eb4d..aa26a4a07a 100644 --- a/src/ngx_http_lua_balancer.c +++ b/src/ngx_http_lua_balancer.c @@ -15,10 +15,35 @@ #include "ngx_http_lua_util.h" #include "ngx_http_lua_directive.h" +#define NGX_BALANCER_DEF_HOST_LEN 32 +typedef struct { + ngx_queue_t queue; + ngx_queue_t hnode; + ngx_uint_t hash; + ngx_http_lua_srv_conf_t *lscf; + ngx_connection_t *connection; + socklen_t socklen; + ngx_sockaddr_t sockaddr; + ngx_sockaddr_t local_sockaddr; + ngx_str_t host; + /* try to avoid allocating memory from the connection pool */ + u_char host_data[NGX_BALANCER_DEF_HOST_LEN]; +} ngx_http_lua_balancer_ka_item_t; /*balancer keepalive item*/ + struct ngx_http_lua_balancer_peer_data_s { - /* the round robin data must be first */ - ngx_http_upstream_rr_peer_data_t rrp; + ngx_uint_t keepalive_requests; + ngx_msec_t keepalive_timeout; + + void *data; + + ngx_event_get_peer_pt original_get_peer; + ngx_event_free_peer_pt original_free_peer; + +#if (NGX_HTTP_SSL) + ngx_event_set_peer_session_pt original_set_session; + ngx_event_save_peer_session_pt original_save_session; +#endif ngx_http_lua_srv_conf_t *conf; ngx_http_request_t *request; @@ -28,15 +53,18 @@ struct ngx_http_lua_balancer_peer_data_s { struct sockaddr *sockaddr; socklen_t socklen; + ngx_addr_t *local; - ngx_str_t *host; - in_port_t port; + ngx_str_t host; + ngx_str_t *addr_text; int last_peer_state; #if !(HAVE_NGX_UPSTREAM_TIMEOUT_FIELDS) - unsigned cloned_upstream_conf; /* :1 */ + unsigned cloned_upstream_conf:1; #endif + + unsigned keepalive:1; }; @@ -45,6 +73,9 @@ static ngx_int_t ngx_http_lua_balancer_set_session(ngx_peer_connection_t *pc, void *data); static void ngx_http_lua_balancer_save_session(ngx_peer_connection_t *pc, void *data); +static ngx_int_t +ngx_http_lua_upstream_get_ssl_name(ngx_http_request_t *r, + ngx_http_upstream_t *u); #endif static ngx_int_t ngx_http_lua_balancer_init(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us); @@ -54,8 +85,20 @@ static ngx_int_t ngx_http_lua_balancer_get_peer(ngx_peer_connection_t *pc, void *data); static ngx_int_t ngx_http_lua_balancer_by_chunk(lua_State *L, ngx_http_request_t *r); -void ngx_http_lua_balancer_free_peer(ngx_peer_connection_t *pc, void *data, - ngx_uint_t state); +static void ngx_http_lua_balancer_free_peer(ngx_peer_connection_t *pc, + void *data, ngx_uint_t state); +static void ngx_http_lua_balancer_notify_peer(ngx_peer_connection_t *pc, + void *data, ngx_uint_t type); +static void ngx_http_lua_balancer_close(ngx_connection_t *c); +static void ngx_http_lua_balancer_dummy_handler(ngx_event_t *ev); +static void ngx_http_lua_balancer_close_handler(ngx_event_t *ev); +static ngx_connection_t *ngx_http_lua_balancer_get_cached_item( + ngx_http_lua_srv_conf_t *lscf, ngx_peer_connection_t *pc, ngx_str_t *name); +static ngx_uint_t ngx_http_lua_balancer_calc_hash(ngx_str_t *name, + struct sockaddr *sockaddr, socklen_t socklen, ngx_addr_t *local); + + +static struct sockaddr *ngx_http_lua_balancer_default_server_sockaddr; ngx_int_t @@ -66,6 +109,7 @@ ngx_http_lua_balancer_handler_file(ngx_http_request_t *r, rc = ngx_http_lua_cache_loadfile(r->connection->log, L, lscf->balancer.src.data, + &lscf->balancer.src_ref, lscf->balancer.src_key); if (rc != NGX_OK) { return rc; @@ -87,8 +131,9 @@ ngx_http_lua_balancer_handler_inline(ngx_http_request_t *r, rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, lscf->balancer.src.data, lscf->balancer.src.len, + &lscf->balancer.src_ref, lscf->balancer.src_key, - "=balancer_by_lua"); + (const char *) lscf->balancer.chunkname); if (rc != NGX_OK) { return rc; } @@ -123,12 +168,16 @@ char * ngx_http_lua_balancer_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - u_char *p; + size_t chunkname_len; + u_char *chunkname; + u_char *cache_key = NULL; u_char *name; ngx_str_t *value; ngx_http_lua_srv_conf_t *lscf = conf; + ngx_url_t url; ngx_http_upstream_srv_conf_t *uscf; + ngx_http_upstream_server_t *us; dd("enter"); @@ -147,49 +196,79 @@ ngx_http_lua_balancer_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, if (cmd->post == ngx_http_lua_balancer_handler_file) { /* Lua code in an external file */ - name = ngx_http_lua_rebase_path(cf->pool, value[1].data, value[1].len); if (name == NULL) { return NGX_CONF_ERROR; } + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } + lscf->balancer.src.data = name; lscf->balancer.src.len = ngx_strlen(name); - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_FILE_KEY_LEN + 1); - if (p == NULL) { + } else { + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, "balancer_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - lscf->balancer.src_key = p; + chunkname = ngx_http_lua_gen_chunk_name(cf, "balancer_by_lua", + sizeof("balancer_by_lua") - 1, + &chunkname_len); + if (chunkname == NULL) { + return NGX_CONF_ERROR; + } - p = ngx_copy(p, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; + /* Don't eval nginx variables for inline lua code */ + lscf->balancer.src = value[1]; + lscf->balancer.chunkname = chunkname; + } - } else { - /* inlined Lua code */ + lscf->balancer.src_key = cache_key; - lscf->balancer.src = value[1]; + /* balancer setup */ - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_INLINE_KEY_LEN + 1); - if (p == NULL) { + uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module); + + if (uscf->servers->nelts == 0) { + us = ngx_array_push(uscf->servers); + if (us == NULL) { return NGX_CONF_ERROR; } - lscf->balancer.src_key = p; + ngx_memzero(us, sizeof(ngx_http_upstream_server_t)); + ngx_memzero(&url, sizeof(ngx_url_t)); - p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; - } + ngx_str_set(&url.url, "0.0.0.1"); + url.default_port = 80; - uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module); + if (ngx_parse_url(cf->pool, &url) != NGX_OK) { + return NGX_CONF_ERROR; + } + + us->name = url.url; + us->addrs = url.addrs; + us->naddrs = url.naddrs; + + ngx_http_lua_balancer_default_server_sockaddr = us->addrs[0].sockaddr; + } if (uscf->peer.init_upstream) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "load balancing method redefined"); + + lscf->balancer.original_init_upstream = uscf->peer.init_upstream; + + } else { + lscf->balancer.original_init_upstream = + ngx_http_upstream_init_round_robin; } uscf->peer.init_upstream = ngx_http_lua_balancer_init; @@ -205,16 +284,58 @@ ngx_http_lua_balancer_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, static ngx_int_t -ngx_http_lua_balancer_init(ngx_conf_t *cf, - ngx_http_upstream_srv_conf_t *us) +ngx_http_lua_balancer_init(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us) { - if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) { + ngx_uint_t i; + ngx_uint_t bucket_cnt; + ngx_queue_t *buckets; + ngx_http_lua_srv_conf_t *lscf; + ngx_http_lua_balancer_ka_item_t *cached; + + lscf = ngx_http_conf_upstream_srv_conf(us, ngx_http_lua_module); + + ngx_conf_init_uint_value(lscf->balancer.max_cached, 32); + + if (lscf->balancer.original_init_upstream(cf, us) != NGX_OK) { return NGX_ERROR; } - /* this callback is called upon individual requests */ + lscf->balancer.original_init_peer = us->peer.init; + us->peer.init = ngx_http_lua_balancer_init_peer; + /* allocate cache items and add to free queue */ + + cached = ngx_pcalloc(cf->pool, + sizeof(ngx_http_lua_balancer_ka_item_t) + * lscf->balancer.max_cached); + if (cached == NULL) { + return NGX_ERROR; + } + + ngx_queue_init(&lscf->balancer.cache); + ngx_queue_init(&lscf->balancer.free); + + for (i = 0; i < lscf->balancer.max_cached; i++) { + ngx_queue_insert_head(&lscf->balancer.free, &cached[i].queue); + cached[i].lscf = lscf; + } + + bucket_cnt = lscf->balancer.max_cached / 2; + bucket_cnt = bucket_cnt > 0 ? bucket_cnt : 1; + buckets = ngx_pcalloc(cf->pool, sizeof(ngx_queue_t) * bucket_cnt); + + if (buckets == NULL) { + return NGX_ERROR; + } + + for (i = 0; i < bucket_cnt; i++) { + ngx_queue_init(&buckets[i]); + } + + lscf->balancer.buckets = buckets; + lscf->balancer.bucket_cnt = bucket_cnt; + return NGX_OK; } @@ -223,33 +344,39 @@ static ngx_int_t ngx_http_lua_balancer_init_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us) { - ngx_http_lua_srv_conf_t *bcf; + ngx_http_lua_srv_conf_t *lscf; ngx_http_lua_balancer_peer_data_t *bp; - bp = ngx_pcalloc(r->pool, sizeof(ngx_http_lua_balancer_peer_data_t)); - if (bp == NULL) { + lscf = ngx_http_conf_upstream_srv_conf(us, ngx_http_lua_module); + + if (lscf->balancer.original_init_peer(r, us) != NGX_OK) { return NGX_ERROR; } - r->upstream->peer.data = &bp->rrp; - - if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) { + bp = ngx_pcalloc(r->pool, sizeof(ngx_http_lua_balancer_peer_data_t)); + if (bp == NULL) { return NGX_ERROR; } + bp->conf = lscf; + bp->request = r; + bp->data = r->upstream->peer.data; + bp->original_get_peer = r->upstream->peer.get; + bp->original_free_peer = r->upstream->peer.free; + + r->upstream->peer.data = bp; r->upstream->peer.get = ngx_http_lua_balancer_get_peer; r->upstream->peer.free = ngx_http_lua_balancer_free_peer; + r->upstream->peer.notify = ngx_http_lua_balancer_notify_peer; #if (NGX_HTTP_SSL) + bp->original_set_session = r->upstream->peer.set_session; + bp->original_save_session = r->upstream->peer.save_session; + r->upstream->peer.set_session = ngx_http_lua_balancer_set_session; r->upstream->peer.save_session = ngx_http_lua_balancer_save_session; #endif - bcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_lua_module); - - bp->conf = bcf; - bp->request = r; - return NGX_OK; } @@ -257,25 +384,30 @@ ngx_http_lua_balancer_init_peer(ngx_http_request_t *r, static ngx_int_t ngx_http_lua_balancer_get_peer(ngx_peer_connection_t *pc, void *data) { + void *pdata; lua_State *L; ngx_int_t rc; + ngx_connection_t *c; ngx_http_request_t *r; +#if (NGX_HTTP_SSL) + ngx_http_upstream_t *u; +#endif ngx_http_lua_ctx_t *ctx; ngx_http_lua_srv_conf_t *lscf; - ngx_http_lua_main_conf_t *lmcf; ngx_http_lua_balancer_peer_data_t *bp = data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, - "lua balancer peer, tries: %ui", pc->tries); - - lscf = bp->conf; + "lua balancer: get peer, tries: %ui", pc->tries); r = bp->request; +#if (NGX_HTTP_SSL) + u = r->upstream; +#endif + lscf = bp->conf; ngx_http_lua_assert(lscf->balancer.handler && r); ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); - if (ctx == NULL) { ctx = ngx_http_lua_create_ctx(r); if (ctx == NULL) { @@ -296,18 +428,18 @@ ngx_http_lua_balancer_get_peer(ngx_peer_connection_t *pc, void *data) bp->sockaddr = NULL; bp->socklen = 0; bp->more_tries = 0; + bp->keepalive_requests = 0; + bp->keepalive_timeout = 0; + bp->keepalive = 0; bp->total_tries++; - lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); - - /* balancer_by_lua does not support yielding and - * there cannot be any conflicts among concurrent requests, - * thus it is safe to store the peer data in the main conf. - */ - lmcf->balancer_peer_data = bp; + pdata = r->upstream->peer.data; + r->upstream->peer.data = bp; rc = lscf->balancer.handler(r, lscf, L); + r->upstream->peer.data = pdata; + if (rc == NGX_ERROR) { return NGX_ERROR; } @@ -329,25 +461,60 @@ ngx_http_lua_balancer_get_peer(ngx_peer_connection_t *pc, void *data) } } + if (bp->local != NULL) { + pc->local = bp->local; + } + if (bp->sockaddr && bp->socklen) { pc->sockaddr = bp->sockaddr; pc->socklen = bp->socklen; + pc->name = bp->addr_text; pc->cached = 0; pc->connection = NULL; - pc->name = bp->host; - - bp->rrp.peers->single = 0; if (bp->more_tries) { r->upstream->peer.tries += bp->more_tries; } - dd("tries: %d", (int) r->upstream->peer.tries); + if (bp->keepalive) { +#if (NGX_HTTP_SSL) + if (bp->host.len == 0 && u->ssl) { + ngx_http_lua_upstream_get_ssl_name(r, u); + bp->host = u->ssl_name; + } +#endif + + c = ngx_http_lua_balancer_get_cached_item(lscf, pc, &bp->host); + + if (c) { + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "lua balancer: keepalive reusing connection %p," + " host: %V, name: %V", + c, bp->addr_text, &bp->host); + return NGX_DONE; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "lua balancer: keepalive no free connection, " + "host: %V, name: %v", bp->addr_text, &bp->host); + } return NGX_OK; } - return ngx_http_upstream_get_round_robin_peer(pc, &bp->rrp); + rc = bp->original_get_peer(pc, bp->data); + if (rc == NGX_ERROR) { + return rc; + } + + if (pc->sockaddr == ngx_http_lua_balancer_default_server_sockaddr) { + ngx_log_error(NGX_LOG_ERR, pc->log, 0, + "lua balancer: no peer set"); + + return NGX_ERROR; + } + + return rc; } @@ -360,6 +527,8 @@ ngx_http_lua_balancer_by_chunk(lua_State *L, ngx_http_request_t *r) /* init nginx context in Lua VM */ ngx_http_lua_set_req(L, r); + +#ifndef OPENRESTY_LUAJIT ngx_http_lua_create_new_globals_table(L, 0 /* narr */, 1 /* nrec */); /* {{{ make new env inheriting main thread's globals table */ @@ -370,6 +539,7 @@ ngx_http_lua_balancer_by_chunk(lua_State *L, ngx_http_request_t *r) /* }}} */ lua_setfenv(L, -2); /* set new running env for the code closure */ +#endif /* OPENRESTY_LUAJIT */ lua_pushcfunction(L, ngx_http_lua_traceback); lua_insert(L, 1); /* put it under chunk and args */ @@ -403,14 +573,24 @@ ngx_http_lua_balancer_by_chunk(lua_State *L, ngx_http_request_t *r) } -void +static void ngx_http_lua_balancer_free_peer(ngx_peer_connection_t *pc, void *data, ngx_uint_t state) { - ngx_http_lua_balancer_peer_data_t *bp = data; + ngx_uint_t hash; + ngx_str_t *host; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_http_upstream_t *u; + ngx_http_lua_balancer_ka_item_t *item; + ngx_http_lua_balancer_peer_data_t *bp = data; + ngx_http_lua_srv_conf_t *lscf = bp->conf; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, - "lua balancer free peer, tries: %ui", pc->tries); + "lua balancer: free peer, tries: %ui", pc->tries); + + u = bp->request->upstream; + c = pc->connection; if (bp->sockaddr && bp->socklen) { bp->last_peer_state = (int) state; @@ -419,12 +599,229 @@ ngx_http_lua_balancer_free_peer(ngx_peer_connection_t *pc, void *data, pc->tries--; } + if (bp->keepalive) { + if (state & NGX_PEER_FAILED + || c == NULL + || c->read->eof + || c->read->error + || c->read->timedout + || c->write->error + || c->write->timedout) + { + goto invalid; + } + + if (bp->keepalive_requests + && c->requests >= bp->keepalive_requests) + { + goto invalid; + } + + if (!u->keepalive) { + goto invalid; + } + + if (!u->request_body_sent) { + goto invalid; + } + + if (ngx_terminate || ngx_exiting) { + goto invalid; + } + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + goto invalid; + } + + if (ngx_queue_empty(&lscf->balancer.free)) { + q = ngx_queue_last(&lscf->balancer.cache); + + item = ngx_queue_data(q, ngx_http_lua_balancer_ka_item_t, + queue); + ngx_queue_remove(q); + ngx_queue_remove(&item->hnode); + + ngx_http_lua_balancer_close(item->connection); + + } else { + q = ngx_queue_head(&lscf->balancer.free); + ngx_queue_remove(q); + + item = ngx_queue_data(q, ngx_http_lua_balancer_ka_item_t, + queue); + } + + host = &bp->host; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "lua balancer: keepalive saving connection %p, " + "host: %V, name: %V", + c, bp->addr_text, host); + + ngx_queue_insert_head(&lscf->balancer.cache, q); + hash = ngx_http_lua_balancer_calc_hash(host, + bp->sockaddr, bp->socklen, + bp->local); + item->hash = hash; + hash %= lscf->balancer.bucket_cnt; + ngx_queue_insert_head(&lscf->balancer.buckets[hash], &item->hnode); + item->connection = c; + pc->connection = NULL; + + c->read->delayed = 0; + ngx_add_timer(c->read, bp->keepalive_timeout); + + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + c->write->handler = ngx_http_lua_balancer_dummy_handler; + c->read->handler = ngx_http_lua_balancer_close_handler; + + c->data = item; + c->idle = 1; + c->log = ngx_cycle->log; + c->read->log = ngx_cycle->log; + c->write->log = ngx_cycle->log; + c->pool->log = ngx_cycle->log; + + item->socklen = pc->socklen; + ngx_memcpy(&item->sockaddr, pc->sockaddr, pc->socklen); + if (pc->local) { + ngx_memcpy(&item->local_sockaddr, + pc->local->sockaddr, pc->local->socklen); + + } else { + ngx_memzero(&item->local_sockaddr, + sizeof(item->local_sockaddr)); + } + + if (host->data && host->len) { + if (host->len <= sizeof(item->host_data)) { + ngx_memcpy(item->host_data, host->data, host->len); + item->host.data = item->host_data; + item->host.len = host->len; + + } else { + item->host.data = ngx_pstrdup(c->pool, host); + if (item->host.data == NULL) { + ngx_http_lua_balancer_close(c); + + ngx_queue_remove(&item->queue); + ngx_queue_remove(&item->hnode); + ngx_queue_insert_head(&item->lscf->balancer.free, + &item->queue); + return; + } + + item->host.len = host->len; + } + + } else { + ngx_str_null(&item->host); + } + + if (c->read->ready) { + ngx_http_lua_balancer_close_handler(c->read); + } + + return; + +invalid: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "lua balancer: keepalive not saving connection %p", + c); + } + return; } - /* fallback */ + bp->original_free_peer(pc, bp->data, state); +} + - ngx_http_upstream_free_round_robin_peer(pc, data, state); +static void +ngx_http_lua_balancer_notify_peer(ngx_peer_connection_t *pc, void *data, + ngx_uint_t type) +{ +#ifdef NGX_HTTP_UPSTREAM_NOTIFY_CACHED_CONNECTION_ERROR + if (type == NGX_HTTP_UPSTREAM_NOTIFY_CACHED_CONNECTION_ERROR) { + pc->tries--; + } +#endif +} + + +static void +ngx_http_lua_balancer_close(ngx_connection_t *c) +{ +#if (NGX_HTTP_SSL) + if (c->ssl) { + c->ssl->no_wait_shutdown = 1; + c->ssl->no_send_shutdown = 1; + + if (ngx_ssl_shutdown(c) == NGX_AGAIN) { + c->ssl->handler = ngx_http_lua_balancer_close; + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "lua balancer: keepalive shutdown " + "connection %p failed", c); + return; + } + } +#endif + + ngx_destroy_pool(c->pool); + ngx_close_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "lua balancer: keepalive closing connection %p", c); +} + + +static void +ngx_http_lua_balancer_dummy_handler(ngx_event_t *ev) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, + "lua balancer: dummy handler"); +} + + +static void +ngx_http_lua_balancer_close_handler(ngx_event_t *ev) +{ + ngx_http_lua_balancer_ka_item_t *item; + + int n; + char buf[1]; + ngx_connection_t *c; + + c = ev->data; + if (c->close || c->read->timedout) { + goto close; + } + + n = recv(c->fd, buf, 1, MSG_PEEK); + + if (n == -1 && ngx_socket_errno == NGX_EAGAIN) { + ev->ready = 0; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + goto close; + } + + return; + } + +close: + + item = c->data; + c->log = ev->log; + + ngx_http_lua_balancer_close(c); + + ngx_queue_remove(&item->queue); + ngx_queue_remove(&item->hnode); + ngx_queue_insert_head(&item->lscf->balancer.free, &item->queue); } @@ -440,7 +837,7 @@ ngx_http_lua_balancer_set_session(ngx_peer_connection_t *pc, void *data) return NGX_OK; } - return ngx_http_upstream_set_round_robin_peer_session(pc, &bp->rrp); + return bp->original_set_session(pc, bp->data); } @@ -454,24 +851,22 @@ ngx_http_lua_balancer_save_session(ngx_peer_connection_t *pc, void *data) return; } - ngx_http_upstream_save_round_robin_peer_session(pc, &bp->rrp); - return; + bp->original_save_session(pc, bp->data); } #endif -#ifndef NGX_LUA_NO_FFI_API - int ngx_http_lua_ffi_balancer_set_current_peer(ngx_http_request_t *r, - const u_char *addr, size_t addr_len, int port, char **err) + const u_char *addr, size_t addr_len, int port, + const u_char *host, size_t host_len, + char **err) { ngx_url_t url; ngx_http_lua_ctx_t *ctx; ngx_http_upstream_t *u; - ngx_http_lua_main_conf_t *lmcf; ngx_http_lua_balancer_peer_data_t *bp; if (r == NULL) { @@ -497,18 +892,6 @@ ngx_http_lua_ffi_balancer_set_current_peer(ngx_http_request_t *r, return NGX_ERROR; } - lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); - - /* we cannot read r->upstream->peer.data here directly because - * it could be overridden by other modules like - * ngx_http_upstream_keepalive_module. - */ - bp = lmcf->balancer_peer_data; - if (bp == NULL) { - *err = "no upstream peer data found"; - return NGX_ERROR; - } - ngx_memzero(&url, sizeof(ngx_url_t)); url.url.data = ngx_palloc(r->pool, addr_len); @@ -532,34 +915,116 @@ ngx_http_lua_ffi_balancer_set_current_peer(ngx_http_request_t *r, return NGX_ERROR; } + bp = (ngx_http_lua_balancer_peer_data_t *) u->peer.data; + if (url.addrs && url.addrs[0].sockaddr) { bp->sockaddr = url.addrs[0].sockaddr; bp->socklen = url.addrs[0].socklen; - bp->host = &url.addrs[0].name; + bp->addr_text = &url.addrs[0].name; } else { *err = "no host allowed"; return NGX_ERROR; } + if (host && host_len) { + bp->host.data = ngx_palloc(r->pool, host_len); + if (bp->host.data == NULL) { + *err = "no memory"; + return NGX_ERROR; + } + + ngx_memcpy(bp->host.data, host, host_len); + bp->host.len = host_len; + +#if (NGX_HTTP_SSL) + if (u->ssl) { + u->ssl_name = bp->host; + } +#endif + + } else { + ngx_str_null(&bp->host); + } + return NGX_OK; } int -ngx_http_lua_ffi_balancer_set_timeouts(ngx_http_request_t *r, - long connect_timeout, long send_timeout, long read_timeout, - char **err) +ngx_http_lua_ffi_balancer_bind_to_local_addr(ngx_http_request_t *r, + const u_char *addr, size_t addr_len, + u_char *errbuf, size_t *errbuf_size) { + u_char *p; ngx_http_lua_ctx_t *ctx; ngx_http_upstream_t *u; + ngx_int_t rc; -#if !(HAVE_NGX_UPSTREAM_TIMEOUT_FIELDS) - ngx_http_upstream_conf_t *ucf; -#endif - ngx_http_lua_main_conf_t *lmcf; ngx_http_lua_balancer_peer_data_t *bp; + if (r == NULL) { + p = ngx_snprintf(errbuf, *errbuf_size, "no request found"); + *errbuf_size = p - errbuf; + return NGX_ERROR; + } + + u = r->upstream; + + if (u == NULL) { + p = ngx_snprintf(errbuf, *errbuf_size, "no upstream found"); + *errbuf_size = p - errbuf; + return NGX_ERROR; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + p = ngx_snprintf(errbuf, *errbuf_size, "no ctx found"); + *errbuf_size = p - errbuf; + return NGX_ERROR; + } + + if ((ctx->context & NGX_HTTP_LUA_CONTEXT_BALANCER) == 0) { + p = ngx_snprintf(errbuf, *errbuf_size, + "API disabled in the current context"); + *errbuf_size = p - errbuf; + return NGX_ERROR; + } + + bp = (ngx_http_lua_balancer_peer_data_t *) u->peer.data; + + if (bp->local == NULL) { + bp->local = ngx_palloc(r->pool, sizeof(ngx_addr_t) + addr_len); + if (bp->local == NULL) { + p = ngx_snprintf(errbuf, *errbuf_size, "no memory"); + *errbuf_size = p - errbuf; + return NGX_ERROR; + } + } + + rc = ngx_parse_addr_port(r->pool, bp->local, (u_char *) addr, addr_len); + if (rc == NGX_ERROR) { + p = ngx_snprintf(errbuf, *errbuf_size, "invalid addr %s", addr); + *errbuf_size = p - errbuf; + return NGX_ERROR; + } + + bp->local->name.len = addr_len; + bp->local->name.data = (u_char *) (bp->local + 1); + ngx_memcpy(bp->local->name.data, addr, addr_len); + + return NGX_OK; +} + + +int +ngx_http_lua_ffi_balancer_enable_keepalive(ngx_http_request_t *r, + unsigned long timeout, unsigned int max_requests, char **err) +{ + ngx_http_upstream_t *u; + ngx_http_lua_ctx_t *ctx; + ngx_http_lua_balancer_peer_data_t *bp; + if (r == NULL) { *err = "no request found"; return NGX_ERROR; @@ -583,15 +1048,60 @@ ngx_http_lua_ffi_balancer_set_timeouts(ngx_http_request_t *r, return NGX_ERROR; } - lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); + bp = (ngx_http_lua_balancer_peer_data_t *) u->peer.data; - bp = lmcf->balancer_peer_data; - if (bp == NULL) { - *err = "no upstream peer data found"; + if (!(bp->sockaddr && bp->socklen)) { + *err = "no current peer set"; return NGX_ERROR; } + bp->keepalive_timeout = (ngx_msec_t) timeout; + bp->keepalive_requests = (ngx_uint_t) max_requests; + bp->keepalive = 1; + + return NGX_OK; +} + + +int +ngx_http_lua_ffi_balancer_set_timeouts(ngx_http_request_t *r, + long connect_timeout, long send_timeout, long read_timeout, + char **err) +{ + ngx_http_lua_ctx_t *ctx; + ngx_http_upstream_t *u; + #if !(HAVE_NGX_UPSTREAM_TIMEOUT_FIELDS) + ngx_http_upstream_conf_t *ucf; + ngx_http_lua_balancer_peer_data_t *bp; +#endif + + if (r == NULL) { + *err = "no request found"; + return NGX_ERROR; + } + + u = r->upstream; + + if (u == NULL) { + *err = "no upstream found"; + return NGX_ERROR; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + *err = "no ctx found"; + return NGX_ERROR; + } + + if ((ctx->context & NGX_HTTP_LUA_CONTEXT_BALANCER) == 0) { + *err = "API disabled in the current context"; + return NGX_ERROR; + } + +#if !(HAVE_NGX_UPSTREAM_TIMEOUT_FIELDS) + bp = (ngx_http_lua_balancer_peer_data_t *) u->peer.data; + if (!bp->cloned_upstream_conf) { /* we clone the upstream conf for the current request so that * we do not affect other requests at all. */ @@ -646,12 +1156,10 @@ ngx_http_lua_ffi_balancer_set_more_tries(ngx_http_request_t *r, int count, char **err) { #if (nginx_version >= 1007005) - ngx_uint_t max_tries, total; + ngx_uint_t max_tries, total; #endif - ngx_http_lua_ctx_t *ctx; - ngx_http_upstream_t *u; - - ngx_http_lua_main_conf_t *lmcf; + ngx_http_lua_ctx_t *ctx; + ngx_http_upstream_t *u; ngx_http_lua_balancer_peer_data_t *bp; if (r == NULL) { @@ -677,13 +1185,7 @@ ngx_http_lua_ffi_balancer_set_more_tries(ngx_http_request_t *r, return NGX_ERROR; } - lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); - - bp = lmcf->balancer_peer_data; - if (bp == NULL) { - *err = "no upstream peer data found"; - return NGX_ERROR; - } + bp = (ngx_http_lua_balancer_peer_data_t *) u->peer.data; #if (nginx_version >= 1007005) max_tries = r->upstream->conf->next_upstream_tries; @@ -709,12 +1211,10 @@ int ngx_http_lua_ffi_balancer_get_last_failure(ngx_http_request_t *r, int *status, char **err) { - ngx_http_lua_ctx_t *ctx; - ngx_http_upstream_t *u; - ngx_http_upstream_state_t *state; - + ngx_http_lua_ctx_t *ctx; + ngx_http_upstream_t *u; + ngx_http_upstream_state_t *state; ngx_http_lua_balancer_peer_data_t *bp; - ngx_http_lua_main_conf_t *lmcf; if (r == NULL) { *err = "no request found"; @@ -739,13 +1239,7 @@ ngx_http_lua_ffi_balancer_get_last_failure(ngx_http_request_t *r, return NGX_ERROR; } - lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); - - bp = lmcf->balancer_peer_data; - if (bp == NULL) { - *err = "no upstream peer data found"; - return NGX_ERROR; - } + bp = (ngx_http_lua_balancer_peer_data_t *) u->peer.data; if (r->upstream_states && r->upstream_states->nelts > 1) { state = r->upstream_states->elts; @@ -758,4 +1252,261 @@ ngx_http_lua_ffi_balancer_get_last_failure(ngx_http_request_t *r, return bp->last_peer_state; } -#endif /* NGX_LUA_NO_FFI_API */ + +int +ngx_http_lua_ffi_balancer_recreate_request(ngx_http_request_t *r, + char **err) +{ + ngx_http_lua_ctx_t *ctx; + ngx_http_upstream_t *u; + + if (r == NULL) { + *err = "no request found"; + return NGX_ERROR; + } + + u = r->upstream; + + if (u == NULL) { + *err = "no upstream found"; + return NGX_ERROR; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + *err = "no ctx found"; + return NGX_ERROR; + } + + if ((ctx->context & NGX_HTTP_LUA_CONTEXT_BALANCER) == 0) { + *err = "API disabled in the current context"; + return NGX_ERROR; + } + + /* u->create_request can not be NULL since we are in balancer phase */ + ngx_http_lua_assert(u->create_request != NULL); + + *err = NULL; + + if (u->request_bufs != NULL && u->request_bufs != r->request_body->bufs) { + /* u->request_bufs already contains a valid request buffer + * remove it from chain first + */ + u->request_bufs = r->request_body->bufs; + } + + return u->create_request(r); +} + + +int +ngx_http_lua_ffi_balancer_set_upstream_tls(ngx_http_request_t *r, int on, + char **err) +{ + ngx_http_lua_ctx_t *ctx; + ngx_http_upstream_t *u; + + if (r == NULL) { + *err = "no request found"; + return NGX_ERROR; + } + + u = r->upstream; + + if (u == NULL) { + *err = "no upstream found"; + return NGX_ERROR; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + *err = "no ctx found"; + return NGX_ERROR; + } + + if ((ctx->context & NGX_HTTP_LUA_CONTEXT_BALANCER) == 0) { + *err = "API disabled in the current context"; + return NGX_ERROR; + } + + if (on == 0) { + u->ssl = 0; + u->schema.len = sizeof("http://") - 1; + + } else { + u->ssl = 1; + u->schema.len = sizeof("https://") - 1; + } + + return NGX_OK; +} + + +char * +ngx_http_lua_balancer_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_int_t n; + ngx_str_t *value; + +#if 0 + ngx_http_upstream_srv_conf_t *uscf; +#endif + ngx_http_lua_srv_conf_t *lscf = conf; + + if (lscf->balancer.max_cached != NGX_CONF_UNSET_UINT) { + return "is duplicate"; + } + + /* read options */ + + value = cf->args->elts; + + n = ngx_atoi(value[1].data, value[1].len); + + if (n == NGX_ERROR || n == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid value \"%V\" in \"%V\" directive", + &value[1], &cmd->name); + return NGX_CONF_ERROR; + } + + lscf->balancer.max_cached = n; + + return NGX_CONF_OK; +} + + +#if (NGX_HTTP_SSL) +static ngx_int_t +ngx_http_lua_upstream_get_ssl_name(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + u_char *p, *last; + ngx_str_t name; + + if (u->conf->ssl_name) { + if (ngx_http_complex_value(r, u->conf->ssl_name, &name) != NGX_OK) { + return NGX_ERROR; + } + + } else { + name = u->ssl_name; + } + + if (name.len == 0) { + goto done; + } + + /* + * ssl name here may contain port, notably if derived from $proxy_host + * or $http_host; we have to strip it. eg: www.example.com:443 + */ + + p = name.data; + last = name.data + name.len; + + if (*p == '[') { + p = ngx_strlchr(p, last, ']'); + + if (p == NULL) { + p = name.data; + } + } + + p = ngx_strlchr(p, last, ':'); + + if (p != NULL) { + name.len = p - name.data; + } + +done: + + u->ssl_name = name; + + return NGX_OK; +} +#endif + + +static ngx_uint_t +ngx_http_lua_balancer_calc_hash(ngx_str_t *name, + struct sockaddr *sockaddr, socklen_t socklen, ngx_addr_t *local) +{ + ngx_uint_t hash; + + hash = ngx_hash_key_lc(name->data, name->len); + hash ^= ngx_hash_key((u_char *) sockaddr, socklen); + if (local != NULL) { + hash ^= ngx_hash_key((u_char *) local->sockaddr, local->socklen); + } + + return hash; +} + + +static ngx_connection_t * +ngx_http_lua_balancer_get_cached_item(ngx_http_lua_srv_conf_t *lscf, + ngx_peer_connection_t *pc, ngx_str_t *name) +{ + ngx_uint_t hash; + ngx_queue_t *q; + ngx_queue_t *head; + ngx_connection_t *c; + struct sockaddr *sockaddr; + socklen_t socklen; + ngx_addr_t *local; + ngx_http_lua_balancer_ka_item_t *item; + + sockaddr = pc->sockaddr; + socklen = pc->socklen; + local = pc->local; + + hash = ngx_http_lua_balancer_calc_hash(name, sockaddr, socklen, pc->local); + head = &lscf->balancer.buckets[hash % lscf->balancer.bucket_cnt]; + + c = NULL; + for (q = ngx_queue_head(head); + q != ngx_queue_sentinel(head); + q = ngx_queue_next(q)) + { + item = ngx_queue_data(q, ngx_http_lua_balancer_ka_item_t, hnode); + if (item->hash != hash) { + continue; + } + + if (name->len == item->host.len + && ngx_memn2cmp((u_char *) &item->sockaddr, + (u_char *) sockaddr, + item->socklen, socklen) == 0 + && ngx_strncasecmp(name->data, + item->host.data, name->len) == 0 + && (local == NULL + || ngx_memn2cmp((u_char *) &item->local_sockaddr, + (u_char *) local->sockaddr, + socklen, local->socklen) == 0)) + { + c = item->connection; + ngx_queue_remove(q); + ngx_queue_remove(&item->queue); + ngx_queue_insert_head(&lscf->balancer.free, &item->queue); + c->idle = 0; + c->sent = 0; + c->log = pc->log; + c->read->log = pc->log; + c->write->log = pc->log; + c->pool->log = pc->log; + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + pc->cached = 1; + pc->connection = c; + return c; + } + } + + return NULL; +} + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_balancer.h b/src/ngx_http_lua_balancer.h index bb49dc02ca..34db2fe82f 100644 --- a/src/ngx_http_lua_balancer.h +++ b/src/ngx_http_lua_balancer.h @@ -23,5 +23,7 @@ char *ngx_http_lua_balancer_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, char *ngx_http_lua_balancer_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +char *ngx_http_lua_balancer_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); #endif /* _NGX_HTTP_LUA_BALANCER_H_INCLUDED_ */ diff --git a/src/ngx_http_lua_bodyfilterby.c b/src/ngx_http_lua_bodyfilterby.c index 2b3c38f7ce..cb5f7eb608 100644 --- a/src/ngx_http_lua_bodyfilterby.c +++ b/src/ngx_http_lua_bodyfilterby.c @@ -15,12 +15,9 @@ #include "ngx_http_lua_exception.h" #include "ngx_http_lua_util.h" #include "ngx_http_lua_pcrefix.h" -#include "ngx_http_lua_time.h" #include "ngx_http_lua_log.h" -#include "ngx_http_lua_regex.h" #include "ngx_http_lua_cache.h" #include "ngx_http_lua_headers.h" -#include "ngx_http_lua_variable.h" #include "ngx_http_lua_string.h" #include "ngx_http_lua_misc.h" #include "ngx_http_lua_consts.h" @@ -32,10 +29,6 @@ static void ngx_http_lua_body_filter_by_lua_env(lua_State *L, static ngx_http_output_body_filter_pt ngx_http_next_body_filter; -/* key for the ngx_chain_t *in pointer in the Lua thread */ -#define ngx_http_lua_chain_key "__ngx_cl" - - /** * Set environment table for the given code closure. * @@ -51,12 +44,14 @@ static void ngx_http_lua_body_filter_by_lua_env(lua_State *L, ngx_http_request_t *r, ngx_chain_t *in) { - /* set nginx request pointer to current lua thread's globals table */ + ngx_http_lua_main_conf_t *lmcf; + ngx_http_lua_set_req(L, r); - lua_pushlightuserdata(L, in); - lua_setglobal(L, ngx_http_lua_chain_key); + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); + lmcf->body_filter_chain = in; +#ifndef OPENRESTY_LUAJIT /** * we want to create empty environment for current script * @@ -79,6 +74,7 @@ ngx_http_lua_body_filter_by_lua_env(lua_State *L, ngx_http_request_t *r, /* }}} */ lua_setfenv(L, -2); /* set new running env for the code closure */ +#endif /* OPENRESTY_LUAJIT */ } @@ -159,12 +155,18 @@ ngx_http_lua_body_filter_inline(ngx_http_request_t *r, ngx_chain_t *in) L = ngx_http_lua_get_lua_vm(r, NULL); + if (!llcf->enable_code_cache) { + llcf->body_filter_src_ref = LUA_REFNIL; + } + /* load Lua inline script (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, llcf->body_filter_src.value.data, llcf->body_filter_src.value.len, + &llcf->body_filter_src_ref, llcf->body_filter_src_key, - "=body_filter_by_lua"); + (const char *) + llcf->body_filter_chunkname); if (rc != NGX_OK) { return NGX_ERROR; } @@ -208,8 +210,13 @@ ngx_http_lua_body_filter_file(ngx_http_request_t *r, ngx_chain_t *in) L = ngx_http_lua_get_lua_vm(r, NULL); + if (!llcf->enable_code_cache) { + llcf->body_filter_src_ref = LUA_REFNIL; + } + /* load Lua script file (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadfile(r->connection->log, L, script_path, + &llcf->body_filter_src_ref, llcf->body_filter_src_key); if (rc != NGX_OK) { return NGX_ERROR; @@ -235,20 +242,17 @@ ngx_http_lua_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ngx_http_lua_ctx_t *ctx; ngx_int_t rc; uint16_t old_context; - ngx_http_cleanup_t *cln; - lua_State *L; + ngx_pool_cleanup_t *cln; ngx_chain_t *out; + ngx_chain_t *cl, *ln; + ngx_http_lua_main_conf_t *lmcf; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "lua body filter for user lua code, uri \"%V\"", &r->uri); - if (in == NULL) { - return ngx_http_next_body_filter(r, in); - } - llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); - if (llcf->body_filter_handler == NULL) { + if (llcf->body_filter_handler == NULL || r->header_only) { dd("no body filter handler found"); return ngx_http_next_body_filter(r, in); } @@ -271,11 +275,54 @@ ngx_http_lua_body_filter(ngx_http_request_t *r, ngx_chain_t *in) in->buf->file_pos = in->buf->file_last; } - return NGX_OK; + in = NULL; + + /* continue to call ngx_http_next_body_filter to process cached data */ + } + + if (in != NULL + && ngx_chain_add_copy(r->pool, &ctx->filter_in_bufs, in) != NGX_OK) + { + return NGX_ERROR; + } + + if (ctx->filter_busy_bufs != NULL + && (r->connection->buffered + & (NGX_HTTP_LOWLEVEL_BUFFERED | NGX_LOWLEVEL_BUFFERED))) + { + /* Socket write buffer was full on last write. + * Try to write the remain data, if still can not write + * do not execute body_filter_by_lua otherwise the `in` chain will be + * replaced by new content from lua and buf of `in` mark as consumed. + * And then ngx_output_chain will call the filter chain again which + * make all the data cached in the memory and long ngx_chain_t link + * cause CPU 100%. + */ + rc = ngx_http_next_body_filter(r, NULL); + + if (rc == NGX_ERROR) { + return rc; + } + + out = NULL; + ngx_chain_update_chains(r->pool, + &ctx->free_bufs, &ctx->filter_busy_bufs, &out, + (ngx_buf_tag_t) &ngx_http_lua_body_filter); + if (rc != NGX_OK + && ctx->filter_busy_bufs != NULL + && (r->connection->buffered + & (NGX_HTTP_LOWLEVEL_BUFFERED | NGX_LOWLEVEL_BUFFERED))) + { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "waiting body filter busy buffer to be sent"); + return NGX_AGAIN; + } + + /* continue to process bufs in ctx->filter_in_bufs */ } if (ctx->cleanup == NULL) { - cln = ngx_http_cleanup_add(r, 0); + cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { return NGX_ERROR; } @@ -288,46 +335,59 @@ ngx_http_lua_body_filter(ngx_http_request_t *r, ngx_chain_t *in) old_context = ctx->context; ctx->context = NGX_HTTP_LUA_CONTEXT_BODY_FILTER; - dd("calling body filter handler"); - rc = llcf->body_filter_handler(r, in); + in = ctx->filter_in_bufs; + ctx->filter_in_bufs = NULL; - dd("calling body filter handler returned %d", (int) rc); + if (in != NULL) { + dd("calling body filter handler"); + rc = llcf->body_filter_handler(r, in); - ctx->context = old_context; + dd("calling body filter handler returned %d", (int) rc); - if (rc != NGX_OK) { - return NGX_ERROR; - } + ctx->context = old_context; - L = ngx_http_lua_get_lua_vm(r, ctx); + if (rc != NGX_OK) { + return NGX_ERROR; + } - lua_getglobal(L, ngx_http_lua_chain_key); - out = lua_touserdata(L, -1); - lua_pop(L, 1); + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); + + /* lmcf->body_filter_chain is the new buffer chain if + * body_filter_by_lua set new body content via ngx.arg[1] = new_content + * otherwise it is the original `in` buffer chain. + */ + out = lmcf->body_filter_chain; + + if (in != out) { + /* content of body was replaced in + * ngx_http_lua_body_filter_param_set and the buffers was marked + * as consumed. + */ + for (cl = in; cl != NULL; cl = ln) { + ln = cl->next; + ngx_free_chain(r->pool, cl); + } - if (in == out) { - return ngx_http_next_body_filter(r, in); - } + if (out == NULL) { + /* do not forward NULL to the next filters because the input is + * not NULL */ + return NGX_OK; + } + } - if (out == NULL) { - /* do not forward NULL to the next filters because the input is - * not NULL */ - return NGX_OK; + } else { + ctx->context = old_context; + out = NULL; } - /* in != out */ rc = ngx_http_next_body_filter(r, out); if (rc == NGX_ERROR) { return NGX_ERROR; } -#if nginx_version >= 1001004 ngx_chain_update_chains(r->pool, -#else - ngx_chain_update_chains( -#endif - &ctx->free_bufs, &ctx->busy_bufs, &out, - (ngx_buf_tag_t) &ngx_http_lua_module); + &ctx->free_bufs, &ctx->filter_busy_bufs, &out, + (ngx_buf_tag_t) &ngx_http_lua_body_filter); return rc; } @@ -345,49 +405,48 @@ ngx_http_lua_body_filter_init(void) int -ngx_http_lua_body_filter_param_get(lua_State *L) +ngx_http_lua_ffi_get_body_filter_param_eof(ngx_http_request_t *r) { - u_char *data, *p; - size_t size; ngx_chain_t *cl; - ngx_buf_t *b; - int idx; ngx_chain_t *in; - idx = luaL_checkint(L, 2); + ngx_http_lua_main_conf_t *lmcf; - dd("index: %d", idx); + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); + in = lmcf->body_filter_chain; - if (idx != 1 && idx != 2) { - lua_pushnil(L); - return 1; + /* asking for the eof argument */ + + for (cl = in; cl; cl = cl->next) { + if (cl->buf->last_buf || cl->buf->last_in_chain) { + return 1; + } } - lua_getglobal(L, ngx_http_lua_chain_key); - in = lua_touserdata(L, -1); + return 0; +} - if (idx == 2) { - /* asking for the eof argument */ - for (cl = in; cl; cl = cl->next) { - if (cl->buf->last_buf || cl->buf->last_in_chain) { - lua_pushboolean(L, 1); - return 1; - } - } +int +ngx_http_lua_ffi_get_body_filter_param_body(ngx_http_request_t *r, + u_char **data_p, size_t *len_p) +{ + size_t size; + ngx_chain_t *cl; + ngx_buf_t *b; + ngx_chain_t *in; - lua_pushboolean(L, 0); - return 1; - } + ngx_http_lua_main_conf_t *lmcf; - /* idx == 1 */ + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); + in = lmcf->body_filter_chain; size = 0; if (in == NULL) { /* being a cleared chain on the Lua land */ - lua_pushliteral(L, ""); - return 1; + *len_p = 0; + return NGX_OK; } if (in->next == NULL) { @@ -395,8 +454,9 @@ ngx_http_lua_body_filter_param_get(lua_State *L) dd("seen only single buffer"); b = in->buf; - lua_pushlstring(L, (char *) b->pos, b->last - b->pos); - return 1; + *data_p = b->pos; + *len_p = b->last - b->pos; + return NGX_OK; } dd("seen multiple buffers"); @@ -411,7 +471,26 @@ ngx_http_lua_body_filter_param_get(lua_State *L) } } - data = (u_char *) lua_newuserdata(L, size); + /* the buf is need and is not allocated from Lua land yet, return with + * the actual size */ + *len_p = size; + return NGX_AGAIN; +} + + +int +ngx_http_lua_ffi_copy_body_filter_param_body(ngx_http_request_t *r, + u_char *data) +{ + u_char *p; + ngx_chain_t *cl; + ngx_buf_t *b; + ngx_chain_t *in; + + ngx_http_lua_main_conf_t *lmcf; + + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); + in = lmcf->body_filter_chain; for (p = data, cl = in; cl; cl = cl->next) { b = cl->buf; @@ -422,8 +501,7 @@ ngx_http_lua_body_filter_param_get(lua_State *L) } } - lua_pushlstring(L, (char *) data, size); - return 1; + return NGX_OK; } @@ -442,6 +520,8 @@ ngx_http_lua_body_filter_param_set(lua_State *L, ngx_http_request_t *r, ngx_chain_t *cl; ngx_chain_t *in; + ngx_http_lua_main_conf_t *lmcf; + idx = luaL_checkint(L, 2); dd("index: %d", idx); @@ -450,20 +530,34 @@ ngx_http_lua_body_filter_param_set(lua_State *L, ngx_http_request_t *r, return luaL_error(L, "bad index: %d", idx); } + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); + if (idx == 2) { /* overwriting the eof flag */ last = lua_toboolean(L, 3); - lua_getglobal(L, ngx_http_lua_chain_key); - in = lua_touserdata(L, -1); - lua_pop(L, 1); + in = lmcf->body_filter_chain; if (last) { ctx->seen_last_in_filter = 1; - /* the "in" chain cannot be NULL and we set the "last_buf" or - * "last_in_chain" flag in the last buf of "in" */ + /* the "in" chain cannot be NULL except that we set arg[1] = "" + * before arg[2] = true + */ + if (in == NULL) { + in = ngx_http_lua_chain_get_free_buf(r->connection->log, + r->pool, + &ctx->free_bufs, 0); + if (in == NULL) { + return luaL_error(L, "no memory"); + } + in->buf->tag = (ngx_buf_tag_t) &ngx_http_lua_body_filter; + lmcf->body_filter_chain = in; + } + + /* we set the "last_buf" or "last_in_chain" flag + * in the last buf of "in" */ for (cl = in; cl; cl = cl->next) { if (cl->next == NULL) { if (r == r->main) { @@ -521,9 +615,7 @@ ngx_http_lua_body_filter_param_set(lua_State *L, ngx_http_request_t *r, case LUA_TNIL: /* discard the buffers */ - lua_getglobal(L, ngx_http_lua_chain_key); /* key val */ - in = lua_touserdata(L, -1); - lua_pop(L, 1); + in = lmcf->body_filter_chain; last = 0; @@ -557,9 +649,7 @@ ngx_http_lua_body_filter_param_set(lua_State *L, ngx_http_request_t *r, lua_typename(L, type)); } - lua_getglobal(L, ngx_http_lua_chain_key); - in = lua_touserdata(L, -1); - lua_pop(L, 1); + in = lmcf->body_filter_chain; last = 0; @@ -590,6 +680,7 @@ ngx_http_lua_body_filter_param_set(lua_State *L, ngx_http_request_t *r, return luaL_error(L, "no memory"); } + cl->buf->tag = (ngx_buf_tag_t) &ngx_http_lua_body_filter; if (type == LUA_TTABLE) { cl->buf->last = ngx_http_lua_copy_str_in_table(L, 3, cl->buf->last); @@ -607,6 +698,8 @@ ngx_http_lua_body_filter_param_set(lua_State *L, ngx_http_request_t *r, if (cl == NULL) { return luaL_error(L, "no memory"); } + + cl->buf->tag = (ngx_buf_tag_t) &ngx_http_lua_body_filter; } if (last) { @@ -625,8 +718,8 @@ ngx_http_lua_body_filter_param_set(lua_State *L, ngx_http_request_t *r, } } - lua_pushlightuserdata(L, cl); - lua_setglobal(L, ngx_http_lua_chain_key); + lmcf->body_filter_chain = cl; + return 0; } diff --git a/src/ngx_http_lua_bodyfilterby.h b/src/ngx_http_lua_bodyfilterby.h index 6a4b306d64..359646f2b5 100644 --- a/src/ngx_http_lua_bodyfilterby.h +++ b/src/ngx_http_lua_bodyfilterby.h @@ -21,7 +21,6 @@ ngx_int_t ngx_http_lua_body_filter_inline(ngx_http_request_t *r, ngx_chain_t *in); ngx_int_t ngx_http_lua_body_filter_file(ngx_http_request_t *r, ngx_chain_t *in); -int ngx_http_lua_body_filter_param_get(lua_State *L); int ngx_http_lua_body_filter_param_set(lua_State *L, ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx); diff --git a/src/ngx_http_lua_cache.c b/src/ngx_http_lua_cache.c index 5ea3069246..534424688d 100644 --- a/src/ngx_http_lua_cache.c +++ b/src/ngx_http_lua_cache.c @@ -19,6 +19,10 @@ #include "ngx_http_lua_util.h" +static u_char *ngx_http_lua_gen_file_cache_key_helper(u_char *out, + const u_char *src, size_t src_len); + + /** * Find code chunk associated with the given key in code cache, * and push it to the top of Lua stack if found. @@ -33,25 +37,61 @@ * */ static ngx_int_t ngx_http_lua_cache_load_code(ngx_log_t *log, lua_State *L, - const char *key) + int *ref, const char *key) { +#ifndef OPENRESTY_LUAJIT int rc; u_char *err; +#endif /* get code cache table */ - lua_pushlightuserdata(L, &ngx_http_lua_code_cache_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + code_cache_key)); lua_rawget(L, LUA_REGISTRYINDEX); /* sp++ */ - dd("Code cache table to load: %p", lua_topointer(L, -1)); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, + "code cache lookup (key='%s', ref=%d)", key, *ref); + + dd("code cache table to load: %p", lua_topointer(L, -1)); if (!lua_istable(L, -1)) { dd("Error: code cache table to load did not exist!!"); return NGX_ERROR; } - lua_getfield(L, -1, key); /* sp++ */ + ngx_http_lua_assert(key != NULL); + + if (*ref == LUA_NOREF) { + lua_getfield(L, -1, key); /* cache closure */ + + } else { + if (*ref == LUA_REFNIL) { + lua_getfield(L, -1, key); /* cache ref */ + + if (!lua_isnumber(L, -1)) { + goto not_found; + } + + *ref = lua_tonumber(L, -1); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, + "code cache setting ref (key='%s', ref=%d)", + key, *ref); + + lua_pop(L, 1); /* cache */ + } + + lua_rawgeti(L, -1, *ref); /* cache closure */ + } if (lua_isfunction(L, -1)) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, + "code cache hit (key='%s', ref=%d)", key, *ref); + +#ifdef OPENRESTY_LUAJIT + lua_remove(L, -2); /* sp-- */ + return NGX_OK; +#else /* call closure factory to gen new closure */ rc = lua_pcall(L, 0, 1, 0); if (rc == 0) { @@ -73,15 +113,21 @@ ngx_http_lua_cache_load_code(ngx_log_t *log, lua_State *L, key, err); lua_pop(L, 2); return NGX_ERROR; +#endif /* OPENRESTY_LUAJIT */ } +not_found: + dd("Value associated with given key in code cache table is not code " "chunk: stack top=%d, top value type=%s\n", - lua_gettop(L), lua_typename(L, -1)); + lua_gettop(L), luaL_typename(L, -1)); /* remove cache table and value from stack */ lua_pop(L, 2); /* sp-=2 */ + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, + "code cache miss (key='%s', ref=%d)", key, *ref); + return NGX_DECLINED; } @@ -100,12 +146,15 @@ ngx_http_lua_cache_load_code(ngx_log_t *log, lua_State *L, * * */ static ngx_int_t -ngx_http_lua_cache_store_code(lua_State *L, const char *key) +ngx_http_lua_cache_store_code(lua_State *L, int *ref, const char *key) { +#ifndef OPENRESTY_LUAJIT int rc; +#endif /* get code cache table */ - lua_pushlightuserdata(L, &ngx_http_lua_code_cache_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + code_cache_key)); lua_rawget(L, LUA_REGISTRYINDEX); dd("Code cache table to store: %p", lua_topointer(L, -1)); @@ -115,18 +164,34 @@ ngx_http_lua_cache_store_code(lua_State *L, const char *key) return NGX_ERROR; } + ngx_http_lua_assert(key != NULL); + lua_pushvalue(L, -2); /* closure cache closure */ - lua_setfield(L, -2, key); /* closure cache */ + + if (*ref == LUA_NOREF) { + /* cache closure by cache key */ + lua_setfield(L, -2, key); /* closure cache */ + + } else { + /* cache closure with reference */ + *ref = luaL_ref(L, -2); /* closure cache */ + + /* cache reference by cache key */ + lua_pushnumber(L, *ref); /* closure cache ref */ + lua_setfield(L, -2, key); /* closure cache */ + } /* remove cache table, leave closure factory at top of stack */ lua_pop(L, 1); /* closure */ +#ifndef OPENRESTY_LUAJIT /* call closure factory to generate new closure */ rc = lua_pcall(L, 0, 1, 0); if (rc != 0) { dd("Error: failed to call closure factory!!"); return NGX_ERROR; } +#endif return NGX_OK; } @@ -134,7 +199,7 @@ ngx_http_lua_cache_store_code(lua_State *L, const char *key) ngx_int_t ngx_http_lua_cache_loadbuffer(ngx_log_t *log, lua_State *L, - const u_char *src, size_t src_len, const u_char *cache_key, + const u_char *src, size_t src_len, int *cache_ref, const u_char *cache_key, const char *name) { int n; @@ -143,13 +208,8 @@ ngx_http_lua_cache_loadbuffer(ngx_log_t *log, lua_State *L, n = lua_gettop(L); - dd("XXX cache key: [%s]", cache_key); - - rc = ngx_http_lua_cache_load_code(log, L, (char *) cache_key); + rc = ngx_http_lua_cache_load_code(log, L, cache_ref, (char *) cache_key); if (rc == NGX_OK) { - /* code chunk loaded from cache, sp++ */ - dd("Code cache hit! cache key='%s', stack top=%d, script='%.*s'", - cache_key, lua_gettop(L), (int) src_len, src); return NGX_OK; } @@ -159,9 +219,6 @@ ngx_http_lua_cache_loadbuffer(ngx_log_t *log, lua_State *L, /* rc == NGX_DECLINED */ - dd("Code cache missed! cache key='%s', stack top=%d, script='%.*s'", - cache_key, lua_gettop(L), (int) src_len, src); - /* load closure factory of inline script to the top of lua stack, sp++ */ rc = ngx_http_lua_clfactory_loadbuffer(L, (char *) src, src_len, name); @@ -184,7 +241,7 @@ ngx_http_lua_cache_loadbuffer(ngx_log_t *log, lua_State *L, /* store closure factory and gen new closure at the top of lua stack to * code cache */ - rc = ngx_http_lua_cache_store_code(L, (char *) cache_key); + rc = ngx_http_lua_cache_store_code(L, cache_ref, (char *) cache_key); if (rc != NGX_OK) { err = "fail to generate new closure from the closure factory"; goto error; @@ -203,11 +260,10 @@ ngx_http_lua_cache_loadbuffer(ngx_log_t *log, lua_State *L, ngx_int_t ngx_http_lua_cache_loadfile(ngx_log_t *log, lua_State *L, - const u_char *script, const u_char *cache_key) + const u_char *script, int *cache_ref, const u_char *cache_key) { int n; ngx_int_t rc, errcode = NGX_ERROR; - u_char *p; u_char buf[NGX_HTTP_LUA_FILE_KEY_LEN + 1]; const char *err = NULL; @@ -216,24 +272,19 @@ ngx_http_lua_cache_loadfile(ngx_log_t *log, lua_State *L, /* calculate digest of script file path */ if (cache_key == NULL) { dd("CACHE file key not pre-calculated...calculating"); - p = ngx_copy(buf, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN); - - p = ngx_http_lua_digest_hex(p, script, ngx_strlen(script)); - *p = '\0'; - cache_key = buf; + cache_key = ngx_http_lua_gen_file_cache_key_helper(buf, script, + ngx_strlen(script)); + *cache_ref = LUA_NOREF; } else { dd("CACHE file key already pre-calculated"); - } - dd("XXX cache key for file: [%s]", cache_key); + ngx_http_lua_assert(cache_ref != NULL && *cache_ref != LUA_NOREF); + } - rc = ngx_http_lua_cache_load_code(log, L, (char *) cache_key); + rc = ngx_http_lua_cache_load_code(log, L, cache_ref, (char *) cache_key); if (rc == NGX_OK) { - /* code chunk loaded from cache, sp++ */ - dd("Code cache hit! cache key='%s', stack top=%d, file path='%s'", - cache_key, lua_gettop(L), script); return NGX_OK; } @@ -243,9 +294,6 @@ ngx_http_lua_cache_loadfile(ngx_log_t *log, lua_State *L, /* rc == NGX_DECLINED */ - dd("Code cache missed! cache key='%s', stack top=%d, file path='%s'", - cache_key, lua_gettop(L), script); - /* load closure factory of script file to the top of lua stack, sp++ */ rc = ngx_http_lua_clfactory_loadfile(L, (char *) script); @@ -259,7 +307,13 @@ ngx_http_lua_cache_loadfile(ngx_log_t *log, lua_State *L, break; case LUA_ERRFILE: - errcode = NGX_HTTP_NOT_FOUND; + if (errno == ENOENT) { + errcode = NGX_HTTP_NOT_FOUND; + + } else { + errcode = NGX_HTTP_SERVICE_UNAVAILABLE; + } + /* fall through */ default: @@ -276,7 +330,7 @@ ngx_http_lua_cache_loadfile(ngx_log_t *log, lua_State *L, /* store closure factory and gen new closure at the top of lua stack * to code cache */ - rc = ngx_http_lua_cache_store_code(L, (char *) cache_key); + rc = ngx_http_lua_cache_store_code(L, cache_ref, (char *) cache_key); if (rc != NGX_OK) { err = "fail to generate new closure from the closure factory"; goto error; @@ -293,4 +347,64 @@ ngx_http_lua_cache_loadfile(ngx_log_t *log, lua_State *L, return errcode; } + +u_char * +ngx_http_lua_gen_chunk_cache_key(ngx_conf_t *cf, const char *tag, + const u_char *src, size_t src_len) +{ + u_char *p, *out; + size_t tag_len; + + tag_len = ngx_strlen(tag); + + out = ngx_palloc(cf->pool, tag_len + NGX_HTTP_LUA_INLINE_KEY_LEN + 2); + if (out == NULL) { + return NULL; + } + + p = ngx_copy(out, tag, tag_len); + p = ngx_copy(p, "_", 1); + p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN); + p = ngx_http_lua_digest_hex(p, src, src_len); + *p = '\0'; + + return out; +} + + +static u_char * +ngx_http_lua_gen_file_cache_key_helper(u_char *out, const u_char *src, + size_t src_len) +{ + u_char *p; + + ngx_http_lua_assert(out != NULL); + + if (out == NULL) { + return NULL; + } + + p = ngx_copy(out, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN); + p = ngx_http_lua_digest_hex(p, src, src_len); + *p = '\0'; + + return out; +} + + +u_char * +ngx_http_lua_gen_file_cache_key(ngx_conf_t *cf, const u_char *src, + size_t src_len) +{ + u_char *out; + + out = ngx_palloc(cf->pool, NGX_HTTP_LUA_FILE_KEY_LEN + 1); + if (out == NULL) { + return NULL; + } + + return ngx_http_lua_gen_file_cache_key_helper(out, src, src_len); +} + + /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_cache.h b/src/ngx_http_lua_cache.h index 52e6d2c94f..9535279d8b 100644 --- a/src/ngx_http_lua_cache.h +++ b/src/ngx_http_lua_cache.h @@ -13,10 +13,14 @@ ngx_int_t ngx_http_lua_cache_loadbuffer(ngx_log_t *log, lua_State *L, - const u_char *src, size_t src_len, const u_char *cache_key, + const u_char *src, size_t src_len, int *cache_ref, const u_char *cache_key, const char *name); ngx_int_t ngx_http_lua_cache_loadfile(ngx_log_t *log, lua_State *L, - const u_char *script, const u_char *cache_key); + const u_char *script, int *cache_ref, const u_char *cache_key); +u_char *ngx_http_lua_gen_chunk_cache_key(ngx_conf_t *cf, const char *tag, + const u_char *src, size_t src_len); +u_char *ngx_http_lua_gen_file_cache_key(ngx_conf_t *cf, const u_char *src, + size_t src_len); #endif /* _NGX_HTTP_LUA_CACHE_H_INCLUDED_ */ diff --git a/src/ngx_http_lua_clfactory.c b/src/ngx_http_lua_clfactory.c index 89698c798d..9eab164661 100644 --- a/src/ngx_http_lua_clfactory.c +++ b/src/ngx_http_lua_clfactory.c @@ -15,11 +15,13 @@ #include "ngx_http_lua_clfactory.h" +#ifndef OPENRESTY_LUAJIT #define CLFACTORY_BEGIN_CODE "return function() " #define CLFACTORY_BEGIN_SIZE (sizeof(CLFACTORY_BEGIN_CODE) - 1) #define CLFACTORY_END_CODE "\nend" #define CLFACTORY_END_SIZE (sizeof(CLFACTORY_END_CODE) - 1) +#endif /* @@ -59,15 +61,16 @@ * length(Instruction) = 4 or 8 * little endian or big endian */ -#define LUA_LITTLE_ENDIAN_4BYTES_CODE \ +#ifndef OPENRESTY_LUAJIT +#define LUA_LITTLE_ENDIAN_4BYTES_CODE \ "\x24\x00\x00\x00\x1e\x00\x00\x01\x1e\x00\x80\x00" -#define LUA_LITTLE_ENDIAN_8BYTES_CODE \ - "\x24\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x00\x01" \ +#define LUA_LITTLE_ENDIAN_8BYTES_CODE \ + "\x24\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x00\x01" \ "\x00\x00\x00\x00\x1e\x00\x80\x00\x00\x00\x00\x00" -#define LUA_BIG_ENDIAN_4BYTES_CODE \ +#define LUA_BIG_ENDIAN_4BYTES_CODE \ "\x00\x00\x00\x24\x01\x00\x00\x1e\x00\x08\x00\x1e" -#define LUA_BIG_ENDIAN_8BYTES_CODE \ - "\x00\x00\x00\x00\x00\x00\x00\x24\x00\x00\x00\x00" \ +#define LUA_BIG_ENDIAN_8BYTES_CODE \ + "\x00\x00\x00\x00\x00\x00\x00\x24\x00\x00\x00\x00" \ "\x01\x00\x00\x1e\x00\x00\x00\x00\x00\x08\x00\x1e" #define LUA_LITTLE_ENDIAN_4BYTES_CODE_LEN (4 + 4 + 4) #define LUA_LITTLE_ENDIAN_8BYTES_CODE_LEN (8 + 8 + 8) @@ -75,6 +78,7 @@ #define LUA_BIG_ENDIAN_8BYTES_CODE_LEN (8 + 8 + 8) #define LUAC_HEADERSIZE 12 #define LUAC_VERSION 0x51 +#endif /* OPENRESTY_LUAJIT */ /* @@ -87,7 +91,7 @@ * | Int | At which line this function is defined * | [linedefined] | * --------------------- - * | Int | At while line this function definition ended + * | Int | At which line this function definition ended * | [lastlinedefined] | * --------------------- * | Char | Number of upvalues referenced by this function @@ -122,9 +126,9 @@ * | at [p[i]] | * --------------------- * | Vector | Debug lineinfo vector - * | [lineinfo] | Empty vector here if dubug info is stripped + * | [lineinfo] | Empty vector here if debug info is stripped * --------------------- - * | Int | Number of local variable in this function + * | Int | Number of local variables in this function * | [sizelocvars] | 0 if debug info is stripped * --------------------- * | String | ------------------------------------ @@ -132,7 +136,7 @@ * | .varname] | | * --------------------- | * | Int | instruction counter | - * | [locvars[i]] | where lcoal var i start to be |-> repeat for i in + * | [locvars[i]] | where local var i start to be |-> repeat for i in * | .startpc] | referenced | [0..sizelocvars] * --------------------- | * | Int | instruction counter, where local | @@ -147,6 +151,7 @@ * --------------------- */ +#ifndef OPENRESTY_LUAJIT #define POS_SOURCE_STR_LEN LUAC_HEADERSIZE #define POS_START_LINE (POS_SOURCE_STR_LEN + sizeof(size_t)) #define POS_LAST_LINE (POS_START_LINE + sizeof(int)) @@ -156,10 +161,11 @@ #define POS_MAX_STACK_SIZE (POS_IS_VAR_ARG + sizeof(char)) #define POS_NUM_OF_INST (POS_MAX_STACK_SIZE +sizeof(char)) #define POS_BYTECODE (POS_NUM_OF_INST + sizeof(int)) -#define MAX_BEGIN_CODE_SIZE \ - (POS_BYTECODE + LUA_LITTLE_ENDIAN_8BYTES_CODE_LEN \ +#define MAX_BEGIN_CODE_SIZE \ + (POS_BYTECODE + LUA_LITTLE_ENDIAN_8BYTES_CODE_LEN \ + sizeof(int) + sizeof(int)) #define MAX_END_CODE_SIZE (sizeof(int) + sizeof(int) + sizeof(int)) +#endif /* OPENRESTY_LUAJIT */ /* * taken from chaoslawful: @@ -225,24 +231,25 @@ /* bytecode for luajit 2.0 */ -#define LJ20_LITTLE_ENDIAN_CODE_STRIPPED \ - "\x14\x03\x00\x01\x00\x01\x00\x03" \ - "\x31\x00\x00\x00\x30\x00\x00\x80\x48\x00\x02\x00" \ +#ifndef OPENRESTY_LUAJIT +#define LJ20_LITTLE_ENDIAN_CODE_STRIPPED \ + "\x14\x03\x00\x01\x00\x01\x00\x03" \ + "\x31\x00\x00\x00\x30\x00\x00\x80\x48\x00\x02\x00" \ "\x00\x00" -#define LJ20_BIG_ENDIAN_CODE_STRIPPED \ - "\x14\x03\x00\x01\x00\x01\x00\x03" \ - "\x00\x00\x00\x31\x80\x00\x00\x30\x00\x02\x00\x48" \ +#define LJ20_BIG_ENDIAN_CODE_STRIPPED \ + "\x14\x03\x00\x01\x00\x01\x00\x03" \ + "\x00\x00\x00\x31\x80\x00\x00\x30\x00\x02\x00\x48" \ "\x00\x00" -#define LJ20_LITTLE_ENDIAN_CODE \ - "\x15\x03\x00\x01\x00\x01\x00\x03\x00" \ - "\x31\x00\x00\x00\x30\x00\x00\x80\x48\x00\x02\x00" \ +#define LJ20_LITTLE_ENDIAN_CODE \ + "\x15\x03\x00\x01\x00\x01\x00\x03\x00" \ + "\x31\x00\x00\x00\x30\x00\x00\x80\x48\x00\x02\x00" \ "\x00\x00" -#define LJ20_BIG_ENDIAN_CODE \ - "\x15\x03\x00\x01\x00\x01\x00\x03\x00" \ - "\x00\x00\x00\x31\x80\x00\x00\x30\x00\x02\x00\x48" \ +#define LJ20_BIG_ENDIAN_CODE \ + "\x15\x03\x00\x01\x00\x01\x00\x03\x00" \ + "\x00\x00\x00\x31\x80\x00\x00\x30\x00\x02\x00\x48" \ "\x00\x00" /* bytecode for luajit 2.1 */ @@ -275,27 +282,29 @@ #define LJ21_BCDUMP_VERSION 2 #define LJ20_BCDUMP_VERSION 1 #define LJ_SIGNATURE "\x1b\x4c\x4a" +#endif /* OPENRESTY_LUAJIT */ typedef enum { NGX_LUA_TEXT_FILE, NGX_LUA_BT_LUA, - NGX_LUA_BT_LJ + NGX_LUA_BT_LJ, } ngx_http_lua_clfactory_file_type_e; enum { - NGX_LUA_READER_BUFSIZE = 4096 + NGX_LUA_READER_BUFSIZE = 4096, }; typedef struct { ngx_http_lua_clfactory_file_type_e file_type; - int sent_begin; - int sent_end; int extraline; FILE *f; +#ifndef OPENRESTY_LUAJIT + int sent_begin; + int sent_end; size_t begin_code_len; size_t end_code_len; size_t rest_len; @@ -307,13 +316,16 @@ typedef struct { char *ptr; char str[MAX_END_CODE_SIZE]; } end_code; +#endif /* OPENRESTY_LUAJIT */ char buff[NGX_LUA_READER_BUFSIZE]; } ngx_http_lua_clfactory_file_ctx_t; typedef struct { +#ifndef OPENRESTY_LUAJIT int sent_begin; int sent_end; +#endif const char *s; size_t size; } ngx_http_lua_clfactory_buffer_ctx_t; @@ -325,9 +337,12 @@ static int ngx_http_lua_clfactory_errfile(lua_State *L, const char *what, int fname_index); static const char *ngx_http_lua_clfactory_getS(lua_State *L, void *ud, size_t *size); +#ifndef OPENRESTY_LUAJIT static long ngx_http_lua_clfactory_file_size(FILE *f); +#endif +#ifndef OPENRESTY_LUAJIT int ngx_http_lua_clfactory_bytecode_prepare(lua_State *L, ngx_http_lua_clfactory_file_ctx_t *lf, int fname_index) @@ -365,12 +380,12 @@ ngx_http_lua_clfactory_bytecode_prepare(lua_State *L, #if defined(DDEBUG) && (DDEBUG) { - dd("==LJ_BT_HEADER=="); - size_t i; - for (i = 0; i < LJ_HEADERSIZE; i++) { - dd("%ld: 0x%02X", i, (unsigned)(u_char) lf->begin_code.str[i]); - } - dd("==LJ_BT_HEADER_END=="); + dd("==LJ_BT_HEADER=="); + size_t i; + for (i = 0; i < LJ_HEADERSIZE; i++) { + dd("%ld: 0x%02X", i, (unsigned)(u_char) lf->begin_code.str[i]); + } + dd("==LJ_BT_HEADER_END=="); } #endif @@ -593,6 +608,7 @@ ngx_http_lua_clfactory_bytecode_prepare(lua_State *L, return LUA_ERRFILE; } +#endif /* OPENRESTY_LUAJIT */ ngx_int_t @@ -612,10 +628,12 @@ ngx_http_lua_clfactory_loadfile(lua_State *L, const char *filename) lf.extraline = 0; lf.file_type = NGX_LUA_TEXT_FILE; +#ifndef OPENRESTY_LUAJIT lf.begin_code.ptr = CLFACTORY_BEGIN_CODE; lf.begin_code_len = CLFACTORY_BEGIN_SIZE; lf.end_code.ptr = CLFACTORY_END_CODE; lf.end_code_len = CLFACTORY_END_SIZE; +#endif lua_pushfstring(L, "@%s", filename); @@ -683,20 +701,27 @@ ngx_http_lua_clfactory_loadfile(lua_State *L, const char *filename) /* skip eventual `#!...' */ } +#ifndef OPENRESTY_LUAJIT status = ngx_http_lua_clfactory_bytecode_prepare(L, &lf, fname_index); if (status != 0) { return status; } +#endif lf.extraline = 0; } +#ifndef OPENRESTY_LUAJIT if (lf.file_type == NGX_LUA_TEXT_FILE) { ungetc(c, lf.f); } lf.sent_begin = lf.sent_end = 0; + +#else + ungetc(c, lf.f); +#endif status = lua_load(L, ngx_http_lua_clfactory_getF, &lf, lua_tostring(L, -1)); @@ -725,8 +750,10 @@ ngx_http_lua_clfactory_loadbuffer(lua_State *L, const char *buff, ls.s = buff; ls.size = size; +#ifndef OPENRESTY_LUAJIT ls.sent_begin = 0; ls.sent_end = 0; +#endif return lua_load(L, ngx_http_lua_clfactory_getS, &ls, name); } @@ -735,7 +762,9 @@ ngx_http_lua_clfactory_loadbuffer(lua_State *L, const char *buff, static const char * ngx_http_lua_clfactory_getF(lua_State *L, void *ud, size_t *size) { +#ifndef OPENRESTY_LUAJIT char *buf; +#endif size_t num; ngx_http_lua_clfactory_file_ctx_t *lf; @@ -748,6 +777,7 @@ ngx_http_lua_clfactory_getF(lua_State *L, void *ud, size_t *size) return "\n"; } +#ifndef OPENRESTY_LUAJIT if (lf->sent_begin == 0) { lf->sent_begin = 1; *size = lf->begin_code_len; @@ -761,12 +791,14 @@ ngx_http_lua_clfactory_getF(lua_State *L, void *ud, size_t *size) return buf; } +#endif /* OPENRESTY_LUAJIT */ num = fread(lf->buff, 1, sizeof(lf->buff), lf->f); dd("fread returned %d", (int) num); if (num == 0) { +#ifndef OPENRESTY_LUAJIT if (lf->sent_end == 0) { lf->sent_end = 1; *size = lf->end_code_len; @@ -780,11 +812,13 @@ ngx_http_lua_clfactory_getF(lua_State *L, void *ud, size_t *size) return buf; } +#endif /* OPENRESTY_LUAJIT */ *size = 0; return NULL; } +#ifndef OPENRESTY_LUAJIT if (lf->file_type == NGX_LUA_BT_LJ) { /* skip the footer(\x00) in luajit */ @@ -800,6 +834,7 @@ ngx_http_lua_clfactory_getF(lua_State *L, void *ud, size_t *size) } } } +#endif /* OPENRESTY_LUAJIT */ *size = num; return lf->buff; @@ -833,19 +868,23 @@ ngx_http_lua_clfactory_getS(lua_State *L, void *ud, size_t *size) { ngx_http_lua_clfactory_buffer_ctx_t *ls = ud; +#ifndef OPENRESTY_LUAJIT if (ls->sent_begin == 0) { ls->sent_begin = 1; *size = CLFACTORY_BEGIN_SIZE; return CLFACTORY_BEGIN_CODE; } +#endif if (ls->size == 0) { +#ifndef OPENRESTY_LUAJIT if (ls->sent_end == 0) { ls->sent_end = 1; *size = CLFACTORY_END_SIZE; return CLFACTORY_END_CODE; } +#endif return NULL; } @@ -857,6 +896,7 @@ ngx_http_lua_clfactory_getS(lua_State *L, void *ud, size_t *size) } +#ifndef OPENRESTY_LUAJIT static long ngx_http_lua_clfactory_file_size(FILE *f) { @@ -882,6 +922,7 @@ ngx_http_lua_clfactory_file_size(FILE *f) return len; } +#endif /* OPENRESTY_LUAJIT */ /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_common.h b/src/ngx_http_lua_common.h index 1ad2f8bcbf..40c330baff 100644 --- a/src/ngx_http_lua_common.h +++ b/src/ngx_http_lua_common.h @@ -9,6 +9,8 @@ #define _NGX_HTTP_LUA_COMMON_H_INCLUDED_ +#include "ngx_http_lua_autoconf.h" + #include #include #include @@ -17,61 +19,94 @@ #include #include -#include +#include #include #include -#if (NGX_PCRE) +#if defined(NDK) && NDK +#include + +typedef struct { + size_t size; + int ref; + u_char *key; + u_char *chunkname; + ngx_str_t script; +} ngx_http_lua_set_var_data_t; +#endif -#include -#if (PCRE_MAJOR > 8) || (PCRE_MAJOR == 8 && PCRE_MINOR >= 21) -# define LUA_HAVE_PCRE_JIT 1 +#ifdef NGX_LUA_USE_ASSERT +#include +# define ngx_http_lua_assert(a) assert(a) #else -# define LUA_HAVE_PCRE_JIT 0 +# define ngx_http_lua_assert(a) #endif + +/** + * max positive +1.7976931348623158e+308 + * min positive +2.2250738585072014e-308 + */ +#ifndef NGX_DOUBLE_LEN +#define NGX_DOUBLE_LEN 25 #endif -#if !defined(nginx_version) || (nginx_version < 1006000) -#error at least nginx 1.6.0 is required but found an older version -#endif +#if (NGX_PCRE) +# if (NGX_PCRE2) +# define LUA_HAVE_PCRE_JIT 1 +# else +#include -#if defined(NDK) && NDK -#include +# if (PCRE_MAJOR > 8) || (PCRE_MAJOR == 8 && PCRE_MINOR >= 21) +# define LUA_HAVE_PCRE_JIT 1 +# else +# define LUA_HAVE_PCRE_JIT 0 +# endif +# endif #endif +#if (nginx_version < 1006000) +# error at least nginx 1.6.0 is required but found an older version +#endif + #if LUA_VERSION_NUM != 501 # error unsupported Lua language version #endif +#if !defined(LUAJIT_VERSION_NUM) || (LUAJIT_VERSION_NUM < 20000) +# error unsupported LuaJIT version +#endif + #if (!defined OPENSSL_NO_OCSP && defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB) # define NGX_HTTP_LUA_USE_OCSP 1 #endif +#ifndef NGX_HTTP_PERMANENT_REDIRECT +# define NGX_HTTP_PERMANENT_REDIRECT 308 +#endif #ifndef NGX_HAVE_SHA1 # if (nginx_version >= 1011002) -# define NGX_HAVE_SHA1 1 +# define NGX_HAVE_SHA1 1 # endif #endif - #ifndef MD5_DIGEST_LENGTH -#define MD5_DIGEST_LENGTH 16 +# define MD5_DIGEST_LENGTH 16 #endif +#ifndef NGX_HTTP_LUA_MAX_ARGS +# define NGX_HTTP_LUA_MAX_ARGS 100 +#endif -#ifdef NGX_LUA_USE_ASSERT -# include -# define ngx_http_lua_assert(a) assert(a) -#else -# define ngx_http_lua_assert(a) +#ifndef NGX_HTTP_LUA_MAX_HEADERS +# define NGX_HTTP_LUA_MAX_HEADERS 100 #endif @@ -79,80 +114,88 @@ #define NGX_HTTP_LUA_INLINE_TAG "nhli_" -#define NGX_HTTP_LUA_INLINE_TAG_LEN \ +#define NGX_HTTP_LUA_INLINE_TAG_LEN \ (sizeof(NGX_HTTP_LUA_INLINE_TAG) - 1) -#define NGX_HTTP_LUA_INLINE_KEY_LEN \ +#define NGX_HTTP_LUA_INLINE_KEY_LEN \ (NGX_HTTP_LUA_INLINE_TAG_LEN + 2 * MD5_DIGEST_LENGTH) /* Nginx HTTP Lua File tag prefix */ #define NGX_HTTP_LUA_FILE_TAG "nhlf_" -#define NGX_HTTP_LUA_FILE_TAG_LEN \ +#define NGX_HTTP_LUA_FILE_TAG_LEN \ (sizeof(NGX_HTTP_LUA_FILE_TAG) - 1) -#define NGX_HTTP_LUA_FILE_KEY_LEN \ +#define NGX_HTTP_LUA_FILE_KEY_LEN \ (NGX_HTTP_LUA_FILE_TAG_LEN + 2 * MD5_DIGEST_LENGTH) -#if defined(NDK) && NDK -typedef struct { - size_t size; - u_char *key; - ngx_str_t script; -} ngx_http_lua_set_var_data_t; +/* must be within 32 bits */ +#define NGX_HTTP_LUA_CONTEXT_SET 0x00000001 +#define NGX_HTTP_LUA_CONTEXT_REWRITE 0x00000002 +#define NGX_HTTP_LUA_CONTEXT_ACCESS 0x00000004 +#define NGX_HTTP_LUA_CONTEXT_CONTENT 0x00000008 +#define NGX_HTTP_LUA_CONTEXT_LOG 0x00000010 +#define NGX_HTTP_LUA_CONTEXT_HEADER_FILTER 0x00000020 +#define NGX_HTTP_LUA_CONTEXT_BODY_FILTER 0x00000040 +#define NGX_HTTP_LUA_CONTEXT_TIMER 0x00000080 +#define NGX_HTTP_LUA_CONTEXT_INIT_WORKER 0x00000100 +#define NGX_HTTP_LUA_CONTEXT_BALANCER 0x00000200 +#define NGX_HTTP_LUA_CONTEXT_SSL_CERT 0x00000400 +#define NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE 0x00000800 +#define NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH 0x00001000 +#define NGX_HTTP_LUA_CONTEXT_EXIT_WORKER 0x00002000 +#define NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO 0x00004000 +#define NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE 0x00008000 + +#ifdef HAVE_PROXY_SSL_PATCH +#define NGX_HTTP_LUA_CONTEXT_PROXY_SSL_VERIFY 0x00010000 #endif -#ifndef NGX_HTTP_LUA_MAX_ARGS -#define NGX_HTTP_LUA_MAX_ARGS 100 -#endif +#define NGX_HTTP_LUA_FFI_NO_REQ_CTX -100 +#define NGX_HTTP_LUA_FFI_BAD_CONTEXT -101 -#ifndef NGX_HTTP_LUA_MAX_HEADERS -#define NGX_HTTP_LUA_MAX_HEADERS 100 +#if (NGX_PTR_SIZE >= 8 && !defined(_WIN64)) +# define ngx_http_lua_lightudata_mask(ludata) \ + ((void *) ((uintptr_t) (&ngx_http_lua_##ludata) & ((1UL << 47) - 1))) +#else +# define ngx_http_lua_lightudata_mask(ludata) \ + (&ngx_http_lua_##ludata) #endif -/* must be within 16 bit */ -#define NGX_HTTP_LUA_CONTEXT_SET 0x0001 -#define NGX_HTTP_LUA_CONTEXT_REWRITE 0x0002 -#define NGX_HTTP_LUA_CONTEXT_ACCESS 0x0004 -#define NGX_HTTP_LUA_CONTEXT_CONTENT 0x0008 -#define NGX_HTTP_LUA_CONTEXT_LOG 0x0010 -#define NGX_HTTP_LUA_CONTEXT_HEADER_FILTER 0x0020 -#define NGX_HTTP_LUA_CONTEXT_BODY_FILTER 0x0040 -#define NGX_HTTP_LUA_CONTEXT_TIMER 0x0080 -#define NGX_HTTP_LUA_CONTEXT_INIT_WORKER 0x0100 -#define NGX_HTTP_LUA_CONTEXT_BALANCER 0x0200 -#define NGX_HTTP_LUA_CONTEXT_SSL_CERT 0x0400 -#define NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE 0x0800 -#define NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH 0x1000 - +typedef struct ngx_http_lua_co_ctx_s ngx_http_lua_co_ctx_t; -#ifndef NGX_LUA_NO_FFI_API -#define NGX_HTTP_LUA_FFI_NO_REQ_CTX -100 -#define NGX_HTTP_LUA_FFI_BAD_CONTEXT -101 -#endif +typedef struct ngx_http_lua_sema_mm_s ngx_http_lua_sema_mm_t; +typedef struct ngx_http_lua_srv_conf_s ngx_http_lua_srv_conf_t; typedef struct ngx_http_lua_main_conf_s ngx_http_lua_main_conf_t; -typedef union ngx_http_lua_srv_conf_u ngx_http_lua_srv_conf_t; - -typedef struct ngx_http_lua_balancer_peer_data_s - ngx_http_lua_balancer_peer_data_t; +typedef struct ngx_http_lua_loc_conf_s ngx_http_lua_loc_conf_t; +typedef struct ngx_http_lua_header_val_s ngx_http_lua_header_val_t; -typedef struct ngx_http_lua_sema_mm_s ngx_http_lua_sema_mm_t; +typedef struct ngx_http_lua_posted_thread_s ngx_http_lua_posted_thread_t; +typedef struct ngx_http_lua_balancer_peer_data_s + ngx_http_lua_balancer_peer_data_t; typedef ngx_int_t (*ngx_http_lua_main_conf_handler_pt)(ngx_log_t *log, ngx_http_lua_main_conf_t *lmcf, lua_State *L); + typedef ngx_int_t (*ngx_http_lua_srv_conf_handler_pt)(ngx_http_request_t *r, ngx_http_lua_srv_conf_t *lscf, lua_State *L); +typedef ngx_int_t (*ngx_http_lua_loc_conf_handler_pt)(ngx_http_request_t *r, + ngx_http_lua_loc_conf_t *llcf, lua_State *L); + +typedef ngx_int_t (*ngx_http_lua_set_header_pt)(ngx_http_request_t *r, + ngx_http_lua_header_val_t *hv, ngx_str_t *value); + typedef struct { u_char *package; @@ -160,6 +203,13 @@ typedef struct { } ngx_http_lua_preload_hook_t; +typedef struct { + int ref; + lua_State *co; + ngx_queue_t queue; +} ngx_http_lua_thread_ref_t; + + struct ngx_http_lua_main_conf_s { lua_State *lua; ngx_pool_cleanup_t *vm_cleanup; @@ -178,15 +228,22 @@ struct ngx_http_lua_main_conf_s { ngx_connection_t *watcher; /* for watching the process exit event */ + ngx_int_t lua_thread_cache_max_entries; + + ngx_hash_t builtin_headers_out; + #if (NGX_PCRE) ngx_int_t regex_cache_entries; ngx_int_t regex_cache_max_entries; ngx_int_t regex_match_limit; +#endif #if (LUA_HAVE_PCRE_JIT) +#if (NGX_PCRE2) + pcre2_jit_stack *jit_stack; +#else pcre_jit_stack *jit_stack; #endif - #endif ngx_array_t *shm_zones; /* of ngx_shm_zone_t* */ @@ -200,14 +257,35 @@ struct ngx_http_lua_main_conf_s { ngx_http_lua_main_conf_handler_pt init_handler; ngx_str_t init_src; + u_char *init_chunkname; ngx_http_lua_main_conf_handler_pt init_worker_handler; ngx_str_t init_worker_src; + u_char *init_worker_chunkname; - ngx_http_lua_balancer_peer_data_t *balancer_peer_data; - /* balancer_by_lua does not support yielding and - * there cannot be any conflicts among concurrent requests, - * thus it is safe to store the peer data in the main conf. + ngx_http_lua_main_conf_handler_pt exit_worker_handler; + ngx_str_t exit_worker_src; + u_char *exit_worker_chunkname; + + ngx_chain_t *body_filter_chain; + /* neither yielding nor recursion is possible in + * body_filter_by_lua*, so there cannot be any races among + * concurrent requests when storing the chain + * data pointer in the main conf. + */ + + ngx_http_variable_value_t *setby_args; + /* neither yielding nor recursion is possible in + * set_by_lua*, so there cannot be any races among + * concurrent requests when storing the args pointer + * in the main conf. + */ + + size_t setby_nargs; + /* neither yielding nor recursion is possible in + * set_by_lua*, so there cannot be any races among + * concurrent requests when storing the nargs in the + * main conf. */ ngx_uint_t shm_zones_inited; @@ -215,15 +293,26 @@ struct ngx_http_lua_main_conf_s { ngx_http_lua_sema_mm_t *sema_mm; ngx_uint_t malloc_trim_cycle; /* a cycle is defined as the number - of reqeusts */ + of requests */ ngx_uint_t malloc_trim_req_count; -#if nginx_version >= 1011011 + ngx_uint_t directive_line; + +#if (nginx_version >= 1011011) /* the following 2 fields are only used by ngx.req.raw_headers() for now */ ngx_buf_t **busy_buf_ptrs; ngx_int_t busy_buf_ptr_count; #endif + ngx_int_t host_var_index; + + ngx_flag_t set_sa_restart; + + ngx_queue_t free_lua_threads; /* of ngx_http_lua_thread_ref_t */ + ngx_queue_t cached_lua_threads; /* of ngx_http_lua_thread_ref_t */ + + ngx_uint_t worker_thread_vm_pool_size; + unsigned requires_header_filter:1; unsigned requires_body_filter:1; unsigned requires_capture_filter:1; @@ -232,43 +321,87 @@ struct ngx_http_lua_main_conf_s { unsigned requires_log:1; unsigned requires_shm:1; unsigned requires_capture_log:1; + unsigned requires_server_rewrite:1; }; -union ngx_http_lua_srv_conf_u { -#if (NGX_HTTP_SSL) +struct ngx_http_lua_srv_conf_s { struct { +#if (NGX_HTTP_SSL) ngx_http_lua_srv_conf_handler_pt ssl_cert_handler; ngx_str_t ssl_cert_src; u_char *ssl_cert_src_key; + u_char *ssl_cert_chunkname; + int ssl_cert_src_ref; ngx_http_lua_srv_conf_handler_pt ssl_sess_store_handler; ngx_str_t ssl_sess_store_src; u_char *ssl_sess_store_src_key; + u_char *ssl_sess_store_chunkname; + int ssl_sess_store_src_ref; ngx_http_lua_srv_conf_handler_pt ssl_sess_fetch_handler; ngx_str_t ssl_sess_fetch_src; u_char *ssl_sess_fetch_src_key; - } srv; + u_char *ssl_sess_fetch_chunkname; + int ssl_sess_fetch_src_ref; + + ngx_http_lua_srv_conf_handler_pt ssl_client_hello_handler; + ngx_str_t ssl_client_hello_src; + u_char *ssl_client_hello_src_key; + u_char *ssl_client_hello_chunkname; + int ssl_client_hello_src_ref; #endif - struct { - ngx_str_t src; - u_char *src_key; + ngx_http_lua_srv_conf_handler_pt server_rewrite_handler; + ngx_http_complex_value_t server_rewrite_src; + u_char *server_rewrite_src_key; + u_char *server_rewrite_chunkname; + int server_rewrite_src_ref; + } srv; - ngx_http_lua_srv_conf_handler_pt handler; + struct { + ngx_uint_t max_cached; + ngx_queue_t cache; + ngx_queue_t free; + ngx_queue_t *buckets; + ngx_uint_t bucket_cnt; + ngx_http_upstream_init_pt original_init_upstream; + ngx_http_upstream_init_peer_pt original_init_peer; + + ngx_http_lua_srv_conf_handler_pt handler; + ngx_str_t src; + u_char *src_key; + u_char *chunkname; + int src_ref; } balancer; }; -typedef struct { +struct ngx_http_lua_loc_conf_s { #if (NGX_HTTP_SSL) ngx_ssl_t *ssl; /* shared by SSL cosockets */ + ngx_array_t *ssl_certificates; + ngx_array_t *ssl_certificate_keys; ngx_uint_t ssl_protocols; ngx_str_t ssl_ciphers; ngx_uint_t ssl_verify_depth; ngx_str_t ssl_trusted_certificate; ngx_str_t ssl_crl; + ngx_str_t ssl_key_log; +#if (nginx_version >= 1019004) + ngx_array_t *ssl_conf_commands; +#endif + +#ifdef HAVE_PROXY_SSL_PATCH + ngx_http_lua_loc_conf_handler_pt proxy_ssl_verify_handler; + ngx_str_t proxy_ssl_verify_src; + u_char *proxy_ssl_verify_src_key; + u_char *proxy_ssl_verify_chunkname; + int proxy_ssl_verify_src_ref; + ngx_flag_t upstream_skip_openssl_default_verify; +#endif + #endif ngx_flag_t force_read_body; /* whether force request body to @@ -287,12 +420,15 @@ typedef struct { ngx_http_output_body_filter_pt body_filter_handler; + + u_char *rewrite_chunkname; ngx_http_complex_value_t rewrite_src; /* rewrite_by_lua inline script/script file path */ u_char *rewrite_src_key; /* cached key for rewrite_src */ + int rewrite_src_ref; u_char *access_chunkname; ngx_http_complex_value_t access_src; /* access_by_lua @@ -300,6 +436,7 @@ typedef struct { file path */ u_char *access_src_key; /* cached key for access_src */ + int access_src_ref; u_char *content_chunkname; ngx_http_complex_value_t content_src; /* content_by_lua @@ -307,6 +444,7 @@ typedef struct { file path */ u_char *content_src_key; /* cached key for content_src */ + int content_src_ref; u_char *log_chunkname; @@ -314,17 +452,22 @@ typedef struct { file path */ u_char *log_src_key; /* cached key for log_src */ + int log_src_ref; ngx_http_complex_value_t header_filter_src; /* header_filter_by_lua inline script/script file path */ + u_char *header_filter_chunkname; u_char *header_filter_src_key; /* cached key for header_filter_src */ + int header_filter_src_ref; ngx_http_complex_value_t body_filter_src; u_char *body_filter_src_key; + u_char *body_filter_chunkname; + int body_filter_src_ref; ngx_msec_t keepalive_timeout; ngx_msec_t connect_timeout; @@ -340,14 +483,14 @@ typedef struct { ngx_flag_t log_socket_errors; ngx_flag_t check_client_abort; ngx_flag_t use_default_type; -} ngx_http_lua_loc_conf_t; +}; typedef enum { NGX_HTTP_LUA_USER_CORO_NOP = 0, NGX_HTTP_LUA_USER_CORO_RESUME = 1, NGX_HTTP_LUA_USER_CORO_YIELD = 2, - NGX_HTTP_LUA_USER_THREAD_RESUME = 3 + NGX_HTTP_LUA_USER_THREAD_RESUME = 3, } ngx_http_lua_user_coro_op_t; @@ -360,21 +503,12 @@ typedef enum { } ngx_http_lua_co_status_t; -typedef struct ngx_http_lua_co_ctx_s ngx_http_lua_co_ctx_t; - -typedef struct ngx_http_lua_posted_thread_s ngx_http_lua_posted_thread_t; - struct ngx_http_lua_posted_thread_s { ngx_http_lua_co_ctx_t *co_ctx; ngx_http_lua_posted_thread_t *next; }; -enum { - NGX_HTTP_LUA_SUBREQ_TRUNCATED = 1 -}; - - struct ngx_http_lua_co_ctx_s { void *data; /* user state for cosockets */ @@ -382,6 +516,7 @@ struct ngx_http_lua_co_ctx_s { ngx_http_lua_co_ctx_t *parent_co_ctx; ngx_http_lua_posted_thread_t *zombie_child_threads; + ngx_http_lua_posted_thread_t **next_zombie_child_thread; ngx_http_cleanup_pt cleanup; @@ -393,6 +528,11 @@ struct ngx_http_lua_co_ctx_s { uint8_t *sr_flags; + unsigned nresults_from_worker_thread; /* number of results + * from worker + * thread callback */ + unsigned nrets; /* ngx_http_lua_run_thread nrets arg. */ + unsigned nsubreqs; /* number of subrequests of the * current request */ @@ -431,6 +571,13 @@ struct ngx_http_lua_co_ctx_s { the ngx.thread.spawn() call */ unsigned sem_resume_status:1; + + unsigned is_wrap:1; /* set when creating coroutines via + coroutine.wrap */ + + unsigned propagate_error:1; /* set when propagating an error + from a coroutine to its + parent */ }; @@ -441,7 +588,7 @@ typedef struct { typedef struct ngx_http_lua_ctx_s { - /* for lua_coce_cache off: */ + /* for lua_code_cache off: */ ngx_http_lua_vm_state_t *vm_state; ngx_http_request_t *request; @@ -471,7 +618,10 @@ typedef struct ngx_http_lua_ctx_s { ngx_chain_t *busy_bufs; ngx_chain_t *free_recv_bufs; - ngx_http_cleanup_pt *cleanup; + ngx_chain_t *filter_in_bufs; /* for the body filter */ + ngx_chain_t *filter_busy_bufs; /* for the body filter */ + + ngx_pool_cleanup_pt *cleanup; ngx_http_cleanup_t *free_cleanup; /* free list of cleanup records */ @@ -497,7 +647,7 @@ typedef struct ngx_http_lua_ctx_s { int uthreads; /* number of active user threads */ - uint16_t context; /* the current running directive context + uint32_t context; /* the current running directive context (or running phase) for the current Lua chunk */ @@ -528,7 +678,9 @@ typedef struct ngx_http_lua_ctx_s { unsigned headers_set:1; /* whether the user has set custom response headers */ - + unsigned mime_set:1; /* whether the user has set Content-Type + response header */ + unsigned entered_server_rewrite_phase:1; unsigned entered_rewrite_phase:1; unsigned entered_access_phase:1; unsigned entered_content_phase:1; @@ -555,13 +707,6 @@ typedef struct ngx_http_lua_ctx_s { } ngx_http_lua_ctx_t; -typedef struct ngx_http_lua_header_val_s ngx_http_lua_header_val_t; - - -typedef ngx_int_t (*ngx_http_lua_set_header_pt)(ngx_http_request_t *r, - ngx_http_lua_header_val_t *hv, ngx_str_t *value); - - struct ngx_http_lua_header_val_s { ngx_http_complex_value_t value; ngx_uint_t hash; @@ -576,7 +721,6 @@ typedef struct { ngx_str_t name; ngx_uint_t offset; ngx_http_lua_set_header_pt handler; - } ngx_http_lua_set_header_t; diff --git a/src/ngx_http_lua_consts.c b/src/ngx_http_lua_consts.c index 30a86f106e..f2dcf4a490 100644 --- a/src/ngx_http_lua_consts.c +++ b/src/ngx_http_lua_consts.c @@ -119,10 +119,11 @@ ngx_http_lua_inject_http_consts(lua_State *L) lua_pushinteger(L, NGX_HTTP_MOVED_TEMPORARILY); lua_setfield(L, -2, "HTTP_MOVED_TEMPORARILY"); -#if defined(nginx_version) && nginx_version >= 8042 lua_pushinteger(L, NGX_HTTP_SEE_OTHER); lua_setfield(L, -2, "HTTP_SEE_OTHER"); -#endif + + lua_pushinteger(L, NGX_HTTP_PERMANENT_REDIRECT); + lua_setfield(L, -2, "HTTP_PERMANENT_REDIRECT"); lua_pushinteger(L, NGX_HTTP_NOT_MODIFIED); lua_setfield(L, -2, "HTTP_NOT_MODIFIED"); @@ -175,6 +176,10 @@ ngx_http_lua_inject_http_consts(lua_State *L) lua_pushinteger(L, NGX_HTTP_INTERNAL_SERVER_ERROR); lua_setfield(L, -2, "HTTP_INTERNAL_SERVER_ERROR"); + lua_pushinteger(L, NGX_HTTP_NOT_IMPLEMENTED); + lua_setfield(L, -2, "HTTP_NOT_IMPLEMENTED"); + + /* keep for backward compatibility */ lua_pushinteger(L, NGX_HTTP_NOT_IMPLEMENTED); lua_setfield(L, -2, "HTTP_METHOD_NOT_IMPLEMENTED"); diff --git a/src/ngx_http_lua_contentby.c b/src/ngx_http_lua_contentby.c index ecd6c0e591..039f23ffcd 100644 --- a/src/ngx_http_lua_contentby.c +++ b/src/ngx_http_lua_contentby.c @@ -29,7 +29,7 @@ ngx_http_lua_content_by_chunk(lua_State *L, ngx_http_request_t *r) lua_State *co; ngx_event_t *rev; ngx_http_lua_ctx_t *ctx; - ngx_http_cleanup_t *cln; + ngx_pool_cleanup_t *cln; ngx_http_lua_loc_conf_t *llcf; @@ -63,9 +63,11 @@ ngx_http_lua_content_by_chunk(lua_State *L, ngx_http_request_t *r) /* move code closure to new coroutine */ lua_xmove(L, co, 1); +#ifndef OPENRESTY_LUAJIT /* set closure's env table to new coroutine's globals table */ ngx_http_lua_get_globals_table(co); lua_setfenv(co, -2); +#endif /* save nginx request in coroutine globals table */ ngx_http_lua_set_req(co, r); @@ -77,9 +79,11 @@ ngx_http_lua_content_by_chunk(lua_State *L, ngx_http_request_t *r) ctx->cur_co_ctx->co_top = 1; #endif + ngx_http_lua_attach_co_ctx_to_L(co, ctx->cur_co_ctx); + /* {{{ register request cleanup hooks */ if (ctx->cleanup == NULL) { - cln = ngx_http_cleanup_add(r, 0); + cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -200,10 +204,6 @@ ngx_http_lua_content_handler(ngx_http_request_t *r) ngx_http_lua_content_phase_post_read); if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { -#if (nginx_version < 1002006) || \ - (nginx_version >= 1003000 && nginx_version < 1003009) - r->main->count--; -#endif return rc; } @@ -267,8 +267,13 @@ ngx_http_lua_content_handler_file(ngx_http_request_t *r) L = ngx_http_lua_get_lua_vm(r, NULL); + if (!llcf->enable_code_cache) { + llcf->content_src_ref = LUA_REFNIL; + } + /* load Lua script file (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadfile(r->connection->log, L, script_path, + &llcf->content_src_ref, llcf->content_src_key); if (rc != NGX_OK) { if (rc < NGX_HTTP_SPECIAL_RESPONSE) { @@ -296,10 +301,15 @@ ngx_http_lua_content_handler_inline(ngx_http_request_t *r) L = ngx_http_lua_get_lua_vm(r, NULL); + if (!llcf->enable_code_cache) { + llcf->content_src_ref = LUA_REFNIL; + } + /* load Lua inline script (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, llcf->content_src.value.data, llcf->content_src.value.len, + &llcf->content_src_ref, llcf->content_src_key, (const char *) llcf->content_chunkname); diff --git a/src/ngx_http_lua_control.c b/src/ngx_http_lua_control.c index ae36505f9d..8358abcbb6 100644 --- a/src/ngx_http_lua_control.c +++ b/src/ngx_http_lua_control.c @@ -18,7 +18,6 @@ static int ngx_http_lua_ngx_exec(lua_State *L); static int ngx_http_lua_ngx_redirect(lua_State *L); -static int ngx_http_lua_ngx_exit(lua_State *L); static int ngx_http_lua_on_abort(lua_State *L); @@ -35,14 +34,6 @@ ngx_http_lua_inject_control_api(ngx_log_t *log, lua_State *L) lua_pushcfunction(L, ngx_http_lua_ngx_exec); lua_setfield(L, -2, "exec"); - lua_pushcfunction(L, ngx_http_lua_ngx_exit); - lua_setfield(L, -2, "throw_error"); /* deprecated */ - - /* ngx.exit */ - - lua_pushcfunction(L, ngx_http_lua_ngx_exit); - lua_setfield(L, -2, "exit"); - /* ngx.on_abort */ lua_pushcfunction(L, ngx_http_lua_on_abort); @@ -100,6 +91,7 @@ ngx_http_lua_ngx_exec(lua_State *L) } ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE + | NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE | NGX_HTTP_LUA_CONTEXT_ACCESS | NGX_HTTP_LUA_CONTEXT_CONTENT); @@ -195,9 +187,12 @@ ngx_http_lua_ngx_redirect(lua_State *L) int n; u_char *p; u_char *uri; + u_char byte; size_t len; ngx_table_elt_t *h; ngx_http_request_t *r; + size_t buf_len; + u_char *buf; n = lua_gettop(L); @@ -213,10 +208,12 @@ ngx_http_lua_ngx_redirect(lua_State *L) if (rc != NGX_HTTP_MOVED_TEMPORARILY && rc != NGX_HTTP_MOVED_PERMANENTLY && rc != NGX_HTTP_SEE_OTHER + && rc != NGX_HTTP_PERMANENT_REDIRECT && rc != NGX_HTTP_TEMPORARY_REDIRECT) { return luaL_error(L, "only ngx.HTTP_MOVED_TEMPORARILY, " "ngx.HTTP_MOVED_PERMANENTLY, " + "ngx.HTTP_PERMANENT_REDIRECT, " "ngx.HTTP_SEE_OTHER, and " "ngx.HTTP_TEMPORARY_REDIRECT are allowed"); } @@ -236,6 +233,7 @@ ngx_http_lua_ngx_redirect(lua_State *L) } ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE + | NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE | NGX_HTTP_LUA_CONTEXT_ACCESS | NGX_HTTP_LUA_CONTEXT_CONTENT); @@ -246,6 +244,19 @@ ngx_http_lua_ngx_redirect(lua_State *L) "the headers"); } + if (ngx_http_lua_check_unsafe_uri_bytes(r, p, len, &byte) != NGX_OK) { + buf_len = ngx_http_lua_escape_log(NULL, p, len) + 1; + buf = ngx_palloc(r->pool, buf_len); + if (buf == NULL) { + return NGX_ERROR; + } + + ngx_http_lua_escape_log(buf, p, len); + buf[buf_len - 1] = '\0'; + return luaL_error(L, "unsafe byte \"0x%02x\" in redirect uri \"%s\"", + byte, buf); + } + uri = ngx_palloc(r->pool, len); if (uri == NULL) { return luaL_error(L, "no memory"); @@ -269,6 +280,9 @@ ngx_http_lua_ngx_redirect(lua_State *L) h->value.len = len; h->value.data = uri; +#if defined(nginx_version) && nginx_version >= 1023000 + h->next = NULL; +#endif ngx_str_set(&h->key, "Location"); r->headers_out.status = rc; @@ -293,111 +307,10 @@ ngx_http_lua_ngx_redirect(lua_State *L) } -static int -ngx_http_lua_ngx_exit(lua_State *L) -{ - ngx_int_t rc; - ngx_http_request_t *r; - ngx_http_lua_ctx_t *ctx; - - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one argument"); - } - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } - - ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); - if (ctx == NULL) { - return luaL_error(L, "no request ctx found"); - } - - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE - | NGX_HTTP_LUA_CONTEXT_ACCESS - | NGX_HTTP_LUA_CONTEXT_CONTENT - | NGX_HTTP_LUA_CONTEXT_TIMER - | NGX_HTTP_LUA_CONTEXT_HEADER_FILTER - | NGX_HTTP_LUA_CONTEXT_BALANCER - | NGX_HTTP_LUA_CONTEXT_SSL_CERT - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH); - - rc = (ngx_int_t) luaL_checkinteger(L, 1); - - if (ctx->context & (NGX_HTTP_LUA_CONTEXT_SSL_CERT - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH)) - { - -#if (NGX_HTTP_SSL) - - ctx->exit_code = rc; - ctx->exited = 1; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua exit with code %i", rc); - - if (ctx->context == NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE) { - return 0; - } - - return lua_yield(L, 0); - -#else - - return luaL_error(L, "no SSL support"); - -#endif - } - - if (ctx->no_abort - && rc != NGX_ERROR - && rc != NGX_HTTP_CLOSE - && rc != NGX_HTTP_REQUEST_TIME_OUT - && rc != NGX_HTTP_CLIENT_CLOSED_REQUEST) - { - return luaL_error(L, "attempt to abort with pending subrequests"); - } - - if ((r->header_sent || ctx->header_sent) - && rc >= NGX_HTTP_SPECIAL_RESPONSE - && rc != NGX_HTTP_REQUEST_TIME_OUT - && rc != NGX_HTTP_CLIENT_CLOSED_REQUEST - && rc != NGX_HTTP_CLOSE) - { - if (rc != (ngx_int_t) r->headers_out.status) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "attempt to " - "set status %i via ngx.exit after sending out the " - "response status %ui", rc, r->headers_out.status); - } - - rc = NGX_HTTP_OK; - } - - dd("setting exit code: %d", (int) rc); - - ctx->exit_code = rc; - ctx->exited = 1; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua exit with code %i", ctx->exit_code); - - if (ctx->context & (NGX_HTTP_LUA_CONTEXT_HEADER_FILTER - | NGX_HTTP_LUA_CONTEXT_BALANCER)) - { - return 0; - } - - dd("calling yield"); - return lua_yield(L, 0); -} - - static int ngx_http_lua_on_abort(lua_State *L) { + int co_ref; ngx_http_request_t *r; ngx_http_lua_ctx_t *ctx; ngx_http_lua_co_ctx_t *coctx = NULL; @@ -428,17 +341,9 @@ ngx_http_lua_on_abort(lua_State *L) return 2; } - ngx_http_lua_coroutine_create_helper(L, r, ctx, &coctx); - - lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); - lua_rawget(L, LUA_REGISTRYINDEX); - lua_pushvalue(L, -2); - - dd("on_wait thread 1: %p", lua_tothread(L, -1)); - - coctx->co_ref = luaL_ref(L, -2); - lua_pop(L, 1); + ngx_http_lua_coroutine_create_helper(L, r, ctx, &coctx, &co_ref); + coctx->co_ref = co_ref; coctx->is_uthread = 1; ctx->on_abort_co_ctx = coctx; @@ -452,13 +357,20 @@ ngx_http_lua_on_abort(lua_State *L) } -#ifndef NGX_LUA_NO_FFI_API int ngx_http_lua_ffi_exit(ngx_http_request_t *r, int status, u_char *err, size_t *errlen) { ngx_http_lua_ctx_t *ctx; + if (status == NGX_AGAIN || status == NGX_DONE) { + *errlen = ngx_snprintf(err, *errlen, + "bad argument to 'ngx.exit': does not accept " + "NGX_AGAIN or NGX_DONE") + - err; + return NGX_ERROR; + } + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); if (ctx == NULL) { *errlen = ngx_snprintf(err, *errlen, "no request ctx found") - err; @@ -466,11 +378,16 @@ ngx_http_lua_ffi_exit(ngx_http_request_t *r, int status, u_char *err, } if (ngx_http_lua_ffi_check_context(ctx, NGX_HTTP_LUA_CONTEXT_REWRITE + | NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE | NGX_HTTP_LUA_CONTEXT_ACCESS | NGX_HTTP_LUA_CONTEXT_CONTENT | NGX_HTTP_LUA_CONTEXT_TIMER | NGX_HTTP_LUA_CONTEXT_HEADER_FILTER | NGX_HTTP_LUA_CONTEXT_BALANCER +#ifdef HAVE_PROXY_SSL_PATCH + | NGX_HTTP_LUA_CONTEXT_PROXY_SSL_VERIFY +#endif + | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_HTTP_LUA_CONTEXT_SSL_CERT | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH, @@ -481,6 +398,10 @@ ngx_http_lua_ffi_exit(ngx_http_request_t *r, int status, u_char *err, } if (ctx->context & (NGX_HTTP_LUA_CONTEXT_SSL_CERT +#ifdef HAVE_PROXY_SSL_PATCH + | NGX_HTTP_LUA_CONTEXT_PROXY_SSL_VERIFY +#endif + | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH)) { @@ -548,6 +469,6 @@ ngx_http_lua_ffi_exit(ngx_http_request_t *r, int status, u_char *err, return NGX_OK; } -#endif /* NGX_LUA_NO_FFI_API */ + /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_coroutine.c b/src/ngx_http_lua_coroutine.c index b790814a2b..1580c5b28c 100644 --- a/src/ngx_http_lua_coroutine.c +++ b/src/ngx_http_lua_coroutine.c @@ -25,6 +25,7 @@ static int ngx_http_lua_coroutine_create(lua_State *L); +static int ngx_http_lua_coroutine_wrap(lua_State *L); static int ngx_http_lua_coroutine_resume(lua_State *L); static int ngx_http_lua_coroutine_yield(lua_State *L); static int ngx_http_lua_coroutine_status(lua_State *L); @@ -58,34 +59,75 @@ ngx_http_lua_coroutine_create(lua_State *L) return luaL_error(L, "no request ctx found"); } - return ngx_http_lua_coroutine_create_helper(L, r, ctx, NULL); + return ngx_http_lua_coroutine_create_helper(L, r, ctx, NULL, NULL); +} + + +static int +ngx_http_lua_coroutine_wrap_runner(lua_State *L) +{ + /* retrieve closure and insert it at the bottom of + * the stack for coroutine.resume() */ + lua_pushvalue(L, lua_upvalueindex(1)); + lua_insert(L, 1); + + return ngx_http_lua_coroutine_resume(L); +} + + +static int +ngx_http_lua_coroutine_wrap(lua_State *L) +{ + ngx_http_request_t *r; + ngx_http_lua_ctx_t *ctx; + ngx_http_lua_co_ctx_t *coctx = NULL; + + r = ngx_http_lua_get_req(L); + if (r == NULL) { + return luaL_error(L, "no request found"); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + return luaL_error(L, "no request ctx found"); + } + + ngx_http_lua_coroutine_create_helper(L, r, ctx, &coctx, NULL); + + coctx->is_wrap = 1; + + lua_pushcclosure(L, ngx_http_lua_coroutine_wrap_runner, 1); + + return 1; } int ngx_http_lua_coroutine_create_helper(lua_State *L, ngx_http_request_t *r, - ngx_http_lua_ctx_t *ctx, ngx_http_lua_co_ctx_t **pcoctx) + ngx_http_lua_ctx_t *ctx, ngx_http_lua_co_ctx_t **pcoctx, int *co_ref) { lua_State *vm; /* the Lua VM */ lua_State *co; /* new coroutine to be created */ ngx_http_lua_co_ctx_t *coctx; /* co ctx for the new coroutine */ + ngx_http_lua_main_conf_t *lmcf; luaL_argcheck(L, lua_isfunction(L, 1) && !lua_iscfunction(L, 1), 1, "Lua function expected"); - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE - | NGX_HTTP_LUA_CONTEXT_ACCESS - | NGX_HTTP_LUA_CONTEXT_CONTENT - | NGX_HTTP_LUA_CONTEXT_TIMER - | NGX_HTTP_LUA_CONTEXT_SSL_CERT - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH); + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); vm = ngx_http_lua_get_lua_vm(r, ctx); /* create new coroutine on root Lua state, so it always yields * to main Lua thread */ - co = lua_newthread(vm); + if (co_ref == NULL) { + co = lua_newthread(vm); + + } else { + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); + *co_ref = ngx_http_lua_new_cached_thread(vm, &co, lmcf, 0); + } ngx_http_lua_probe_user_coroutine_create(r, L, co); @@ -98,20 +140,30 @@ ngx_http_lua_coroutine_create_helper(lua_State *L, ngx_http_request_t *r, } else { ngx_memzero(coctx, sizeof(ngx_http_lua_co_ctx_t)); + coctx->next_zombie_child_thread = &coctx->zombie_child_threads; coctx->co_ref = LUA_NOREF; } coctx->co = co; coctx->co_status = NGX_HTTP_LUA_CO_SUSPENDED; +#ifdef OPENRESTY_LUAJIT + ngx_http_lua_set_req(co, r); + ngx_http_lua_attach_co_ctx_to_L(co, coctx); +#else /* make new coroutine share globals of the parent coroutine. * NOTE: globals don't have to be separated! */ ngx_http_lua_get_globals_table(L); lua_xmove(L, co, 1); ngx_http_lua_set_globals_table(co); +#endif lua_xmove(vm, L, 1); /* move coroutine from main thread to L */ + if (co_ref) { + lua_pop(vm, 1); /* pop coroutines */ + } + lua_pushvalue(L, 1); /* copy entry function to top of L*/ lua_xmove(L, co, 1); /* move entry function from L to co */ @@ -150,12 +202,7 @@ ngx_http_lua_coroutine_resume(lua_State *L) return luaL_error(L, "no request ctx found"); } - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE - | NGX_HTTP_LUA_CONTEXT_ACCESS - | NGX_HTTP_LUA_CONTEXT_CONTENT - | NGX_HTTP_LUA_CONTEXT_TIMER - | NGX_HTTP_LUA_CONTEXT_SSL_CERT - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH); + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); p_coctx = ctx->cur_co_ctx; if (p_coctx == NULL) { @@ -211,12 +258,7 @@ ngx_http_lua_coroutine_yield(lua_State *L) return luaL_error(L, "no request ctx found"); } - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE - | NGX_HTTP_LUA_CONTEXT_ACCESS - | NGX_HTTP_LUA_CONTEXT_CONTENT - | NGX_HTTP_LUA_CONTEXT_TIMER - | NGX_HTTP_LUA_CONTEXT_SSL_CERT - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH); + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); coctx = ctx->cur_co_ctx; @@ -246,7 +288,7 @@ ngx_http_lua_inject_coroutine_api(ngx_log_t *log, lua_State *L) int rc; /* new coroutine table */ - lua_createtable(L, 0 /* narr */, 14 /* nrec */); + lua_createtable(L, 0 /* narr */, 16 /* nrec */); /* get old coroutine table */ lua_getglobal(L, "coroutine"); @@ -258,6 +300,9 @@ ngx_http_lua_inject_coroutine_api(ngx_log_t *log, lua_State *L) lua_getfield(L, -1, "create"); lua_setfield(L, -3, "_create"); + lua_getfield(L, -1, "wrap"); + lua_setfield(L, -3, "_wrap"); + lua_getfield(L, -1, "resume"); lua_setfield(L, -3, "_resume"); @@ -273,6 +318,9 @@ ngx_http_lua_inject_coroutine_api(ngx_log_t *log, lua_State *L) lua_pushcfunction(L, ngx_http_lua_coroutine_create); lua_setfield(L, -2, "__create"); + lua_pushcfunction(L, ngx_http_lua_coroutine_wrap); + lua_setfield(L, -2, "__wrap"); + lua_pushcfunction(L, ngx_http_lua_coroutine_resume); lua_setfield(L, -2, "__resume"); @@ -287,16 +335,28 @@ ngx_http_lua_inject_coroutine_api(ngx_log_t *log, lua_State *L) /* inject coroutine APIs */ { const char buf[] = - "local keys = {'create', 'yield', 'resume', 'status'}\n" + "local keys = {'create', 'yield', 'resume', 'status', 'wrap'}\n" +#ifdef OPENRESTY_LUAJIT + "local get_req = require 'thread.exdata'\n" +#else "local getfenv = getfenv\n" +#endif "for _, key in ipairs(keys) do\n" "local std = coroutine['_' .. key]\n" "local ours = coroutine['__' .. key]\n" "local raw_ctx = ngx._phase_ctx\n" "coroutine[key] = function (...)\n" +#ifdef OPENRESTY_LUAJIT + "local r = get_req()\n" +#else "local r = getfenv(0).__ngx_req\n" - "if r then\n" +#endif + "if r ~= nil then\n" +#ifdef OPENRESTY_LUAJIT + "local ctx = raw_ctx()\n" +#else "local ctx = raw_ctx(r)\n" +#endif /* ignore header and body filters */ "if ctx ~= 0x020 and ctx ~= 0x040 then\n" "return ours(...)\n" @@ -305,24 +365,18 @@ ngx_http_lua_inject_coroutine_api(ngx_log_t *log, lua_State *L) "return std(...)\n" "end\n" "end\n" - "local create, resume = coroutine.create, coroutine.resume\n" - "coroutine.wrap = function(f)\n" - "local co = create(f)\n" - "return function(...) return select(2, resume(co, ...)) end\n" - "end\n" - "package.loaded.coroutine = coroutine"; - + "package.loaded.coroutine = coroutine" #if 0 "debug.sethook(function () collectgarbage() end, 'rl', 1)" #endif ; - rc = luaL_loadbuffer(L, buf, sizeof(buf) - 1, "=coroutine.wrap"); + rc = luaL_loadbuffer(L, buf, sizeof(buf) - 1, "=coroutine_api"); } if (rc != 0) { ngx_log_error(NGX_LOG_ERR, log, 0, - "failed to load Lua code for coroutine.wrap(): %i: %s", + "failed to load Lua code for coroutine_api: %i: %s", rc, lua_tostring(L, -1)); lua_pop(L, 1); @@ -332,7 +386,7 @@ ngx_http_lua_inject_coroutine_api(ngx_log_t *log, lua_State *L) rc = lua_pcall(L, 0, 0, 0); if (rc != 0) { ngx_log_error(NGX_LOG_ERR, log, 0, - "failed to run the Lua code for coroutine.wrap(): %i: %s", + "failed to run the Lua code for coroutine_api: %i: %s", rc, lua_tostring(L, -1)); lua_pop(L, 1); } @@ -361,12 +415,7 @@ ngx_http_lua_coroutine_status(lua_State *L) return luaL_error(L, "no request ctx found"); } - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE - | NGX_HTTP_LUA_CONTEXT_ACCESS - | NGX_HTTP_LUA_CONTEXT_CONTENT - | NGX_HTTP_LUA_CONTEXT_TIMER - | NGX_HTTP_LUA_CONTEXT_SSL_CERT - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH); + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); coctx = ngx_http_lua_get_co_ctx(co, ctx); if (coctx == NULL) { diff --git a/src/ngx_http_lua_coroutine.h b/src/ngx_http_lua_coroutine.h index 8b7bc9057a..03e32f3878 100644 --- a/src/ngx_http_lua_coroutine.h +++ b/src/ngx_http_lua_coroutine.h @@ -15,7 +15,7 @@ void ngx_http_lua_inject_coroutine_api(ngx_log_t *log, lua_State *L); int ngx_http_lua_coroutine_create_helper(lua_State *L, ngx_http_request_t *r, - ngx_http_lua_ctx_t *ctx, ngx_http_lua_co_ctx_t **pcoctx); + ngx_http_lua_ctx_t *ctx, ngx_http_lua_co_ctx_t **pcoctx, int *co_ref); #endif /* _NGX_HTTP_LUA_COROUTINE_H_INCLUDED_ */ diff --git a/src/ngx_http_lua_ctx.c b/src/ngx_http_lua_ctx.c index a14bb4d495..d5431be724 100644 --- a/src/ngx_http_lua_ctx.c +++ b/src/ngx_http_lua_ctx.c @@ -11,6 +11,7 @@ #include "ngx_http_lua_util.h" +#include "ngx_http_lua_ssl.h" #include "ngx_http_lua_ctx.h" @@ -21,79 +22,16 @@ typedef struct { static ngx_int_t ngx_http_lua_ngx_ctx_add_cleanup(ngx_http_request_t *r, - int ref); + ngx_pool_t *pool, int ref); static void ngx_http_lua_ngx_ctx_cleanup(void *data); -int -ngx_http_lua_ngx_get_ctx(lua_State *L) -{ - ngx_http_request_t *r; - ngx_http_lua_ctx_t *ctx; - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request found"); - } - - ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); - if (ctx == NULL) { - return luaL_error(L, "no request ctx found"); - } - - if (ctx->ctx_ref == LUA_NOREF) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua create ngx.ctx table for the current request"); - - lua_pushliteral(L, ngx_http_lua_ctx_tables_key); - lua_rawget(L, LUA_REGISTRYINDEX); - lua_createtable(L, 0 /* narr */, 4 /* nrec */); - lua_pushvalue(L, -1); - ctx->ctx_ref = luaL_ref(L, -3); - - if (ngx_http_lua_ngx_ctx_add_cleanup(r, ctx->ctx_ref) != NGX_OK) { - return luaL_error(L, "no memory"); - } - - return 1; - } - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua fetching existing ngx.ctx table for the current " - "request"); - - lua_pushliteral(L, ngx_http_lua_ctx_tables_key); - lua_rawget(L, LUA_REGISTRYINDEX); - lua_rawgeti(L, -1, ctx->ctx_ref); - - return 1; -} - - -int -ngx_http_lua_ngx_set_ctx(lua_State *L) -{ - ngx_http_request_t *r; - ngx_http_lua_ctx_t *ctx; - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request found"); - } - - ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); - if (ctx == NULL) { - return luaL_error(L, "no request ctx found"); - } - - return ngx_http_lua_ngx_set_ctx_helper(L, r, ctx, 3); -} - - int ngx_http_lua_ngx_set_ctx_helper(lua_State *L, ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx, int index) { + ngx_pool_t *pool; + if (index < 0) { index = lua_gettop(L) + index + 1; } @@ -108,7 +46,8 @@ ngx_http_lua_ngx_set_ctx_helper(lua_State *L, ngx_http_request_t *r, ctx->ctx_ref = luaL_ref(L, -2); lua_pop(L, 1); - if (ngx_http_lua_ngx_ctx_add_cleanup(r, ctx->ctx_ref) != NGX_OK) { + pool = r->pool; + if (ngx_http_lua_ngx_ctx_add_cleanup(r, pool, ctx->ctx_ref) != NGX_OK) { return luaL_error(L, "no memory"); } @@ -130,44 +69,95 @@ ngx_http_lua_ngx_set_ctx_helper(lua_State *L, ngx_http_request_t *r, } -#ifndef NGX_LUA_NO_FFI_API int -ngx_http_lua_ffi_get_ctx_ref(ngx_http_request_t *r) +ngx_http_lua_ffi_get_ctx_ref(ngx_http_request_t *r, int *in_ssl_phase, + int *ssl_ctx_ref) { - ngx_http_lua_ctx_t *ctx; + ngx_http_lua_ctx_t *ctx; +#if (NGX_HTTP_SSL) + ngx_http_lua_ssl_ctx_t *ssl_ctx; +#endif ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); if (ctx == NULL) { return NGX_HTTP_LUA_FFI_NO_REQ_CTX; } - return ctx->ctx_ref; + if (ctx->ctx_ref >= 0 || in_ssl_phase == NULL) { + return ctx->ctx_ref; + } + + *in_ssl_phase = ctx->context & (NGX_HTTP_LUA_CONTEXT_SSL_CERT + | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE); + *ssl_ctx_ref = LUA_NOREF; + +#if (NGX_HTTP_SSL) + if (r->connection->ssl != NULL) { + ssl_ctx = ngx_http_lua_ssl_get_ctx(r->connection->ssl->connection); + + if (ssl_ctx != NULL) { + *ssl_ctx_ref = ssl_ctx->ctx_ref; + } + } +#endif + + return LUA_NOREF; } int ngx_http_lua_ffi_set_ctx_ref(ngx_http_request_t *r, int ref) { - ngx_http_lua_ctx_t *ctx; + ngx_pool_t *pool; + ngx_http_lua_ctx_t *ctx; +#if (NGX_HTTP_SSL) + ngx_connection_t *c; + ngx_http_lua_ssl_ctx_t *ssl_ctx; +#endif ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); if (ctx == NULL) { return NGX_HTTP_LUA_FFI_NO_REQ_CTX; } +#if (NGX_HTTP_SSL) + if (ctx->context & (NGX_HTTP_LUA_CONTEXT_SSL_CERT + | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE)) + { + ssl_ctx = ngx_http_lua_ssl_get_ctx(r->connection->ssl->connection); + if (ssl_ctx == NULL) { + return NGX_ERROR; + } + + ssl_ctx->ctx_ref = ref; + c = ngx_ssl_get_connection(r->connection->ssl->connection); + pool = c->pool; + + } else { + pool = r->pool; + } + +#else + pool = r->pool; +#endif + ctx->ctx_ref = ref; - if (ngx_http_lua_ngx_ctx_add_cleanup(r, ref) != NGX_OK) { + if (ngx_http_lua_ngx_ctx_add_cleanup(r, pool, ref) != NGX_OK) { return NGX_ERROR; } return NGX_OK; } -#endif /* NGX_LUA_NO_FFI_API */ static ngx_int_t -ngx_http_lua_ngx_ctx_add_cleanup(ngx_http_request_t *r, int ref) +ngx_http_lua_ngx_ctx_add_cleanup(ngx_http_request_t *r, ngx_pool_t *pool, + int ref) { lua_State *L; ngx_pool_cleanup_t *cln; @@ -178,7 +168,7 @@ ngx_http_lua_ngx_ctx_add_cleanup(ngx_http_request_t *r, int ref) ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); L = ngx_http_lua_get_lua_vm(r, ctx); - cln = ngx_pool_cleanup_add(r->pool, + cln = ngx_pool_cleanup_add(pool, sizeof(ngx_http_lua_ngx_ctx_cleanup_data_t)); if (cln == NULL) { return NGX_ERROR; diff --git a/src/ngx_http_lua_ctx.h b/src/ngx_http_lua_ctx.h index f73f73e5c1..7596238c07 100644 --- a/src/ngx_http_lua_ctx.h +++ b/src/ngx_http_lua_ctx.h @@ -12,8 +12,6 @@ #include "ngx_http_lua_common.h" -int ngx_http_lua_ngx_get_ctx(lua_State *L); -int ngx_http_lua_ngx_set_ctx(lua_State *L); int ngx_http_lua_ngx_set_ctx_helper(lua_State *L, ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx, int index); diff --git a/src/ngx_http_lua_directive.c b/src/ngx_http_lua_directive.c index 014a47237c..f42aae9d51 100644 --- a/src/ngx_http_lua_directive.c +++ b/src/ngx_http_lua_directive.c @@ -17,12 +17,14 @@ #include "ngx_http_lua_cache.h" #include "ngx_http_lua_contentby.h" #include "ngx_http_lua_accessby.h" +#include "ngx_http_lua_server_rewriteby.h" #include "ngx_http_lua_rewriteby.h" #include "ngx_http_lua_logby.h" #include "ngx_http_lua_headerfilterby.h" #include "ngx_http_lua_bodyfilterby.h" #include "ngx_http_lua_initby.h" #include "ngx_http_lua_initworkerby.h" +#include "ngx_http_lua_exitworkerby.h" #include "ngx_http_lua_shdict.h" #include "ngx_http_lua_ssl_certby.h" #include "ngx_http_lua_lex.h" @@ -31,6 +33,12 @@ #include "ngx_http_lua_log.h" +/* the max length is 60, after deducting the fixed four characters "=(:)" + * only 56 left. + */ +#define LJ_CHUNKNAME_MAX_LEN 56 + + typedef struct ngx_http_lua_block_parser_ctx_s ngx_http_lua_block_parser_ctx_t; @@ -42,8 +50,6 @@ typedef struct ngx_http_lua_block_parser_ctx_s static ngx_int_t ngx_http_lua_set_by_lua_init(ngx_http_request_t *r); #endif -static u_char *ngx_http_lua_gen_chunk_name(ngx_conf_t *cf, const char *tag, - size_t tag_len); static ngx_int_t ngx_http_lua_conf_read_lua_token(ngx_conf_t *cf, ngx_http_lua_block_parser_ctx_t *ctx); static u_char *ngx_http_lua_strlstrn(u_char *s1, u_char *last, u_char *s2, @@ -66,7 +72,7 @@ enum { FOUND_RIGHT_LBRACKET, FOUND_COMMENT_LINE, FOUND_DOUBLE_QUOTED, - FOUND_SINGLE_QUOTED + FOUND_SINGLE_QUOTED, }; @@ -179,6 +185,17 @@ ngx_http_lua_code_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } +char * +ngx_http_lua_load_resty_core(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "lua_load_resty_core is deprecated (the lua-resty-core " + "library is required since ngx_lua v0.10.16)"); + + return NGX_CONF_OK; +} + + char * ngx_http_lua_package_cpath(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { @@ -221,6 +238,30 @@ ngx_http_lua_package_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } +char * +ngx_http_lua_regex_cache_max_entries(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ +#if (NGX_PCRE) + return ngx_conf_set_num_slot(cf, cmd, conf); +#else + return NGX_CONF_OK; +#endif +} + + +char * +ngx_http_lua_regex_match_limit(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ +#if (NGX_PCRE) + return ngx_conf_set_num_slot(cf, cmd, conf); +#else + return NGX_CONF_OK; +#endif +} + + #if defined(NDK) && NDK char * ngx_http_lua_set_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, @@ -244,7 +285,9 @@ ngx_http_lua_set_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, char * ngx_http_lua_set_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - u_char *p; + size_t chunkname_len; + u_char *chunkname; + u_char *cache_key; ngx_str_t *value; ngx_str_t target; ndk_set_var_t filter; @@ -269,20 +312,25 @@ ngx_http_lua_set_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - filter_data->size = filter.size; - - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_INLINE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, "set_by_lua", + value[2].data, + value[2].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - filter_data->key = p; - - p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[2].data, value[2].len); - *p = '\0'; + chunkname = ngx_http_lua_gen_chunk_name(cf, "set_by_lua", + sizeof("set_by_lua") - 1, + &chunkname_len); + if (chunkname == NULL) { + return NGX_CONF_ERROR; + } + filter_data->key = cache_key; + filter_data->chunkname = chunkname; + filter_data->ref = LUA_REFNIL; filter_data->script = value[2]; + filter_data->size = filter.size; filter.data = filter_data; @@ -293,12 +341,14 @@ ngx_http_lua_set_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) char * ngx_http_lua_set_by_lua_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - u_char *p; + u_char *cache_key = NULL; ngx_str_t *value; ngx_str_t target; ndk_set_var_t filter; - ngx_http_lua_set_var_data_t *filter_data; + ngx_http_lua_set_var_data_t *filter_data; + ngx_http_complex_value_t cv; + ngx_http_compile_complex_value_t ccv; /* * value[0] = "set_by_lua_file" @@ -319,18 +369,28 @@ ngx_http_lua_set_by_lua_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - filter_data->size = filter.size; + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + ccv.cf = cf; + ccv.value = &value[2]; + ccv.complex_value = &cv; - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_FILE_KEY_LEN + 1); - if (p == NULL) { + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } - filter_data->key = p; + if (cv.lengths == NULL) { + /* no variable found */ + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[2].data, + value[2].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } + } - p = ngx_copy(p, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[2].data, value[2].len); - *p = '\0'; + filter_data->key = cache_key; + filter_data->ref = LUA_REFNIL; + filter_data->size = filter.size; + filter_data->chunkname = NULL; ngx_str_null(&filter_data->script); @@ -359,7 +419,9 @@ ngx_http_lua_filter_set_by_lua_inline(ngx_http_request_t *r, ngx_str_t *val, rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, filter_data->script.data, filter_data->script.len, - filter_data->key, "=set_by_lua"); + &filter_data->ref, + filter_data->key, + (const char *) filter_data->chunkname); if (rc != NGX_OK) { return NGX_ERROR; } @@ -411,6 +473,7 @@ ngx_http_lua_filter_set_by_lua_file(ngx_http_request_t *r, ngx_str_t *val, /* load Lua script file (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadfile(r->connection->log, L, script_path, + &filter_data->ref, filter_data->key); if (rc != NGX_OK) { return NGX_ERROR; @@ -448,7 +511,8 @@ ngx_http_lua_rewrite_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, char * ngx_http_lua_rewrite_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - u_char *p, *chunkname; + size_t chunkname_len; + u_char *cache_key = NULL, *chunkname; ngx_str_t *value; ngx_http_lua_main_conf_t *lmcf; ngx_http_lua_loc_conf_t *llcf = conf; @@ -457,10 +521,6 @@ ngx_http_lua_rewrite_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) dd("enter"); -#if defined(nginx_version) && nginx_version >= 8042 && nginx_version <= 8053 - return "does not work with " NGINX_VER; -#endif - /* must specify a content handler */ if (cmd->post == NULL) { return NGX_CONF_ERROR; @@ -482,58 +542,154 @@ ngx_http_lua_rewrite_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (cmd->post == ngx_http_lua_rewrite_handler_inline) { chunkname = ngx_http_lua_gen_chunk_name(cf, "rewrite_by_lua", - sizeof("rewrite_by_lua") - 1); + sizeof("rewrite_by_lua") - 1, + &chunkname_len); if (chunkname == NULL) { return NGX_CONF_ERROR; } - llcf->rewrite_chunkname = chunkname; + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, "rewrite_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } /* Don't eval nginx variables for inline lua code */ - llcf->rewrite_src.value = value[1]; + llcf->rewrite_chunkname = chunkname; + + } else { + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = &llcf->rewrite_src; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (llcf->rewrite_src.lengths == NULL) { + /* no variable found */ + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } + } + } + + llcf->rewrite_src_key = cache_key; + llcf->rewrite_handler = (ngx_http_handler_pt) cmd->post; + + lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_lua_module); - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_INLINE_KEY_LEN + 1); - if (p == NULL) { + lmcf->requires_rewrite = 1; + lmcf->requires_capture_filter = 1; + + return NGX_CONF_OK; +} + + +char * +ngx_http_lua_server_rewrite_by_lua_block(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf) +{ + char *rv; + ngx_conf_t save; + save = *cf; + cf->handler = ngx_http_lua_server_rewrite_by_lua; + cf->handler_conf = conf; + + rv = ngx_http_lua_conf_lua_block_parse(cf, cmd); + + *cf = save; + + return rv; +} + + +char * +ngx_http_lua_server_rewrite_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + size_t chunkname_len; + u_char *cache_key = NULL, *chunkname; + ngx_str_t *value; + ngx_http_lua_main_conf_t *lmcf; + ngx_http_lua_srv_conf_t *lscf = conf; + + ngx_http_compile_complex_value_t ccv; + + dd("enter"); + + /* must specify a content handler */ + if (cmd->post == NULL) { + return NGX_CONF_ERROR; + } + + if (lscf->srv.server_rewrite_handler) { + return "is duplicate"; + } + + value = cf->args->elts; + + if (value[1].len == 0) { + /* Oops...Invalid location conf */ + ngx_conf_log_error(NGX_LOG_ERR, cf, 0, + "invalid location config: no runnable Lua code"); + + return NGX_CONF_ERROR; + } + + if (cmd->post == ngx_http_lua_server_rewrite_handler_inline) { + chunkname = + ngx_http_lua_gen_chunk_name(cf, "server_rewrite_by_lua", + sizeof("server_rewrite_by_lua") - 1, + &chunkname_len); + if (chunkname == NULL) { return NGX_CONF_ERROR; } - llcf->rewrite_src_key = p; + cache_key = + ngx_http_lua_gen_chunk_cache_key(cf, "server_rewrite_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } - p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; + /* Don't eval nginx variables for inline lua code */ + lscf->srv.server_rewrite_src.value = value[1]; + lscf->srv.server_rewrite_chunkname = chunkname; } else { ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; - ccv.complex_value = &llcf->rewrite_src; + ccv.complex_value = &lscf->srv.server_rewrite_src; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; } - if (llcf->rewrite_src.lengths == NULL) { + if (lscf->srv.server_rewrite_src.lengths == NULL) { /* no variable found */ - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_FILE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - - llcf->rewrite_src_key = p; - - p = ngx_copy(p, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; } } - llcf->rewrite_handler = (ngx_http_handler_pt) cmd->post; + lscf->srv.server_rewrite_src_key = cache_key; + lscf->srv.server_rewrite_handler = + (ngx_http_lua_srv_conf_handler_pt) cmd->post; lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_lua_module); - lmcf->requires_rewrite = 1; + lmcf->requires_server_rewrite = 1; lmcf->requires_capture_filter = 1; return NGX_CONF_OK; @@ -562,7 +718,8 @@ ngx_http_lua_access_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, char * ngx_http_lua_access_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - u_char *p, *chunkname; + size_t chunkname_len; + u_char *cache_key = NULL, *chunkname; ngx_str_t *value; ngx_http_lua_main_conf_t *lmcf; ngx_http_lua_loc_conf_t *llcf = conf; @@ -592,27 +749,22 @@ ngx_http_lua_access_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (cmd->post == ngx_http_lua_access_handler_inline) { chunkname = ngx_http_lua_gen_chunk_name(cf, "access_by_lua", - sizeof("access_by_lua") - 1); + sizeof("access_by_lua") - 1, + &chunkname_len); if (chunkname == NULL) { return NGX_CONF_ERROR; } - llcf->access_chunkname = chunkname; - - /* Don't eval nginx variables for inline lua code */ - - llcf->access_src.value = value[1]; - - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_INLINE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, "access_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - llcf->access_src_key = p; - - p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; + /* Don't eval nginx variables for inline lua code */ + llcf->access_src.value = value[1]; + llcf->access_chunkname = chunkname; } else { ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); @@ -626,19 +778,15 @@ ngx_http_lua_access_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (llcf->access_src.lengths == NULL) { /* no variable found */ - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_FILE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - - llcf->access_src_key = p; - - p = ngx_copy(p, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; } } + llcf->access_src_key = cache_key; llcf->access_handler = (ngx_http_handler_pt) cmd->post; lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_lua_module); @@ -672,8 +820,8 @@ ngx_http_lua_content_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, char * ngx_http_lua_content_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - u_char *p; - u_char *chunkname; + size_t chunkname_len; + u_char *cache_key = NULL, *chunkname; ngx_str_t *value; ngx_http_core_loc_conf_t *clcf; ngx_http_lua_main_conf_t *lmcf; @@ -706,29 +854,22 @@ ngx_http_lua_content_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (cmd->post == ngx_http_lua_content_handler_inline) { chunkname = ngx_http_lua_gen_chunk_name(cf, "content_by_lua", - sizeof("content_by_lua") - 1); + sizeof("content_by_lua") - 1, + &chunkname_len); if (chunkname == NULL) { return NGX_CONF_ERROR; } - llcf->content_chunkname = chunkname; - - dd("chunkname: %s", chunkname); - - /* Don't eval nginx variables for inline lua code */ - - llcf->content_src.value = value[1]; - - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_INLINE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, "content_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - llcf->content_src_key = p; - - p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; + /* Don't eval nginx variables for inline lua code */ + llcf->content_src.value = value[1]; + llcf->content_chunkname = chunkname; } else { ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); @@ -742,19 +883,15 @@ ngx_http_lua_content_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (llcf->content_src.lengths == NULL) { /* no variable found */ - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_FILE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - - llcf->content_src_key = p; - - p = ngx_copy(p, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; } } + llcf->content_src_key = cache_key; llcf->content_handler = (ngx_http_handler_pt) cmd->post; lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_lua_module); @@ -795,7 +932,8 @@ ngx_http_lua_log_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, char * ngx_http_lua_log_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - u_char *p, *chunkname; + size_t chunkname_len; + u_char *cache_key = NULL, *chunkname; ngx_str_t *value; ngx_http_lua_main_conf_t *lmcf; ngx_http_lua_loc_conf_t *llcf = conf; @@ -825,27 +963,22 @@ ngx_http_lua_log_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (cmd->post == ngx_http_lua_log_handler_inline) { chunkname = ngx_http_lua_gen_chunk_name(cf, "log_by_lua", - sizeof("log_by_lua") - 1); + sizeof("log_by_lua") - 1, + &chunkname_len); if (chunkname == NULL) { return NGX_CONF_ERROR; } - llcf->log_chunkname = chunkname; - - /* Don't eval nginx variables for inline lua code */ - - llcf->log_src.value = value[1]; - - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_INLINE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, "log_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - llcf->log_src_key = p; - - p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; + /* Don't eval nginx variables for inline lua code */ + llcf->log_src.value = value[1]; + llcf->log_chunkname = chunkname; } else { ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); @@ -859,19 +992,15 @@ ngx_http_lua_log_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (llcf->log_src.lengths == NULL) { /* no variable found */ - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_FILE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - - llcf->log_src_key = p; - - p = ngx_copy(p, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; } } + llcf->log_src_key = cache_key; llcf->log_handler = (ngx_http_handler_pt) cmd->post; lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_lua_module); @@ -905,7 +1034,8 @@ char * ngx_http_lua_header_filter_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - u_char *p; + size_t chunkname_len; + u_char *cache_key = NULL, *chunkname; ngx_str_t *value; ngx_http_lua_main_conf_t *lmcf; ngx_http_lua_loc_conf_t *llcf = conf; @@ -933,19 +1063,22 @@ ngx_http_lua_header_filter_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, } if (cmd->post == ngx_http_lua_header_filter_inline) { - /* Don't eval nginx variables for inline lua code */ - llcf->header_filter_src.value = value[1]; - - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_INLINE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, "header_filter_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - llcf->header_filter_src_key = p; + chunkname = ngx_http_lua_gen_chunk_name(cf, "header_filter_by_lua", + sizeof("header_filter_by_lua") - 1, &chunkname_len); + if (chunkname == NULL) { + return NGX_CONF_ERROR; + } - p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; + /* Don't eval nginx variables for inline lua code */ + llcf->header_filter_src.value = value[1]; + llcf->header_filter_chunkname = chunkname; } else { ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); @@ -959,19 +1092,15 @@ ngx_http_lua_header_filter_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, if (llcf->header_filter_src.lengths == NULL) { /* no variable found */ - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_FILE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - - llcf->header_filter_src_key = p; - - p = ngx_copy(p, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; } } + llcf->header_filter_src_key = cache_key; llcf->header_filter_handler = (ngx_http_handler_pt) cmd->post; lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_lua_module); @@ -1005,7 +1134,8 @@ char * ngx_http_lua_body_filter_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - u_char *p; + size_t chunkname_len; + u_char *cache_key = NULL, *chunkname; ngx_str_t *value; ngx_http_lua_main_conf_t *lmcf; ngx_http_lua_loc_conf_t *llcf = conf; @@ -1033,19 +1163,23 @@ ngx_http_lua_body_filter_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, } if (cmd->post == ngx_http_lua_body_filter_inline) { - /* Don't eval nginx variables for inline lua code */ - llcf->body_filter_src.value = value[1]; + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, "body_filter_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_INLINE_KEY_LEN + 1); - if (p == NULL) { + chunkname = ngx_http_lua_gen_chunk_name(cf, "body_filter_by_lua", + sizeof("body_filter_by_lua") - 1, &chunkname_len); + if (chunkname == NULL) { return NGX_CONF_ERROR; } - llcf->body_filter_src_key = p; - p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; + /* Don't eval nginx variables for inline lua code */ + llcf->body_filter_src.value = value[1]; + llcf->body_filter_chunkname = chunkname; } else { ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); @@ -1059,19 +1193,15 @@ ngx_http_lua_body_filter_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, if (llcf->body_filter_src.lengths == NULL) { /* no variable found */ - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_FILE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - - llcf->body_filter_src_key = p; - - p = ngx_copy(p, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; } } + llcf->body_filter_src_key = cache_key; llcf->body_filter_handler = (ngx_http_output_body_filter_pt) cmd->post; lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_lua_module); @@ -1109,6 +1239,8 @@ ngx_http_lua_init_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, u_char *name; ngx_str_t *value; ngx_http_lua_main_conf_t *lmcf = conf; + size_t chunkname_len; + u_char *chunkname; dd("enter"); @@ -1144,6 +1276,15 @@ ngx_http_lua_init_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, } else { lmcf->init_src = value[1]; + + chunkname = ngx_http_lua_gen_chunk_name(cf, "init_by_lua", + sizeof("init_by_lua") - 1, + &chunkname_len); + if (chunkname == NULL) { + return NGX_CONF_ERROR; + } + + lmcf->init_chunkname = chunkname; } return NGX_CONF_OK; @@ -1176,6 +1317,8 @@ ngx_http_lua_init_worker_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, u_char *name; ngx_str_t *value; ngx_http_lua_main_conf_t *lmcf = conf; + size_t chunkname_len; + u_char *chunkname; dd("enter"); @@ -1204,6 +1347,83 @@ ngx_http_lua_init_worker_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, } else { lmcf->init_worker_src = value[1]; + + chunkname = ngx_http_lua_gen_chunk_name(cf, "init_worker_by_lua", + sizeof("init_worker_by_lua") - 1, &chunkname_len); + if (chunkname == NULL) { + return NGX_CONF_ERROR; + } + + lmcf->init_worker_chunkname = chunkname; + } + + return NGX_CONF_OK; +} + + +char * +ngx_http_lua_exit_worker_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + char *rv; + ngx_conf_t save; + + save = *cf; + cf->handler = ngx_http_lua_exit_worker_by_lua; + cf->handler_conf = conf; + + rv = ngx_http_lua_conf_lua_block_parse(cf, cmd); + + *cf = save; + + return rv; +} + + +char * +ngx_http_lua_exit_worker_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + u_char *name; + ngx_str_t *value; + ngx_http_lua_main_conf_t *lmcf = conf; + size_t chunkname_len; + u_char *chunkname; + + /* must specify a content handler */ + if (cmd->post == NULL) { + return NGX_CONF_ERROR; + } + + if (lmcf->exit_worker_handler) { + return "is duplicate"; + } + + value = cf->args->elts; + + lmcf->exit_worker_handler = (ngx_http_lua_main_conf_handler_pt) cmd->post; + + if (cmd->post == ngx_http_lua_exit_worker_by_file) { + name = ngx_http_lua_rebase_path(cf->pool, value[1].data, + value[1].len); + if (name == NULL) { + return NGX_CONF_ERROR; + } + + lmcf->exit_worker_src.data = name; + lmcf->exit_worker_src.len = ngx_strlen(name); + + } else { + lmcf->exit_worker_src = value[1]; + + chunkname = ngx_http_lua_gen_chunk_name(cf, "exit_worker_by_lua", + sizeof("exit_worker_by_lua")- 1, + &chunkname_len); + if (chunkname == NULL) { + return NGX_CONF_ERROR; + } + + lmcf->exit_worker_chunkname = chunkname; } return NGX_CONF_OK; @@ -1216,7 +1436,7 @@ ngx_http_lua_set_by_lua_init(ngx_http_request_t *r) { lua_State *L; ngx_http_lua_ctx_t *ctx; - ngx_http_cleanup_t *cln; + ngx_pool_cleanup_t *cln; ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); if (ctx == NULL) { @@ -1231,7 +1451,7 @@ ngx_http_lua_set_by_lua_init(ngx_http_request_t *r) } if (ctx->cleanup == NULL) { - cln = ngx_http_cleanup_add(r, 0); + cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { return NGX_ERROR; } @@ -1247,11 +1467,20 @@ ngx_http_lua_set_by_lua_init(ngx_http_request_t *r) #endif -static u_char * -ngx_http_lua_gen_chunk_name(ngx_conf_t *cf, const char *tag, size_t tag_len) +u_char * +ngx_http_lua_gen_chunk_name(ngx_conf_t *cf, const char *tag, size_t tag_len, + size_t *chunkname_len) { u_char *p, *out; size_t len; + ngx_uint_t start_line; + ngx_str_t *conf_prefix; + ngx_str_t *filename; + u_char *filename_end; + const char *pre_str = ""; + ngx_uint_t reserve_len; + + ngx_http_lua_main_conf_t *lmcf; len = sizeof("=(:)") - 1 + tag_len + cf->conf_file->file.name.len + NGX_INT64_LEN + 1; @@ -1261,27 +1490,58 @@ ngx_http_lua_gen_chunk_name(ngx_conf_t *cf, const char *tag, size_t tag_len) return NULL; } - if (cf->conf_file->file.name.len) { - p = cf->conf_file->file.name.data + cf->conf_file->file.name.len; - while (--p >= cf->conf_file->file.name.data) { - if (*p == '/' || *p == '\\') { - p++; + lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_lua_module); + start_line = lmcf->directive_line > 0 + ? lmcf->directive_line : cf->conf_file->line; + p = ngx_snprintf(out, len, "%d", start_line); + reserve_len = tag_len + p - out; + + filename = &cf->conf_file->file.name; + filename_end = filename->data + filename->len; + if (filename->len > 0) { + if (filename->len >= 11) { + p = filename_end - 11; + if ((*p == '/' || *p == '\\') + && ngx_memcmp(p, "/nginx.conf", 11) == 0) + { + p++; /* now p is nginx.conf */ goto found; } } - p++; + conf_prefix = &cf->cycle->conf_prefix; + p = filename->data + conf_prefix->len; + if ((conf_prefix->len < filename->len) + && ngx_memcmp(conf_prefix->data, + filename->data, conf_prefix->len) == 0) + { + /* files in conf_prefix directory, use the relative path */ + if (filename_end - p + reserve_len > LJ_CHUNKNAME_MAX_LEN) { + p = filename_end - LJ_CHUNKNAME_MAX_LEN + reserve_len + 3; + pre_str = "..."; + } - } else { - p = cf->conf_file->file.name.data; + goto found; + } + } + + p = filename->data; + + if (filename->len + reserve_len <= LJ_CHUNKNAME_MAX_LEN) { + goto found; } + p = filename_end - LJ_CHUNKNAME_MAX_LEN + reserve_len + 3; + pre_str = "..."; + found: - ngx_snprintf(out, len, "=%*s(%*s:%d)%Z", - tag_len, tag, cf->conf_file->file.name.data - + cf->conf_file->file.name.len - p, - p, cf->conf_file->line); + + p = ngx_snprintf(out, len, "=%*s(%s%*s:%d)%Z", + tag_len, tag, pre_str, filename_end - p, + p, start_line); + + *chunkname_len = p - out - 1; /* exclude the trailing '\0' byte */ return out; } @@ -1291,6 +1551,7 @@ ngx_http_lua_gen_chunk_name(ngx_conf_t *cf, const char *tag, size_t tag_len) char * ngx_http_lua_conf_lua_block_parse(ngx_conf_t *cf, ngx_command_t *cmd) { + ngx_http_lua_main_conf_t *lmcf; ngx_http_lua_block_parser_ctx_t ctx; int level = 1; @@ -1303,7 +1564,7 @@ ngx_http_lua_conf_lua_block_parse(ngx_conf_t *cf, ngx_command_t *cmd) ngx_array_t *saved; enum { parse_block = 0, - parse_param + parse_param, } type; if (cf->conf_file->file.fd != NGX_INVALID_FILE) { @@ -1324,6 +1585,9 @@ ngx_http_lua_conf_lua_block_parse(ngx_conf_t *cf, ngx_command_t *cmd) ctx.token_len = 0; start_line = cf->conf_file->line; + lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_lua_module); + lmcf->directive_line = start_line; + dd("init start line: %d", (int) start_line); ctx.start_line = start_line; @@ -1385,6 +1649,7 @@ ngx_http_lua_conf_lua_block_parse(ngx_conf_t *cf, ngx_command_t *cmd) if (dst == NULL) { return NGX_CONF_ERROR; } + dst->len = len; dst->len--; /* skip the trailing '}' block terminator */ @@ -1392,6 +1657,7 @@ ngx_http_lua_conf_lua_block_parse(ngx_conf_t *cf, ngx_command_t *cmd) if (p == NULL) { return NGX_CONF_ERROR; } + dst->data = p; for (i = 0; i < cf->args->nelts; i++) { @@ -1440,6 +1706,8 @@ ngx_http_lua_conf_lua_block_parse(ngx_conf_t *cf, ngx_command_t *cmd) done: + lmcf->directive_line = 0; + if (rc == NGX_ERROR) { return NGX_CONF_ERROR; } @@ -1453,7 +1721,7 @@ ngx_http_lua_conf_read_lua_token(ngx_conf_t *cf, ngx_http_lua_block_parser_ctx_t *ctx) { enum { - OVEC_SIZE = 2 + OVEC_SIZE = 2, }; int i, rc; int ovec[OVEC_SIZE]; @@ -1464,12 +1732,12 @@ ngx_http_lua_conf_read_lua_token(ngx_conf_t *cf, ngx_uint_t start_line; ngx_str_t *word; ngx_buf_t *b; -#if nginx_version >= 1009002 +#if (nginx_version >= 1009002) ngx_buf_t *dump; #endif b = cf->conf_file->buffer; -#if nginx_version >= 1009002 +#if (nginx_version >= 1009002) dump = cf->conf_file->dump; #endif start = b->pos; @@ -1483,7 +1751,7 @@ ngx_http_lua_conf_read_lua_token(ngx_conf_t *cf, for ( ;; ) { if (b->pos >= b->last - || (b->last - b->pos < (b->end - b->start) / 3 + || (b->last - b->pos < (b->end - b->start) / 2 && cf->conf_file->file.offset < file_size)) { @@ -1540,7 +1808,7 @@ ngx_http_lua_conf_read_lua_token(ngx_conf_t *cf, b->last = b->start + len + n; start = b->start; -#if nginx_version >= 1009002 +#if (nginx_version >= 1009002) if (dump) { dump->last = ngx_cpymem(dump->last, b->start + len, size); } @@ -1578,7 +1846,7 @@ ngx_http_lua_conf_read_lua_token(ngx_conf_t *cf, } if (len) { - ngx_memcpy(b->start, b->pos, len); + ngx_memmove(b->start, b->pos, len); } size = (ssize_t) (file_size - cf->conf_file->file.offset); @@ -1655,7 +1923,8 @@ ngx_http_lua_conf_read_lua_token(ngx_conf_t *cf, if (q == NULL) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "Lua code block missing the closing " - "long bracket \"%*s\"", + "long bracket \"%*s\", " + "the inlined Lua code may be too long", b->pos + ovec[1] - p, p); return NGX_ERROR; } diff --git a/src/ngx_http_lua_directive.h b/src/ngx_http_lua_directive.h index 5abfe4dde2..4bec5e3094 100644 --- a/src/ngx_http_lua_directive.h +++ b/src/ngx_http_lua_directive.h @@ -17,10 +17,18 @@ char *ngx_http_lua_package_cpath(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); char *ngx_http_lua_package_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +char *ngx_http_lua_regex_cache_max_entries(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +char *ngx_http_lua_regex_match_limit(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); char *ngx_http_lua_content_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); char *ngx_http_lua_content_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +char *ngx_http_lua_server_rewrite_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +char *ngx_http_lua_server_rewrite_by_lua_block(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); char *ngx_http_lua_rewrite_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); char *ngx_http_lua_rewrite_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, @@ -49,7 +57,13 @@ char *ngx_http_lua_init_worker_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); char *ngx_http_lua_init_worker_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +char *ngx_http_lua_exit_worker_by_lua_block(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); +char *ngx_http_lua_exit_worker_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); char *ngx_http_lua_code_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +char *ngx_http_lua_load_resty_core(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); #if defined(NDK) && NDK @@ -71,7 +85,8 @@ char *ngx_http_lua_conf_lua_block_parse(ngx_conf_t *cf, ngx_command_t *cmd); char *ngx_http_lua_capture_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); - +u_char *ngx_http_lua_gen_chunk_name(ngx_conf_t *cf, const char *tag, + size_t tag_len, size_t *chunkname_len); #endif /* _NGX_HTTP_LUA_DIRECTIVE_H_INCLUDED_ */ diff --git a/src/ngx_http_lua_exception.h b/src/ngx_http_lua_exception.h index a70708a94c..93dfb8f57a 100644 --- a/src/ngx_http_lua_exception.h +++ b/src/ngx_http_lua_exception.h @@ -12,13 +12,13 @@ #include "ngx_http_lua_common.h" -#define NGX_LUA_EXCEPTION_TRY \ +#define NGX_LUA_EXCEPTION_TRY \ if (setjmp(ngx_http_lua_exception) == 0) -#define NGX_LUA_EXCEPTION_CATCH \ +#define NGX_LUA_EXCEPTION_CATCH \ else -#define NGX_LUA_EXCEPTION_THROW(x) \ +#define NGX_LUA_EXCEPTION_THROW(x) \ longjmp(ngx_http_lua_exception, (x)) diff --git a/src/ngx_http_lua_exitworkerby.c b/src/ngx_http_lua_exitworkerby.c new file mode 100644 index 0000000000..e8f65110ae --- /dev/null +++ b/src/ngx_http_lua_exitworkerby.c @@ -0,0 +1,127 @@ + +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + + +#ifndef DDEBUG +#define DDEBUG 0 +#endif +#include "ddebug.h" + + +#include "ngx_http_lua_exitworkerby.h" +#include "ngx_http_lua_util.h" + +#if (NGX_THREADS) +#include "ngx_http_lua_worker_thread.h" +#endif + + +void +ngx_http_lua_exit_worker(ngx_cycle_t *cycle) +{ + ngx_http_lua_main_conf_t *lmcf; + ngx_connection_t *c = NULL; + ngx_http_request_t *r = NULL; + ngx_http_lua_ctx_t *ctx; + ngx_http_conf_ctx_t *conf_ctx; + +#if (NGX_THREADS) + ngx_http_lua_thread_exit_process(); +#endif + + lmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_lua_module); + if (lmcf == NULL + || lmcf->exit_worker_handler == NULL + || lmcf->lua == NULL +#if !(NGX_WIN32) + || (ngx_process == NGX_PROCESS_HELPER +# ifdef HAVE_PRIVILEGED_PROCESS_PATCH + && !ngx_is_privileged_agent +# endif + ) +#endif /* NGX_WIN32 */ + ) + { + return; + } + + conf_ctx = ((ngx_http_conf_ctx_t *) cycle->conf_ctx[ngx_http_module.index]); + + c = ngx_http_lua_create_fake_connection(NULL); + if (c == NULL) { + goto failed; + } + + c->log = ngx_cycle->log; + + r = ngx_http_lua_create_fake_request(c); + if (r == NULL) { + goto failed; + } + + r->main_conf = conf_ctx->main_conf; + r->srv_conf = conf_ctx->srv_conf; + r->loc_conf = conf_ctx->loc_conf; + + ctx = ngx_http_lua_create_ctx(r); + if (ctx == NULL) { + goto failed; + } + + ctx->context = NGX_HTTP_LUA_CONTEXT_EXIT_WORKER; + ctx->cur_co_ctx = NULL; + + ngx_http_lua_set_req(lmcf->lua, r); + + (void) lmcf->exit_worker_handler(cycle->log, lmcf, lmcf->lua); + + ngx_destroy_pool(c->pool); + return; + +failed: + + if (c) { + ngx_http_lua_close_fake_connection(c); + } + + return; +} + + +ngx_int_t +ngx_http_lua_exit_worker_by_inline(ngx_log_t *log, + ngx_http_lua_main_conf_t *lmcf, lua_State *L) +{ + int status; + const char *chunkname; + + if (lmcf->exit_worker_chunkname == NULL) { + chunkname = "=exit_worker_by_lua"; + + } else { + chunkname = (const char *) lmcf->exit_worker_chunkname; + } + + status = luaL_loadbuffer(L, (char *) lmcf->exit_worker_src.data, + lmcf->exit_worker_src.len, chunkname) + || ngx_http_lua_do_call(log, L); + + return ngx_http_lua_report(log, L, status, "exit_worker_by_lua"); +} + + +ngx_int_t +ngx_http_lua_exit_worker_by_file(ngx_log_t *log, ngx_http_lua_main_conf_t *lmcf, + lua_State *L) +{ + int status; + + status = luaL_loadfile(L, (char *) lmcf->exit_worker_src.data) + || ngx_http_lua_do_call(log, L); + + return ngx_http_lua_report(log, L, status, "exit_worker_by_lua_file"); +} + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_exitworkerby.h b/src/ngx_http_lua_exitworkerby.h new file mode 100644 index 0000000000..3a4274c042 --- /dev/null +++ b/src/ngx_http_lua_exitworkerby.h @@ -0,0 +1,25 @@ + +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + + +#ifndef _NGX_HTTP_LUA_EXITWORKERBY_H_INCLUDED_ +#define _NGX_HTTP_LUA_EXITWORKERBY_H_INCLUDED_ + + +#include "ngx_http_lua_common.h" + + +ngx_int_t ngx_http_lua_exit_worker_by_inline(ngx_log_t *log, + ngx_http_lua_main_conf_t *lmcf, lua_State *L); + +ngx_int_t ngx_http_lua_exit_worker_by_file(ngx_log_t *log, + ngx_http_lua_main_conf_t *lmcf, lua_State *L); + +void ngx_http_lua_exit_worker(ngx_cycle_t *cycle); + + +#endif /* _NGX_HTTP_LUA_EXITWORKERBY_H_INCLUDED_ */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_headerfilterby.c b/src/ngx_http_lua_headerfilterby.c index b504530b90..d41c055ef5 100644 --- a/src/ngx_http_lua_headerfilterby.c +++ b/src/ngx_http_lua_headerfilterby.c @@ -13,12 +13,9 @@ #include "ngx_http_lua_exception.h" #include "ngx_http_lua_util.h" #include "ngx_http_lua_pcrefix.h" -#include "ngx_http_lua_time.h" #include "ngx_http_lua_log.h" -#include "ngx_http_lua_regex.h" #include "ngx_http_lua_cache.h" #include "ngx_http_lua_headers.h" -#include "ngx_http_lua_variable.h" #include "ngx_http_lua_string.h" #include "ngx_http_lua_misc.h" #include "ngx_http_lua_consts.h" @@ -42,9 +39,9 @@ static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static void ngx_http_lua_header_filter_by_lua_env(lua_State *L, ngx_http_request_t *r) { - /* set nginx request pointer to current lua thread's globals table */ ngx_http_lua_set_req(L, r); +#ifndef OPENRESTY_LUAJIT /** * we want to create empty environment for current script * @@ -68,6 +65,7 @@ ngx_http_lua_header_filter_by_lua_env(lua_State *L, ngx_http_request_t *r) /* }}} */ lua_setfenv(L, -2); /* set new running env for the code closure */ +#endif /* OPENRESTY_LUAJIT */ } @@ -167,12 +165,18 @@ ngx_http_lua_header_filter_inline(ngx_http_request_t *r) L = ngx_http_lua_get_lua_vm(r, NULL); + if (!llcf->enable_code_cache) { + llcf->header_filter_src_ref = LUA_REFNIL; + } + /* load Lua inline script (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, llcf->header_filter_src.value.data, llcf->header_filter_src.value.len, + &llcf->header_filter_src_ref, llcf->header_filter_src_key, - "=header_filter_by_lua"); + (const char *) + llcf->header_filter_chunkname); if (rc != NGX_OK) { return NGX_ERROR; } @@ -210,8 +214,13 @@ ngx_http_lua_header_filter_file(ngx_http_request_t *r) L = ngx_http_lua_get_lua_vm(r, NULL); + if (!llcf->enable_code_cache) { + llcf->header_filter_src_ref = LUA_REFNIL; + } + /* load Lua script file (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadfile(r->connection->log, L, script_path, + &llcf->header_filter_src_ref, llcf->header_filter_src_key); if (rc != NGX_OK) { return NGX_ERROR; @@ -230,7 +239,7 @@ ngx_http_lua_header_filter(ngx_http_request_t *r) ngx_http_lua_loc_conf_t *llcf; ngx_http_lua_ctx_t *ctx; ngx_int_t rc; - ngx_http_cleanup_t *cln; + ngx_pool_cleanup_t *cln; uint16_t old_context; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -259,7 +268,7 @@ ngx_http_lua_header_filter(ngx_http_request_t *r) } if (ctx->cleanup == NULL) { - cln = ngx_http_cleanup_add(r, 0); + cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { return NGX_ERROR; } diff --git a/src/ngx_http_lua_headers.c b/src/ngx_http_lua_headers.c index 2456fd15b5..85836a1289 100644 --- a/src/ngx_http_lua_headers.c +++ b/src/ngx_http_lua_headers.c @@ -20,17 +20,39 @@ static int ngx_http_lua_ngx_req_http_version(lua_State *L); static int ngx_http_lua_ngx_req_raw_header(lua_State *L); static int ngx_http_lua_ngx_req_header_set_helper(lua_State *L); -static int ngx_http_lua_ngx_header_get(lua_State *L); -static int ngx_http_lua_ngx_header_set(lua_State *L); -static int ngx_http_lua_ngx_req_get_headers(lua_State *L); -static int ngx_http_lua_ngx_req_header_clear(lua_State *L); -static int ngx_http_lua_ngx_req_header_set(lua_State *L); static int ngx_http_lua_ngx_resp_get_headers(lua_State *L); -#if nginx_version >= 1011011 +static int ngx_http_lua_ngx_req_header_set(lua_State *L); +#if (nginx_version >= 1011011) void ngx_http_lua_ngx_raw_header_cleanup(void *data); #endif +void +ngx_http_lua_inject_resp_header_api(lua_State *L) +{ + lua_createtable(L, 0, 1); /* .resp */ + + lua_pushcfunction(L, ngx_http_lua_ngx_resp_get_headers); + lua_setfield(L, -2, "get_headers"); + + lua_setfield(L, -2, "resp"); +} + + +void +ngx_http_lua_inject_req_header_api(lua_State *L) +{ + lua_pushcfunction(L, ngx_http_lua_ngx_req_http_version); + lua_setfield(L, -2, "http_version"); + + lua_pushcfunction(L, ngx_http_lua_ngx_req_raw_header); + lua_setfield(L, -2, "raw_header"); + + lua_pushcfunction(L, ngx_http_lua_ngx_req_header_set); + lua_setfield(L, -2, "set_header"); +} + + static int ngx_http_lua_ngx_req_http_version(lua_State *L) { @@ -62,6 +84,12 @@ ngx_http_lua_ngx_req_http_version(lua_State *L) break; #endif +#ifdef NGX_HTTP_VERSION_30 + case NGX_HTTP_VERSION_30: + lua_pushnumber(L, 3.0); + break; +#endif + default: lua_pushnil(L); break; @@ -80,7 +108,7 @@ ngx_http_lua_ngx_req_raw_header(lua_State *L) size_t size; ngx_buf_t *b, *first = NULL; ngx_int_t i, j; -#if nginx_version >= 1011011 +#if (nginx_version >= 1011011) ngx_buf_t **bb; ngx_chain_t *cl; ngx_http_lua_main_conf_t *lmcf; @@ -101,7 +129,7 @@ ngx_http_lua_ngx_req_raw_header(lua_State *L) return luaL_error(L, "no request object found"); } -#if nginx_version >= 1011011 +#if (nginx_version >= 1011011) lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); #endif @@ -113,7 +141,7 @@ ngx_http_lua_ngx_req_raw_header(lua_State *L) #if (NGX_HTTP_V2) if (mr->stream) { - return luaL_error(L, "http v2 not supported yet"); + return luaL_error(L, "http2 requests not supported yet"); } #endif @@ -121,7 +149,7 @@ ngx_http_lua_ngx_req_raw_header(lua_State *L) dd("hc->nbusy: %d", (int) hc->nbusy); if (hc->nbusy) { -#if nginx_version >= 1011011 +#if (nginx_version >= 1011011) dd("hc->busy: %p %p %p %p", hc->busy->buf->start, hc->busy->buf->pos, hc->busy->buf->last, hc->busy->buf->end); #else @@ -145,6 +173,12 @@ ngx_http_lua_ngx_req_raw_header(lua_State *L) size = 0; b = c->buffer; + if (mr->request_line.len == 0) { + /* return empty string on invalid request */ + lua_pushlstring(L, "", 0); + return 1; + } + if (mr->request_line.data[mr->request_line.len] == CR) { line_break_len = 2; @@ -163,7 +197,7 @@ ngx_http_lua_ngx_req_raw_header(lua_State *L) dd("size: %d", (int) size); if (hc->nbusy) { -#if nginx_version >= 1011011 +#if (nginx_version >= 1011011) if (hc->nbusy > lmcf->busy_buf_ptr_count) { if (lmcf->busy_buf_ptrs) { ngx_free(lmcf->busy_buf_ptrs); @@ -186,7 +220,7 @@ ngx_http_lua_ngx_req_raw_header(lua_State *L) #endif b = NULL; -#if nginx_version >= 1011011 +#if (nginx_version >= 1011011) bb = lmcf->busy_buf_ptrs; for (i = hc->nbusy; i > 0; i--) { b = bb[i - 1]; @@ -269,7 +303,7 @@ ngx_http_lua_ngx_req_raw_header(lua_State *L) if (hc->nbusy) { -#if nginx_version >= 1011011 +#if (nginx_version >= 1011011) bb = lmcf->busy_buf_ptrs; for (i = hc->nbusy - 1; i >= 0; i--) { b = bb[i]; @@ -386,109 +420,6 @@ ngx_http_lua_ngx_req_raw_header(lua_State *L) } -static int -ngx_http_lua_ngx_req_get_headers(lua_State *L) -{ - ngx_list_part_t *part; - ngx_table_elt_t *header; - ngx_http_request_t *r; - ngx_uint_t i; - int n; - int max; - int raw = 0; - int count = 0; - - n = lua_gettop(L); - - if (n >= 1) { - if (lua_isnil(L, 1)) { - max = NGX_HTTP_LUA_MAX_HEADERS; - - } else { - max = luaL_checkinteger(L, 1); - } - - if (n >= 2) { - raw = lua_toboolean(L, 2); - } - - } else { - max = NGX_HTTP_LUA_MAX_HEADERS; - } - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } - - ngx_http_lua_check_fake_request(L, r); - - part = &r->headers_in.headers.part; - count = part->nelts; - while (part->next) { - part = part->next; - count += part->nelts; - } - - if (max > 0 && count > max) { - count = max; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua exceeding request header limit %d", max); - } - - lua_createtable(L, 0, count); - - if (!raw) { - lua_pushlightuserdata(L, &ngx_http_lua_headers_metatable_key); - lua_rawget(L, LUA_REGISTRYINDEX); - lua_setmetatable(L, -2); - } - - part = &r->headers_in.headers.part; - header = part->elts; - - for (i = 0; /* void */; i++) { - - dd("stack top: %d", lua_gettop(L)); - - if (i >= part->nelts) { - if (part->next == NULL) { - break; - } - - part = part->next; - header = part->elts; - i = 0; - } - - if (raw) { - lua_pushlstring(L, (char *) header[i].key.data, header[i].key.len); - - } else { - lua_pushlstring(L, (char *) header[i].lowcase_key, - header[i].key.len); - } - - /* stack: table key */ - - lua_pushlstring(L, (char *) header[i].value.data, - header[i].value.len); /* stack: table key value */ - - ngx_http_lua_set_multi_value_table(L, -3); - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua request header: \"%V: %V\"", - &header[i].key, &header[i].value); - - if (--count == 0) { - return 1; - } - } - - return 1; -} - - static int ngx_http_lua_ngx_resp_get_headers(lua_State *L) { @@ -496,7 +427,6 @@ ngx_http_lua_ngx_resp_get_headers(lua_State *L) ngx_table_elt_t *header; ngx_http_request_t *r; ngx_http_lua_ctx_t *ctx; - ngx_int_t rc; u_char *lowcase_key = NULL; size_t lowcase_key_sz = 0; ngx_uint_t i; @@ -504,6 +434,10 @@ ngx_http_lua_ngx_resp_get_headers(lua_State *L) int max; int raw = 0; int count = 0; + int truncated = 0; + int extra = 0; + u_char *p = NULL; + size_t len = 0; n = lua_gettop(L); @@ -533,42 +467,27 @@ ngx_http_lua_ngx_resp_get_headers(lua_State *L) return luaL_error(L, "no ctx found"); } - if (!ctx->headers_set) { - rc = ngx_http_lua_set_content_type(r); - if (rc != NGX_OK) { - return luaL_error(L, - "failed to set default content type: %d", - (int) rc); - } - - ctx->headers_set = 1; - } - ngx_http_lua_check_fake_request(L, r); part = &r->headers_out.headers.part; count = part->nelts; - while (part->next) { + while (part->next != NULL) { part = part->next; count += part->nelts; } - if (max > 0 && count > max) { - count = max; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua exceeding request header limit %d", max); - } - - lua_createtable(L, 0, count); + lua_createtable(L, 0, count + 2); if (!raw) { - lua_pushlightuserdata(L, &ngx_http_lua_headers_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + headers_metatable_key)); lua_rawget(L, LUA_REGISTRYINDEX); lua_setmetatable(L, -2); } #if 1 if (r->headers_out.content_type.len) { + extra++; lua_pushliteral(L, "content-type"); lua_pushlstring(L, (char *) r->headers_out.content_type.data, r->headers_out.content_type.len); @@ -578,11 +497,27 @@ ngx_http_lua_ngx_resp_get_headers(lua_State *L) if (r->headers_out.content_length == NULL && r->headers_out.content_length_n >= 0) { + extra++; lua_pushliteral(L, "content-length"); - lua_pushfstring(L, "%d", (int) r->headers_out.content_length_n); + if (r->headers_out.content_length_n > NGX_MAX_INT32_VALUE) { + p = ngx_palloc(r->pool, NGX_OFF_T_LEN); + if (p == NULL) { + return luaL_error(L, "no memory"); + } + + len = ngx_snprintf(p, NGX_OFF_T_LEN, "%O", + r->headers_out.content_length_n) - p; + + lua_pushlstring(L, (char *) p, len); + + } else { + lua_pushfstring(L, "%d", (int) r->headers_out.content_length_n); + } + lua_rawset(L, -3); } + extra++; lua_pushliteral(L, "connection"); if (r->headers_out.status == NGX_HTTP_SWITCHING_PROTOCOLS) { lua_pushliteral(L, "upgrade"); @@ -593,15 +528,25 @@ ngx_http_lua_ngx_resp_get_headers(lua_State *L) } else { lua_pushliteral(L, "close"); } + lua_rawset(L, -3); if (r->chunked) { + extra++; lua_pushliteral(L, "transfer-encoding"); lua_pushliteral(L, "chunked"); lua_rawset(L, -3); } #endif + if (max > 0 && count + extra > max) { + truncated = 1; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua exceeding response header limit %d > %d", + count + extra, max); + count = max - extra; + } + part = &r->headers_out.headers.part; header = part->elts; @@ -654,225 +599,17 @@ ngx_http_lua_ngx_resp_get_headers(lua_State *L) "lua response header: \"%V: %V\"", &header[i].key, &header[i].value); - if (--count == 0) { - return 1; - } - } - - return 1; -} - - -static int -ngx_http_lua_ngx_header_get(lua_State *L) -{ - ngx_http_request_t *r; - u_char *p, c; - ngx_str_t key; - ngx_uint_t i; - size_t len; - ngx_http_lua_loc_conf_t *llcf; - ngx_http_lua_ctx_t *ctx; - ngx_int_t rc; - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } - - ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); - if (ctx == NULL) { - return luaL_error(L, "no ctx found"); - } - - ngx_http_lua_check_fake_request(L, r); - - /* we skip the first argument that is the table */ - p = (u_char *) luaL_checklstring(L, 2, &len); - - dd("key: %.*s, len %d", (int) len, p, (int) len); - - llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); - if (llcf->transform_underscores_in_resp_headers - && memchr(p, '_', len) != NULL) - { - key.data = (u_char *) lua_newuserdata(L, len); - if (key.data == NULL) { - return luaL_error(L, "no memory"); - } - - /* replace "_" with "-" */ - for (i = 0; i < len; i++) { - c = p[i]; - if (c == '_') { - c = '-'; - } - key.data[i] = c; - } - - } else { - key.data = p; - } - - key.len = len; - - if (!ctx->headers_set) { - rc = ngx_http_lua_set_content_type(r); - if (rc != NGX_OK) { - return luaL_error(L, - "failed to set default content type: %d", - (int) rc); - } - - ctx->headers_set = 1; - } - - return ngx_http_lua_get_output_header(L, r, &key); -} - - -static int -ngx_http_lua_ngx_header_set(lua_State *L) -{ - ngx_http_request_t *r; - u_char *p; - ngx_str_t key; - ngx_str_t value; - ngx_uint_t i; - size_t len; - ngx_http_lua_ctx_t *ctx; - ngx_int_t rc; - ngx_uint_t n; - ngx_http_lua_loc_conf_t *llcf; - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } - - ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); - if (ctx == NULL) { - return luaL_error(L, "no ctx"); - } - - ngx_http_lua_check_fake_request(L, r); - - if (r->header_sent || ctx->header_sent) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "attempt to " - "set ngx.header.HEADER after sending out " - "response headers"); - return 0; - } - - /* we skip the first argument that is the table */ - p = (u_char *) luaL_checklstring(L, 2, &len); - - dd("key: %.*s, len %d", (int) len, p, (int) len); - - key.data = ngx_palloc(r->pool, len + 1); - if (key.data == NULL) { - return luaL_error(L, "no memory"); - } - - ngx_memcpy(key.data, p, len); - key.data[len] = '\0'; - key.len = len; - - llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); - - if (llcf->transform_underscores_in_resp_headers) { - /* replace "_" with "-" */ - p = key.data; - for (i = 0; i < len; i++) { - if (p[i] == '_') { - p[i] = '-'; - } - } - } - - if (!ctx->headers_set) { - rc = ngx_http_lua_set_content_type(r); - if (rc != NGX_OK) { - return luaL_error(L, - "failed to set default content type: %d", - (int) rc); - } - - ctx->headers_set = 1; - } - - if (lua_type(L, 3) == LUA_TNIL) { - ngx_str_null(&value); - - } else if (lua_type(L, 3) == LUA_TTABLE) { - n = lua_objlen(L, 3); - if (n == 0) { - ngx_str_null(&value); - - } else { - for (i = 1; i <= n; i++) { - dd("header value table index %d", (int) i); - - lua_rawgeti(L, 3, i); - p = (u_char *) luaL_checklstring(L, -1, &len); - - value.data = ngx_palloc(r->pool, len); - if (value.data == NULL) { - return luaL_error(L, "no memory"); - } - - ngx_memcpy(value.data, p, len); - value.len = len; - - rc = ngx_http_lua_set_output_header(r, key, value, - i == 1 /* override */); - - if (rc == NGX_ERROR) { - return luaL_error(L, - "failed to set header %s (error: %d)", - key.data, (int) rc); - } - } - - return 0; - } - - } else { - p = (u_char *) luaL_checklstring(L, 3, &len); - value.data = ngx_palloc(r->pool, len); - if (value.data == NULL) { - return luaL_error(L, "no memory"); + if (--count <= 0) { + break; } + } /* for */ - ngx_memcpy(value.data, p, len); - value.len = len; - } - - dd("key: %.*s, value: %.*s", - (int) key.len, key.data, (int) value.len, value.data); - - rc = ngx_http_lua_set_output_header(r, key, value, 1 /* override */); - - if (rc == NGX_ERROR) { - return luaL_error(L, "failed to set header %s (error: %d)", - key.data, (int) rc); - } - - return 0; -} - - -static int -ngx_http_lua_ngx_req_header_clear(lua_State *L) -{ - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one arguments, but seen %d", - lua_gettop(L)); + if (truncated) { + lua_pushliteral(L, "truncated"); + return 2; } - lua_pushnil(L); - - return ngx_http_lua_ngx_req_header_set_helper(L); + return 1; } @@ -952,7 +689,7 @@ ngx_http_lua_ngx_req_header_set_helper(lua_State *L) p = (u_char *) luaL_checklstring(L, -1, &len); /* - * we also copy the trailling '\0' char here because nginx + * we also copy the trailing '\0' char here because nginx * header values must be null-terminated * */ @@ -980,7 +717,7 @@ ngx_http_lua_ngx_req_header_set_helper(lua_State *L) } else { /* - * we also copy the trailling '\0' char here because nginx + * we also copy the trailing '\0' char here because nginx * header values must be null-terminated * */ @@ -1008,49 +745,6 @@ ngx_http_lua_ngx_req_header_set_helper(lua_State *L) } -void -ngx_http_lua_inject_resp_header_api(lua_State *L) -{ - lua_newtable(L); /* .header */ - - lua_createtable(L, 0, 2); /* metatable for .header */ - lua_pushcfunction(L, ngx_http_lua_ngx_header_get); - lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, ngx_http_lua_ngx_header_set); - lua_setfield(L, -2, "__newindex"); - lua_setmetatable(L, -2); - - lua_setfield(L, -2, "header"); - - lua_createtable(L, 0, 1); /* .resp */ - - lua_pushcfunction(L, ngx_http_lua_ngx_resp_get_headers); - lua_setfield(L, -2, "get_headers"); - - lua_setfield(L, -2, "resp"); -} - - -void -ngx_http_lua_inject_req_header_api(lua_State *L) -{ - lua_pushcfunction(L, ngx_http_lua_ngx_req_http_version); - lua_setfield(L, -2, "http_version"); - - lua_pushcfunction(L, ngx_http_lua_ngx_req_raw_header); - lua_setfield(L, -2, "raw_header"); - - lua_pushcfunction(L, ngx_http_lua_ngx_req_header_clear); - lua_setfield(L, -2, "clear_header"); - - lua_pushcfunction(L, ngx_http_lua_ngx_req_header_set); - lua_setfield(L, -2, "set_header"); - - lua_pushcfunction(L, ngx_http_lua_ngx_req_get_headers); - lua_setfield(L, -2, "get_headers"); -} - - void ngx_http_lua_create_headers_metatable(ngx_log_t *log, lua_State *L) { @@ -1060,7 +754,8 @@ ngx_http_lua_create_headers_metatable(ngx_log_t *log, lua_State *L) "local new_key = string.gsub(string.lower(key), '_', '-')\n" "if new_key ~= key then return tb[new_key] else return nil end"; - lua_pushlightuserdata(L, &ngx_http_lua_headers_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + headers_metatable_key)); /* metatable for ngx.req.get_headers(_, true) and * ngx.resp.get_headers(_, true) */ @@ -1081,32 +776,85 @@ ngx_http_lua_create_headers_metatable(ngx_log_t *log, lua_State *L) } -#ifndef NGX_LUA_NO_FFI_API int -ngx_http_lua_ffi_req_get_headers_count(ngx_http_request_t *r, int max) +ngx_http_lua_ffi_req_get_headers_count(ngx_http_request_t *r, int max, + int *truncated) { int count; ngx_list_part_t *part; +#if (NGX_HTTP_V3) + int has_host = 0; + ngx_uint_t i; + ngx_table_elt_t *header; +#endif if (r->connection->fd == (ngx_socket_t) -1) { return NGX_HTTP_LUA_FFI_BAD_CONTEXT; } + *truncated = 0; + if (max < 0) { max = NGX_HTTP_LUA_MAX_HEADERS; } part = &r->headers_in.headers.part; + +#if (NGX_HTTP_V3) + count = 0; + header = part->elts; + + if (r->http_version == NGX_HTTP_VERSION_30 + && r->headers_in.server.data != NULL) + { + has_host = 1; + count++; + } + + if (has_host == 1) { + for (i = 0; /* void */; i++) { + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].key.len == 4 + && ngx_strncasecmp(header[i].key.data, + (u_char *) "host", 4) == 0) + { + continue; + } + + count++; + } + + } else { + count = part->nelts; + while (part->next != NULL) { + part = part->next; + count += part->nelts; + } + } +#else count = part->nelts; - while (part->next) { + while (part->next != NULL) { part = part->next; count += part->nelts; } +#endif if (max > 0 && count > max) { + *truncated = 1; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua exceeding request header limit %d > %d", count, + max); count = max; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua exceeding request header limit %d", max); } return count; @@ -1121,12 +869,29 @@ ngx_http_lua_ffi_req_get_headers(ngx_http_request_t *r, ngx_uint_t i; ngx_list_part_t *part; ngx_table_elt_t *header; +#if (NGX_HTTP_V3) + int has_host = 0; +#endif if (count <= 0) { return NGX_OK; } n = 0; + +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30 + && r->headers_in.server.data != NULL) + { + out[n].key.data = (u_char *) "host"; + out[n].key.len = sizeof("host") - 1; + out[n].value.len = r->headers_in.server.len; + out[n].value.data = r->headers_in.server.data; + has_host = 1; + ++n; + } +#endif + part = &r->headers_in.headers.part; header = part->elts; @@ -1142,6 +907,14 @@ ngx_http_lua_ffi_req_get_headers(ngx_http_request_t *r, i = 0; } +#if (NGX_HTTP_V3) + if (has_host == 1 && header[i].key.len == 4 + && ngx_strncasecmp(header[i].key.data, (u_char *) "host", 4) == 0) + { + continue; + } +#endif + if (raw) { out[n].key.data = header[i].key.data; out[n].key.len = (int) header[i].key.len; @@ -1166,7 +939,8 @@ ngx_http_lua_ffi_req_get_headers(ngx_http_request_t *r, int ngx_http_lua_ffi_set_resp_header(ngx_http_request_t *r, const u_char *key_data, size_t key_len, int is_nil, const u_char *sval, size_t sval_len, - ngx_http_lua_ffi_str_t *mvals, size_t mvals_len, char **errmsg) + ngx_http_lua_ffi_str_t *mvals, size_t mvals_len, int override, + char **errmsg) { u_char *p; ngx_str_t value, key; @@ -1213,15 +987,7 @@ ngx_http_lua_ffi_set_resp_header(ngx_http_request_t *r, const u_char *key_data, } } - if (!ctx->headers_set) { - rc = ngx_http_lua_set_content_type(r); - if (rc != NGX_OK) { - *errmsg = "failed to set default content type"; - return NGX_ERROR; - } - - ctx->headers_set = 1; - } + ctx->headers_set = 1; if (is_nil) { value.data = NULL; @@ -1248,8 +1014,8 @@ ngx_http_lua_ffi_set_resp_header(ngx_http_request_t *r, const u_char *key_data, ngx_memcpy(value.data, p, len); value.len = len; - rc = ngx_http_lua_set_output_header(r, key, value, - i == 0 /* override */); + rc = ngx_http_lua_set_output_header(r, ctx, key, value, + override && i == 0); if (rc == NGX_ERROR) { *errmsg = "failed to set header"; @@ -1274,7 +1040,7 @@ ngx_http_lua_ffi_set_resp_header(ngx_http_request_t *r, const u_char *key_data, dd("key: %.*s, value: %.*s", (int) key.len, key.data, (int) value.len, value.data); - rc = ngx_http_lua_set_output_header(r, key, value, 1 /* override */); + rc = ngx_http_lua_set_output_header(r, ctx, key, value, override); if (rc == NGX_ERROR) { *errmsg = "failed to set header"; @@ -1291,11 +1057,15 @@ ngx_http_lua_ffi_set_resp_header(ngx_http_request_t *r, const u_char *key_data, int -ngx_http_lua_ffi_req_header_set_single_value(ngx_http_request_t *r, - const u_char *key, size_t key_len, const u_char *value, size_t value_len) +ngx_http_lua_ffi_req_set_header(ngx_http_request_t *r, const u_char *key, + size_t key_len, const u_char *value, size_t value_len, + ngx_http_lua_ffi_str_t *mvals, size_t mvals_len, int override, + char **errmsg) { - ngx_str_t k; - ngx_str_t v; + u_char *p; + size_t len; + ngx_uint_t i; + ngx_str_t k, v; if (r->connection->fd == (ngx_socket_t) -1) { /* fake request */ return NGX_HTTP_LUA_FFI_BAD_CONTEXT; @@ -1307,45 +1077,83 @@ ngx_http_lua_ffi_req_header_set_single_value(ngx_http_request_t *r, k.data = ngx_palloc(r->pool, key_len + 1); if (k.data == NULL) { - return NGX_ERROR; + goto nomem; } + ngx_memcpy(k.data, key, key_len); k.data[key_len] = '\0'; - k.len = key_len; - if (value_len == 0) { + if (mvals) { + if (mvals_len > 0) { + for (i = 0; i < mvals_len; i++) { + p = mvals[i].data; + len = mvals[i].len; + + v.data = ngx_palloc(r->pool, len + 1); + if (v.data == NULL) { + goto nomem; + } + + ngx_memcpy(v.data, p, len); + v.data[len] = '\0'; + v.len = len; + + if (ngx_http_lua_set_input_header(r, k, v, override && i == 0) + != NGX_OK) + { + goto failed; + } + } + + return NGX_OK; + } + v.data = NULL; v.len = 0; - } else { + } else if (value) { v.data = ngx_palloc(r->pool, value_len + 1); if (v.data == NULL) { - return NGX_ERROR; + goto nomem; } + ngx_memcpy(v.data, value, value_len); v.data[value_len] = '\0'; - } + v.len = value_len; - v.len = value_len; + } else { + v.data = NULL; + v.len = 0; + } - if (ngx_http_lua_set_input_header(r, k, v, 1 /* override */) - != NGX_OK) - { - return NGX_ERROR; + if (ngx_http_lua_set_input_header(r, k, v, override) != NGX_OK) { + goto failed; } return NGX_OK; + +nomem: + + *errmsg = "no memory"; + return NGX_ERROR; + +failed: + + *errmsg = "failed to set header"; + return NGX_ERROR; } int ngx_http_lua_ffi_get_resp_header(ngx_http_request_t *r, const u_char *key, size_t key_len, - u_char *key_buf, ngx_http_lua_ffi_str_t *values, int max_nvalues) + u_char *key_buf, ngx_http_lua_ffi_str_t *values, int max_nvalues, + char **errmsg) { int found; u_char c, *p; + time_t last_modified; ngx_uint_t i; ngx_table_elt_t *h; ngx_list_part_t *part; @@ -1359,19 +1167,10 @@ ngx_http_lua_ffi_get_resp_header(ngx_http_request_t *r, ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); if (ctx == NULL) { - /* *errmsg = "no ctx found"; */ + *errmsg = "no ctx found"; return NGX_ERROR; } - if (!ctx->headers_set) { - if (ngx_http_lua_set_content_type(r) != NGX_OK) { - /* *errmsg = "failed to set default content type"; */ - return NGX_ERROR; - } - - ctx->headers_set = 1; - } - llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); if (llcf->transform_underscores_in_resp_headers && memchr(key, '_', key_len) != NULL) @@ -1397,6 +1196,7 @@ ngx_http_lua_ffi_get_resp_header(ngx_http_request_t *r, { p = ngx_palloc(r->pool, NGX_OFF_T_LEN); if (p == NULL) { + *errmsg = "no memory"; return NGX_ERROR; } @@ -1410,8 +1210,8 @@ ngx_http_lua_ffi_get_resp_header(ngx_http_request_t *r, break; case 12: - if (r->headers_out.content_type.len - && ngx_strncasecmp(key_buf, (u_char *) "Content-Type", 12) == 0) + if (ngx_strncasecmp(key_buf, (u_char *) "Content-Type", 12) == 0 + && r->headers_out.content_type.len) { values[0].data = r->headers_out.content_type.data; values[0].len = r->headers_out.content_type.len; @@ -1420,6 +1220,28 @@ ngx_http_lua_ffi_get_resp_header(ngx_http_request_t *r, break; + case 13: + if (ngx_strncasecmp(key_buf, (u_char *) "Last-Modified", 13) == 0) { + last_modified = r->headers_out.last_modified_time; + if (last_modified >= 0) { + p = ngx_palloc(r->pool, + sizeof("Mon, 28 Sep 1970 06:00:00 GMT")); + if (p == NULL) { + *errmsg = "no memory"; + return NGX_ERROR; + } + + values[0].data = p; + values[0].len = ngx_http_time(p, last_modified) - p; + + return 1; + } + + return 0; + } + + break; + default: break; } @@ -1479,10 +1301,9 @@ ngx_http_lua_ffi_get_resp_header(ngx_http_request_t *r, return found; } -#endif /* NGX_LUA_NO_FFI_API */ -#if nginx_version >= 1011011 +#if (nginx_version >= 1011011) void ngx_http_lua_ngx_raw_header_cleanup(void *data) { @@ -1498,4 +1319,18 @@ ngx_http_lua_ngx_raw_header_cleanup(void *data) #endif +#if (NGX_DARWIN) +int +ngx_http_lua_ffi_set_resp_header_macos(ngx_http_lua_set_resp_header_params_t *p) +{ + return ngx_http_lua_ffi_set_resp_header(p->r, (const u_char *) p->key_data, + p->key_len, p->is_nil, + (const u_char *) p->sval, + p->sval_len, + p->mvals, p->mvals_len, + p->override, p->errmsg); +} +#endif + + /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_headers.h b/src/ngx_http_lua_headers.h index ee4d21c1ae..679136169a 100644 --- a/src/ngx_http_lua_headers.h +++ b/src/ngx_http_lua_headers.h @@ -15,7 +15,7 @@ void ngx_http_lua_inject_resp_header_api(lua_State *L); void ngx_http_lua_inject_req_header_api(lua_State *L); void ngx_http_lua_create_headers_metatable(ngx_log_t *log, lua_State *L); -#if nginx_version >= 1011011 +#if (nginx_version >= 1011011) void ngx_http_lua_ngx_raw_header_cleanup(void *data); #endif diff --git a/src/ngx_http_lua_headers_in.c b/src/ngx_http_lua_headers_in.c index 4852b2f692..e756700b06 100644 --- a/src/ngx_http_lua_headers_in.c +++ b/src/ngx_http_lua_headers_in.c @@ -56,13 +56,10 @@ static ngx_http_lua_set_header_t ngx_http_lua_set_handlers[] = { offsetof(ngx_http_headers_in_t, if_modified_since), ngx_http_set_builtin_header }, -#if defined(nginx_version) && nginx_version >= 9002 { ngx_string("If-Unmodified-Since"), offsetof(ngx_http_headers_in_t, if_unmodified_since), ngx_http_set_builtin_header }, -#endif -#if defined(nginx_version) && nginx_version >= 1003003 { ngx_string("If-Match"), offsetof(ngx_http_headers_in_t, if_match), ngx_http_set_builtin_header }, @@ -70,7 +67,6 @@ static ngx_http_lua_set_header_t ngx_http_lua_set_handlers[] = { { ngx_string("If-None-Match"), offsetof(ngx_http_headers_in_t, if_none_match), ngx_http_set_builtin_header }, -#endif { ngx_string("User-Agent"), offsetof(ngx_http_headers_in_t, user_agent), @@ -104,11 +100,9 @@ static ngx_http_lua_set_header_t ngx_http_lua_set_handlers[] = { offsetof(ngx_http_headers_in_t, expect), ngx_http_set_builtin_header }, -#if defined(nginx_version) && nginx_version >= 1003013 { ngx_string("Upgrade"), offsetof(ngx_http_headers_in_t, upgrade), ngx_http_set_builtin_header }, -#endif #if (NGX_HTTP_GZIP) { ngx_string("Accept-Encoding"), @@ -158,9 +152,15 @@ static ngx_http_lua_set_header_t ngx_http_lua_set_handlers[] = { ngx_http_set_builtin_header }, #endif +#if defined(nginx_version) && nginx_version >= 1023000 + { ngx_string("Cookie"), + offsetof(ngx_http_headers_in_t, cookie), + ngx_http_set_builtin_multi_header }, +#else { ngx_string("Cookie"), offsetof(ngx_http_headers_in_t, cookies), ngx_http_set_builtin_multi_header }, +#endif { ngx_null_string, 0, ngx_http_set_header } }; @@ -280,6 +280,9 @@ ngx_http_set_header_helper(ngx_http_request_t *r, ngx_http_lua_header_val_t *hv, h->key = hv->key; h->value = *value; +#if defined(nginx_version) && nginx_version >= 1023000 + h->next = NULL; +#endif h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); if (h->lowcase_key == NULL) { @@ -346,7 +349,7 @@ ngx_http_lua_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) enum { sw_usual = 0, sw_literal, - sw_rest + sw_rest, } state; dot_pos = host->len; @@ -365,6 +368,7 @@ ngx_http_lua_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) if (dot_pos == i - 1) { return NGX_DECLINED; } + dot_pos = i; break; @@ -432,10 +436,14 @@ static ngx_int_t ngx_http_set_host_header(ngx_http_request_t *r, ngx_http_lua_header_val_t *hv, ngx_str_t *value) { - ngx_str_t host; + ngx_str_t host; + ngx_http_lua_main_conf_t *lmcf; + ngx_http_variable_value_t *var; dd("server new value len: %d", (int) value->len); + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); + if (value->len) { host= *value; @@ -449,6 +457,10 @@ ngx_http_set_host_header(ngx_http_request_t *r, ngx_http_lua_header_val_t *hv, r->headers_in.server = *value; } + var = &r->variables[lmcf->host_var_index]; + var->valid = 0; + var->not_found = 0; + return ngx_http_set_builtin_header(r, hv, value); } @@ -560,7 +572,7 @@ ngx_http_set_content_length_header(ngx_http_request_t *r, return ngx_http_clear_content_length_header(r, hv, value); } - len = ngx_atosz(value->data, value->len); + len = ngx_atoof(value->data, value->len); if (len == NGX_ERROR) { return NGX_ERROR; } @@ -577,6 +589,47 @@ static ngx_int_t ngx_http_set_builtin_multi_header(ngx_http_request_t *r, ngx_http_lua_header_val_t *hv, ngx_str_t *value) { +#if defined(nginx_version) && nginx_version >= 1023000 + ngx_table_elt_t **headers, **ph, *h; + + headers = (ngx_table_elt_t **) ((char *) &r->headers_in + hv->offset); + + if (!hv->no_override && *headers != NULL) { +#if defined(DDEBUG) && (DDEBUG) + int nelts = 0; + + for (h = *headers; h; h = h->next) { + nelts++; + } + + dd("clear multi-value headers: %d", nelts); +#endif + + *headers = NULL; + } + + if (ngx_http_set_header_helper(r, hv, value, &h) == NGX_ERROR) { + return NGX_ERROR; + } + + if (value->len == 0) { + return NGX_OK; + } + + dd("new multi-value header: %p", h); + + if (*headers) { + for (ph = headers; *ph; ph = &(*ph)->next) { /* void */ } + *ph = h; + + } else { + *headers = h; + } + + h->next = NULL; + + return NGX_OK; +#else ngx_array_t *headers; ngx_table_elt_t **v, *h; @@ -623,6 +676,7 @@ ngx_http_set_builtin_multi_header(ngx_http_request_t *r, *v = h; return NGX_OK; +#endif } @@ -651,12 +705,28 @@ ngx_http_lua_set_input_header(ngx_http_request_t *r, ngx_str_t key, { ngx_http_lua_header_val_t hv; ngx_http_lua_set_header_t *handlers = ngx_http_lua_set_handlers; - + ngx_int_t rc; ngx_uint_t i; dd("set header value: %.*s", (int) value.len, value.data); - hv.hash = ngx_hash_key_lc(key.data, key.len); + rc = ngx_http_lua_copy_escaped_header(r, &key, 1); + if (rc != NGX_OK) { + return NGX_ERROR; + } + + rc = ngx_http_lua_copy_escaped_header(r, &value, 0); + if (rc != NGX_OK) { + return NGX_ERROR; + } + + if (value.len > 0) { + hv.hash = ngx_hash_key_lc(key.data, key.len); + + } else { + hv.hash = 0; + } + hv.key = key; hv.offset = 0; @@ -742,6 +812,7 @@ ngx_http_lua_rm_header_helper(ngx_list_t *l, ngx_list_part_t *cur, if (part->next == NULL) { return NGX_ERROR; } + part = part->next; } @@ -786,6 +857,7 @@ ngx_http_lua_rm_header_helper(ngx_list_t *l, ngx_list_part_t *cur, if (part->next == NULL) { return NGX_ERROR; } + part = part->next; } diff --git a/src/ngx_http_lua_headers_out.c b/src/ngx_http_lua_headers_out.c index b908eae05a..c51146a3fc 100644 --- a/src/ngx_http_lua_headers_out.c +++ b/src/ngx_http_lua_headers_out.c @@ -106,6 +106,12 @@ static ngx_http_lua_set_header_t ngx_http_lua_set_handlers[] = { offsetof(ngx_http_headers_out_t, cache_control), ngx_http_set_builtin_multi_header }, +#if (nginx_version >= 1013009) + { ngx_string("Link"), + offsetof(ngx_http_headers_out_t, link), + ngx_http_set_builtin_multi_header }, +#endif + { ngx_null_string, 0, ngx_http_set_header } }; @@ -223,6 +229,9 @@ ngx_http_set_header_helper(ngx_http_request_t *r, ngx_http_lua_header_val_t *hv, h->key = hv->key; h->value = *value; +#if defined(nginx_version) && nginx_version >= 1023000 + h->next = NULL; +#endif h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); if (h->lowcase_key == NULL) { @@ -305,6 +314,69 @@ static ngx_int_t ngx_http_set_builtin_multi_header(ngx_http_request_t *r, ngx_http_lua_header_val_t *hv, ngx_str_t *value) { +#if defined(nginx_version) && nginx_version >= 1023000 + ngx_table_elt_t **headers, *h, *ho, **ph; + + headers = (ngx_table_elt_t **) ((char *) &r->headers_out + hv->offset); + + if (hv->no_override) { + for (h = *headers; h; h = h->next) { + if (!h->hash) { + h->value = *value; + h->hash = hv->hash; + return NGX_OK; + } + } + + goto create; + } + + /* override old values (if any) */ + + if (*headers) { + for (h = (*headers)->next; h; h = h->next) { + h->hash = 0; + h->value.len = 0; + } + + h = *headers; + + h->value = *value; + + if (value->len == 0) { + h->hash = 0; + + } else { + h->hash = hv->hash; + } + + return NGX_OK; + } + +create: + + for (ph = headers; *ph; ph = &(*ph)->next) { /* void */ } + + ho = ngx_list_push(&r->headers_out.headers); + if (ho == NULL) { + return NGX_ERROR; + } + + ho->value = *value; + + if (value->len == 0) { + ho->hash = 0; + + } else { + ho->hash = hv->hash; + } + + ho->key = hv->key; + ho->next = NULL; + *ph = ho; + + return NGX_OK; +#else ngx_array_t *pa; ngx_table_elt_t *ho, **ph; ngx_uint_t i; @@ -378,6 +450,7 @@ ngx_http_set_builtin_multi_header(ngx_http_request_t *r, *ph = ho; return NGX_OK; +#endif } @@ -444,7 +517,7 @@ ngx_http_set_content_length_header(ngx_http_request_t *r, return ngx_http_clear_content_length_header(r, hv, value); } - len = ngx_atosz(value->data, value->len); + len = ngx_atoof(value->data, value->len); if (len == NGX_ERROR) { return NGX_ERROR; } @@ -476,59 +549,50 @@ ngx_http_clear_builtin_header(ngx_http_request_t *r, ngx_int_t -ngx_http_lua_set_output_header(ngx_http_request_t *r, ngx_str_t key, - ngx_str_t value, unsigned override) +ngx_http_lua_set_output_header(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx, + ngx_str_t key, ngx_str_t value, unsigned override) { ngx_http_lua_header_val_t hv; - ngx_http_lua_set_header_t *handlers = ngx_http_lua_set_handlers; - ngx_uint_t i; + ngx_http_lua_main_conf_t *lmcf; + ngx_http_lua_set_header_t *lsh; + ngx_hash_t *hash; dd("set header value: %.*s", (int) value.len, value.data); + if (ngx_http_lua_copy_escaped_header(r, &key, 1) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_http_lua_copy_escaped_header(r, &value, 0) != NGX_OK) { + return NGX_ERROR; + } + hv.hash = ngx_hash_key_lc(key.data, key.len); hv.key = key; hv.offset = 0; hv.no_override = !override; - hv.handler = NULL; - - for (i = 0; handlers[i].name.len; i++) { - if (hv.key.len != handlers[i].name.len - || ngx_strncasecmp(hv.key.data, handlers[i].name.data, - handlers[i].name.len) != 0) - { - dd("hv key comparison: %s <> %s", handlers[i].name.data, - hv.key.data); - - continue; + hv.handler = ngx_http_set_header; + + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); + hash = &lmcf->builtin_headers_out; + lsh = ngx_http_lua_hash_find_lc(hash, hv.hash, hv.key.data, hv.key.len); + if (lsh) { + dd("Matched handler: %s %s", lsh->name.data, hv.key.data); + hv.offset = lsh->offset; + hv.handler = lsh->handler; + if (hv.handler == ngx_http_set_content_type_header) { + ctx->mime_set = 1; } - - dd("Matched handler: %s %s", handlers[i].name.data, hv.key.data); - - hv.offset = handlers[i].offset; - hv.handler = handlers[i].handler; - - break; } - if (handlers[i].name.len == 0 && handlers[i].handler) { - hv.offset = handlers[i].offset; - hv.handler = handlers[i].handler; - } - -#if 1 - if (hv.handler == NULL) { - return NGX_ERROR; - } -#endif - return hv.handler(r, &hv, &value); } int ngx_http_lua_get_output_header(lua_State *L, ngx_http_request_t *r, - ngx_str_t *key) + ngx_http_lua_ctx_t *ctx, ngx_str_t *key) { ngx_table_elt_t *h; ngx_list_part_t *part; @@ -550,8 +614,8 @@ ngx_http_lua_get_output_header(lua_State *L, ngx_http_request_t *r, break; case 12: - if (r->headers_out.content_type.len - && ngx_strncasecmp(key->data, (u_char *) "Content-Type", 12) == 0) + if (ngx_strncasecmp(key->data, (u_char *) "Content-Type", 12) == 0 + && r->headers_out.content_type.len) { lua_pushlstring(L, (char *) r->headers_out.content_type.data, r->headers_out.content_type.len); @@ -634,4 +698,48 @@ ngx_http_lua_get_output_header(lua_State *L, ngx_http_request_t *r, return 1; } + +ngx_int_t +ngx_http_lua_init_builtin_headers_out(ngx_conf_t *cf, + ngx_http_lua_main_conf_t *lmcf) +{ + ngx_array_t headers; + ngx_hash_key_t *hk; + ngx_hash_init_t hash; + ngx_http_lua_set_header_t *handlers = ngx_http_lua_set_handlers; + ngx_uint_t count; + + count = sizeof(ngx_http_lua_set_handlers) + / sizeof(ngx_http_lua_set_header_t); + + if (ngx_array_init(&headers, cf->temp_pool, count, sizeof(ngx_hash_key_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + while (handlers->name.data) { + hk = ngx_array_push(&headers); + if (hk == NULL) { + return NGX_ERROR; + } + + hk->key = handlers->name; + hk->key_hash = ngx_hash_key_lc(handlers->name.data, handlers->name.len); + hk->value = (void *) handlers; + + handlers++; + } + + hash.hash = &lmcf->builtin_headers_out; + hash.key = ngx_hash_key_lc; + hash.max_size = 512; + hash.bucket_size = ngx_align(64, ngx_cacheline_size); + hash.name = "builtin_headers_out_hash"; + hash.pool = cf->pool; + hash.temp_pool = NULL; + + return ngx_hash_init(&hash, headers.elts, headers.nelts); +} + /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_headers_out.h b/src/ngx_http_lua_headers_out.h index ef5e6d421c..eb3c794723 100644 --- a/src/ngx_http_lua_headers_out.h +++ b/src/ngx_http_lua_headers_out.h @@ -12,10 +12,28 @@ #include "ngx_http_lua_common.h" -ngx_int_t ngx_http_lua_set_output_header(ngx_http_request_t *r, ngx_str_t key, - ngx_str_t value, unsigned override); +#if (NGX_DARWIN) +typedef struct { + ngx_http_request_t *r; + const char *key_data; + size_t key_len; + int is_nil; + const char *sval; + size_t sval_len; + void *mvals; + size_t mvals_len; + int override; + char **errmsg; +} ngx_http_lua_set_resp_header_params_t; +#endif + + +ngx_int_t ngx_http_lua_set_output_header(ngx_http_request_t *r, + ngx_http_lua_ctx_t *ctx, ngx_str_t key, ngx_str_t value, unsigned override); int ngx_http_lua_get_output_header(lua_State *L, ngx_http_request_t *r, - ngx_str_t *key); + ngx_http_lua_ctx_t *ctx, ngx_str_t *key); +ngx_int_t ngx_http_lua_init_builtin_headers_out(ngx_conf_t *cf, + ngx_http_lua_main_conf_t *lmcf); #endif /* _NGX_HTTP_LUA_HEADERS_OUT_H_INCLUDED_ */ diff --git a/src/ngx_http_lua_initby.c b/src/ngx_http_lua_initby.c index e8941da6ca..1f956e7ebd 100644 --- a/src/ngx_http_lua_initby.c +++ b/src/ngx_http_lua_initby.c @@ -18,9 +18,18 @@ ngx_http_lua_init_by_inline(ngx_log_t *log, ngx_http_lua_main_conf_t *lmcf, lua_State *L) { int status; + const char *chunkname; + + + if (lmcf->init_chunkname == NULL) { + chunkname = "=init_by_lua"; + + } else { + chunkname = (const char *) lmcf->init_chunkname; + } status = luaL_loadbuffer(L, (char *) lmcf->init_src.data, - lmcf->init_src.len, "=init_by_lua") + lmcf->init_src.len, chunkname) || ngx_http_lua_do_call(log, L); return ngx_http_lua_report(log, L, status, "init_by_lua"); diff --git a/src/ngx_http_lua_initworkerby.c b/src/ngx_http_lua_initworkerby.c index 91cfef5e47..edb68df08a 100644 --- a/src/ngx_http_lua_initworkerby.c +++ b/src/ngx_http_lua_initworkerby.c @@ -12,6 +12,7 @@ #include "ngx_http_lua_initworkerby.h" #include "ngx_http_lua_util.h" +#include "ngx_http_lua_pipe.h" static u_char *ngx_http_lua_log_init_worker_error(ngx_log_t *log, @@ -25,6 +26,7 @@ ngx_http_lua_init_worker(ngx_cycle_t *cycle) void *cur, *prev; ngx_uint_t i; ngx_conf_t conf; + ngx_conf_file_t cf_file; ngx_cycle_t *fake_cycle; ngx_module_t **modules; ngx_open_file_t *file, *ofile; @@ -46,13 +48,15 @@ ngx_http_lua_init_worker(ngx_cycle_t *cycle) /* lmcf != NULL && lmcf->lua != NULL */ - /* disable init_worker_by_lua* and destroy lua VM in cache processes */ +#if !(NGX_WIN32) if (ngx_process == NGX_PROCESS_HELPER -#if defined(HAVE_PRIVILEGED_PROCESS_PATCH) && !NGX_WIN32 +# ifdef HAVE_PRIVILEGED_PROCESS_PATCH && !ngx_is_privileged_agent -#endif +# endif ) { + /* disable init_worker_by_lua* and destroy lua VM in cache processes */ + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "lua close the global Lua VM %p in the " "cache helper process %P", lmcf->lua, ngx_pid); @@ -63,6 +67,20 @@ ngx_http_lua_init_worker(ngx_cycle_t *cycle) return NGX_OK; } +# ifdef HAVE_NGX_LUA_PIPE + if (ngx_http_lua_pipe_add_signal_handler(cycle) != NGX_OK) { + return NGX_ERROR; + } +# endif + +#endif /* NGX_WIN32 */ + +#if (NGX_HTTP_LUA_HAVE_SA_RESTART) + if (lmcf->set_sa_restart) { + ngx_http_lua_set_sa_restart(ngx_cycle->log); + } +#endif + if (lmcf->init_worker_handler == NULL) { return NGX_OK; } @@ -97,35 +115,29 @@ ngx_http_lua_init_worker(ngx_cycle_t *cycle) ngx_memcpy(fake_cycle, cycle, sizeof(ngx_cycle_t)); -#if defined(nginx_version) && nginx_version >= 9007 - ngx_queue_init(&fake_cycle->reusable_connections_queue); -#endif - if (ngx_array_init(&fake_cycle->listening, cycle->pool, - cycle->listening.nelts || 1, + cycle->listening.nelts ? cycle->listening.nelts : 1, sizeof(ngx_listening_t)) != NGX_OK) { goto failed; } -#if defined(nginx_version) && nginx_version >= 1003007 - - if (ngx_array_init(&fake_cycle->paths, cycle->pool, cycle->paths.nelts || 1, + if (ngx_array_init(&fake_cycle->paths, cycle->pool, + cycle->paths.nelts ? cycle->paths.nelts : 1, sizeof(ngx_path_t *)) != NGX_OK) { goto failed; } -#endif - part = &cycle->open_files.part; ofile = part->elts; - if (ngx_list_init(&fake_cycle->open_files, cycle->pool, part->nelts || 1, + if (ngx_list_init(&fake_cycle->open_files, cycle->pool, + part->nelts ? part->nelts : 1, sizeof(ngx_open_file_t)) != NGX_OK) { @@ -138,6 +150,7 @@ ngx_http_lua_init_worker(ngx_cycle_t *cycle) if (part->next == NULL) { break; } + part = part->next; ofile = part->elts; i = 0; @@ -163,6 +176,10 @@ ngx_http_lua_init_worker(ngx_cycle_t *cycle) conf.pool = fake_cycle->pool; conf.log = cycle->log; + ngx_memzero(&cf_file, sizeof(cf_file)); + cf_file.file.name = cycle->conf_file; + conf.conf_file = &cf_file; + http_ctx.loc_conf = ngx_pcalloc(conf.pool, sizeof(void *) * ngx_http_max_module); if (http_ctx.loc_conf == NULL) { @@ -175,7 +192,7 @@ ngx_http_lua_init_worker(ngx_cycle_t *cycle) return NGX_ERROR; } -#if defined(nginx_version) && nginx_version >= 1009011 +#if (nginx_version >= 1009011) modules = cycle->modules; #else modules = ngx_modules; @@ -260,26 +277,11 @@ ngx_http_lua_init_worker(ngx_cycle_t *cycle) clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); -#if defined(nginx_version) && nginx_version >= 1003014 - -# if nginx_version >= 1009000 - +#if (nginx_version >= 1009000) ngx_set_connection_log(r->connection, clcf->error_log); -# else - - ngx_http_set_connection_log(r->connection, clcf->error_log); - -# endif - #else - - c->log->file = clcf->error_log->file; - - if (!(c->log->log_level & NGX_LOG_DEBUG_CONNECTION)) { - c->log->log_level = clcf->error_log->log_level; - } - + ngx_http_set_connection_log(r->connection, clcf->error_log); #endif ctx = ngx_http_lua_create_ctx(r); @@ -295,6 +297,8 @@ ngx_http_lua_init_worker(ngx_cycle_t *cycle) (void) lmcf->init_worker_handler(cycle->log, lmcf, lmcf->lua); + ngx_http_lua_set_req(lmcf->lua, NULL); + ngx_destroy_pool(c->pool); return NGX_OK; @@ -317,9 +321,17 @@ ngx_http_lua_init_worker_by_inline(ngx_log_t *log, ngx_http_lua_main_conf_t *lmcf, lua_State *L) { int status; + const char *chunkname; + + if (lmcf->init_worker_chunkname == NULL) { + chunkname = "=init_worker_by_lua"; + + } else { + chunkname = (const char *) lmcf->init_worker_chunkname; + } status = luaL_loadbuffer(L, (char *) lmcf->init_worker_src.data, - lmcf->init_worker_src.len, "=init_worker_by_lua") + lmcf->init_worker_src.len, chunkname) || ngx_http_lua_do_call(log, L); return ngx_http_lua_report(log, L, status, "init_worker_by_lua"); @@ -352,3 +364,6 @@ ngx_http_lua_log_init_worker_error(ngx_log_t *log, u_char *buf, size_t len) return ngx_snprintf(buf, len, ", context: init_worker_by_lua*"); } + + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_input_filters.c b/src/ngx_http_lua_input_filters.c new file mode 100644 index 0000000000..87a9d8c1bc --- /dev/null +++ b/src/ngx_http_lua_input_filters.c @@ -0,0 +1,137 @@ + +/* + * Copyright (C) by OpenResty Inc. + */ + + +#ifndef DDEBUG +#define DDEBUG 0 +#endif +#include "ddebug.h" + + +#include "ngx_http_lua_common.h" + + +ngx_int_t +ngx_http_lua_read_bytes(ngx_buf_t *src, ngx_chain_t *buf_in, size_t *rest, + ssize_t bytes, ngx_log_t *log) +{ + if (bytes == 0) { + return NGX_ERROR; + } + + if ((size_t) bytes >= *rest) { + + buf_in->buf->last += *rest; + src->pos += *rest; + *rest = 0; + + return NGX_OK; + } + + /* bytes < *rest */ + + buf_in->buf->last += bytes; + src->pos += bytes; + *rest -= bytes; + + return NGX_AGAIN; +} + + +ngx_int_t +ngx_http_lua_read_all(ngx_buf_t *src, ngx_chain_t *buf_in, ssize_t bytes, + ngx_log_t *log) +{ + if (bytes == 0) { + return NGX_OK; + } + + buf_in->buf->last += bytes; + src->pos += bytes; + + return NGX_AGAIN; +} + + +ngx_int_t +ngx_http_lua_read_any(ngx_buf_t *src, ngx_chain_t *buf_in, size_t *max, + ssize_t bytes, ngx_log_t *log) +{ + if (bytes == 0) { + return NGX_ERROR; + } + + if (bytes >= (ssize_t) *max) { + bytes = (ssize_t) *max; + } + + buf_in->buf->last += bytes; + src->pos += bytes; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_lua_read_line(ngx_buf_t *src, ngx_chain_t *buf_in, ssize_t bytes, + ngx_log_t *log) +{ + u_char *dst; + u_char c; +#if (NGX_DEBUG) + u_char *begin; +#endif + +#if (NGX_DEBUG) + begin = src->pos; +#endif + + if (bytes == 0) { + return NGX_ERROR; + } + + dd("already read: %p: %.*s", buf_in, + (int) (buf_in->buf->last - buf_in->buf->pos), buf_in->buf->pos); + + dd("data read: %.*s", (int) bytes, src->pos); + + dst = buf_in->buf->last; + + while (bytes--) { + + c = *src->pos++; + + switch (c) { + case '\n': + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, + "lua read the final line part: \"%*s\"", + src->pos - 1 - begin, begin); + + buf_in->buf->last = dst; + + dd("read a line: %p: %.*s", buf_in, + (int) (buf_in->buf->last - buf_in->buf->pos), buf_in->buf->pos); + + return NGX_OK; + + case '\r': + /* ignore it */ + break; + + default: + *dst++ = c; + break; + } + } + +#if (NGX_DEBUG) + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, + "lua read partial line data: %*s", dst - begin, begin); +#endif + + buf_in->buf->last = dst; + + return NGX_AGAIN; +} diff --git a/src/ngx_http_lua_input_filters.h b/src/ngx_http_lua_input_filters.h new file mode 100644 index 0000000000..046d40fda1 --- /dev/null +++ b/src/ngx_http_lua_input_filters.h @@ -0,0 +1,29 @@ + +/* + * Copyright (C) by OpenResty Inc. + */ + + +#ifndef _NGX_HTTP_LUA_INPUT_FILTERS_H_INCLUDED_ +#define _NGX_HTTP_LUA_INPUT_FILTERS_H_INCLUDED_ + + +#include "ngx_http_lua_common.h" + + +ngx_int_t ngx_http_lua_read_bytes(ngx_buf_t *src, ngx_chain_t *buf_in, + size_t *rest, ssize_t bytes, ngx_log_t *log); + +ngx_int_t ngx_http_lua_read_all(ngx_buf_t *src, ngx_chain_t *buf_in, + ssize_t bytes, ngx_log_t *log); + +ngx_int_t ngx_http_lua_read_any(ngx_buf_t *src, ngx_chain_t *buf_in, + size_t *max, ssize_t bytes, ngx_log_t *log); + +ngx_int_t ngx_http_lua_read_line(ngx_buf_t *src, ngx_chain_t *buf_in, + ssize_t bytes, ngx_log_t *log); + + +#endif /* _NGX_HTTP_LUA_INPUT_FILTERS_H_INCLUDED_ */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_lex.c b/src/ngx_http_lua_lex.c index 45cf7540e7..c9064030c2 100644 --- a/src/ngx_http_lua_lex.c +++ b/src/ngx_http_lua_lex.c @@ -3,8 +3,7 @@ * * WARNING: DO NOT EVER EDIT THIS FILE!! * - * This file was automatically generated by the re.pl script of sregex's - * "dfa-multi-re" git branch. + * This file was automatically generated by the OpenResty Regex compiler. */ diff --git a/src/ngx_http_lua_log.c b/src/ngx_http_lua_log.c index 0042a5e2b4..43ab82095a 100644 --- a/src/ngx_http_lua_log.c +++ b/src/ngx_http_lua_log.c @@ -14,6 +14,7 @@ #include "ngx_http_lua_log.h" #include "ngx_http_lua_util.h" #include "ngx_http_lua_log_ringbuf.h" +#include "ngx_http_lua_output.h" static int ngx_http_lua_print(lua_State *L); @@ -121,6 +122,7 @@ log_wrapper(ngx_log_t *log, const char *ident, ngx_uint_t level, if (*p == '/' || *p == '\\') { name.data = p + 1; } + p++; } @@ -142,6 +144,9 @@ log_wrapper(ngx_log_t *log, const char *ident, ngx_uint_t level, type = lua_type(L, i); switch (type) { case LUA_TNUMBER: + size += ngx_http_lua_get_num_len(L, i); + break; + case LUA_TSTRING: lua_tolstring(L, i, &len); size += len; @@ -194,7 +199,7 @@ log_wrapper(ngx_log_t *log, const char *ident, ngx_uint_t level, *p++ = ':'; p = ngx_snprintf(p, NGX_INT_T_LEN, "%d", - ar.currentline ? ar.currentline : ar.linedefined); + ar.currentline > 0 ? ar.currentline : ar.linedefined); *p++ = ':'; *p++ = ' '; @@ -210,6 +215,9 @@ log_wrapper(ngx_log_t *log, const char *ident, ngx_uint_t level, type = lua_type(L, i); switch (type) { case LUA_TNUMBER: + p = ngx_http_lua_write_num(L, i, p); + break; + case LUA_TSTRING: q = (u_char *) lua_tolstring(L, i, &len); p = ngx_copy(p, q, len); @@ -340,7 +348,6 @@ ngx_http_lua_capture_log_handler(ngx_log_t *log, #endif -#ifndef NGX_LUA_NO_FFI_API int ngx_http_lua_ffi_errlog_set_filter_level(int level, u_char *err, size_t *errlen) { @@ -451,6 +458,5 @@ ngx_http_lua_ffi_raw_log(ngx_http_request_t *r, int level, u_char *s, return NGX_OK; } -#endif /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_log_ringbuf.c b/src/ngx_http_lua_log_ringbuf.c index 04640697ff..40fbff1e08 100644 --- a/src/ngx_http_lua_log_ringbuf.c +++ b/src/ngx_http_lua_log_ringbuf.c @@ -17,7 +17,7 @@ typedef struct { enum { - HEADER_LEN = sizeof(ngx_http_lua_log_ringbuf_header_t) + HEADER_LEN = sizeof(ngx_http_lua_log_ringbuf_header_t), }; @@ -223,3 +223,6 @@ ngx_http_lua_log_ringbuf_read(ngx_http_lua_log_ringbuf_t *rb, int *log_level, return NGX_OK; } + + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_logby.c b/src/ngx_http_lua_logby.c index 0f1d2f35b3..cded8d8d04 100644 --- a/src/ngx_http_lua_logby.c +++ b/src/ngx_http_lua_logby.c @@ -15,18 +15,13 @@ #include "ngx_http_lua_exception.h" #include "ngx_http_lua_util.h" #include "ngx_http_lua_pcrefix.h" -#include "ngx_http_lua_time.h" #include "ngx_http_lua_log.h" -#include "ngx_http_lua_regex.h" #include "ngx_http_lua_cache.h" #include "ngx_http_lua_headers.h" -#include "ngx_http_lua_variable.h" #include "ngx_http_lua_string.h" #include "ngx_http_lua_misc.h" #include "ngx_http_lua_consts.h" #include "ngx_http_lua_shdict.h" -#include "ngx_http_lua_util.h" -#include "ngx_http_lua_exception.h" #if (NGX_HTTP_LUA_HAVE_MALLOC_TRIM) #include #endif @@ -38,9 +33,9 @@ static ngx_int_t ngx_http_lua_log_by_chunk(lua_State *L, ngx_http_request_t *r); static void ngx_http_lua_log_by_lua_env(lua_State *L, ngx_http_request_t *r) { - /* set nginx request pointer to current lua thread's globals table */ ngx_http_lua_set_req(L, r); +#ifndef OPENRESTY_LUAJIT /** * we want to create empty environment for current script * @@ -64,6 +59,7 @@ ngx_http_lua_log_by_lua_env(lua_State *L, ngx_http_request_t *r) /* }}} */ lua_setfenv(L, -2); /* set new running env for the code closure */ +#endif /* OPENRESTY_LUAJIT */ } @@ -73,6 +69,9 @@ ngx_http_lua_log_handler(ngx_http_request_t *r) #if (NGX_HTTP_LUA_HAVE_MALLOC_TRIM) ngx_uint_t trim_cycle, trim_nreq; ngx_http_lua_main_conf_t *lmcf; +#if (NGX_DEBUG) + ngx_int_t trim_ret; +#endif #endif ngx_http_lua_loc_conf_t *llcf; ngx_http_lua_ctx_t *ctx; @@ -92,8 +91,9 @@ ngx_http_lua_log_handler(ngx_http_request_t *r) lmcf->malloc_trim_req_count = 0; #if (NGX_DEBUG) + trim_ret = malloc_trim(1); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "malloc_trim(1) returned %d", malloc_trim(1)); + "malloc_trim(1) returned %d", trim_ret); #else (void) malloc_trim(1); #endif @@ -149,10 +149,15 @@ ngx_http_lua_log_handler_inline(ngx_http_request_t *r) L = ngx_http_lua_get_lua_vm(r, NULL); + if (!llcf->enable_code_cache) { + llcf->log_src_ref = LUA_REFNIL; + } + /* load Lua inline script (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, llcf->log_src.value.data, llcf->log_src.value.len, + &llcf->log_src_ref, llcf->log_src_key, (const char *) llcf->log_chunkname); if (rc != NGX_OK) { @@ -187,8 +192,13 @@ ngx_http_lua_log_handler_file(ngx_http_request_t *r) L = ngx_http_lua_get_lua_vm(r, NULL); + if (!llcf->enable_code_cache) { + llcf->log_src_ref = LUA_REFNIL; + } + /* load Lua script file (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadfile(r->connection->log, L, script_path, + &llcf->log_src_ref, llcf->log_src_key); if (rc != NGX_OK) { return NGX_ERROR; diff --git a/src/ngx_http_lua_logby.h b/src/ngx_http_lua_logby.h index af8aaaa756..2b011c884e 100644 --- a/src/ngx_http_lua_logby.h +++ b/src/ngx_http_lua_logby.h @@ -14,7 +14,6 @@ ngx_int_t ngx_http_lua_log_handler(ngx_http_request_t *r); ngx_int_t ngx_http_lua_log_handler_inline(ngx_http_request_t *r); ngx_int_t ngx_http_lua_log_handler_file(ngx_http_request_t *r); -void ngx_http_lua_inject_logby_ngx_api(ngx_conf_t *cf, lua_State *L); #endif /* _NGX_HTTP_LUA_LOGBY_H_INCLUDED_ */ diff --git a/src/ngx_http_lua_misc.c b/src/ngx_http_lua_misc.c index f96e2f273e..8b74167161 100644 --- a/src/ngx_http_lua_misc.c +++ b/src/ngx_http_lua_misc.c @@ -12,28 +12,12 @@ #include "ngx_http_lua_misc.h" -#include "ngx_http_lua_ctx.h" #include "ngx_http_lua_util.h" -static int ngx_http_lua_ngx_get(lua_State *L); -static int ngx_http_lua_ngx_set(lua_State *L); static int ngx_http_lua_ngx_req_is_internal(lua_State *L); -void -ngx_http_lua_inject_misc_api(lua_State *L) -{ - /* ngx. getter and setter */ - lua_createtable(L, 0, 2); /* metatable for .ngx */ - lua_pushcfunction(L, ngx_http_lua_ngx_get); - lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, ngx_http_lua_ngx_set); - lua_setfield(L, -2, "__newindex"); - lua_setmetatable(L, -2); -} - - void ngx_http_lua_inject_req_misc_api(lua_State *L) { @@ -57,150 +41,6 @@ ngx_http_lua_ngx_req_is_internal(lua_State *L) } -static int -ngx_http_lua_ngx_get(lua_State *L) -{ - int status; - ngx_http_request_t *r; - u_char *p; - size_t len; - ngx_http_lua_ctx_t *ctx; - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - lua_pushnil(L); - return 1; - } - - ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); - if (ctx == NULL) { - lua_pushnil(L); - return 1; - } - - p = (u_char *) luaL_checklstring(L, -1, &len); - - dd("ngx get %s", p); - - if (len == sizeof("status") - 1 - && ngx_strncmp(p, "status", sizeof("status") - 1) == 0) - { - ngx_http_lua_check_fake_request(L, r); - - if (r->err_status) { - status = r->err_status; - - } else if (r->headers_out.status) { - status = r->headers_out.status; - - } else if (r->http_version == NGX_HTTP_VERSION_9) { - status = 9; - - } else { - status = 0; - } - - lua_pushinteger(L, status); - return 1; - } - - if (len == sizeof("ctx") - 1 - && ngx_strncmp(p, "ctx", sizeof("ctx") - 1) == 0) - { - return ngx_http_lua_ngx_get_ctx(L); - } - - if (len == sizeof("is_subrequest") - 1 - && ngx_strncmp(p, "is_subrequest", sizeof("is_subrequest") - 1) == 0) - { - lua_pushboolean(L, r != r->main); - return 1; - } - - if (len == sizeof("headers_sent") - 1 - && ngx_strncmp(p, "headers_sent", sizeof("headers_sent") - 1) == 0) - { - ngx_http_lua_check_fake_request(L, r); - - dd("headers sent: %d", r->header_sent || ctx->header_sent); - - lua_pushboolean(L, r->header_sent || ctx->header_sent); - return 1; - } - - dd("key %s not matched", p); - - lua_pushnil(L); - return 1; -} - - -static int -ngx_http_lua_ngx_set(lua_State *L) -{ - ngx_http_request_t *r; - u_char *p; - size_t len; - - /* we skip the first argument that is the table */ - p = (u_char *) luaL_checklstring(L, 2, &len); - - if (len == sizeof("status") - 1 - && ngx_strncmp(p, "status", sizeof("status") - 1) == 0) - { - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } - - if (r->header_sent) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "attempt to set ngx.status after sending out " - "response headers"); - return 0; - } - - if (r->err_status) { - r->err_status = 0; - } - - ngx_http_lua_check_fake_request(L, r); - - /* get the value */ - r->headers_out.status = (ngx_uint_t) luaL_checknumber(L, 3); - - if (r->headers_out.status == 101) { - /* - * XXX work-around a bug in the Nginx core that 101 does - * not have a default status line - */ - - ngx_str_set(&r->headers_out.status_line, "101 Switching Protocols"); - - } else { - r->headers_out.status_line.len = 0; - } - - return 0; - } - - if (len == sizeof("ctx") - 1 - && ngx_strncmp(p, "ctx", sizeof("ctx") - 1) == 0) - { - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } - - return ngx_http_lua_ngx_set_ctx(L); - } - - lua_rawset(L, -3); - return 0; -} - - -#ifndef NGX_LUA_NO_FFI_API int ngx_http_lua_ffi_get_resp_status(ngx_http_request_t *r) { @@ -224,8 +64,11 @@ ngx_http_lua_ffi_get_resp_status(ngx_http_request_t *r) int -ngx_http_lua_ffi_set_resp_status(ngx_http_request_t *r, int status) +ngx_http_lua_ffi_set_resp_status_and_reason(ngx_http_request_t *r, int status, + const char *reason, size_t reason_len) { + u_char *buf; + if (r->connection->fd == (ngx_socket_t) -1) { return NGX_HTTP_LUA_FFI_BAD_CONTEXT; } @@ -237,6 +80,14 @@ ngx_http_lua_ffi_set_resp_status(ngx_http_request_t *r, int status) return NGX_DECLINED; } + /* per RFC-7230 sec 3.1.2, the status line must be 3 digits, it also makes + * buffer size calculation easier */ + if (status < 100 || status > 999) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "invalid HTTP status code %d", status); + return NGX_DECLINED; + } + r->headers_out.status = status; if (r->err_status) { @@ -251,6 +102,18 @@ ngx_http_lua_ffi_set_resp_status(ngx_http_request_t *r, int status) ngx_str_set(&r->headers_out.status_line, "101 Switching Protocols"); + } else if (reason != NULL && reason_len > 0) { + reason_len += 4; /* "ddd " */ + buf = ngx_palloc(r->pool, reason_len); + if (buf == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no memory"); + return NGX_DECLINED; + } + + ngx_snprintf(buf, reason_len, "%d %s", status, reason); + r->headers_out.status_line.len = reason_len; + r->headers_out.status_line.data = buf; + } else { r->headers_out.status_line.len = 0; } @@ -259,6 +122,24 @@ ngx_http_lua_ffi_set_resp_status(ngx_http_request_t *r, int status) } +int +ngx_http_lua_ffi_set_resp_status(ngx_http_request_t *r, int status) +{ + return ngx_http_lua_ffi_set_resp_status_and_reason(r, status, NULL, 0); +} + + +int +ngx_http_lua_ffi_req_is_internal(ngx_http_request_t *r) +{ + if (r->connection->fd == (ngx_socket_t) -1) { + return NGX_HTTP_LUA_FFI_BAD_CONTEXT; + } + + return r->internal; +} + + int ngx_http_lua_ffi_is_subrequest(ngx_http_request_t *r) { @@ -286,7 +167,33 @@ ngx_http_lua_ffi_headers_sent(ngx_http_request_t *r) return r->header_sent ? 1 : 0; } -#endif /* NGX_LUA_NO_FFI_API */ + + +int +ngx_http_lua_ffi_get_conf_env(u_char *name, u_char **env_buf, size_t *name_len) +{ + ngx_uint_t i; + ngx_str_t *var; + ngx_core_conf_t *ccf; + + ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_core_module); + + var = ccf->env.elts; + + for (i = 0; i < ccf->env.nelts; i++) { + if (var[i].data[var[i].len] == '=' + && ngx_strncmp(name, var[i].data, var[i].len) == 0) + { + *env_buf = var[i].data; + *name_len = var[i].len; + + return NGX_OK; + } + } + + return NGX_DECLINED; +} /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_misc.h b/src/ngx_http_lua_misc.h index c26d869b30..061ccf279a 100644 --- a/src/ngx_http_lua_misc.h +++ b/src/ngx_http_lua_misc.h @@ -12,8 +12,6 @@ #include "ngx_http_lua_common.h" -void ngx_http_lua_inject_misc_api(lua_State *L); - void ngx_http_lua_inject_req_misc_api(lua_State *L); diff --git a/src/ngx_http_lua_module.c b/src/ngx_http_lua_module.c index 9d914e86e8..16726abbe1 100644 --- a/src/ngx_http_lua_module.c +++ b/src/ngx_http_lua_module.c @@ -14,6 +14,7 @@ #include "ngx_http_lua_directive.h" #include "ngx_http_lua_capturefilter.h" #include "ngx_http_lua_contentby.h" +#include "ngx_http_lua_server_rewriteby.h" #include "ngx_http_lua_rewriteby.h" #include "ngx_http_lua_accessby.h" #include "ngx_http_lua_logby.h" @@ -22,13 +23,24 @@ #include "ngx_http_lua_bodyfilterby.h" #include "ngx_http_lua_initby.h" #include "ngx_http_lua_initworkerby.h" +#include "ngx_http_lua_exitworkerby.h" #include "ngx_http_lua_probe.h" #include "ngx_http_lua_semaphore.h" #include "ngx_http_lua_balancer.h" +#include "ngx_http_lua_ssl_client_helloby.h" #include "ngx_http_lua_ssl_certby.h" #include "ngx_http_lua_ssl_session_storeby.h" #include "ngx_http_lua_ssl_session_fetchby.h" + +#ifdef HAVE_PROXY_SSL_PATCH +#include "ngx_http_lua_proxy_ssl_verifyby.h" +#endif + #include "ngx_http_lua_headers.h" +#include "ngx_http_lua_headers_out.h" +#if !(NGX_WIN32) +#include "ngx_http_lua_pipe.h" +#endif static void *ngx_http_lua_create_main_conf(ngx_conf_t *cf); @@ -43,11 +55,25 @@ static char *ngx_http_lua_merge_loc_conf(ngx_conf_t *cf, void *parent, static ngx_int_t ngx_http_lua_init(ngx_conf_t *cf); static char *ngx_http_lua_lowat_check(ngx_conf_t *cf, void *post, void *data); #if (NGX_HTTP_SSL) +static ngx_int_t ngx_http_lua_merge_ssl(ngx_conf_t *cf, + ngx_http_lua_loc_conf_t *conf, ngx_http_lua_loc_conf_t *prev); static ngx_int_t ngx_http_lua_set_ssl(ngx_conf_t *cf, ngx_http_lua_loc_conf_t *llcf); +static void key_log_callback(const ngx_ssl_conn_t *ssl_conn, + const char *line); +static void ngx_http_lua_ssl_cleanup_key_log(void *data); +static ngx_int_t ngx_http_lua_ssl_key_log(ngx_conf_t *cf, ngx_ssl_t *ssl, + ngx_str_t *file); +#if (nginx_version >= 1019004) +static char *ngx_http_lua_ssl_conf_command_check(ngx_conf_t *cf, void *post, + void *data); +#endif #endif static char *ngx_http_lua_malloc_trim(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +#if (NGX_PCRE2) +extern void ngx_http_lua_regex_cleanup(void *data); +#endif static ngx_conf_post_t ngx_http_lua_lowat_post = @@ -57,7 +83,7 @@ static ngx_conf_post_t ngx_http_lua_lowat_post = static volatile ngx_cycle_t *ngx_http_lua_prev_cycle = NULL; -#if (NGX_HTTP_SSL) && defined(nginx_version) && nginx_version >= 1001013 +#if (NGX_HTTP_SSL) static ngx_conf_bitmask_t ngx_http_lua_ssl_protocols[] = { { ngx_string("SSLv2"), NGX_SSL_SSLv2 }, @@ -65,14 +91,36 @@ static ngx_conf_bitmask_t ngx_http_lua_ssl_protocols[] = { { ngx_string("TLSv1"), NGX_SSL_TLSv1 }, { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 }, { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 }, +#ifdef NGX_SSL_TLSv1_3 + { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 }, +#endif { ngx_null_string, 0 } }; +#if (nginx_version >= 1019004) +static ngx_conf_post_t ngx_http_lua_ssl_conf_command_post = + { ngx_http_lua_ssl_conf_command_check }; +#endif + #endif static ngx_command_t ngx_http_lua_cmds[] = { + { ngx_string("lua_load_resty_core"), + NGX_HTTP_MAIN_CONF|NGX_CONF_FLAG, + ngx_http_lua_load_resty_core, + NGX_HTTP_MAIN_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("lua_thread_cache_max_entries"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_MAIN_CONF_OFFSET, + offsetof(ngx_http_lua_main_conf_t, lua_thread_cache_max_entries), + NULL }, + { ngx_string("lua_max_running_timers"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, @@ -101,21 +149,34 @@ static ngx_command_t ngx_http_lua_cmds[] = { 0, NULL }, -#if (NGX_PCRE) + { ngx_string("lua_sa_restart"), + NGX_HTTP_MAIN_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_MAIN_CONF_OFFSET, + offsetof(ngx_http_lua_main_conf_t, set_sa_restart), + NULL }, + { ngx_string("lua_regex_cache_max_entries"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, + ngx_http_lua_regex_cache_max_entries, NGX_HTTP_MAIN_CONF_OFFSET, +#if (NGX_PCRE) offsetof(ngx_http_lua_main_conf_t, regex_cache_max_entries), +#else + 0, +#endif NULL }, { ngx_string("lua_regex_match_limit"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, + ngx_http_lua_regex_match_limit, NGX_HTTP_MAIN_CONF_OFFSET, +#if (NGX_PCRE) offsetof(ngx_http_lua_main_conf_t, regex_match_limit), - NULL }, +#else + 0, #endif + NULL }, { ngx_string("lua_package_cpath"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, @@ -205,11 +266,25 @@ static ngx_command_t ngx_http_lua_cmds[] = { 0, (void *) ngx_http_lua_init_worker_by_file }, + { ngx_string("exit_worker_by_lua_block"), + NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_http_lua_exit_worker_by_lua_block, + NGX_HTTP_MAIN_CONF_OFFSET, + 0, + (void *) ngx_http_lua_exit_worker_by_inline }, + + { ngx_string("exit_worker_by_lua_file"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_http_lua_exit_worker_by_lua, + NGX_HTTP_MAIN_CONF_OFFSET, + 0, + (void *) ngx_http_lua_exit_worker_by_file }, + #if defined(NDK) && NDK - /* set_by_lua $res { inline Lua code } [$arg1 [$arg2 [...]]] */ + /* set_by_lua_block $res { inline Lua code } */ { ngx_string("set_by_lua_block"), NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF - |NGX_CONF_1MORE|NGX_CONF_BLOCK, + |NGX_CONF_TAKE1|NGX_CONF_BLOCK, ngx_http_lua_set_by_lua_block, NGX_HTTP_LOC_CONF_OFFSET, 0, @@ -234,6 +309,22 @@ static ngx_command_t ngx_http_lua_cmds[] = { (void *) ngx_http_lua_filter_set_by_lua_file }, #endif + /* server_rewrite_by_lua_block { } */ + { ngx_string("server_rewrite_by_lua_block"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_http_lua_server_rewrite_by_lua_block, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + (void *) ngx_http_lua_server_rewrite_handler_inline }, + + /* server_rewrite_by_lua_file filename; */ + { ngx_string("server_rewrite_by_lua_file"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_lua_server_rewrite_by_lua, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + (void *) ngx_http_lua_server_rewrite_handler_file }, + /* rewrite_by_lua "" */ { ngx_string("rewrite_by_lua"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF @@ -415,6 +506,13 @@ static ngx_command_t ngx_http_lua_cmds[] = { 0, (void *) ngx_http_lua_balancer_handler_file }, + { ngx_string("balancer_keepalive"), + NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1, + ngx_http_lua_balancer_keepalive, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_lua_srv_conf_t, balancer.max_cached), + NULL }, + { ngx_string("lua_socket_keepalive_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF |NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, @@ -497,8 +595,6 @@ static ngx_command_t ngx_http_lua_cmds[] = { #if (NGX_HTTP_SSL) -# if defined(nginx_version) && nginx_version >= 1001013 - { ngx_string("lua_ssl_protocols"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, @@ -506,8 +602,6 @@ static ngx_command_t ngx_http_lua_cmds[] = { offsetof(ngx_http_lua_loc_conf_t, ssl_protocols), &ngx_http_lua_ssl_protocols }, -# endif - { ngx_string("lua_ssl_ciphers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, @@ -515,6 +609,20 @@ static ngx_command_t ngx_http_lua_cmds[] = { offsetof(ngx_http_lua_loc_conf_t, ssl_ciphers), NULL }, + { ngx_string("ssl_client_hello_by_lua_block"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_http_lua_ssl_client_hello_by_lua_block, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + (void *) ngx_http_lua_ssl_client_hello_handler_inline }, + + { ngx_string("ssl_client_hello_by_lua_file"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_lua_ssl_client_hello_by_lua, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + (void *) ngx_http_lua_ssl_client_hello_handler_file }, + { ngx_string("ssl_certificate_by_lua_block"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_http_lua_ssl_cert_by_lua_block, @@ -557,6 +665,30 @@ static ngx_command_t ngx_http_lua_cmds[] = { 0, (void *) ngx_http_lua_ssl_sess_fetch_handler_file }, +#ifdef HAVE_PROXY_SSL_PATCH + /* same context as proxy_pass directive */ + { ngx_string("proxy_ssl_verify_by_lua_block"), + NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_http_lua_proxy_ssl_verify_by_lua_block, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + (void *) ngx_http_lua_proxy_ssl_verify_handler_inline }, + + { ngx_string("proxy_ssl_verify_by_lua_file"), + NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, + ngx_http_lua_proxy_ssl_verify_by_lua, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + (void *) ngx_http_lua_proxy_ssl_verify_handler_file }, + + { ngx_string("lua_upstream_skip_openssl_default_verify"), + NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_lua_loc_conf_t, upstream_skip_openssl_default_verify), + NULL }, +#endif + { ngx_string("lua_ssl_verify_depth"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, @@ -564,6 +696,20 @@ static ngx_command_t ngx_http_lua_cmds[] = { offsetof(ngx_http_lua_loc_conf_t, ssl_verify_depth), NULL }, + { ngx_string("lua_ssl_certificate"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_array_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_lua_loc_conf_t, ssl_certificates), + NULL }, + + { ngx_string("lua_ssl_certificate_key"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_array_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_lua_loc_conf_t, ssl_certificate_keys), + NULL }, + { ngx_string("lua_ssl_trusted_certificate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, @@ -578,6 +724,21 @@ static ngx_command_t ngx_http_lua_cmds[] = { offsetof(ngx_http_lua_loc_conf_t, ssl_crl), NULL }, + { ngx_string("lua_ssl_key_log"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_lua_loc_conf_t, ssl_key_log), + NULL }, + +#if (nginx_version >= 1019004) + { ngx_string("lua_ssl_conf_command"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, + ngx_conf_set_keyval_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_lua_loc_conf_t, ssl_conf_commands), + &ngx_http_lua_ssl_conf_command_post }, +#endif #endif /* NGX_HTTP_SSL */ { ngx_string("lua_malloc_trim"), @@ -587,11 +748,18 @@ static ngx_command_t ngx_http_lua_cmds[] = { 0, NULL }, + { ngx_string("lua_worker_thread_vm_pool_size"), + NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_MAIN_CONF_OFFSET, + offsetof(ngx_http_lua_main_conf_t, worker_thread_vm_pool_size), + NULL }, + ngx_null_command }; -ngx_http_module_t ngx_http_lua_module_ctx = { +static ngx_http_module_t ngx_http_lua_module_ctx = { NULL, /* preconfiguration */ ngx_http_lua_init, /* postconfiguration */ @@ -616,7 +784,7 @@ ngx_module_t ngx_http_lua_module = { ngx_http_lua_init_worker, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ - NULL, /* exit process */ + ngx_http_lua_exit_worker, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; @@ -632,12 +800,20 @@ ngx_http_lua_init(ngx_conf_t *cf) volatile ngx_cycle_t *saved_cycle; ngx_http_core_main_conf_t *cmcf; ngx_http_lua_main_conf_t *lmcf; -#if !defined(NGX_LUA_NO_FFI_API) || nginx_version >= 1011011 ngx_pool_cleanup_t *cln; -#endif + ngx_str_t name = ngx_string("host"); + + if (ngx_process == NGX_PROCESS_SIGNALLER || ngx_test_config) { + return NGX_OK; + } lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_lua_module); + lmcf->host_var_index = ngx_http_get_variable_index(cf, &name); + if (lmcf->host_var_index == NGX_ERROR) { + return NGX_ERROR; + } + if (ngx_http_lua_prev_cycle != ngx_cycle) { ngx_http_lua_prev_cycle = ngx_cycle; multi_http_blocks = 0; @@ -663,6 +839,16 @@ ngx_http_lua_init(ngx_conf_t *cf) cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + if (lmcf->requires_server_rewrite) { + h = ngx_array_push( + &cmcf->phases[NGX_HTTP_SERVER_REWRITE_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_http_lua_server_rewrite_handler; + } + if (lmcf->requires_rewrite) { h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers); if (h == NULL) { @@ -713,7 +899,6 @@ ngx_http_lua_init(ngx_conf_t *cf) } } -#ifndef NGX_LUA_NO_FFI_API /* add the cleanup of semaphores after the lua_close */ cln = ngx_pool_cleanup_add(cf->pool, 0); if (cln == NULL) { @@ -722,9 +907,23 @@ ngx_http_lua_init(ngx_conf_t *cf) cln->data = lmcf; cln->handler = ngx_http_lua_sema_mm_cleanup; + +#if (NGX_PCRE2) + /* add the cleanup of pcre2 regex */ + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->data = lmcf; + cln->handler = ngx_http_lua_regex_cleanup; +#endif + +#ifdef HAVE_NGX_LUA_PIPE + ngx_http_lua_pipe_init(); #endif -#if nginx_version >= 1011011 +#if (nginx_version >= 1011011) cln = ngx_pool_cleanup_add(cf->pool, 0); if (cln == NULL) { return NGX_ERROR; @@ -737,18 +936,67 @@ ngx_http_lua_init(ngx_conf_t *cf) if (lmcf->lua == NULL) { dd("initializing lua vm"); +#ifndef OPENRESTY_LUAJIT + if (ngx_process != NGX_PROCESS_SIGNALLER && !ngx_test_config) { + ngx_log_error(NGX_LOG_ALERT, cf->log, 0, + "detected a LuaJIT version which is not OpenResty's" + "; many optimizations will be disabled and " + "performance will be compromised (see " + "https://github.com/openresty/luajit2 for " + "OpenResty's LuaJIT or, even better, consider using " + "the OpenResty releases from https://openresty.org/" + "en/download.html)"); + } +#else +# if !defined(HAVE_LUA_RESETTHREAD) + ngx_log_error(NGX_LOG_ALERT, cf->log, 0, + "detected an old version of OpenResty's LuaJIT missing " + "the lua_resetthread API and thus the " + "performance will be compromised; please upgrade to the " + "latest version of OpenResty's LuaJIT: " + "https://github.com/openresty/luajit2"); +# endif +# if !defined(HAVE_LUA_EXDATA2) + ngx_log_error(NGX_LOG_ALERT, cf->log, 0, + "detected an old version of OpenResty's LuaJIT missing " + "the exdata2 API and thus the " + "performance will be compromised; please upgrade to the " + "latest version of OpenResty's LuaJIT: " + "https://github.com/openresty/luajit2"); +# endif +#endif + ngx_http_lua_content_length_hash = ngx_http_lua_hash_literal("content-length"); ngx_http_lua_location_hash = ngx_http_lua_hash_literal("location"); - lmcf->lua = ngx_http_lua_init_vm(NULL, cf->cycle, cf->pool, lmcf, - cf->log, NULL); - if (lmcf->lua == NULL) { - ngx_conf_log_error(NGX_LOG_ERR, cf, 0, - "failed to initialize Lua VM"); + rc = ngx_http_lua_init_vm(&lmcf->lua, NULL, cf->cycle, cf->pool, + lmcf, cf->log, NULL); + if (rc != NGX_OK) { + if (rc == NGX_DECLINED) { + ngx_http_lua_assert(lmcf->lua != NULL); + + ngx_conf_log_error(NGX_LOG_ALERT, cf, 0, + "failed to load the 'resty.core' module " + "(https://github.com/openresty/lua-resty" + "-core); ensure you are using an OpenResty " + "release from https://openresty.org/en/" + "download.html (reason: %s)", + lua_tostring(lmcf->lua, -1)); + + } else { + /* rc == NGX_ERROR */ + ngx_conf_log_error(NGX_LOG_ALERT, cf, 0, + "failed to initialize Lua VM"); + } + return NGX_ERROR; } + /* rc == NGX_OK */ + + ngx_http_lua_assert(lmcf->lua != NULL); + if (!lmcf->requires_shm && lmcf->init_handler) { saved_cycle = ngx_cycle; ngx_cycle = cf->cycle; @@ -802,9 +1050,7 @@ ngx_http_lua_lowat_check(ngx_conf_t *cf, void *post, void *data) static void * ngx_http_lua_create_main_conf(ngx_conf_t *cf) { -#ifndef NGX_LUA_NO_FFI_API ngx_int_t rc; -#endif ngx_http_lua_main_conf_t *lmcf; @@ -825,6 +1071,13 @@ ngx_http_lua_create_main_conf(ngx_conf_t *cf) * lmcf->shm_zones = NULL; * lmcf->init_handler = NULL; * lmcf->init_src = { 0, NULL }; + * lmcf->init_chunkname = NULL; + * lmcf->init_worker_handler = NULL; + * lmcf->init_worker_src = { 0, NULL }; + * lmcf->init_worker_chunkname = NULL; + * lmcf->exit_worker_handler = NULL; + * lmcf->exit_worker_src = { 0, NULL }; + * lmcf->exit_worker_chunkname = NULL; * lmcf->shm_zones_inited = 0; * lmcf->shdict_zones = NULL; * lmcf->preload_hooks = NULL; @@ -840,6 +1093,7 @@ ngx_http_lua_create_main_conf(ngx_conf_t *cf) lmcf->pool = cf->pool; lmcf->max_pending_timers = NGX_CONF_UNSET; lmcf->max_running_timers = NGX_CONF_UNSET; + lmcf->lua_thread_cache_max_entries = NGX_CONF_UNSET; #if (NGX_PCRE) lmcf->regex_cache_max_entries = NGX_CONF_UNSET; lmcf->regex_match_limit = NGX_CONF_UNSET; @@ -847,18 +1101,20 @@ ngx_http_lua_create_main_conf(ngx_conf_t *cf) lmcf->postponed_to_rewrite_phase_end = NGX_CONF_UNSET; lmcf->postponed_to_access_phase_end = NGX_CONF_UNSET; + lmcf->set_sa_restart = NGX_CONF_UNSET; + #if (NGX_HTTP_LUA_HAVE_MALLOC_TRIM) lmcf->malloc_trim_cycle = NGX_CONF_UNSET_UINT; #endif -#ifndef NGX_LUA_NO_FFI_API rc = ngx_http_lua_sema_mm_init(cf, lmcf); if (rc != NGX_OK) { return NULL; } + lmcf->worker_thread_vm_pool_size = NGX_CONF_UNSET; + dd("nginx Lua module main config structure initialized!"); -#endif return lmcf; } @@ -867,7 +1123,27 @@ ngx_http_lua_create_main_conf(ngx_conf_t *cf) static char * ngx_http_lua_init_main_conf(ngx_conf_t *cf, void *conf) { - ngx_http_lua_main_conf_t *lmcf = conf; +#ifdef HAVE_LUA_RESETTHREAD + ngx_int_t i, n; + ngx_http_lua_thread_ref_t *trefs; +#endif + + ngx_http_lua_main_conf_t *lmcf = conf; + + if (lmcf->lua_thread_cache_max_entries < 0) { + lmcf->lua_thread_cache_max_entries = 1024; + +#ifndef HAVE_LUA_RESETTHREAD + + } else if (lmcf->lua_thread_cache_max_entries > 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "lua_thread_cache_max_entries has no effect when " + "LuaJIT has no support for the lua_resetthread API " + "(you forgot to use OpenResty's LuaJIT?)"); + return NGX_CONF_ERROR; + +#endif + } #if (NGX_PCRE) if (lmcf->regex_cache_max_entries == NGX_CONF_UNSET) { @@ -887,6 +1163,12 @@ ngx_http_lua_init_main_conf(ngx_conf_t *cf, void *conf) lmcf->max_running_timers = 256; } +#if (NGX_HTTP_LUA_HAVE_SA_RESTART) + if (lmcf->set_sa_restart == NGX_CONF_UNSET) { + lmcf->set_sa_restart = 1; + } +#endif + #if (NGX_HTTP_LUA_HAVE_MALLOC_TRIM) if (lmcf->malloc_trim_cycle == NGX_CONF_UNSET_UINT) { lmcf->malloc_trim_cycle = 1000; /* number of reqs */ @@ -895,6 +1177,39 @@ ngx_http_lua_init_main_conf(ngx_conf_t *cf, void *conf) lmcf->cycle = cf->cycle; + ngx_queue_init(&lmcf->free_lua_threads); + ngx_queue_init(&lmcf->cached_lua_threads); + +#ifdef HAVE_LUA_RESETTHREAD + n = lmcf->lua_thread_cache_max_entries; + + if (n > 0) { + trefs = ngx_palloc(cf->pool, n * sizeof(ngx_http_lua_thread_ref_t)); + if (trefs == NULL) { + return NGX_CONF_ERROR; + } + + for (i = 0; i < n; i++) { + trefs[i].ref = LUA_NOREF; + trefs[i].co = NULL; + ngx_queue_insert_head(&lmcf->free_lua_threads, &trefs[i].queue); + } + } +#endif + + if (lmcf->worker_thread_vm_pool_size == NGX_CONF_UNSET_UINT) { + lmcf->worker_thread_vm_pool_size = 10; + } + + if (ngx_http_lua_init_builtin_headers_out(cf, lmcf) != NGX_OK) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "init header out error"); + + return NGX_CONF_ERROR; + } + + dd("init built in headers out hash size: %ld", + lmcf->builtin_headers_out.size); + return NGX_CONF_OK; } @@ -910,23 +1225,49 @@ ngx_http_lua_create_srv_conf(ngx_conf_t *cf) } /* set by ngx_pcalloc: + * lscf->srv.ssl_client_hello_handler = NULL; + * lscf->srv.ssl_client_hello_src = { 0, NULL }; + * lscf->srv.ssl_client_hello_chunkname = NULL; + * lscf->srv.ssl_client_hello_src_key = NULL; + * * lscf->srv.ssl_cert_handler = NULL; * lscf->srv.ssl_cert_src = { 0, NULL }; + * lscf->srv.ssl_cert_chunkname = NULL; * lscf->srv.ssl_cert_src_key = NULL; * - * lscf->srv.ssl_session_store_handler = NULL; - * lscf->srv.ssl_session_store_src = { 0, NULL }; - * lscf->srv.ssl_session_store_src_key = NULL; + * lscf->srv.ssl_sess_store_handler = NULL; + * lscf->srv.ssl_sess_store_src = { 0, NULL }; + * lscf->srv.ssl_sess_store_chunkname = NULL; + * lscf->srv.ssl_sess_store_src_key = NULL; * - * lscf->srv.ssl_session_fetch_handler = NULL; - * lscf->srv.ssl_session_fetch_src = { 0, NULL }; - * lscf->srv.ssl_session_fetch_src_key = NULL; + * lscf->srv.ssl_sess_fetch_handler = NULL; + * lscf->srv.ssl_sess_fetch_src = { 0, NULL }; + * lscf->srv.ssl_sess_fetch_chunkname = NULL; + * lscf->srv.ssl_sess_fetch_src_key = NULL; * + * lscf->srv.server_rewrite_handler = NULL; + * lscf->srv.server_rewrite_src = { 0, NULL }; + * lscf->srv.server_rewrite_chunkname = NULL; + * lscf->srv.server_rewrite_src_key = NULL; + * + * lscf->balancer.original_init_upstream = NULL; + * lscf->balancer.original_init_peer = NULL; * lscf->balancer.handler = NULL; * lscf->balancer.src = { 0, NULL }; + * lscf->balancer.chunkname = NULL; * lscf->balancer.src_key = NULL; */ +#if (NGX_HTTP_SSL) + lscf->srv.ssl_client_hello_src_ref = LUA_REFNIL; + lscf->srv.ssl_cert_src_ref = LUA_REFNIL; + lscf->srv.ssl_sess_store_src_ref = LUA_REFNIL; + lscf->srv.ssl_sess_fetch_src_ref = LUA_REFNIL; +#endif + + lscf->srv.server_rewrite_src_ref = LUA_REFNIL; + lscf->balancer.src_ref = LUA_REFNIL; + lscf->balancer.max_cached = NGX_CONF_UNSET_UINT; return lscf; } @@ -934,18 +1275,62 @@ ngx_http_lua_create_srv_conf(ngx_conf_t *cf) static char * ngx_http_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) { + ngx_http_lua_srv_conf_t *conf = child; + ngx_http_lua_srv_conf_t *prev = parent; + #if (NGX_HTTP_SSL) - ngx_http_lua_srv_conf_t *prev = parent; - ngx_http_lua_srv_conf_t *conf = child; ngx_http_ssl_srv_conf_t *sscf; dd("merge srv conf"); + if (conf->srv.ssl_client_hello_src.len == 0) { + conf->srv.ssl_client_hello_src = prev->srv.ssl_client_hello_src; + conf->srv.ssl_client_hello_src_ref = prev->srv.ssl_client_hello_src_ref; + conf->srv.ssl_client_hello_src_key = prev->srv.ssl_client_hello_src_key; + conf->srv.ssl_client_hello_handler = prev->srv.ssl_client_hello_handler; + conf->srv.ssl_client_hello_chunkname + = prev->srv.ssl_client_hello_chunkname; + } + + if (conf->srv.ssl_client_hello_src.len) { + sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); + if (sscf == NULL || sscf->ssl.ctx == NULL) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl configured for the server"); + + return NGX_CONF_ERROR; + } +#ifdef LIBRESSL_VERSION_NUMBER + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "LibreSSL does not support by ssl_client_hello_by_lua*"); + return NGX_CONF_ERROR; + +#else + +#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB + + SSL_CTX_set_client_hello_cb(sscf->ssl.ctx, + ngx_http_lua_ssl_client_hello_handler, + NULL); + +#else + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "OpenSSL too old to support " + "ssl_client_hello_by_lua*"); + return NGX_CONF_ERROR; + +#endif +#endif + } + if (conf->srv.ssl_cert_src.len == 0) { conf->srv.ssl_cert_src = prev->srv.ssl_cert_src; + conf->srv.ssl_cert_src_ref = prev->srv.ssl_cert_src_ref; conf->srv.ssl_cert_src_key = prev->srv.ssl_cert_src_key; conf->srv.ssl_cert_handler = prev->srv.ssl_cert_handler; + conf->srv.ssl_cert_chunkname = prev->srv.ssl_cert_chunkname; } if (conf->srv.ssl_cert_src.len) { @@ -960,7 +1345,7 @@ ngx_http_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) #ifdef LIBRESSL_VERSION_NUMBER ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "LibreSSL does not support ssl_certificate_by_lua*"); + "LibreSSL is not supported by ssl_certificate_by_lua*"); return NGX_CONF_ERROR; #else @@ -982,8 +1367,10 @@ ngx_http_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->srv.ssl_sess_store_src.len == 0) { conf->srv.ssl_sess_store_src = prev->srv.ssl_sess_store_src; + conf->srv.ssl_sess_store_src_ref = prev->srv.ssl_sess_store_src_ref; conf->srv.ssl_sess_store_src_key = prev->srv.ssl_sess_store_src_key; conf->srv.ssl_sess_store_handler = prev->srv.ssl_sess_store_handler; + conf->srv.ssl_sess_store_chunkname = prev->srv.ssl_sess_store_chunkname; } if (conf->srv.ssl_sess_store_src.len) { @@ -991,7 +1378,7 @@ ngx_http_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) if (sscf && sscf->ssl.ctx) { #ifdef LIBRESSL_VERSION_NUMBER ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "LibreSSL does not support " + "LibreSSL is not supported by " "ssl_session_store_by_lua*"); return NGX_CONF_ERROR; @@ -1004,8 +1391,10 @@ ngx_http_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->srv.ssl_sess_fetch_src.len == 0) { conf->srv.ssl_sess_fetch_src = prev->srv.ssl_sess_fetch_src; + conf->srv.ssl_sess_fetch_src_ref = prev->srv.ssl_sess_fetch_src_ref; conf->srv.ssl_sess_fetch_src_key = prev->srv.ssl_sess_fetch_src_key; conf->srv.ssl_sess_fetch_handler = prev->srv.ssl_sess_fetch_handler; + conf->srv.ssl_sess_fetch_chunkname = prev->srv.ssl_sess_fetch_chunkname; } if (conf->srv.ssl_sess_fetch_src.len) { @@ -1013,7 +1402,7 @@ ngx_http_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) if (sscf && sscf->ssl.ctx) { #ifdef LIBRESSL_VERSION_NUMBER ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "LibreSSL does not support " + "LibreSSL is not supported by " "ssl_session_fetch_by_lua*"); return NGX_CONF_ERROR; @@ -1025,6 +1414,16 @@ ngx_http_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } #endif /* NGX_HTTP_SSL */ + + if (conf->srv.server_rewrite_src.value.len == 0) { + conf->srv.server_rewrite_src = prev->srv.server_rewrite_src; + conf->srv.server_rewrite_src_ref = prev->srv.server_rewrite_src_ref; + conf->srv.server_rewrite_src_key = prev->srv.server_rewrite_src_key; + conf->srv.server_rewrite_handler = prev->srv.server_rewrite_handler; + conf->srv.server_rewrite_chunkname + = prev->srv.server_rewrite_chunkname; + } + return NGX_CONF_OK; } @@ -1042,31 +1441,45 @@ ngx_http_lua_create_loc_conf(ngx_conf_t *cf) /* set by ngx_pcalloc: * conf->access_src = {{ 0, NULL }, NULL, NULL, NULL}; * conf->access_src_key = NULL + * conf->access_handler = NULL; + * conf->access_chunkname = NULL; + * * conf->rewrite_src = {{ 0, NULL }, NULL, NULL, NULL}; - * conf->rewrite_src_key = NULL + * conf->rewrite_src_key = NULL; * conf->rewrite_handler = NULL; + * conf->rewrite_chunkname = NULL; * * conf->content_src = {{ 0, NULL }, NULL, NULL, NULL}; - * conf->content_src_key = NULL + * conf->content_src_key = NULL; * conf->content_handler = NULL; + * conf->content_chunkname = NULL; * * conf->log_src = {{ 0, NULL }, NULL, NULL, NULL}; - * conf->log_src_key = NULL + * conf->log_src_key = NULL; * conf->log_handler = NULL; + * conf->log_chunkname = NULL; * * conf->header_filter_src = {{ 0, NULL }, NULL, NULL, NULL}; - * conf->header_filter_src_key = NULL + * conf->header_filter_src_key = NULL; * conf->header_filter_handler = NULL; + * conf->header_filter_chunkname = NULL; * * conf->body_filter_src = {{ 0, NULL }, NULL, NULL, NULL}; - * conf->body_filter_src_key = NULL + * conf->body_filter_src_key = NULL; * conf->body_filter_handler = NULL; + * conf->body_filter_chunkname = NULL; * * conf->ssl = 0; * conf->ssl_protocols = 0; * conf->ssl_ciphers = { 0, NULL }; * conf->ssl_trusted_certificate = { 0, NULL }; * conf->ssl_crl = { 0, NULL }; + * conf->ssl_key_log = { 0, NULL }; + * + * conf->proxy_ssl_verify_handler = NULL; + * conf->proxy_ssl_verify_src = { 0, NULL }; + * conf->proxy_ssl_verify_chunkname = NULL; + * conf->proxy_ssl_verify_src_key = NULL; */ conf->force_read_body = NGX_CONF_UNSET; @@ -1086,8 +1499,24 @@ ngx_http_lua_create_loc_conf(ngx_conf_t *cf) conf->transform_underscores_in_resp_headers = NGX_CONF_UNSET; conf->log_socket_errors = NGX_CONF_UNSET; + conf->rewrite_src_ref = LUA_REFNIL; + conf->access_src_ref = LUA_REFNIL; + conf->content_src_ref = LUA_REFNIL; + conf->header_filter_src_ref = LUA_REFNIL; + conf->body_filter_src_ref = LUA_REFNIL; + conf->log_src_ref = LUA_REFNIL; + #if (NGX_HTTP_SSL) conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; + conf->ssl_certificates = NGX_CONF_UNSET_PTR; + conf->ssl_certificate_keys = NGX_CONF_UNSET_PTR; +#if (nginx_version >= 1019004) + conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; +#endif +#ifdef HAVE_PROXY_SSL_PATCH + conf->proxy_ssl_verify_src_ref = LUA_REFNIL; + conf->upstream_skip_openssl_default_verify = NGX_CONF_UNSET; +#endif #endif return conf; @@ -1103,6 +1532,7 @@ ngx_http_lua_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->rewrite_src.value.len == 0) { conf->rewrite_src = prev->rewrite_src; conf->rewrite_handler = prev->rewrite_handler; + conf->rewrite_src_ref = prev->rewrite_src_ref; conf->rewrite_src_key = prev->rewrite_src_key; conf->rewrite_chunkname = prev->rewrite_chunkname; } @@ -1110,6 +1540,7 @@ ngx_http_lua_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->access_src.value.len == 0) { conf->access_src = prev->access_src; conf->access_handler = prev->access_handler; + conf->access_src_ref = prev->access_src_ref; conf->access_src_key = prev->access_src_key; conf->access_chunkname = prev->access_chunkname; } @@ -1117,6 +1548,7 @@ ngx_http_lua_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->content_src.value.len == 0) { conf->content_src = prev->content_src; conf->content_handler = prev->content_handler; + conf->content_src_ref = prev->content_src_ref; conf->content_src_key = prev->content_src_key; conf->content_chunkname = prev->content_chunkname; } @@ -1124,6 +1556,7 @@ ngx_http_lua_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->log_src.value.len == 0) { conf->log_src = prev->log_src; conf->log_handler = prev->log_handler; + conf->log_src_ref = prev->log_src_ref; conf->log_src_key = prev->log_src_key; conf->log_chunkname = prev->log_chunkname; } @@ -1131,34 +1564,71 @@ ngx_http_lua_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->header_filter_src.value.len == 0) { conf->header_filter_src = prev->header_filter_src; conf->header_filter_handler = prev->header_filter_handler; + conf->header_filter_src_ref = prev->header_filter_src_ref; conf->header_filter_src_key = prev->header_filter_src_key; + conf->header_filter_chunkname = prev->header_filter_chunkname; } if (conf->body_filter_src.value.len == 0) { conf->body_filter_src = prev->body_filter_src; conf->body_filter_handler = prev->body_filter_handler; + conf->body_filter_src_ref = prev->body_filter_src_ref; conf->body_filter_src_key = prev->body_filter_src_key; + conf->body_filter_chunkname = prev->body_filter_chunkname; } #if (NGX_HTTP_SSL) -# if defined(nginx_version) && nginx_version >= 1001013 + if (ngx_http_lua_merge_ssl(cf, conf, prev) != NGX_OK) { + return NGX_CONF_ERROR; + } ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols, - (NGX_CONF_BITMASK_SET|NGX_SSL_SSLv3 + (NGX_CONF_BITMASK_SET |NGX_SSL_TLSv1|NGX_SSL_TLSv1_1 - |NGX_SSL_TLSv1_2)); - -# endif + |NGX_SSL_TLSv1_2 +#ifdef NGX_SSL_TLSv1_3 + |NGX_SSL_TLSv1_3 +#endif + )); ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, "DEFAULT"); ngx_conf_merge_uint_value(conf->ssl_verify_depth, prev->ssl_verify_depth, 1); + ngx_conf_merge_ptr_value(conf->ssl_certificates, + prev->ssl_certificates, NULL); + ngx_conf_merge_ptr_value(conf->ssl_certificate_keys, + prev->ssl_certificate_keys, NULL); ngx_conf_merge_str_value(conf->ssl_trusted_certificate, prev->ssl_trusted_certificate, ""); ngx_conf_merge_str_value(conf->ssl_crl, prev->ssl_crl, ""); + ngx_conf_merge_str_value(conf->ssl_key_log, prev->ssl_key_log, ""); + +#if (nginx_version >= 1019004) + ngx_conf_merge_ptr_value(conf->ssl_conf_commands, prev->ssl_conf_commands, + NULL); +#endif + +#ifdef HAVE_PROXY_SSL_PATCH + if (conf->proxy_ssl_verify_src.len == 0) { + conf->proxy_ssl_verify_src = prev->proxy_ssl_verify_src; + conf->proxy_ssl_verify_handler = prev->proxy_ssl_verify_handler; + conf->proxy_ssl_verify_src_ref = prev->proxy_ssl_verify_src_ref; + conf->proxy_ssl_verify_src_key = prev->proxy_ssl_verify_src_key; + conf->proxy_ssl_verify_chunkname = prev->proxy_ssl_verify_chunkname; + } + + if (conf->proxy_ssl_verify_src.len) { + if (ngx_http_lua_proxy_ssl_verify_set_callback(cf) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + ngx_conf_merge_value(conf->upstream_skip_openssl_default_verify, + prev->upstream_skip_openssl_default_verify, 0); +#endif if (ngx_http_lua_set_ssl(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; @@ -1204,17 +1674,78 @@ ngx_http_lua_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) #if (NGX_HTTP_SSL) +static ngx_int_t +ngx_http_lua_merge_ssl(ngx_conf_t *cf, + ngx_http_lua_loc_conf_t *conf, ngx_http_lua_loc_conf_t *prev) +{ + ngx_uint_t preserve; + + if (conf->ssl_protocols == 0 + && conf->ssl_ciphers.data == NULL + && conf->ssl_verify_depth == NGX_CONF_UNSET_UINT + && conf->ssl_certificates == NGX_CONF_UNSET_PTR + && conf->ssl_certificate_keys == NGX_CONF_UNSET_PTR + && conf->ssl_trusted_certificate.data == NULL + && conf->ssl_crl.data == NULL + && conf->ssl_key_log.data == NULL +#if (nginx_version >= 1019004) + && conf->ssl_conf_commands == NGX_CONF_UNSET_PTR +#endif + ) + { + if (prev->ssl) { + conf->ssl = prev->ssl; + return NGX_OK; + } + + preserve = 1; + + } else { + preserve = 0; + } + + conf->ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); + if (conf->ssl == NULL) { + return NGX_ERROR; + } + + conf->ssl->log = cf->log; + + /* + * special handling to preserve conf->ssl_* in the "http" section + * to inherit it to all servers + */ + + if (preserve) { + prev->ssl = conf->ssl; + } + + return NGX_OK; +} + + static ngx_int_t ngx_http_lua_set_ssl(ngx_conf_t *cf, ngx_http_lua_loc_conf_t *llcf) { ngx_pool_cleanup_t *cln; - llcf->ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); - if (llcf->ssl == NULL) { - return NGX_ERROR; + if (llcf->ssl->ctx) { + return NGX_OK; } - llcf->ssl->log = cf->log; + if (llcf->ssl_certificates) { + if (llcf->ssl_certificate_keys == NULL + || llcf->ssl_certificate_keys->nelts + < llcf->ssl_certificates->nelts) + { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"lua_ssl_certificate_key\" is defined " + "for certificate \"%V\"", + ((ngx_str_t *) llcf->ssl_certificates->elts) + + llcf->ssl_certificates->nelts - 1); + return NGX_ERROR; + } + } if (ngx_ssl_create(llcf->ssl, llcf->ssl_protocols, NULL) != NGX_OK) { return NGX_ERROR; @@ -1222,6 +1753,7 @@ ngx_http_lua_set_ssl(ngx_conf_t *cf, ngx_http_lua_loc_conf_t *llcf) cln = ngx_pool_cleanup_add(cf->pool, 0); if (cln == NULL) { + ngx_ssl_cleanup_ctx(llcf->ssl); return NGX_ERROR; } @@ -1238,37 +1770,156 @@ ngx_http_lua_set_ssl(ngx_conf_t *cf, ngx_http_lua_loc_conf_t *llcf) return NGX_ERROR; } - if (llcf->ssl_trusted_certificate.len) { + if (llcf->ssl_certificates + && ngx_ssl_certificates(cf, llcf->ssl, + llcf->ssl_certificates, + llcf->ssl_certificate_keys, + NULL) + != NGX_OK) + { + return NGX_ERROR; + } -#if defined(nginx_version) && nginx_version >= 1003007 + if (llcf->ssl_trusted_certificate.len + && ngx_ssl_trusted_certificate(cf, llcf->ssl, + &llcf->ssl_trusted_certificate, + llcf->ssl_verify_depth) + != NGX_OK) + { + return NGX_ERROR; + } - if (ngx_ssl_trusted_certificate(cf, llcf->ssl, - &llcf->ssl_trusted_certificate, - llcf->ssl_verify_depth) - != NGX_OK) - { - return NGX_ERROR; - } + dd("ssl crl: %.*s", (int) llcf->ssl_crl.len, llcf->ssl_crl.data); -#else + if (ngx_ssl_crl(cf, llcf->ssl, &llcf->ssl_crl) != NGX_OK) { + return NGX_ERROR; + } - ngx_log_error(NGX_LOG_CRIT, cf->log, 0, "at least nginx 1.3.7 is " - "required for the \"lua_ssl_trusted_certificate\" " - "directive"); + if (ngx_http_lua_ssl_key_log(cf, llcf->ssl, &llcf->ssl_key_log) + != NGX_OK) + { return NGX_ERROR; + } +#if (nginx_version >= 1019004) + if (ngx_ssl_conf_commands(cf, llcf->ssl, llcf->ssl_conf_commands) + != NGX_OK) + { + return NGX_ERROR; + } #endif + + return NGX_OK; +} + + +static void +key_log_callback(const ngx_ssl_conn_t *ssl_conn, const char *line) +{ + ngx_http_lua_ssl_key_log_t *ssl_key_log; + ngx_connection_t *c; + + ssl_key_log = SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl_conn), + ngx_http_lua_ssl_key_log_index); + if (ssl_key_log == NULL) { + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + ngx_ssl_error(NGX_LOG_DEBUG, c->log, 0, "get ssl key log failed"); + + return; } - dd("ssl crl: %.*s", (int) llcf->ssl_crl.len, llcf->ssl_crl.data); + (void) ngx_write_fd(ssl_key_log->fd, (void *) line, ngx_strlen(line)); + (void) ngx_write_fd(ssl_key_log->fd, (void *) "\n", 1); +} - if (ngx_ssl_crl(cf, llcf->ssl, &llcf->ssl_crl) != NGX_OK) { + +static void +ngx_http_lua_ssl_cleanup_key_log(void *data) +{ + ngx_http_lua_ssl_key_log_t *ssl_key_log = data; + + if (ngx_close_file(ssl_key_log->fd) == NGX_FILE_ERROR) { + ngx_ssl_error(NGX_LOG_ALERT, ssl_key_log->ssl->log, 0, + ngx_close_file_n "(\"%V\") failed", ssl_key_log->name); + } +} + + +static ngx_int_t +ngx_http_lua_ssl_key_log(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file) +{ + ngx_fd_t fd; + ngx_http_lua_ssl_key_log_t *ssl_key_log; + ngx_pool_cleanup_t *cln; + + if (!file->len) { + return NGX_OK; + } + + if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_http_lua_ssl_init(cf->log) != NGX_OK) { + return NGX_ERROR; + } + + /* + * append so that existing keylog file contents can be preserved + */ + fd = ngx_open_file(file->data, NGX_FILE_APPEND, NGX_FILE_CREATE_OR_OPEN, + NGX_FILE_DEFAULT_ACCESS); + if (fd == NGX_INVALID_FILE) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, ngx_open_file_n + "(\"%V\") failed", file); return NGX_ERROR; } + ssl_key_log = ngx_palloc(cf->pool, sizeof(ngx_http_lua_ssl_key_log_t)); + if (ssl_key_log == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "ngx_pcalloc() failed"); + return NGX_ERROR; + } + + ssl_key_log->ssl = ssl; + ssl_key_log->fd = fd; + ssl_key_log->name = *file; + + if (SSL_CTX_set_ex_data(ssl->ctx, ngx_http_lua_ssl_key_log_index, + ssl_key_log) == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set_ex_data() failed"); + return NGX_ERROR; + } + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + ngx_http_lua_ssl_cleanup_key_log(ssl_key_log); + return NGX_ERROR; + } + + cln->handler = ngx_http_lua_ssl_cleanup_key_log; + cln->data = ssl_key_log; + + SSL_CTX_set_keylog_callback(ssl->ctx, key_log_callback); + return NGX_OK; } + +#if (nginx_version >= 1019004) +static char * +ngx_http_lua_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) +{ +#ifndef SSL_CONF_FLAG_FILE + return "is not supported on this platform"; +#endif + + return NGX_CONF_OK; +} +#endif + #endif /* NGX_HTTP_SSL */ diff --git a/src/ngx_http_lua_ndk.c b/src/ngx_http_lua_ndk.c index 24b80b4d36..72ff7de815 100644 --- a/src/ngx_http_lua_ndk.c +++ b/src/ngx_http_lua_ndk.c @@ -19,93 +19,20 @@ static ndk_set_var_value_pt ngx_http_lookup_ndk_set_var_directive(u_char *name, size_t name_len); -static int ngx_http_lua_ndk_set_var_get(lua_State *L); -static int ngx_http_lua_ndk_set_var_set(lua_State *L); -static int ngx_http_lua_run_set_var_directive(lua_State *L); -int -ngx_http_lua_ndk_set_var_get(lua_State *L) -{ - ndk_set_var_value_pt func; - size_t len; - u_char *p; - - p = (u_char *) luaL_checklstring(L, 2, &len); - - dd("ndk.set_var metatable __index: %s", p); - - func = ngx_http_lookup_ndk_set_var_directive(p, len); - - if (func == NULL) { - return luaL_error(L, "ndk.set_var: directive \"%s\" not found " - "or does not use ndk_set_var_value", p); - } - - lua_pushvalue(L, -1); /* table key key */ - lua_pushvalue(L, -1); /* table key key key */ - lua_pushlightuserdata(L, (void *) func); /* table key key key func */ - lua_pushcclosure(L, ngx_http_lua_run_set_var_directive, 2); - /* table key key closure */ - lua_rawset(L, 1); /* table key */ - lua_rawget(L, 1); /* table closure */ - - return 1; -} - - -int -ngx_http_lua_ndk_set_var_set(lua_State *L) -{ - return luaL_error(L, "Not allowed"); -} - - -int -ngx_http_lua_run_set_var_directive(lua_State *L) +void +ngx_http_lua_inject_ndk_api(lua_State *L) { - ngx_int_t rc; - ndk_set_var_value_pt func; - ngx_str_t res; - ngx_http_variable_value_t arg; - u_char *p; - size_t len; - ngx_http_request_t *r; - - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one argument"); - } - -#if 1 - ngx_memzero(&arg, sizeof(ngx_http_variable_value_t)); - - arg.valid = 1; -#endif - - arg.data = (u_char *) luaL_checklstring(L, 1, &len); - arg.len = len; - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } - - p = (u_char *) luaL_checklstring(L, lua_upvalueindex(1), &len); - - dd("calling set_var func for %s", p); - - func = (ndk_set_var_value_pt) lua_touserdata(L, lua_upvalueindex(2)); - - rc = func(r, &res, &arg); - - if (rc != NGX_OK) { - return luaL_error(L, "calling directive %s failed with code %d", - p, (int) rc); - } + lua_createtable(L, 0, 1 /* nrec */); /* ndk.* */ - lua_pushlstring(L, (char *) res.data, res.len); + lua_getglobal(L, "package"); /* ndk package */ + lua_getfield(L, -1, "loaded"); /* ndk package loaded */ + lua_pushvalue(L, -3); /* ndk package loaded ndk */ + lua_setfield(L, -2, "ndk"); /* ndk package loaded */ + lua_pop(L, 2); - return 1; + lua_setglobal(L, "ndk"); } @@ -119,7 +46,7 @@ ngx_http_lookup_ndk_set_var_directive(u_char *name, ngx_module_t **modules; ngx_command_t *cmd; -#if defined(nginx_version) && nginx_version >= 1009011 +#if (nginx_version >= 1009011) modules = ngx_cycle->modules; #else modules = ngx_modules; @@ -160,32 +87,48 @@ ngx_http_lookup_ndk_set_var_directive(u_char *name, } -void -ngx_http_lua_inject_ndk_api(lua_State *L) +int +ngx_http_lua_ffi_ndk_lookup_directive(const u_char *var_data, + size_t var_len, ndk_set_var_value_pt *func) { - lua_createtable(L, 0, 1 /* nrec */); /* ndk.* */ + *func = ngx_http_lookup_ndk_set_var_directive((u_char *) var_data, var_len); - lua_newtable(L); /* .set_var */ + if (*func == NULL) { + return NGX_ERROR; + } - lua_createtable(L, 0, 2 /* nrec */); /* metatable for .set_var */ - lua_pushcfunction(L, ngx_http_lua_ndk_set_var_get); - lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, ngx_http_lua_ndk_set_var_set); - lua_setfield(L, -2, "__newindex"); - lua_setmetatable(L, -2); + return NGX_OK; +} - lua_setfield(L, -2, "set_var"); - lua_getglobal(L, "package"); /* ndk package */ - lua_getfield(L, -1, "loaded"); /* ndk package loaded */ - lua_pushvalue(L, -3); /* ndk package loaded ndk */ - lua_setfield(L, -2, "ndk"); /* ndk package loaded */ - lua_pop(L, 2); +int +ngx_http_lua_ffi_ndk_set_var_get(ngx_http_request_t *r, + ndk_set_var_value_pt func, const u_char *arg_data, size_t arg_len, + ngx_http_lua_ffi_str_t *value) +{ + ngx_int_t rc; + ngx_str_t res; + ngx_http_variable_value_t arg; - lua_setglobal(L, "ndk"); + ngx_memzero(&arg, sizeof(ngx_http_variable_value_t)); + arg.valid = 1; + + arg.data = (u_char *) arg_data; + arg.len = arg_len; + + rc = func(r, &res, &arg); + + if (rc != NGX_OK) { + return rc; + } + + value->data = res.data; + value->len = res.len; + return NGX_OK; } #endif /* defined(NDK) && NDK */ + /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_output.c b/src/ngx_http_lua_output.c index 0fe8840f95..8681947263 100644 --- a/src/ngx_http_lua_output.c +++ b/src/ngx_http_lua_output.c @@ -63,6 +63,7 @@ ngx_http_lua_ngx_echo(lua_State *L, unsigned newline) } ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE + | NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE | NGX_HTTP_LUA_CONTEXT_ACCESS | NGX_HTTP_LUA_CONTEXT_CONTENT); @@ -93,6 +94,9 @@ ngx_http_lua_ngx_echo(lua_State *L, unsigned newline) switch (type) { case LUA_TNUMBER: + size += ngx_http_lua_get_num_len(L, i); + break; + case LUA_TSTRING: lua_tolstring(L, i, &len); @@ -173,6 +177,9 @@ ngx_http_lua_ngx_echo(lua_State *L, unsigned newline) type = lua_type(L, i); switch (type) { case LUA_TNUMBER: + b->last = ngx_http_lua_write_num(L, i, b->last); + break; + case LUA_TSTRING: p = lua_tolstring(L, i, &len); b->last = ngx_copy(b->last, (u_char *) p, len); @@ -238,6 +245,11 @@ ngx_http_lua_ngx_echo(lua_State *L, unsigned newline) return 2; } + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "%s has %sbusy bufs", + newline ? "lua say response" : "lua print response", + ctx->busy_bufs != NULL ? "" : "no "); + dd("downstream write: %d, buf len: %d", (int) rc, (int) (b->last - b->pos)); @@ -301,8 +313,10 @@ ngx_http_lua_calc_strlen_in_table(lua_State *L, int index, int arg_i, switch (type) { case LUA_TNUMBER: - case LUA_TSTRING: + size += ngx_http_lua_get_num_len(L, -1); + break; + case LUA_TSTRING: lua_tolstring(L, -1, &len); size += len; break; @@ -396,6 +410,9 @@ ngx_http_lua_copy_str_in_table(lua_State *L, int index, u_char *dst) type = lua_type(L, -1); switch (type) { case LUA_TNUMBER: + dst = ngx_http_lua_write_num(L, -1, dst); + break; + case LUA_TSTRING: p = (u_char *) lua_tolstring(L, -1, &len); dst = ngx_copy(dst, p, len); @@ -483,6 +500,7 @@ ngx_http_lua_ngx_flush(lua_State *L) } ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE + | NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE | NGX_HTTP_LUA_CONTEXT_ACCESS | NGX_HTTP_LUA_CONTEXT_CONTENT); @@ -548,7 +566,8 @@ ngx_http_lua_ngx_flush(lua_State *L) wev = r->connection->write; - if (wait && (r->connection->buffered & NGX_HTTP_LOWLEVEL_BUFFERED + if (wait && (r->connection->buffered + & (NGX_HTTP_LOWLEVEL_BUFFERED | NGX_LOWLEVEL_BUFFERED) || wev->delayed)) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -636,6 +655,7 @@ ngx_http_lua_ngx_eof(lua_State *L) } ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE + | NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE | NGX_HTTP_LUA_CONTEXT_ACCESS | NGX_HTTP_LUA_CONTEXT_CONTENT); @@ -698,19 +718,18 @@ ngx_http_lua_ngx_send_headers(lua_State *L) } ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE + | NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE | NGX_HTTP_LUA_CONTEXT_ACCESS | NGX_HTTP_LUA_CONTEXT_CONTENT); - if (!r->header_sent && !ctx->header_sent) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua send headers"); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua send headers"); - rc = ngx_http_lua_send_header_if_needed(r, ctx); - if (rc == NGX_ERROR || rc > NGX_OK) { - lua_pushnil(L); - lua_pushliteral(L, "nginx output filter error"); - return 2; - } + rc = ngx_http_lua_send_header_if_needed(r, ctx); + if (rc == NGX_ERROR || rc > NGX_OK) { + lua_pushnil(L); + lua_pushliteral(L, "nginx output filter error"); + return 2; } lua_pushinteger(L, 1); diff --git a/src/ngx_http_lua_output.h b/src/ngx_http_lua_output.h index 109a4b44b8..e60f4c7e30 100644 --- a/src/ngx_http_lua_output.h +++ b/src/ngx_http_lua_output.h @@ -23,6 +23,55 @@ ngx_int_t ngx_http_lua_flush_resume_helper(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx); +/* Get the maximum possible length, not the actual length */ +static ngx_inline size_t +ngx_http_lua_get_num_len(lua_State *L, int idx) +{ + double num; + + num = (double) lua_tonumber(L, idx); + if (num == (double) (int32_t) num) { + return NGX_INT32_LEN; + } + + return NGX_DOUBLE_LEN; +} + + +static ngx_inline u_char * +ngx_http_lua_write_num(lua_State *L, int idx, u_char *dst) +{ + double num; + int n; + + num = (double) lua_tonumber(L, idx); + /* + * luajit format number with only 14 significant digits. + * To be consistent with lujit, don't use (double) (long) below + * or integer greater than 99,999,999,999,999 will different from luajit. + */ + if (num == (double) (int32_t) num) { + dst = ngx_snprintf(dst, NGX_INT64_LEN, "%D", (int32_t) num); + + } else { + /* + * The maximum number of significant digits is 14 in lua. + * Please refer to lj_strfmt.c for more details. + */ + n = snprintf((char *) dst, NGX_DOUBLE_LEN, "%.14g", num); + if (n < 0) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "snprintf(\"%f\") failed"); + + } else { + dst += n; + } + } + + return dst; +} + + #endif /* _NGX_HTTP_LUA_OUTPUT_H_INCLUDED_ */ /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_pcrefix.c b/src/ngx_http_lua_pcrefix.c index 562847a639..5693b5495c 100644 --- a/src/ngx_http_lua_pcrefix.c +++ b/src/ngx_http_lua_pcrefix.c @@ -18,15 +18,61 @@ static ngx_pool_t *ngx_http_lua_pcre_pool = NULL; + +#if (NGX_PCRE2) +static ngx_uint_t ngx_regex_direct_alloc; +#else static void *(*old_pcre_malloc)(size_t); static void (*old_pcre_free)(void *ptr); +#endif /* XXX: work-around to nginx regex subsystem, must init a memory pool * to use PCRE functions. As PCRE still has memory-leaking problems, * and nginx overwrote pcre_malloc/free hooks with its own static * functions, so nobody else can reuse nginx regex subsystem... */ -static void * +#if (NGX_PCRE2) + +void * +ngx_http_lua_pcre_malloc(size_t size, void *data) +{ + dd("lua pcre pool is %p", ngx_http_lua_pcre_pool); + + if (ngx_http_lua_pcre_pool) { + return ngx_palloc(ngx_http_lua_pcre_pool, size); + } + + if (ngx_regex_direct_alloc) { + return ngx_alloc(size, ngx_cycle->log); + } + + fprintf(stderr, "error: lua pcre malloc failed due to empty pcre pool"); + + return NULL; +} + + +void +ngx_http_lua_pcre_free(void *ptr, void *data) +{ + dd("lua pcre pool is %p", ngx_http_lua_pcre_pool); + + if (ngx_http_lua_pcre_pool) { + ngx_pfree(ngx_http_lua_pcre_pool, ptr); + return; + } + + if (ngx_regex_direct_alloc) { + ngx_free(ptr); + return; + } + + fprintf(stderr, "error: lua pcre free failed due to empty pcre pool"); +} + +#else + +void * ngx_http_lua_pcre_malloc(size_t size) { dd("lua pcre pool is %p", ngx_http_lua_pcre_pool); @@ -54,6 +100,41 @@ ngx_http_lua_pcre_free(void *ptr) fprintf(stderr, "error: lua pcre free failed due to empty pcre pool"); } +#endif + + +#if (NGX_PCRE2) + +ngx_pool_t * +ngx_http_lua_pcre_malloc_init(ngx_pool_t *pool) +{ + ngx_pool_t *old_pool; + + dd("lua pcre pool was %p", ngx_http_lua_pcre_pool); + + ngx_regex_direct_alloc = (pool == NULL) ? 1 : 0; + + old_pool = ngx_http_lua_pcre_pool; + ngx_http_lua_pcre_pool = pool; + + dd("lua pcre pool is %p", ngx_http_lua_pcre_pool); + + return old_pool; +} + + +void +ngx_http_lua_pcre_malloc_done(ngx_pool_t *old_pool) +{ + dd("lua pcre pool was %p", ngx_http_lua_pcre_pool); + + ngx_http_lua_pcre_pool = old_pool; + ngx_regex_direct_alloc = 0; + + dd("lua pcre pool is %p", ngx_http_lua_pcre_pool); +} + +#else ngx_pool_t * ngx_http_lua_pcre_malloc_init(ngx_pool_t *pool) @@ -101,6 +182,7 @@ ngx_http_lua_pcre_malloc_done(ngx_pool_t *old_pool) } } +#endif #endif /* NGX_PCRE */ /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_pcrefix.h b/src/ngx_http_lua_pcrefix.h index 80f29f9e75..e1097b72eb 100644 --- a/src/ngx_http_lua_pcrefix.h +++ b/src/ngx_http_lua_pcrefix.h @@ -13,8 +13,15 @@ #if (NGX_PCRE) + ngx_pool_t *ngx_http_lua_pcre_malloc_init(ngx_pool_t *pool); void ngx_http_lua_pcre_malloc_done(ngx_pool_t *old_pool); + +#if NGX_PCRE2 +void *ngx_http_lua_pcre_malloc(size_t size, void *data); +void ngx_http_lua_pcre_free(void *ptr, void *data); +#endif + #endif diff --git a/src/ngx_http_lua_phase.c b/src/ngx_http_lua_phase.c index 304c88ae13..85f1e7a312 100644 --- a/src/ngx_http_lua_phase.c +++ b/src/ngx_http_lua_phase.c @@ -10,105 +10,9 @@ #include "ddebug.h" -#include "ngx_http_lua_phase.h" -#include "ngx_http_lua_util.h" -#include "ngx_http_lua_ctx.h" +#include "ngx_http_lua_common.h" -static int ngx_http_lua_ngx_get_phase(lua_State *L); - - -static int -ngx_http_lua_ngx_get_phase(lua_State *L) -{ - ngx_http_request_t *r; - ngx_http_lua_ctx_t *ctx; - - r = ngx_http_lua_get_req(L); - - /* If we have no request object, assume we are called from the "init" - * phase. */ - - if (r == NULL) { - lua_pushliteral(L, "init"); - return 1; - } - - ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); - if (ctx == NULL) { - return luaL_error(L, "no request ctx found"); - } - - switch (ctx->context) { - case NGX_HTTP_LUA_CONTEXT_INIT_WORKER: - lua_pushliteral(L, "init_worker"); - break; - - case NGX_HTTP_LUA_CONTEXT_SET: - lua_pushliteral(L, "set"); - break; - - case NGX_HTTP_LUA_CONTEXT_REWRITE: - lua_pushliteral(L, "rewrite"); - break; - - case NGX_HTTP_LUA_CONTEXT_ACCESS: - lua_pushliteral(L, "access"); - break; - - case NGX_HTTP_LUA_CONTEXT_CONTENT: - lua_pushliteral(L, "content"); - break; - - case NGX_HTTP_LUA_CONTEXT_LOG: - lua_pushliteral(L, "log"); - break; - - case NGX_HTTP_LUA_CONTEXT_HEADER_FILTER: - lua_pushliteral(L, "header_filter"); - break; - - case NGX_HTTP_LUA_CONTEXT_BODY_FILTER: - lua_pushliteral(L, "body_filter"); - break; - - case NGX_HTTP_LUA_CONTEXT_TIMER: - lua_pushliteral(L, "timer"); - break; - - case NGX_HTTP_LUA_CONTEXT_BALANCER: - lua_pushliteral(L, "balancer"); - break; - - case NGX_HTTP_LUA_CONTEXT_SSL_CERT: - lua_pushliteral(L, "ssl_cert"); - break; - - case NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE: - lua_pushliteral(L, "ssl_session_store"); - break; - - case NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH: - lua_pushliteral(L, "ssl_session_fetch"); - break; - - default: - return luaL_error(L, "unknown phase: %#x", (int) ctx->context); - } - - return 1; -} - - -void -ngx_http_lua_inject_phase_api(lua_State *L) -{ - lua_pushcfunction(L, ngx_http_lua_ngx_get_phase); - lua_setfield(L, -2, "get_phase"); -} - - -#ifndef NGX_LUA_NO_FFI_API int ngx_http_lua_ffi_get_phase(ngx_http_request_t *r, char **err) { @@ -122,7 +26,6 @@ ngx_http_lua_ffi_get_phase(ngx_http_request_t *r, char **err) return ctx->context; } -#endif /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_phase.h b/src/ngx_http_lua_phase.h deleted file mode 100644 index dcb670f221..0000000000 --- a/src/ngx_http_lua_phase.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef _NGX_HTTP_LUA_PHASE_H_INCLUDED_ -#define _NGX_HTTP_LUA_PHASE_H_INCLUDED_ - - -#include "ngx_http_lua_common.h" - - -void ngx_http_lua_inject_phase_api(lua_State *L); - - -#endif /* _NGX_HTTP_LUA_PHASE_H_INCLUDED_ */ - -/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_pipe.c b/src/ngx_http_lua_pipe.c new file mode 100644 index 0000000000..8c0884bcc2 --- /dev/null +++ b/src/ngx_http_lua_pipe.c @@ -0,0 +1,2576 @@ + +/* + * Copyright (C) by OpenResty Inc. + */ + + +#ifndef DDEBUG +#define DDEBUG 0 +#endif +#include "ddebug.h" + + +#include "ngx_http_lua_common.h" +#include "ngx_http_lua_input_filters.h" +#include "ngx_http_lua_util.h" +#include "ngx_http_lua_pipe.h" +#if (NGX_HTTP_LUA_HAVE_SIGNALFD) +#include +#endif + + +#ifdef HAVE_NGX_LUA_PIPE +static ngx_rbtree_node_t *ngx_http_lua_pipe_lookup_pid(ngx_rbtree_key_t key); +#if !(NGX_HTTP_LUA_HAVE_SIGNALFD) +static void ngx_http_lua_pipe_sigchld_handler(int signo, siginfo_t *siginfo, + void *ucontext); +#endif +static void ngx_http_lua_pipe_sigchld_event_handler(ngx_event_t *ev); +static ssize_t ngx_http_lua_pipe_fd_read(ngx_connection_t *c, u_char *buf, + size_t size); +static ssize_t ngx_http_lua_pipe_fd_write(ngx_connection_t *c, u_char *buf, + size_t size); +static void ngx_http_lua_pipe_close_helper(ngx_http_lua_pipe_t *pipe, + ngx_http_lua_pipe_ctx_t *pipe_ctx, ngx_event_t *ev); +static void ngx_http_lua_pipe_close_stdin(ngx_http_lua_pipe_t *pipe); +static void ngx_http_lua_pipe_close_stdout(ngx_http_lua_pipe_t *pipe); +static void ngx_http_lua_pipe_close_stderr(ngx_http_lua_pipe_t *pipe); +static void ngx_http_lua_pipe_proc_finalize(ngx_http_lua_ffi_pipe_proc_t *proc); +static ngx_int_t ngx_http_lua_pipe_get_lua_ctx(ngx_http_request_t *r, + ngx_http_lua_ctx_t **ctx, u_char *errbuf, size_t *errbuf_size); +static void ngx_http_lua_pipe_put_error(ngx_http_lua_pipe_ctx_t *pipe_ctx, + u_char *errbuf, size_t *errbuf_size); +static void ngx_http_lua_pipe_put_data(ngx_http_lua_pipe_t *pipe, + ngx_http_lua_pipe_ctx_t *pipe_ctx, u_char **buf, size_t *buf_size); +static ngx_int_t ngx_http_lua_pipe_add_input_buffer(ngx_http_lua_pipe_t *pipe, + ngx_http_lua_pipe_ctx_t *pipe_ctx); +static ngx_int_t ngx_http_lua_pipe_read_all(void *data, ssize_t bytes); +static ngx_int_t ngx_http_lua_pipe_read_bytes(void *data, ssize_t bytes); +static ngx_int_t ngx_http_lua_pipe_read_line(void *data, ssize_t bytes); +static ngx_int_t ngx_http_lua_pipe_read_any(void *data, ssize_t bytes); +static ngx_int_t ngx_http_lua_pipe_read(ngx_http_lua_pipe_t *pipe, + ngx_http_lua_pipe_ctx_t *pipe_ctx); +static ngx_int_t ngx_http_lua_pipe_init_ctx( + ngx_http_lua_pipe_ctx_t **pipe_ctx_pt, int fd, ngx_pool_t *pool, + u_char *errbuf, size_t *errbuf_size); +static ngx_int_t ngx_http_lua_pipe_write(ngx_http_lua_pipe_t *pipe, + ngx_http_lua_pipe_ctx_t *pipe_ctx); +static int ngx_http_lua_pipe_read_stdout_retval( + ngx_http_lua_ffi_pipe_proc_t *proc, lua_State *L); +static int ngx_http_lua_pipe_read_stderr_retval( + ngx_http_lua_ffi_pipe_proc_t *proc, lua_State *L); +static int ngx_http_lua_pipe_read_retval_helper( + ngx_http_lua_ffi_pipe_proc_t *proc, lua_State *L, int from_stderr); +static int ngx_http_lua_pipe_write_retval(ngx_http_lua_ffi_pipe_proc_t *proc, + lua_State *L); +static int ngx_http_lua_pipe_wait_retval(ngx_http_lua_ffi_pipe_proc_t *proc, + lua_State *L); +static void ngx_http_lua_pipe_resume_helper(ngx_event_t *ev, + ngx_http_lua_co_ctx_t *wait_co_ctx); +static void ngx_http_lua_pipe_resume_read_stdout_handler(ngx_event_t *ev); +static void ngx_http_lua_pipe_resume_read_stderr_handler(ngx_event_t *ev); +static void ngx_http_lua_pipe_resume_write_handler(ngx_event_t *ev); +static void ngx_http_lua_pipe_resume_wait_handler(ngx_event_t *ev); +static ngx_int_t ngx_http_lua_pipe_resume(ngx_http_request_t *r); +static void ngx_http_lua_pipe_dummy_event_handler(ngx_event_t *ev); +static void ngx_http_lua_pipe_clear_event(ngx_event_t *ev); +static void ngx_http_lua_pipe_proc_read_stdout_cleanup(void *data); +static void ngx_http_lua_pipe_proc_read_stderr_cleanup(void *data); +static void ngx_http_lua_pipe_proc_write_cleanup(void *data); +static void ngx_http_lua_pipe_proc_wait_cleanup(void *data); +static void ngx_http_lua_pipe_reap_pids(ngx_event_t *ev); +static void ngx_http_lua_pipe_reap_timer_handler(ngx_event_t *ev); +void ngx_http_lua_ffi_pipe_proc_destroy( + ngx_http_lua_ffi_pipe_proc_t *proc); + + +static ngx_rbtree_t ngx_http_lua_pipe_rbtree; +static ngx_rbtree_node_t ngx_http_lua_pipe_proc_sentinel; +static ngx_event_t ngx_reap_pid_event; + + +#if (NGX_HTTP_LUA_HAVE_SIGNALFD) +static int ngx_http_lua_signalfd; +static struct signalfd_siginfo ngx_http_lua_pipe_notification; + +#define ngx_http_lua_read_sigfd ngx_http_lua_signalfd + +#else +static int ngx_http_lua_sigchldfd[2]; +static u_char ngx_http_lua_pipe_notification[1]; + +#define ngx_http_lua_read_sigfd ngx_http_lua_sigchldfd[0] +#define ngx_http_lua_write_sigfd ngx_http_lua_sigchldfd[1] +#endif + + +static ngx_connection_t *ngx_http_lua_sigfd_conn = NULL; + + +/* The below signals are ignored by Nginx. + * We need to reset them for the spawned child processes. */ +ngx_http_lua_pipe_signal_t ngx_signals[] = { + { SIGSYS, "SIGSYS" }, + { SIGPIPE, "SIGPIPE" }, + { 0, NULL } +}; + + +enum { + PIPE_ERR_CLOSED = 1, + PIPE_ERR_SYSCALL, + PIPE_ERR_NOMEM, + PIPE_ERR_TIMEOUT, + PIPE_ERR_ADD_READ_EV, + PIPE_ERR_ADD_WRITE_EV, + PIPE_ERR_ABORTED, +}; + + +enum { + PIPE_READ_ALL = 0, + PIPE_READ_BYTES, + PIPE_READ_LINE, + PIPE_READ_ANY, +}; + + +#define REASON_EXIT "exit" +#define REASON_SIGNAL "signal" +#define REASON_UNKNOWN "unknown" + +#define REASON_RUNNING_CODE 0 +#define REASON_EXIT_CODE 1 +#define REASON_SIGNAL_CODE 2 +#define REASON_UNKNOWN_CODE 3 + + +void +ngx_http_lua_pipe_init(void) +{ + ngx_rbtree_init(&ngx_http_lua_pipe_rbtree, + &ngx_http_lua_pipe_proc_sentinel, ngx_rbtree_insert_value); +} + + +ngx_int_t +ngx_http_lua_pipe_add_signal_handler(ngx_cycle_t *cycle) +{ + ngx_event_t *rev; +#if (NGX_HTTP_LUA_HAVE_SIGNALFD) + sigset_t set; + +#else + int rc; + struct sigaction sa; +#endif + + ngx_reap_pid_event.handler = ngx_http_lua_pipe_reap_timer_handler; + ngx_reap_pid_event.log = cycle->log; + ngx_reap_pid_event.data = cycle; + ngx_reap_pid_event.cancelable = 1; + + if (!ngx_reap_pid_event.timer_set) { + ngx_add_timer(&ngx_reap_pid_event, 1000); + } + +#if (NGX_HTTP_LUA_HAVE_SIGNALFD) + if (sigemptyset(&set) != 0) { + ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno, + "lua pipe init signal set failed"); + return NGX_ERROR; + } + + if (sigaddset(&set, SIGCHLD) != 0) { + ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno, + "lua pipe add SIGCHLD to signal set failed"); + return NGX_ERROR; + } + + if (sigprocmask(SIG_BLOCK, &set, NULL) != 0) { + ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno, + "lua pipe block SIGCHLD failed"); + return NGX_ERROR; + } + + ngx_http_lua_signalfd = signalfd(-1, &set, SFD_NONBLOCK|SFD_CLOEXEC); + if (ngx_http_lua_signalfd < 0) { + ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno, + "lua pipe create signalfd instance failed"); + return NGX_ERROR; + } + +#else /* !(NGX_HTTP_LUA_HAVE_SIGNALFD) */ +# if (NGX_HTTP_LUA_HAVE_PIPE2) + rc = pipe2(ngx_http_lua_sigchldfd, O_NONBLOCK|O_CLOEXEC); +# else + rc = pipe(ngx_http_lua_sigchldfd); +# endif + + if (rc == -1) { + ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno, + "lua pipe init SIGCHLD fd failed"); + return NGX_ERROR; + } + +# if !(NGX_HTTP_LUA_HAVE_PIPE2) + if (ngx_nonblocking(ngx_http_lua_read_sigfd) == -1) { + ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno, "lua pipe " + ngx_nonblocking_n " SIGCHLD read fd failed"); + goto failed; + } + + if (ngx_nonblocking(ngx_http_lua_write_sigfd) == -1) { + ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno, "lua pipe " + ngx_nonblocking_n " SIGCHLD write fd failed"); + goto failed; + } + + /* it's ok not to set the pipe fd with O_CLOEXEC. This requires + * extra syscall */ +# endif /* !(NGX_HTTP_LUA_HAVE_PIPE2) */ +#endif /* NGX_HTTP_LUA_HAVE_SIGNALFD */ + + ngx_http_lua_sigfd_conn = ngx_get_connection(ngx_http_lua_read_sigfd, + cycle->log); + if (ngx_http_lua_sigfd_conn == NULL) { + goto failed; + } + + ngx_http_lua_sigfd_conn->log = cycle->log; + ngx_http_lua_sigfd_conn->recv = ngx_http_lua_pipe_fd_read; + rev = ngx_http_lua_sigfd_conn->read; + rev->log = ngx_http_lua_sigfd_conn->log; + rev->handler = ngx_http_lua_pipe_sigchld_event_handler; + +#ifdef HAVE_SOCKET_CLOEXEC_PATCH + rev->skip_socket_leak_check = 1; +#endif + + if (ngx_handle_read_event(rev, 0) == NGX_ERROR) { + goto failed; + } + +#if !(NGX_HTTP_LUA_HAVE_SIGNALFD) + ngx_memzero(&sa, sizeof(struct sigaction)); + sa.sa_sigaction = ngx_http_lua_pipe_sigchld_handler; + sa.sa_flags = SA_SIGINFO; + + if (sigemptyset(&sa.sa_mask) != 0) { + ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno, + "lua pipe init signal mask failed"); + goto failed; + } + + if (sigaction(SIGCHLD, &sa, NULL) == -1) { + ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno, + "lua pipe sigaction(SIGCHLD) failed"); + goto failed; + } +#endif + + return NGX_OK; + +failed: + + if (ngx_http_lua_sigfd_conn != NULL) { + ngx_close_connection(ngx_http_lua_sigfd_conn); + ngx_http_lua_sigfd_conn = NULL; + } + + if (close(ngx_http_lua_read_sigfd) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, + "lua pipe close the read sigfd failed"); + } + +#if !(NGX_HTTP_LUA_HAVE_SIGNALFD) + if (close(ngx_http_lua_write_sigfd) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, + "lua pipe close the write sigfd failed"); + } +#endif + + return NGX_ERROR; +} + + +static ngx_rbtree_node_t * +ngx_http_lua_pipe_lookup_pid(ngx_rbtree_key_t key) +{ + ngx_rbtree_node_t *node, *sentinel; + + node = ngx_http_lua_pipe_rbtree.root; + sentinel = ngx_http_lua_pipe_rbtree.sentinel; + + while (node != sentinel) { + if (key < node->key) { + node = node->left; + continue; + } + + if (key > node->key) { + node = node->right; + continue; + } + + return node; + } + + return NULL; +} + + +#if !(NGX_HTTP_LUA_HAVE_SIGNALFD) +static void +ngx_http_lua_pipe_sigchld_handler(int signo, siginfo_t *siginfo, + void *ucontext) +{ + ngx_err_t err, saved_err; + ngx_int_t n; + + saved_err = ngx_errno; + + for ( ;; ) { + n = write(ngx_http_lua_write_sigfd, ngx_http_lua_pipe_notification, + sizeof(ngx_http_lua_pipe_notification)); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, + "lua pipe SIGCHLD fd write siginfo:%p", siginfo); + + if (n >= 0) { + break; + } + + err = ngx_errno; + + if (err != NGX_EINTR) { + if (err != NGX_EAGAIN) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, err, + "lua pipe SIGCHLD fd write failed"); + } + + break; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, err, + "lua pipe SIGCHLD fd write was interrupted"); + } + + ngx_set_errno(saved_err); +} +#endif + + +static void +ngx_http_lua_pipe_sigchld_event_handler(ngx_event_t *ev) +{ + int n; + ngx_connection_t *c = ev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, + "lua pipe reaping children"); + + for ( ;; ) { +#if (NGX_HTTP_LUA_HAVE_SIGNALFD) + n = c->recv(c, (u_char *) &ngx_http_lua_pipe_notification, +#else + n = c->recv(c, ngx_http_lua_pipe_notification, +#endif + sizeof(ngx_http_lua_pipe_notification)); + + if (n <= 0) { + if (n == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe SIGCHLD fd read failed"); + } + + break; + } + + ngx_http_lua_pipe_reap_pids(ev); + } +} + + +static void +ngx_http_lua_pipe_reap_pids(ngx_event_t *ev) +{ + int status; + ngx_pid_t pid; + ngx_rbtree_node_t *node; + ngx_http_lua_pipe_node_t *pipe_node; + + for ( ;; ) { + pid = waitpid(-1, &status, WNOHANG); + + if (pid == 0) { + break; + } + + if (pid < 0) { + if (ngx_errno != NGX_ECHILD) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe waitpid failed"); + } + + break; + } + + /* This log is ported from Nginx's signal handler since we override + * or block it in this implementation. */ + ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0, + "signal %d (SIGCHLD) received from %P", + SIGCHLD, pid); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe SIGCHLD fd read pid:%P status:%d", pid, + status); + + node = ngx_http_lua_pipe_lookup_pid(pid); + if (node != NULL) { + pipe_node = (ngx_http_lua_pipe_node_t *) &node->color; + if (pipe_node->wait_co_ctx != NULL) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe resume process:%p waiting for %P", + pipe_node->proc, pid); + + /* + * We need the extra parentheses around the first argument + * of ngx_post_event() just to work around macro issues in + * nginx cores older than 1.7.12 (exclusive). + */ + ngx_post_event((&pipe_node->wait_co_ctx->sleep), + &ngx_posted_events); + } + + /* TODO: we should proactively close and free up the pipe after + * the user consume all the data in the pipe. + */ + pipe_node->proc->pipe->dead = 1; + + if (WIFSIGNALED(status)) { + pipe_node->status = WTERMSIG(status); + pipe_node->reason_code = REASON_SIGNAL_CODE; + + } else if (WIFEXITED(status)) { + pipe_node->status = WEXITSTATUS(status); + pipe_node->reason_code = REASON_EXIT_CODE; + + } else { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "lua pipe unknown exit status %d from " + "process %P", status, pid); + pipe_node->status = status; + pipe_node->reason_code = REASON_UNKNOWN_CODE; + } + } + } +} + + +static void +ngx_http_lua_pipe_reap_timer_handler(ngx_event_t *ev) +{ + ngx_http_lua_pipe_reap_pids(ev); + + if (!ngx_exiting) { + ngx_add_timer(&ngx_reap_pid_event, 1000); + ngx_reap_pid_event.timedout = 0; + } +} + + +static ssize_t +ngx_http_lua_pipe_fd_read(ngx_connection_t *c, u_char *buf, size_t size) +{ + ssize_t n; + ngx_err_t err; + ngx_event_t *rev; + + rev = c->read; + + do { + n = read(c->fd, buf, size); + + err = ngx_errno; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "read: fd:%d %z of %uz", c->fd, n, size); + + if (n == 0) { + rev->ready = 0; + rev->eof = 1; + return 0; + } + + if (n > 0) { + if ((size_t) n < size + && !(ngx_event_flags & NGX_USE_GREEDY_EVENT)) + { + rev->ready = 0; + } + + return n; + } + + if (err == NGX_EAGAIN || err == NGX_EINTR) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "read() not ready"); + n = NGX_AGAIN; + + } else { + n = ngx_connection_error(c, err, "read() failed"); + break; + } + + } while (err == NGX_EINTR); + + rev->ready = 0; + + if (n == NGX_ERROR) { + rev->error = 1; + } + + return n; +} + + +static ssize_t +ngx_http_lua_pipe_fd_write(ngx_connection_t *c, u_char *buf, size_t size) +{ + ssize_t n; + ngx_err_t err; + ngx_event_t *wev; + + wev = c->write; + + do { + n = write(c->fd, buf, size); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "write: fd:%d %z of %uz", c->fd, n, size); + + if (n >= 0) { + if ((size_t) n != size) { + wev->ready = 0; + } + + return n; + } + + err = ngx_errno; + + if (err == NGX_EAGAIN || err == NGX_EINTR) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "write() not ready"); + n = NGX_AGAIN; + + } else if (err != NGX_EPIPE) { + n = ngx_connection_error(c, err, "write() failed"); + break; + } + + } while (err == NGX_EINTR); + + wev->ready = 0; + + if (n == NGX_ERROR) { + wev->error = 1; + } + + return n; +} + + +#if !(NGX_HTTP_LUA_HAVE_EXECVPE) +static int +ngx_http_lua_execvpe(const char *program, char * const argv[], + char * const envp[]) +{ + int rc; + char **saved = environ; + + environ = (char **) envp; + rc = execvp(program, argv); + environ = saved; + return rc; +} +#endif + + +int +ngx_http_lua_ffi_pipe_spawn(ngx_http_request_t *r, + ngx_http_lua_ffi_pipe_proc_t *proc, + const char *file, const char **argv, int merge_stderr, size_t buffer_size, + const char **environ, u_char *errbuf, size_t *errbuf_size) +{ + int rc; + int in[2]; + int out[2]; + int err[2]; + int stdin_fd, stdout_fd, stderr_fd; + int errlog_fd, temp_errlog_fd; + ngx_pid_t pid; + ssize_t pool_size; + ngx_pool_t *pool; + ngx_uint_t i; + ngx_listening_t *ls; + ngx_http_lua_pipe_t *pp; + ngx_rbtree_node_t *node; + ngx_http_lua_pipe_node_t *pipe_node; + struct sigaction sa; + ngx_http_lua_pipe_signal_t *sig; + ngx_pool_cleanup_t *cln; + sigset_t set; + + pool_size = ngx_align(NGX_MIN_POOL_SIZE + buffer_size * 2, + NGX_POOL_ALIGNMENT); + + pool = ngx_create_pool(pool_size, ngx_cycle->log); + if (pool == NULL) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "no memory") + - errbuf; + return NGX_ERROR; + } + + pp = ngx_pcalloc(pool, sizeof(ngx_http_lua_pipe_t) + + offsetof(ngx_rbtree_node_t, color) + + sizeof(ngx_http_lua_pipe_node_t)); + if (pp == NULL) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "no memory") + - errbuf; + goto free_pool; + } + + rc = pipe(in); + if (rc == -1) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "pipe failed: %s", + strerror(errno)) + - errbuf; + goto free_pool; + } + + rc = pipe(out); + if (rc == -1) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "pipe failed: %s", + strerror(errno)) + - errbuf; + goto close_in_fd; + } + + if (!merge_stderr) { + rc = pipe(err); + if (rc == -1) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, + "pipe failed: %s", strerror(errno)) + - errbuf; + goto close_in_out_fd; + } + } + + pid = fork(); + if (pid == -1) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "fork failed: %s", + strerror(errno)) + - errbuf; + goto close_in_out_err_fd; + } + + if (pid == 0) { + +#if (NGX_HAVE_CPU_AFFINITY) + /* reset the CPU affinity mask */ + ngx_uint_t log_level; + ngx_cpuset_t child_cpu_affinity; + + if (ngx_process == NGX_PROCESS_WORKER + && ngx_get_cpu_affinity(ngx_worker) != NULL) + { + CPU_ZERO(&child_cpu_affinity); + + for (i = 0; i < (ngx_uint_t) ngx_min(ngx_ncpu, CPU_SETSIZE); i++) { + CPU_SET(i, &child_cpu_affinity); + } + + log_level = ngx_cycle->log->log_level; + ngx_cycle->log->log_level = NGX_LOG_WARN; + ngx_setaffinity(&child_cpu_affinity, ngx_cycle->log); + ngx_cycle->log->log_level = log_level; + } +#endif + + /* reset the handler of ignored signals to the default */ + for (sig = ngx_signals; sig->signo != 0; sig++) { + ngx_memzero(&sa, sizeof(struct sigaction)); + sa.sa_handler = SIG_DFL; + + if (sigemptyset(&sa.sa_mask) != 0) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe child init signal mask failed"); + exit(EXIT_FAILURE); + } + + if (sigaction(sig->signo, &sa, NULL) == -1) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe child reset signal handler for %s " + "failed", sig->signame); + exit(EXIT_FAILURE); + } + } + + /* reset signal mask */ + if (sigemptyset(&set) != 0) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe child init signal set failed"); + exit(EXIT_FAILURE); + } + + if (sigprocmask(SIG_SETMASK, &set, NULL) != 0) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe child reset signal mask failed"); + exit(EXIT_FAILURE); + } + + /* close listening socket fd */ + ls = ngx_cycle->listening.elts; + for (i = 0; i < ngx_cycle->listening.nelts; i++) { + if (ls[i].fd != (ngx_socket_t) -1 && + ngx_close_socket(ls[i].fd) == -1) + { + ngx_log_error(NGX_LOG_WARN, ngx_cycle->log, ngx_socket_errno, + "lua pipe child " ngx_close_socket_n + " %V failed", &ls[i].addr_text); + } + } + + /* close and dup pipefd */ + if (close(in[1]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "lua pipe child failed to close the in[1] " + "pipe fd"); + } + + if (close(out[0]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "lua pipe child failed to close the out[0] " + "pipe fd"); + } + + if (ngx_cycle->log->file && ngx_cycle->log->file->fd == STDERR_FILENO) { + errlog_fd = ngx_cycle->log->file->fd; + temp_errlog_fd = dup(errlog_fd); + + if (temp_errlog_fd == -1) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe child dup errlog fd failed"); + exit(EXIT_FAILURE); + } + + if (ngx_cloexec(temp_errlog_fd) == -1) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe child new errlog fd " ngx_cloexec_n + " failed"); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe child dup old errlog fd %d to new fd %d", + ngx_cycle->log->file->fd, temp_errlog_fd); + + ngx_cycle->log->file->fd = temp_errlog_fd; + } + + if (dup2(in[0], STDIN_FILENO) == -1) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe child dup2 stdin failed"); + exit(EXIT_FAILURE); + } + + if (dup2(out[1], STDOUT_FILENO) == -1) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe child dup2 stdout failed"); + exit(EXIT_FAILURE); + } + + if (merge_stderr) { + if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe child dup2 stderr failed"); + exit(EXIT_FAILURE); + } + + } else { + if (close(err[0]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "lua pipe child failed to close the err[0] " + "pipe fd"); + } + + if (dup2(err[1], STDERR_FILENO) == -1) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe child dup2 stderr failed"); + exit(EXIT_FAILURE); + } + } + + if (close(in[0]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "lua pipe failed to close the in[0]"); + } + + if (close(out[1]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "lua pipe failed to close the out[1]"); + } + + if (!merge_stderr) { + if (close(err[1]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "lua pipe failed to close the err[1]"); + } + } + + if (environ != NULL) { +#if (NGX_HTTP_LUA_HAVE_EXECVPE) + if (execvpe(file, (char * const *) argv, (char * const *) environ) +#else + if (ngx_http_lua_execvpe(file, (char * const *) argv, + (char * const *) environ) +#endif + == -1) + { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe child execvpe() failed while " + "executing %s", file); + } + + } else { + if (execvp(file, (char * const *) argv) == -1) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe child execvp() failed while " + "executing %s", file); + } + } + + exit(EXIT_FAILURE); + } + + /* parent process */ + if (close(in[0]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "lua pipe: failed to close the in[0] pipe fd"); + } + + stdin_fd = in[1]; + + if (ngx_nonblocking(stdin_fd) == -1) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, + ngx_nonblocking_n " failed: %s", + strerror(errno)) + - errbuf; + goto close_in_out_err_fd; + } + + pp->stdin_fd = stdin_fd; + + if (close(out[1]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "lua pipe: failed to close the out[1] pipe fd"); + } + + stdout_fd = out[0]; + + if (ngx_nonblocking(stdout_fd) == -1) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, + ngx_nonblocking_n " failed: %s", + strerror(errno)) + - errbuf; + goto close_in_out_err_fd; + } + + pp->stdout_fd = stdout_fd; + + if (!merge_stderr) { + if (close(err[1]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "lua pipe: failed to close the err[1] pipe fd"); + } + + stderr_fd = err[0]; + + if (ngx_nonblocking(stderr_fd) == -1) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, + ngx_nonblocking_n " failed: %s", + strerror(errno)) + - errbuf; + goto close_in_out_err_fd; + } + + pp->stderr_fd = stderr_fd; + } + + if (pp->cleanup == NULL) { + cln = ngx_pool_cleanup_add(r->pool, 0); + + if (cln == NULL) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "no memory") + - errbuf; + goto close_in_out_err_fd; + } + + cln->handler = (ngx_pool_cleanup_pt) ngx_http_lua_ffi_pipe_proc_destroy; + cln->data = proc; + pp->cleanup = &cln->handler; + pp->r = r; + } + + node = (ngx_rbtree_node_t *) (pp + 1); + node->key = pid; + pipe_node = (ngx_http_lua_pipe_node_t *) &node->color; + pipe_node->proc = proc; + ngx_rbtree_insert(&ngx_http_lua_pipe_rbtree, node); + + pp->node = node; + pp->pool = pool; + pp->merge_stderr = merge_stderr; + pp->buffer_size = buffer_size; + + proc->_pid = pid; + proc->pipe = pp; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe spawn process:%p pid:%P merge_stderr:%d " + "buffer_size:%uz", proc, pid, merge_stderr, buffer_size); + return NGX_OK; + +close_in_out_err_fd: + + if (!merge_stderr) { + if (close(err[0]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "failed to close the err[0] pipe fd"); + } + + if (close(err[1]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "failed to close the err[1] pipe fd"); + } + } + +close_in_out_fd: + + if (close(out[0]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "failed to close the out[0] pipe fd"); + } + + if (close(out[1]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "failed to close the out[1] pipe fd"); + } + +close_in_fd: + + if (close(in[0]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "failed to close the in[0] pipe fd"); + } + + if (close(in[1]) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "failed to close the in[1] pipe fd"); + } + +free_pool: + + ngx_destroy_pool(pool); + return NGX_ERROR; +} + + +static void +ngx_http_lua_pipe_close_helper(ngx_http_lua_pipe_t *pipe, + ngx_http_lua_pipe_ctx_t *pipe_ctx, ngx_event_t *ev) +{ + if (ev->handler != ngx_http_lua_pipe_dummy_event_handler) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe abort blocking operation pipe_ctx:%p ev:%p", + pipe_ctx, ev); + + if (pipe->dead) { + pipe_ctx->err_type = PIPE_ERR_CLOSED; + + } else { + pipe_ctx->err_type = PIPE_ERR_ABORTED; + } + + ngx_post_event(ev, &ngx_posted_events); + return; + } + + ngx_close_connection(pipe_ctx->c); + pipe_ctx->c = NULL; +} + + +static void +ngx_http_lua_pipe_close_stdin(ngx_http_lua_pipe_t *pipe) +{ + ngx_event_t *wev; + + if (pipe->stdin_ctx == NULL) { + if (pipe->stdin_fd != -1) { + if (close(pipe->stdin_fd) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "failed to close the stdin pipe fd"); + } + + pipe->stdin_fd = -1; + } + + } else if (pipe->stdin_ctx->c != NULL) { + wev = pipe->stdin_ctx->c->write; + ngx_http_lua_pipe_close_helper(pipe, pipe->stdin_ctx, wev); + } +} + + +static void +ngx_http_lua_pipe_close_stdout(ngx_http_lua_pipe_t *pipe) +{ + ngx_event_t *rev; + + if (pipe->stdout_ctx == NULL) { + if (pipe->stdout_fd != -1) { + if (close(pipe->stdout_fd) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "failed to close the stdout pipe fd"); + } + + pipe->stdout_fd = -1; + } + + } else if (pipe->stdout_ctx->c != NULL) { + rev = pipe->stdout_ctx->c->read; + ngx_http_lua_pipe_close_helper(pipe, pipe->stdout_ctx, rev); + } +} + + +static void +ngx_http_lua_pipe_close_stderr(ngx_http_lua_pipe_t *pipe) +{ + ngx_event_t *rev; + + if (pipe->stderr_ctx == NULL) { + if (pipe->stderr_fd != -1) { + if (close(pipe->stderr_fd) == -1) { + ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno, + "failed to close the stderr pipe fd"); + } + + pipe->stderr_fd = -1; + } + + } else if (pipe->stderr_ctx->c != NULL) { + rev = pipe->stderr_ctx->c->read; + ngx_http_lua_pipe_close_helper(pipe, pipe->stderr_ctx, rev); + } +} + + +int +ngx_http_lua_ffi_pipe_proc_shutdown_stdin(ngx_http_lua_ffi_pipe_proc_t *proc, + u_char *errbuf, size_t *errbuf_size) +{ + ngx_http_lua_pipe_t *pipe; + + pipe = proc->pipe; + if (pipe == NULL || pipe->closed) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf; + return NGX_ERROR; + } + + ngx_http_lua_pipe_close_stdin(pipe); + + return NGX_OK; +} + + +int +ngx_http_lua_ffi_pipe_proc_shutdown_stdout(ngx_http_lua_ffi_pipe_proc_t *proc, + u_char *errbuf, size_t *errbuf_size) +{ + ngx_http_lua_pipe_t *pipe; + + pipe = proc->pipe; + if (pipe == NULL || pipe->closed) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf; + return NGX_ERROR; + } + + ngx_http_lua_pipe_close_stdout(pipe); + + return NGX_OK; +} + + +int +ngx_http_lua_ffi_pipe_proc_shutdown_stderr(ngx_http_lua_ffi_pipe_proc_t *proc, + u_char *errbuf, size_t *errbuf_size) +{ + ngx_http_lua_pipe_t *pipe; + + pipe = proc->pipe; + if (pipe == NULL || pipe->closed) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf; + return NGX_ERROR; + } + + if (pipe->merge_stderr) { + /* stdout is used internally as stderr when merge_stderr is true */ + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "merged to stdout") + - errbuf; + return NGX_ERROR; + } + + ngx_http_lua_pipe_close_stderr(pipe); + + return NGX_OK; +} + + +static void +ngx_http_lua_pipe_proc_finalize(ngx_http_lua_ffi_pipe_proc_t *proc) +{ + ngx_http_lua_pipe_t *pipe; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe finalize process:%p pid:%P", + proc, proc->_pid); + pipe = proc->pipe; + + if (pipe->node) { + ngx_rbtree_delete(&ngx_http_lua_pipe_rbtree, pipe->node); + pipe->node = NULL; + } + + pipe->dead = 1; + + ngx_http_lua_pipe_close_stdin(pipe); + ngx_http_lua_pipe_close_stdout(pipe); + + if (!pipe->merge_stderr) { + ngx_http_lua_pipe_close_stderr(pipe); + } + + pipe->closed = 1; +} + + +void +ngx_http_lua_ffi_pipe_proc_destroy(ngx_http_lua_ffi_pipe_proc_t *proc) +{ + ngx_http_lua_pipe_t *pipe; + + pipe = proc->pipe; + if (pipe == NULL) { + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe destroy process:%p pid:%P", proc, proc->_pid); + + if (!pipe->dead) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe kill process:%p pid:%P", proc, proc->_pid); + + if (kill(proc->_pid, SIGKILL) == -1) { + if (ngx_errno != ESRCH) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "lua pipe failed to kill process:%p pid:%P", + proc, proc->_pid); + } + } + } + + if (pipe->cleanup != NULL) { + *pipe->cleanup = NULL; + ngx_http_lua_cleanup_free(pipe->r, pipe->cleanup); + pipe->cleanup = NULL; + } + + ngx_http_lua_pipe_proc_finalize(proc); + ngx_destroy_pool(pipe->pool); + proc->pipe = NULL; +} + + +static ngx_int_t +ngx_http_lua_pipe_get_lua_ctx(ngx_http_request_t *r, + ngx_http_lua_ctx_t **ctx, u_char *errbuf, size_t *errbuf_size) +{ + int rc; + + *ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (*ctx == NULL) { + return NGX_HTTP_LUA_FFI_NO_REQ_CTX; + } + + rc = ngx_http_lua_ffi_check_context(*ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE, + errbuf, errbuf_size); + if (rc != NGX_OK) { + return NGX_HTTP_LUA_FFI_BAD_CONTEXT; + } + + return NGX_OK; +} + + +static void +ngx_http_lua_pipe_put_error(ngx_http_lua_pipe_ctx_t *pipe_ctx, u_char *errbuf, + size_t *errbuf_size) +{ + switch (pipe_ctx->err_type) { + + case PIPE_ERR_CLOSED: + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf; + break; + + case PIPE_ERR_SYSCALL: + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "%s", + strerror(pipe_ctx->pipe_errno)) + - errbuf; + break; + + case PIPE_ERR_NOMEM: + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "no memory") + - errbuf; + break; + + case PIPE_ERR_TIMEOUT: + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "timeout") + - errbuf; + break; + + case PIPE_ERR_ADD_READ_EV: + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, + "failed to add read event") + - errbuf; + break; + + case PIPE_ERR_ADD_WRITE_EV: + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, + "failed to add write event") + - errbuf; + break; + + case PIPE_ERR_ABORTED: + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "aborted") - errbuf; + break; + + default: + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "unexpected err type: %d", pipe_ctx->err_type); + ngx_http_lua_assert(NULL); + } +} + + +static void +ngx_http_lua_pipe_put_data(ngx_http_lua_pipe_t *pipe, + ngx_http_lua_pipe_ctx_t *pipe_ctx, u_char **buf, size_t *buf_size) +{ + size_t size = 0; + size_t chunk_size; + size_t nbufs; + u_char *p; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_chain_t **ll; + + nbufs = 0; + ll = NULL; + + for (cl = pipe_ctx->bufs_in; cl; cl = cl->next) { + b = cl->buf; + chunk_size = b->last - b->pos; + + if (cl->next) { + ll = &cl->next; + } + + size += chunk_size; + + nbufs++; + } + + if (*buf_size < size) { + *buf = NULL; + *buf_size = size; + + return; + } + + *buf_size = size; + + p = *buf; + for (cl = pipe_ctx->bufs_in; cl; cl = cl->next) { + b = cl->buf; + chunk_size = b->last - b->pos; + p = ngx_cpymem(p, b->pos, chunk_size); + } + + if (nbufs > 1 && ll) { + *ll = pipe->free_bufs; + pipe->free_bufs = pipe_ctx->bufs_in; + pipe_ctx->bufs_in = pipe_ctx->buf_in; + } + + if (pipe_ctx->buffer.pos == pipe_ctx->buffer.last) { + pipe_ctx->buffer.pos = pipe_ctx->buffer.start; + pipe_ctx->buffer.last = pipe_ctx->buffer.start; + } + + if (pipe_ctx->bufs_in) { + pipe_ctx->buf_in->buf->last = pipe_ctx->buffer.pos; + pipe_ctx->buf_in->buf->pos = pipe_ctx->buffer.pos; + } +} + + +static ngx_int_t +ngx_http_lua_pipe_add_input_buffer(ngx_http_lua_pipe_t *pipe, + ngx_http_lua_pipe_ctx_t *pipe_ctx) +{ + ngx_chain_t *cl; + + cl = ngx_http_lua_chain_get_free_buf(ngx_cycle->log, pipe->pool, + &pipe->free_bufs, + pipe->buffer_size); + + if (cl == NULL) { + pipe_ctx->err_type = PIPE_ERR_NOMEM; + return NGX_ERROR; + } + + pipe_ctx->buf_in->next = cl; + pipe_ctx->buf_in = cl; + pipe_ctx->buffer = *cl->buf; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_lua_pipe_read_all(void *data, ssize_t bytes) +{ + ngx_http_lua_pipe_ctx_t *pipe_ctx = data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "lua pipe read all"); + return ngx_http_lua_read_all(&pipe_ctx->buffer, pipe_ctx->buf_in, bytes, + ngx_cycle->log); +} + + +static ngx_int_t +ngx_http_lua_pipe_read_bytes(void *data, ssize_t bytes) +{ + ngx_int_t rc; + ngx_http_lua_pipe_ctx_t *pipe_ctx = data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe read bytes %z", bytes); + + rc = ngx_http_lua_read_bytes(&pipe_ctx->buffer, pipe_ctx->buf_in, + &pipe_ctx->rest, bytes, ngx_cycle->log); + if (rc == NGX_ERROR) { + pipe_ctx->err_type = PIPE_ERR_CLOSED; + return NGX_ERROR; + } + + return rc; +} + + +static ngx_int_t +ngx_http_lua_pipe_read_line(void *data, ssize_t bytes) +{ + ngx_int_t rc; + ngx_http_lua_pipe_ctx_t *pipe_ctx = data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe read line"); + rc = ngx_http_lua_read_line(&pipe_ctx->buffer, pipe_ctx->buf_in, bytes, + ngx_cycle->log); + if (rc == NGX_ERROR) { + pipe_ctx->err_type = PIPE_ERR_CLOSED; + return NGX_ERROR; + } + + return rc; +} + + +static ngx_int_t +ngx_http_lua_pipe_read_any(void *data, ssize_t bytes) +{ + ngx_int_t rc; + ngx_http_lua_pipe_ctx_t *pipe_ctx = data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "lua pipe read any"); + rc = ngx_http_lua_read_any(&pipe_ctx->buffer, pipe_ctx->buf_in, + &pipe_ctx->rest, bytes, ngx_cycle->log); + if (rc == NGX_ERROR) { + pipe_ctx->err_type = PIPE_ERR_CLOSED; + return NGX_ERROR; + } + + return rc; +} + + +static ngx_int_t +ngx_http_lua_pipe_read(ngx_http_lua_pipe_t *pipe, + ngx_http_lua_pipe_ctx_t *pipe_ctx) +{ + int rc; + int read; + size_t size; + ssize_t n; + ngx_buf_t *b; + ngx_event_t *rev; + ngx_connection_t *c; + + c = pipe_ctx->c; + rev = c->read; + b = &pipe_ctx->buffer; + read = 0; + + for ( ;; ) { + size = b->last - b->pos; + + if (size || pipe_ctx->eof) { + rc = pipe_ctx->input_filter(pipe_ctx->input_filter_ctx, size); + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe read done pipe:%p", pipe_ctx); + return NGX_OK; + } + + /* rc == NGX_AGAIN */ + continue; + } + + if (read && !rev->ready) { + break; + } + + size = b->end - b->last; + + if (size == 0) { + rc = ngx_http_lua_pipe_add_input_buffer(pipe, pipe_ctx); + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + b = &pipe_ctx->buffer; + size = (size_t) (b->end - b->last); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe try to read data %uz pipe:%p", + size, pipe_ctx); + + n = c->recv(c, b->last, size); + read = 1; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe read data returned %z pipe:%p", n, pipe_ctx); + + if (n == NGX_AGAIN) { + break; + } + + if (n == 0) { + pipe_ctx->eof = 1; + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe closed pipe:%p", pipe_ctx); + continue; + } + + if (n == NGX_ERROR) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, ngx_errno, + "lua pipe read data error pipe:%p", pipe_ctx); + + pipe_ctx->err_type = PIPE_ERR_SYSCALL; + pipe_ctx->pipe_errno = ngx_errno; + return NGX_ERROR; + } + + b->last += n; + } + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_lua_pipe_init_ctx(ngx_http_lua_pipe_ctx_t **pipe_ctx_pt, int fd, + ngx_pool_t *pool, u_char *errbuf, size_t *errbuf_size) +{ + ngx_connection_t *c; + + if (fd == -1) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf; + return NGX_ERROR; + } + + *pipe_ctx_pt = ngx_pcalloc(pool, sizeof(ngx_http_lua_pipe_ctx_t)); + if (*pipe_ctx_pt == NULL) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "no memory") + - errbuf; + return NGX_ERROR; + } + + c = ngx_get_connection(fd, ngx_cycle->log); + if (c == NULL) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "no connection") + - errbuf; + return NGX_ERROR; + } + + c->log = ngx_cycle->log; + c->recv = ngx_http_lua_pipe_fd_read; + c->read->handler = ngx_http_lua_pipe_dummy_event_handler; + c->read->log = c->log; + +#ifdef HAVE_SOCKET_CLOEXEC_PATCH + c->read->skip_socket_leak_check = 1; +#endif + + c->send = ngx_http_lua_pipe_fd_write; + c->write->handler = ngx_http_lua_pipe_dummy_event_handler; + c->write->log = c->log; + (*pipe_ctx_pt)->c = c; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe init pipe ctx:%p fd:*%d", *pipe_ctx_pt, fd); + + return NGX_OK; +} + + +int +ngx_http_lua_ffi_pipe_proc_read(ngx_http_request_t *r, + ngx_http_lua_ffi_pipe_proc_t *proc, int from_stderr, int reader_type, + size_t length, u_char **buf, size_t *buf_size, u_char *errbuf, + size_t *errbuf_size) +{ + int rc; + ngx_msec_t timeout; + ngx_event_t *rev; + ngx_connection_t *c; + ngx_http_lua_ctx_t *ctx; + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_co_ctx_t *wait_co_ctx; + ngx_http_lua_pipe_ctx_t *pipe_ctx; + + rc = ngx_http_lua_pipe_get_lua_ctx(r, &ctx, errbuf, errbuf_size); + if (rc != NGX_OK) { + return rc; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua pipe read process:%p pid:%P", proc, proc->_pid); + + pipe = proc->pipe; + if (pipe == NULL || pipe->closed) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf; + return NGX_ERROR; + } + + if (pipe->merge_stderr && from_stderr) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "merged to stdout") + - errbuf; + return NGX_ERROR; + } + + if (from_stderr) { + if (pipe->stderr_ctx == NULL) { + if (ngx_http_lua_pipe_init_ctx(&pipe->stderr_ctx, pipe->stderr_fd, + pipe->pool, errbuf, + errbuf_size) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + pipe->stderr_ctx->err_type = 0; + } + + pipe_ctx = pipe->stderr_ctx; + + } else { + if (pipe->stdout_ctx == NULL) { + if (ngx_http_lua_pipe_init_ctx(&pipe->stdout_ctx, pipe->stdout_fd, + pipe->pool, errbuf, + errbuf_size) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + pipe->stdout_ctx->err_type = 0; + } + + pipe_ctx = pipe->stdout_ctx; + } + + c = pipe_ctx->c; + if (c == NULL) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf; + return NGX_ERROR; + } + + rev = c->read; + if (rev->handler != ngx_http_lua_pipe_dummy_event_handler) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "pipe busy reading") + - errbuf; + return NGX_ERROR; + } + + pipe_ctx->input_filter_ctx = pipe_ctx; + + switch (reader_type) { + + case PIPE_READ_ALL: + pipe_ctx->input_filter = ngx_http_lua_pipe_read_all; + break; + + case PIPE_READ_BYTES: + pipe_ctx->input_filter = ngx_http_lua_pipe_read_bytes; + break; + + case PIPE_READ_LINE: + pipe_ctx->input_filter = ngx_http_lua_pipe_read_line; + break; + + case PIPE_READ_ANY: + pipe_ctx->input_filter = ngx_http_lua_pipe_read_any; + break; + + default: + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "unexpected reader_type: %d", reader_type); + ngx_http_lua_assert(NULL); + } + + pipe_ctx->rest = length; + + if (pipe_ctx->bufs_in == NULL) { + pipe_ctx->bufs_in = + ngx_http_lua_chain_get_free_buf(ngx_cycle->log, pipe->pool, + &pipe->free_bufs, + pipe->buffer_size); + + if (pipe_ctx->bufs_in == NULL) { + pipe_ctx->err_type = PIPE_ERR_NOMEM; + goto error; + } + + pipe_ctx->buf_in = pipe_ctx->bufs_in; + pipe_ctx->buffer = *pipe_ctx->buf_in->buf; + } + + rc = ngx_http_lua_pipe_read(pipe, pipe_ctx); + if (rc == NGX_ERROR) { + goto error; + } + + if (rc == NGX_OK) { + ngx_http_lua_pipe_put_data(pipe, pipe_ctx, buf, buf_size); + return NGX_OK; + } + + /* rc == NGX_AGAIN */ + wait_co_ctx = ctx->cur_co_ctx; + + c->data = wait_co_ctx; + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + pipe_ctx->err_type = PIPE_ERR_ADD_READ_EV; + goto error; + } + + wait_co_ctx->data = proc; + + if (from_stderr) { + rev->handler = ngx_http_lua_pipe_resume_read_stderr_handler; + wait_co_ctx->cleanup = ngx_http_lua_pipe_proc_read_stderr_cleanup; + timeout = proc->stderr_read_timeout; + + } else { + rev->handler = ngx_http_lua_pipe_resume_read_stdout_handler; + wait_co_ctx->cleanup = ngx_http_lua_pipe_proc_read_stdout_cleanup; + timeout = proc->stdout_read_timeout; + } + + if (timeout > 0) { + ngx_add_timer(rev, timeout); + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua pipe add timer for reading: %d(ms) process:%p " + "pid:%P pipe:%p ev:%p", timeout, proc, proc->_pid, pipe, + rev); + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua pipe read yielding process:%p pid:%P pipe:%p", proc, + proc->_pid, pipe); + + return NGX_AGAIN; + +error: + + if (pipe_ctx->bufs_in) { + ngx_http_lua_pipe_put_data(pipe, pipe_ctx, buf, buf_size); + ngx_http_lua_pipe_put_error(pipe_ctx, errbuf, errbuf_size); + return NGX_DECLINED; + } + + ngx_http_lua_pipe_put_error(pipe_ctx, errbuf, errbuf_size); + + return NGX_ERROR; +} + + +/* + * ngx_http_lua_ffi_pipe_get_read_result should only be called just after + * ngx_http_lua_ffi_pipe_proc_read, so we omit most of the sanity check already + * done in ngx_http_lua_ffi_pipe_proc_read. + */ +int +ngx_http_lua_ffi_pipe_get_read_result(ngx_http_request_t *r, + ngx_http_lua_ffi_pipe_proc_t *proc, int from_stderr, u_char **buf, + size_t *buf_size, u_char *errbuf, size_t *errbuf_size) +{ + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_pipe_ctx_t *pipe_ctx; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua pipe get read result process:%p pid:%P", proc, + proc->_pid); + + pipe = proc->pipe; + pipe_ctx = from_stderr ? pipe->stderr_ctx : pipe->stdout_ctx; + + if (!pipe_ctx->err_type) { + ngx_http_lua_pipe_put_data(pipe, pipe_ctx, buf, buf_size); + return NGX_OK; + } + + if (pipe_ctx->bufs_in) { + ngx_http_lua_pipe_put_data(pipe, pipe_ctx, buf, buf_size); + ngx_http_lua_pipe_put_error(pipe_ctx, errbuf, errbuf_size); + return NGX_DECLINED; + } + + ngx_http_lua_pipe_put_error(pipe_ctx, errbuf, errbuf_size); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_lua_pipe_write(ngx_http_lua_pipe_t *pipe, + ngx_http_lua_pipe_ctx_t *pipe_ctx) +{ + size_t size; + ngx_int_t n; + ngx_buf_t *b; + ngx_connection_t *c; + + c = pipe_ctx->c; + b = pipe_ctx->buf_in->buf; + + for ( ;; ) { + size = b->last - b->pos; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe try to write data %uz pipe:%p", size, + pipe_ctx); + + n = c->send(c, b->pos, size); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe write returned %i pipe:%p", n, pipe_ctx); + + if (n >= 0) { + b->pos += n; + + if (b->pos == b->last) { + b->pos = b->start; + b->last = b->start; + + if (!pipe->free_bufs) { + pipe->free_bufs = pipe_ctx->buf_in; + + } else { + pipe->free_bufs->next = pipe_ctx->buf_in; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe write done pipe:%p", pipe_ctx); + return NGX_OK; + } + + continue; + } + + /* NGX_ERROR || NGX_AGAIN */ + break; + } + + if (n == NGX_ERROR) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, ngx_errno, + "lua pipe write data error pipe:%p", pipe_ctx); + + if (ngx_errno == NGX_EPIPE) { + pipe_ctx->err_type = PIPE_ERR_CLOSED; + + } else { + pipe_ctx->err_type = PIPE_ERR_SYSCALL; + pipe_ctx->pipe_errno = ngx_errno; + } + + return NGX_ERROR; + } + + return NGX_AGAIN; +} + + +ssize_t +ngx_http_lua_ffi_pipe_proc_write(ngx_http_request_t *r, + ngx_http_lua_ffi_pipe_proc_t *proc, const u_char *data, size_t len, + u_char *errbuf, size_t *errbuf_size) +{ + int rc; + ngx_buf_t *b; + ngx_msec_t timeout; + ngx_chain_t *cl; + ngx_event_t *wev; + ngx_http_lua_ctx_t *ctx; + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_co_ctx_t *wait_co_ctx; + ngx_http_lua_pipe_ctx_t *pipe_ctx; + + rc = ngx_http_lua_pipe_get_lua_ctx(r, &ctx, errbuf, errbuf_size); + if (rc != NGX_OK) { + return rc; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua pipe write process:%p pid:%P", proc, proc->_pid); + + pipe = proc->pipe; + if (pipe == NULL || pipe->closed) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf; + return NGX_ERROR; + } + + if (pipe->stdin_ctx == NULL) { + if (ngx_http_lua_pipe_init_ctx(&pipe->stdin_ctx, pipe->stdin_fd, + pipe->pool, errbuf, + errbuf_size) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + pipe->stdin_ctx->err_type = 0; + } + + pipe_ctx = pipe->stdin_ctx; + if (pipe_ctx->c == NULL) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "closed") - errbuf; + return NGX_ERROR; + } + + wev = pipe_ctx->c->write; + if (wev->handler != ngx_http_lua_pipe_dummy_event_handler) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "pipe busy writing") + - errbuf; + return NGX_ERROR; + } + + pipe_ctx->rest = len; + + cl = ngx_http_lua_chain_get_free_buf(ngx_cycle->log, pipe->pool, + &pipe->free_bufs, len); + if (cl == NULL) { + pipe_ctx->err_type = PIPE_ERR_NOMEM; + goto error; + } + + pipe_ctx->buf_in = cl; + b = pipe_ctx->buf_in->buf; + b->last = ngx_copy(b->last, data, len); + + rc = ngx_http_lua_pipe_write(pipe, pipe_ctx); + if (rc == NGX_ERROR) { + goto error; + } + + if (rc == NGX_OK) { + return len; + } + + /* rc == NGX_AGAIN */ + wait_co_ctx = ctx->cur_co_ctx; + pipe_ctx->c->data = wait_co_ctx; + + wev->handler = ngx_http_lua_pipe_resume_write_handler; + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + pipe_ctx->err_type = PIPE_ERR_ADD_WRITE_EV; + goto error; + } + + wait_co_ctx->data = proc; + wait_co_ctx->cleanup = ngx_http_lua_pipe_proc_write_cleanup; + timeout = proc->write_timeout; + + if (timeout > 0) { + ngx_add_timer(wev, timeout); + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua pipe add timer for writing: %d(ms) process:%p " + "pid:%P pipe:%p ev:%p", timeout, proc, proc->_pid, pipe, + wev); + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua pipe write yielding process:%p pid:%P pipe:%p", proc, + proc->_pid, pipe); + + return NGX_AGAIN; + +error: + + ngx_http_lua_pipe_put_error(pipe_ctx, errbuf, errbuf_size); + return NGX_ERROR; +} + + +/* + * ngx_http_lua_ffi_pipe_get_write_result should only be called just after + * ngx_http_lua_ffi_pipe_proc_write, so we omit most of the sanity check + * already done in ngx_http_lua_ffi_pipe_proc_write. + */ +ssize_t +ngx_http_lua_ffi_pipe_get_write_result(ngx_http_request_t *r, + ngx_http_lua_ffi_pipe_proc_t *proc, u_char *errbuf, size_t *errbuf_size) +{ + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_pipe_ctx_t *pipe_ctx; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua pipe get write result process:%p pid:%P", proc, + proc->_pid); + + pipe = proc->pipe; + pipe_ctx = pipe->stdin_ctx; + + if (pipe_ctx->err_type) { + ngx_http_lua_pipe_put_error(pipe_ctx, errbuf, errbuf_size); + return NGX_ERROR; + } + + return pipe_ctx->rest; +} + + +int +ngx_http_lua_ffi_pipe_proc_wait(ngx_http_request_t *r, + ngx_http_lua_ffi_pipe_proc_t *proc, char **reason, int *status, + u_char *errbuf, size_t *errbuf_size) +{ + int rc; + ngx_rbtree_node_t *node; + ngx_http_lua_ctx_t *ctx; + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_co_ctx_t *wait_co_ctx; + ngx_http_lua_pipe_node_t *pipe_node; + + rc = ngx_http_lua_pipe_get_lua_ctx(r, &ctx, errbuf, errbuf_size); + if (rc != NGX_OK) { + return rc; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua pipe wait process:%p pid:%P", proc, proc->_pid); + + pipe = proc->pipe; + if (pipe == NULL || pipe->closed) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "exited") - errbuf; + return NGX_ERROR; + } + + node = pipe->node; + pipe_node = (ngx_http_lua_pipe_node_t *) &node->color; + if (pipe_node->wait_co_ctx) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "pipe busy waiting") + - errbuf; + return NGX_ERROR; + } + + if (pipe_node->reason_code == REASON_RUNNING_CODE) { + wait_co_ctx = ctx->cur_co_ctx; + wait_co_ctx->data = proc; + ngx_memzero(&wait_co_ctx->sleep, sizeof(ngx_event_t)); + wait_co_ctx->sleep.handler = ngx_http_lua_pipe_resume_wait_handler; + wait_co_ctx->sleep.data = wait_co_ctx; + wait_co_ctx->sleep.log = r->connection->log; + wait_co_ctx->cleanup = ngx_http_lua_pipe_proc_wait_cleanup; + + pipe_node->wait_co_ctx = wait_co_ctx; + + if (proc->wait_timeout > 0) { + ngx_add_timer(&wait_co_ctx->sleep, proc->wait_timeout); + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua pipe add timer for waiting: %d(ms) process:%p " + "pid:%P ev:%p", proc->wait_timeout, proc, + proc->_pid, &wait_co_ctx->sleep); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua pipe wait yielding process:%p pid:%P", proc, + proc->_pid); + + return NGX_AGAIN; + } + + *status = pipe_node->status; + + switch (pipe_node->reason_code) { + + case REASON_EXIT_CODE: + *reason = REASON_EXIT; + break; + + case REASON_SIGNAL_CODE: + *reason = REASON_SIGNAL; + break; + + default: + *reason = REASON_UNKNOWN; + } + + ngx_http_lua_pipe_proc_finalize(proc); + + if (*status == 0) { + return NGX_OK; + } + + return NGX_DECLINED; +} + + +int +ngx_http_lua_ffi_pipe_proc_kill(ngx_http_lua_ffi_pipe_proc_t *proc, int signal, + u_char *errbuf, size_t *errbuf_size) +{ + ngx_pid_t pid; + ngx_http_lua_pipe_t *pipe; + + pipe = proc->pipe; + + if (pipe == NULL || pipe->dead) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "exited") - errbuf; + return NGX_ERROR; + } + + pid = proc->_pid; + + if (kill(pid, signal) == -1) { + switch (ngx_errno) { + case EINVAL: + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "invalid signal") + - errbuf; + break; + + case ESRCH: + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "exited") + - errbuf; + break; + + default: + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, "%s", + strerror(ngx_errno)) + - errbuf; + } + + return NGX_ERROR; + } + + return NGX_OK; +} + + +static int +ngx_http_lua_pipe_read_stdout_retval(ngx_http_lua_ffi_pipe_proc_t *proc, + lua_State *L) +{ + return ngx_http_lua_pipe_read_retval_helper(proc, L, 0); +} + + +static int +ngx_http_lua_pipe_read_stderr_retval(ngx_http_lua_ffi_pipe_proc_t *proc, + lua_State *L) +{ + return ngx_http_lua_pipe_read_retval_helper(proc, L, 1); +} + + +static int +ngx_http_lua_pipe_read_retval_helper(ngx_http_lua_ffi_pipe_proc_t *proc, + lua_State *L, int from_stderr) +{ + int rc; + ngx_msec_t timeout; + ngx_event_t *rev; + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_pipe_ctx_t *pipe_ctx; + + pipe = proc->pipe; + if (from_stderr) { + pipe_ctx = pipe->stderr_ctx; + + } else { + pipe_ctx = pipe->stdout_ctx; + } + + if (pipe->timeout) { + pipe->timeout = 0; + pipe_ctx->err_type = PIPE_ERR_TIMEOUT; + return 0; + } + + if (pipe_ctx->err_type == PIPE_ERR_ABORTED) { + ngx_close_connection(pipe_ctx->c); + pipe_ctx->c = NULL; + return 0; + } + + rc = ngx_http_lua_pipe_read(pipe, pipe_ctx); + if (rc != NGX_AGAIN) { + return 0; + } + + rev = pipe_ctx->c->read; + + if (from_stderr) { + rev->handler = ngx_http_lua_pipe_resume_read_stderr_handler; + timeout = proc->stderr_read_timeout; + + } else { + rev->handler = ngx_http_lua_pipe_resume_read_stdout_handler; + timeout = proc->stdout_read_timeout; + } + + if (timeout > 0) { + ngx_add_timer(rev, timeout); + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe add timer for reading: %d(ms) proc:%p " + "pid:%P pipe:%p ev:%p", timeout, proc, proc->_pid, pipe, + rev); + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe read yielding process:%p pid:%P pipe:%p", proc, + proc->_pid, pipe); + + return NGX_AGAIN; +} + + +static int +ngx_http_lua_pipe_write_retval(ngx_http_lua_ffi_pipe_proc_t *proc, + lua_State *L) +{ + int rc; + ngx_msec_t timeout; + ngx_event_t *wev; + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_pipe_ctx_t *pipe_ctx; + + pipe = proc->pipe; + pipe_ctx = pipe->stdin_ctx; + + if (pipe->timeout) { + pipe->timeout = 0; + pipe_ctx->err_type = PIPE_ERR_TIMEOUT; + return 0; + } + + if (pipe_ctx->err_type == PIPE_ERR_ABORTED) { + ngx_close_connection(pipe_ctx->c); + pipe_ctx->c = NULL; + return 0; + } + + rc = ngx_http_lua_pipe_write(pipe, pipe_ctx); + if (rc != NGX_AGAIN) { + return 0; + } + + wev = pipe_ctx->c->write; + wev->handler = ngx_http_lua_pipe_resume_write_handler; + timeout = proc->write_timeout; + + if (timeout > 0) { + ngx_add_timer(wev, timeout); + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe add timer for writing: %d(ms) proc:%p " + "pid:%P pipe:%p ev:%p", timeout, proc, proc->_pid, pipe, + wev); + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe write yielding process:%p pid:%P pipe:%p", proc, + proc->_pid, pipe); + + return NGX_AGAIN; +} + + +static int +ngx_http_lua_pipe_wait_retval(ngx_http_lua_ffi_pipe_proc_t *proc, lua_State *L) +{ + int nret; + ngx_rbtree_node_t *node; + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_pipe_node_t *pipe_node; + + pipe = proc->pipe; + node = pipe->node; + pipe_node = (ngx_http_lua_pipe_node_t *) &node->color; + pipe_node->wait_co_ctx = NULL; + + if (pipe->timeout) { + pipe->timeout = 0; + lua_pushnil(L); + lua_pushliteral(L, "timeout"); + return 2; + } + + ngx_http_lua_pipe_proc_finalize(pipe_node->proc); + + if (pipe_node->status == 0) { + lua_pushboolean(L, 1); + lua_pushliteral(L, REASON_EXIT); + lua_pushinteger(L, pipe_node->status); + nret = 3; + + } else { + lua_pushboolean(L, 0); + + switch (pipe_node->reason_code) { + + case REASON_EXIT_CODE: + lua_pushliteral(L, REASON_EXIT); + break; + + case REASON_SIGNAL_CODE: + lua_pushliteral(L, REASON_SIGNAL); + break; + + default: + lua_pushliteral(L, REASON_UNKNOWN); + } + + lua_pushinteger(L, pipe_node->status); + nret = 3; + } + + return nret; +} + + +static void +ngx_http_lua_pipe_resume_helper(ngx_event_t *ev, + ngx_http_lua_co_ctx_t *wait_co_ctx) +{ + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_lua_ctx_t *ctx; + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_ffi_pipe_proc_t *proc; + + if (ev->timedout) { + proc = wait_co_ctx->data; + pipe = proc->pipe; + pipe->timeout = 1; + ev->timedout = 0; + } + + ngx_http_lua_pipe_clear_event(ev); + + r = ngx_http_lua_get_req(wait_co_ctx->co); + c = r->connection; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + ngx_http_lua_assert(ctx != NULL); + + ctx->cur_co_ctx = wait_co_ctx; + + if (ctx->entered_content_phase) { + (void) ngx_http_lua_pipe_resume(r); + + } else { + ctx->resume_handler = ngx_http_lua_pipe_resume; + ngx_http_core_run_phases(r); + } + + ngx_http_run_posted_requests(c); +} + + +static void +ngx_http_lua_pipe_resume_read_stdout_handler(ngx_event_t *ev) +{ + ngx_connection_t *c = ev->data; + ngx_http_lua_co_ctx_t *wait_co_ctx; + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_ffi_pipe_proc_t *proc; + + wait_co_ctx = c->data; + proc = wait_co_ctx->data; + pipe = proc->pipe; + pipe->retval_handler = ngx_http_lua_pipe_read_stdout_retval; + ngx_http_lua_pipe_resume_helper(ev, wait_co_ctx); +} + + +static void +ngx_http_lua_pipe_resume_read_stderr_handler(ngx_event_t *ev) +{ + ngx_connection_t *c = ev->data; + ngx_http_lua_co_ctx_t *wait_co_ctx; + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_ffi_pipe_proc_t *proc; + + wait_co_ctx = c->data; + proc = wait_co_ctx->data; + pipe = proc->pipe; + pipe->retval_handler = ngx_http_lua_pipe_read_stderr_retval; + ngx_http_lua_pipe_resume_helper(ev, wait_co_ctx); +} + + +static void +ngx_http_lua_pipe_resume_write_handler(ngx_event_t *ev) +{ + ngx_connection_t *c = ev->data; + ngx_http_lua_co_ctx_t *wait_co_ctx; + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_ffi_pipe_proc_t *proc; + + wait_co_ctx = c->data; + proc = wait_co_ctx->data; + pipe = proc->pipe; + pipe->retval_handler = ngx_http_lua_pipe_write_retval; + ngx_http_lua_pipe_resume_helper(ev, wait_co_ctx); +} + + +static void +ngx_http_lua_pipe_resume_wait_handler(ngx_event_t *ev) +{ + ngx_http_lua_co_ctx_t *wait_co_ctx = ev->data; + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_ffi_pipe_proc_t *proc; + + proc = wait_co_ctx->data; + pipe = proc->pipe; + pipe->retval_handler = ngx_http_lua_pipe_wait_retval; + ngx_http_lua_pipe_resume_helper(ev, wait_co_ctx); +} + + +static ngx_int_t +ngx_http_lua_pipe_resume(ngx_http_request_t *r) +{ + int nret; + lua_State *vm; + ngx_int_t rc; + ngx_uint_t nreqs; + ngx_connection_t *c; + ngx_http_lua_ctx_t *ctx; + ngx_http_lua_pipe_t *pipe; + ngx_http_lua_ffi_pipe_proc_t *proc; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + return NGX_ERROR; + } + + ctx->resume_handler = ngx_http_lua_wev_handler; + ctx->cur_co_ctx->cleanup = NULL; + + proc = ctx->cur_co_ctx->data; + pipe = proc->pipe; + nret = pipe->retval_handler(proc, ctx->cur_co_ctx->co); + if (nret == NGX_AGAIN) { + return NGX_DONE; + } + + c = r->connection; + vm = ngx_http_lua_get_lua_vm(r, ctx); + nreqs = c->requests; + + rc = ngx_http_lua_run_thread(vm, r, ctx, nret); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua run thread returned %d", rc); + + if (rc == NGX_AGAIN) { + return ngx_http_lua_run_posted_threads(c, vm, r, ctx, nreqs); + } + + if (rc == NGX_DONE) { + ngx_http_lua_finalize_request(r, NGX_DONE); + return ngx_http_lua_run_posted_threads(c, vm, r, ctx, nreqs); + } + + /* rc == NGX_ERROR || rc >= NGX_OK */ + + if (ctx->entered_content_phase) { + ngx_http_lua_finalize_request(r, rc); + return NGX_DONE; + } + + return rc; +} + + +static void +ngx_http_lua_pipe_dummy_event_handler(ngx_event_t *ev) +{ + /* do nothing */ +} + + +static void +ngx_http_lua_pipe_clear_event(ngx_event_t *ev) +{ + ev->handler = ngx_http_lua_pipe_dummy_event_handler; + + if (ev->timer_set) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, 0, + "lua pipe del timer for ev:%p", ev); + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, 0, + "lua pipe del posted event for ev:%p", ev); + ngx_delete_posted_event(ev); + } +} + + +static void +ngx_http_lua_pipe_proc_read_stdout_cleanup(void *data) +{ + ngx_event_t *rev; + ngx_connection_t *c; + ngx_http_lua_co_ctx_t *wait_co_ctx = data; + ngx_http_lua_ffi_pipe_proc_t *proc; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe proc read stdout cleanup"); + + proc = wait_co_ctx->data; + c = proc->pipe->stdout_ctx->c; + if (c) { + rev = c->read; + ngx_http_lua_pipe_clear_event(rev); + } + + wait_co_ctx->cleanup = NULL; +} + + +static void +ngx_http_lua_pipe_proc_read_stderr_cleanup(void *data) +{ + ngx_event_t *rev; + ngx_connection_t *c; + ngx_http_lua_co_ctx_t *wait_co_ctx = data; + ngx_http_lua_ffi_pipe_proc_t *proc; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe proc read stderr cleanup"); + + proc = wait_co_ctx->data; + c = proc->pipe->stderr_ctx->c; + if (c) { + rev = c->read; + ngx_http_lua_pipe_clear_event(rev); + } + + wait_co_ctx->cleanup = NULL; +} + + +static void +ngx_http_lua_pipe_proc_write_cleanup(void *data) +{ + ngx_event_t *wev; + ngx_connection_t *c; + ngx_http_lua_co_ctx_t *wait_co_ctx = data; + ngx_http_lua_ffi_pipe_proc_t *proc; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe proc write cleanup"); + + proc = wait_co_ctx->data; + c = proc->pipe->stdin_ctx->c; + if (c) { + wev = c->write; + ngx_http_lua_pipe_clear_event(wev); + } + + wait_co_ctx->cleanup = NULL; +} + + +static void +ngx_http_lua_pipe_proc_wait_cleanup(void *data) +{ + ngx_rbtree_node_t *node; + ngx_http_lua_co_ctx_t *wait_co_ctx = data; + ngx_http_lua_pipe_node_t *pipe_node; + ngx_http_lua_ffi_pipe_proc_t *proc; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua pipe proc wait cleanup"); + + proc = wait_co_ctx->data; + node = proc->pipe->node; + pipe_node = (ngx_http_lua_pipe_node_t *) &node->color; + pipe_node->wait_co_ctx = NULL; + + ngx_http_lua_pipe_clear_event(&wait_co_ctx->sleep); + + wait_co_ctx->cleanup = NULL; +} + + +#endif /* HAVE_NGX_LUA_PIPE */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_pipe.h b/src/ngx_http_lua_pipe.h new file mode 100644 index 0000000000..f1c9283526 --- /dev/null +++ b/src/ngx_http_lua_pipe.h @@ -0,0 +1,96 @@ + +/* + * Copyright (C) by OpenResty Inc. + */ + + +#ifndef _NGX_HTTP_LUA_PIPE_H_INCLUDED_ +#define _NGX_HTTP_LUA_PIPE_H_INCLUDED_ + + +#include "ngx_http_lua_common.h" + + +typedef ngx_int_t (*ngx_http_lua_pipe_input_filter)(void *data, ssize_t bytes); + + +typedef struct { + ngx_connection_t *c; + ngx_http_lua_pipe_input_filter input_filter; + void *input_filter_ctx; + size_t rest; + ngx_chain_t *buf_in; + ngx_chain_t *bufs_in; + ngx_buf_t buffer; + ngx_err_t pipe_errno; + unsigned err_type:16; + unsigned eof:1; +} ngx_http_lua_pipe_ctx_t; + + +typedef struct ngx_http_lua_pipe_s ngx_http_lua_pipe_t; + + +typedef struct { + ngx_pid_t _pid; + ngx_msec_t write_timeout; + ngx_msec_t stdout_read_timeout; + ngx_msec_t stderr_read_timeout; + ngx_msec_t wait_timeout; + /* pipe hides the implementation from the Lua binding */ + ngx_http_lua_pipe_t *pipe; +} ngx_http_lua_ffi_pipe_proc_t; + + +typedef int (*ngx_http_lua_pipe_retval_handler)( + ngx_http_lua_ffi_pipe_proc_t *proc, lua_State *L); + + +struct ngx_http_lua_pipe_s { + ngx_pool_t *pool; + ngx_chain_t *free_bufs; + ngx_rbtree_node_t *node; + int stdin_fd; + int stdout_fd; + int stderr_fd; + ngx_http_lua_pipe_ctx_t *stdin_ctx; + ngx_http_lua_pipe_ctx_t *stdout_ctx; + ngx_http_lua_pipe_ctx_t *stderr_ctx; + ngx_http_lua_pipe_retval_handler retval_handler; + ngx_http_cleanup_pt *cleanup; + ngx_http_request_t *r; + size_t buffer_size; + unsigned closed:1; + unsigned dead:1; + unsigned timeout:1; + unsigned merge_stderr:1; +}; + + +typedef struct { + u_char color; + u_char reason_code; + int status; + ngx_http_lua_co_ctx_t *wait_co_ctx; + ngx_http_lua_ffi_pipe_proc_t *proc; +} ngx_http_lua_pipe_node_t; + + +typedef struct { + int signo; + char *signame; +} ngx_http_lua_pipe_signal_t; + + +#if !(NGX_WIN32) && defined(HAVE_SOCKET_CLOEXEC_PATCH) +#define HAVE_NGX_LUA_PIPE 1 + + +void ngx_http_lua_pipe_init(void); +ngx_int_t ngx_http_lua_pipe_add_signal_handler(ngx_cycle_t *cycle); +#endif + + +#endif /* _NGX_HTTP_LUA_PIPE_H_INCLUDED_ */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_probe.h b/src/ngx_http_lua_probe.h index 37b25002cc..749fa95e65 100644 --- a/src/ngx_http_lua_probe.h +++ b/src/ngx_http_lua_probe.h @@ -44,7 +44,8 @@ #define ngx_http_lua_probe_socket_tcp_receive_done(r, u, data, len) \ NGINX_LUA_HTTP_LUA_SOCKET_TCP_RECEIVE_DONE(r, u, data, len) -#define ngx_http_lua_probe_socket_tcp_setkeepalive_buf_unread(r, u, data, len)\ +#define ngx_http_lua_probe_socket_tcp_setkeepalive_buf_unread(r, u, data, \ + len) \ NGINX_LUA_HTTP_LUA_SOCKET_TCP_SETKEEPALIVE_BUF_UNREAD(r, u, data, len) #define ngx_http_lua_probe_user_thread_spawn(r, creator, newthread) \ diff --git a/src/ngx_http_lua_proxy_ssl_verifyby.c b/src/ngx_http_lua_proxy_ssl_verifyby.c new file mode 100644 index 0000000000..c8d783ab4b --- /dev/null +++ b/src/ngx_http_lua_proxy_ssl_verifyby.c @@ -0,0 +1,877 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef DDEBUG +#define DDEBUG 0 +#endif +#include "ddebug.h" + + +#if (NGX_HTTP_SSL) + + +#include "ngx_http_lua_cache.h" +#include "ngx_http_lua_initworkerby.h" +#include "ngx_http_lua_util.h" +#include "ngx_http_ssl_module.h" +#include "ngx_http_lua_contentby.h" +#include "ngx_http_lua_directive.h" +#include "ngx_http_lua_ssl.h" + +#ifdef HAVE_PROXY_SSL_PATCH +#include "ngx_http_lua_proxy_ssl_verifyby.h" + + +#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x30000020uL) +static void ngx_http_lua_proxy_ssl_verify_done(void *data); +static void ngx_http_lua_proxy_ssl_verify_aborted(void *data); +#endif +static ngx_int_t ngx_http_lua_proxy_ssl_verify_by_chunk(lua_State *L, + ngx_http_request_t *r); + + +ngx_int_t +ngx_http_lua_proxy_ssl_verify_set_callback(ngx_conf_t *cf) +{ + +#if defined(LIBRESSL_VERSION_NUMBER) + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "LibreSSL does not support by proxy_ssl_verify_by_lua*"); + + return NGX_ERROR; + +#elif defined(OPENSSL_IS_BORINGSSL) + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "BoringSSL does not support by proxy_ssl_verify_by_lua*"); + + return NGX_ERROR; + +#else + + void *plcf; + ngx_http_upstream_conf_t *ucf; + ngx_ssl_t *ssl; + + /* + * Nginx doesn't export ngx_http_proxy_loc_conf_t, so we can't directly + * get plcf here, but the first member of plcf is ngx_http_upstream_conf_t + */ + plcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_proxy_module); + ucf = plcf; + + ssl = ucf->ssl; + + if (!ssl->ctx) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "proxy_ssl_verify_by_lua* " + "should be used with proxy_pass https url"); + + return NGX_ERROR; + } + +#if (!defined SSL_ERROR_WANT_RETRY_VERIFY \ + || OPENSSL_VERSION_NUMBER < 0x30000020L) + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "OpenSSL too old to support " + "proxy_ssl_verify_by_lua*"); + + return NGX_ERROR; + +#else + + SSL_CTX_set_cert_verify_callback(ssl->ctx, + ngx_http_lua_proxy_ssl_verify_handler, + NULL); + return NGX_OK; + +#endif + +#endif +} + + +ngx_int_t +ngx_http_lua_proxy_ssl_verify_handler_file(ngx_http_request_t *r, + ngx_http_lua_loc_conf_t *llcf, lua_State *L) +{ + ngx_int_t rc; + + rc = ngx_http_lua_cache_loadfile(r->connection->log, L, + llcf->proxy_ssl_verify_src.data, + &llcf->proxy_ssl_verify_src_ref, + llcf->proxy_ssl_verify_src_key); + if (rc != NGX_OK) { + return rc; + } + + /* make sure we have a valid code chunk */ + ngx_http_lua_assert(lua_isfunction(L, -1)); + + return ngx_http_lua_proxy_ssl_verify_by_chunk(L, r); +} + + +ngx_int_t +ngx_http_lua_proxy_ssl_verify_handler_inline(ngx_http_request_t *r, + ngx_http_lua_loc_conf_t *llcf, lua_State *L) +{ + ngx_int_t rc; + + rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, + llcf->proxy_ssl_verify_src.data, + llcf->proxy_ssl_verify_src.len, + &llcf->proxy_ssl_verify_src_ref, + llcf->proxy_ssl_verify_src_key, + (const char *) llcf->proxy_ssl_verify_chunkname); + if (rc != NGX_OK) { + return rc; + } + + /* make sure we have a valid code chunk */ + ngx_http_lua_assert(lua_isfunction(L, -1)); + + return ngx_http_lua_proxy_ssl_verify_by_chunk(L, r); +} + + +char * +ngx_http_lua_proxy_ssl_verify_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + char *rv; + ngx_conf_t save; + + save = *cf; + cf->handler = ngx_http_lua_proxy_ssl_verify_by_lua; + cf->handler_conf = conf; + + rv = ngx_http_lua_conf_lua_block_parse(cf, cmd); + + *cf = save; + + return rv; +} + + +char * +ngx_http_lua_proxy_ssl_verify_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ +#if defined(LIBRESSL_VERSION_NUMBER) + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "LibreSSL does not support by proxy_ssl_verify_by_lua*"); + + return NGX_CONF_ERROR; + +#elif defined(OPENSSL_IS_BORINGSSL) + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "BoringSSL does not support by proxy_ssl_verify_by_lua*"); + + return NGX_CONF_ERROR; + +#else + +#if (!defined SSL_ERROR_WANT_RETRY_VERIFY \ + || OPENSSL_VERSION_NUMBER < 0x30000020L) + + /* SSL_set_retry_verify() was added in OpenSSL 3.0.2 */ + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "at least OpenSSL 3.0.2 required but found " + OPENSSL_VERSION_TEXT); + + return NGX_CONF_ERROR; + +#else + + size_t chunkname_len; + u_char *chunkname; + u_char *cache_key = NULL; + u_char *name; + ngx_str_t *value; + ngx_http_lua_loc_conf_t *llcf = conf; + + /* must specify a concrete handler */ + if (cmd->post == NULL) { + return NGX_CONF_ERROR; + } + + if (llcf->proxy_ssl_verify_handler) { + return "is duplicate"; + } + + if (ngx_http_lua_ssl_init(cf->log) != NGX_OK) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + llcf->proxy_ssl_verify_handler = + (ngx_http_lua_loc_conf_handler_pt) cmd->post; + + if (cmd->post == ngx_http_lua_proxy_ssl_verify_handler_file) { + /* Lua code in an external file */ + + name = ngx_http_lua_rebase_path(cf->pool, value[1].data, + value[1].len); + if (name == NULL) { + return NGX_CONF_ERROR; + } + + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } + + llcf->proxy_ssl_verify_src.data = name; + llcf->proxy_ssl_verify_src.len = ngx_strlen(name); + + } else { + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, + "proxy_ssl_verify_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } + + chunkname = ngx_http_lua_gen_chunk_name(cf, "proxy_ssl_verify_by_lua", + sizeof("proxy_ssl_verify_by_lua") - 1, + &chunkname_len); + if (chunkname == NULL) { + return NGX_CONF_ERROR; + } + + /* Don't eval nginx variables for inline lua code */ + llcf->proxy_ssl_verify_src = value[1]; + llcf->proxy_ssl_verify_chunkname = chunkname; + } + + llcf->proxy_ssl_verify_src_key = cache_key; + + return NGX_CONF_OK; + +#endif /* SSL_ERROR_WANT_RETRY_VERIFY */ + +#endif +} + + +int +ngx_http_lua_proxy_ssl_verify_handler(X509_STORE_CTX *x509_store, void *arg) +{ +#if defined(LIBRESSL_VERSION_NUMBER) + ngx_connection_t *c; + + c = ngx_ssl_get_connection(ssl_conn); /* upstream connection */ + ngx_ssl_conn_t *ssl_conn; + + ssl_conn = X509_STORE_CTX_get_ex_data(x509_store, + SSL_get_ex_data_X509_STORE_CTX_idx()); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "LibreSSL does not support by proxy_ssl_verify_by_lua*"); + + return 1; + +#elif defined(OPENSSL_IS_BORINGSSL) + ngx_connection_t *c; + ngx_ssl_conn_t *ssl_conn; + + ssl_conn = X509_STORE_CTX_get_ex_data(x509_store, + SSL_get_ex_data_X509_STORE_CTX_idx()); + c = ngx_ssl_get_connection(ssl_conn); /* upstream connection */ + + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "BoringSSL does not support by proxy_ssl_verify_by_lua*"); + + return 1; +#elif defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x30000020uL) + + ngx_connection_t *c; + ngx_ssl_conn_t *ssl_conn; + + ssl_conn = X509_STORE_CTX_get_ex_data(x509_store, + SSL_get_ex_data_X509_STORE_CTX_idx()); + c = ngx_ssl_get_connection(ssl_conn); /* upstream connection */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "OpenSSL(< 3.0.2) does not support by proxy_ssl_verify_by_lua*"); + + return 1; + +#else + + lua_State *L; + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_request_t *r = NULL; + ngx_pool_cleanup_t *cln; + ngx_http_lua_loc_conf_t *llcf; + ngx_http_lua_ssl_ctx_t *cctx; + ngx_ssl_conn_t *ssl_conn; + + ssl_conn = X509_STORE_CTX_get_ex_data(x509_store, + SSL_get_ex_data_X509_STORE_CTX_idx()); + + c = ngx_ssl_get_connection(ssl_conn); /* upstream connection */ + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "proxy ssl verify: connection reusable: %ud", c->reusable); + + cctx = ngx_http_lua_ssl_get_ctx(c->ssl->connection); + + dd("proxy ssl verify handler, cert-verify-ctx=%p", cctx); + + if (cctx && cctx->entered_proxy_ssl_verify_handler) { + /* not the first time */ + + if (cctx->done) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "proxy_ssl_verify_by_lua: " + "cert verify callback exit code: %d", + cctx->exit_code); + + dd("lua proxy ssl verify done, finally"); + return cctx->exit_code; + } + + return SSL_set_retry_verify(ssl_conn); + } + + dd("first time"); + +#if (nginx_version < 1017009) + ngx_reusable_connection(c, 0); +#endif + + r = c->data; + + if (cctx == NULL) { + cctx = ngx_pcalloc(c->pool, sizeof(ngx_http_lua_ssl_ctx_t)); + if (cctx == NULL) { + goto failed; /* error */ + } + + cctx->ctx_ref = LUA_NOREF; + } + + cctx->connection = c; + cctx->request = r; + cctx->x509_store = x509_store; + cctx->exit_code = 1; /* successful by default */ + cctx->original_request_count = r->main->count; + cctx->done = 0; + cctx->entered_proxy_ssl_verify_handler = 1; + cctx->pool = ngx_create_pool(128, c->log); + if (cctx->pool == NULL) { + goto failed; + } + + dd("setting cctx"); + + if (SSL_set_ex_data(c->ssl->connection, ngx_http_lua_ssl_ctx_index, + cctx) == 0) + { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_ex_data() failed"); + goto failed; + } + + llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); + if (llcf->upstream_skip_openssl_default_verify == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "proxy_ssl_verify_by_lua: openssl default verify"); + + rc = X509_verify_cert(x509_store); + if (rc == 0) { + goto failed; + } + } + + /* TODO honor lua_code_cache off */ + L = ngx_http_lua_get_lua_vm(r, NULL); + + c->log->action = "loading proxy ssl verify by lua"; + + rc = llcf->proxy_ssl_verify_handler(r, llcf, L); + + if (rc >= NGX_OK || rc == NGX_ERROR) { + cctx->done = 1; + + if (cctx->cleanup) { + *cctx->cleanup = NULL; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "proxy_ssl_verify_by_lua: handler return value: %i, " + "cert verify callback exit code: %d", rc, + cctx->exit_code); + + c->log->action = "proxy pass SSL handshaking"; + return cctx->exit_code; + } + + /* rc == NGX_DONE */ + + cln = ngx_pool_cleanup_add(cctx->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->handler = ngx_http_lua_proxy_ssl_verify_done; + cln->data = cctx; + + if (cctx->cleanup == NULL) { + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->data = cctx; + cctx->cleanup = &cln->handler; + } + + *cctx->cleanup = ngx_http_lua_proxy_ssl_verify_aborted; + + return SSL_set_retry_verify(ssl_conn); + +failed: + + if (cctx && cctx->pool) { + ngx_destroy_pool(cctx->pool); + } + + return 0; /* verify failure or error */ + +#endif +} + + +#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x30000020uL) +static void +ngx_http_lua_proxy_ssl_verify_done(void *data) +{ + ngx_connection_t *c; + ngx_http_lua_ssl_ctx_t *cctx = data; + + dd("lua proxy ssl verify done"); + + if (cctx->aborted) { + return; + } + + ngx_http_lua_assert(cctx->done == 0); + + cctx->done = 1; + + if (cctx->cleanup) { + *cctx->cleanup = NULL; + } + + c = cctx->connection; + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + c->log->action = "proxy pass SSL handshaking"; + + ngx_post_event(c->write, &ngx_posted_events); +} + + +static void +ngx_http_lua_proxy_ssl_verify_aborted(void *data) +{ + ngx_http_lua_ssl_ctx_t *cctx = data; + + dd("lua proxy ssl verify aborted"); + + if (cctx->done) { + /* completed successfully already */ + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cctx->connection->log, 0, + "proxy_ssl_verify_by_lua: cert verify callback aborted"); + + cctx->aborted = 1; + cctx->connection->ssl = NULL; + cctx->exit_code = 0; + if (cctx->pool) { + ngx_destroy_pool(cctx->pool); + cctx->pool = NULL; + } +} +#endif + + +static ngx_int_t +ngx_http_lua_proxy_ssl_verify_by_chunk(lua_State *L, ngx_http_request_t *r) +{ + int co_ref; + ngx_int_t rc; + lua_State *co; + ngx_http_lua_ctx_t *ctx; + ngx_pool_cleanup_t *cln; + ngx_http_upstream_t *u; + ngx_connection_t *c; + ngx_http_lua_ssl_ctx_t *cctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + + if (ctx == NULL) { + ctx = ngx_http_lua_create_ctx(r); + if (ctx == NULL) { + rc = NGX_ERROR; + ngx_http_lua_finalize_request(r, rc); + return rc; + } + + } else { + dd("reset ctx"); + ngx_http_lua_reset_ctx(r, L, ctx); + } + + ctx->entered_content_phase = 1; + + /* {{{ new coroutine to handle request */ + co = ngx_http_lua_new_thread(r, L, &co_ref); + + if (co == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "lua: failed to create new coroutine to handle request"); + + rc = NGX_ERROR; + ngx_http_lua_finalize_request(r, rc); + return rc; + } + + /* move code closure to new coroutine */ + lua_xmove(L, co, 1); + +#ifndef OPENRESTY_LUAJIT + /* set closure's env table to new coroutine's globals table */ + ngx_http_lua_get_globals_table(co); + lua_setfenv(co, -2); +#endif + + /* save nginx request in coroutine globals table */ + ngx_http_lua_set_req(co, r); + + ctx->cur_co_ctx = &ctx->entry_co_ctx; + ctx->cur_co_ctx->co = co; + ctx->cur_co_ctx->co_ref = co_ref; +#ifdef NGX_LUA_USE_ASSERT + ctx->cur_co_ctx->co_top = 1; +#endif + + ngx_http_lua_attach_co_ctx_to_L(co, ctx->cur_co_ctx); + + /* register request cleanup hooks */ + if (ctx->cleanup == NULL) { + u = r->upstream; + c = u->peer.connection; + cctx = ngx_http_lua_ssl_get_ctx(c->ssl->connection); + + cln = ngx_pool_cleanup_add(cctx->pool, 0); + if (cln == NULL) { + rc = NGX_ERROR; + ngx_http_lua_finalize_request(r, rc); + return rc; + } + + cln->handler = ngx_http_lua_request_cleanup_handler; + cln->data = ctx; + ctx->cleanup = &cln->handler; + } + + ctx->context = NGX_HTTP_LUA_CONTEXT_PROXY_SSL_VERIFY; + + rc = ngx_http_lua_run_thread(L, r, ctx, 0); + + if (rc == NGX_ERROR || rc >= NGX_OK) { + /* do nothing */ + + } else if (rc == NGX_AGAIN) { + rc = ngx_http_lua_content_run_posted_threads(L, r, ctx, 0); + + } else if (rc == NGX_DONE) { + rc = ngx_http_lua_content_run_posted_threads(L, r, ctx, 1); + + } else { + rc = NGX_OK; + } + + ngx_http_lua_finalize_request(r, rc); + return rc; +} + + +/* + * openssl's doc of SSL_CTX_set_cert_verify_callback: + * In any case a viable verification result value must + * be reflected in the error member of x509_store_ctx, + * which can be done using X509_STORE_CTX_set_error. + */ +int +ngx_http_lua_ffi_proxy_ssl_set_verify_result(ngx_http_request_t *r, + int verify_result, char **err) +{ +#if defined(LIBRESSL_VERSION_NUMBER) + + *err = "LibreSSL does not support this function"; + + return NGX_ERROR; + +#elif defined(OPENSSL_IS_BORINGSSL) + + *err = "BoringSSL does not support this function"; + + return NGX_ERROR; + +#else + +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + ngx_http_upstream_t *u; + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + ngx_http_lua_ssl_ctx_t *cctx; + X509_STORE_CTX *x509_store; + + u = r->upstream; + if (u == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + c = u->peer.connection; + if (c == NULL || c->ssl == NULL) { + *err = "bad upstream connection"; + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + dd("get cctx session"); + + c = ngx_ssl_get_connection(ssl_conn); + + cctx = ngx_http_lua_ssl_get_ctx(c->ssl->connection); + if (cctx == NULL) { + *err = "bad lua context"; + return NGX_ERROR; + } + + x509_store = cctx->x509_store; + + X509_STORE_CTX_set_error(x509_store, verify_result); + + return NGX_OK; +#else + *err = "OpenSSL too old to support this function"; + + return NGX_ERROR; +#endif + +#endif +} + + +int +ngx_http_lua_ffi_proxy_ssl_get_verify_result(ngx_http_request_t *r, char **err) +{ +#if defined(LIBRESSL_VERSION_NUMBER) + + *err = "LibreSSL does not support this function"; + + return NGX_ERROR; + +#elif defined(OPENSSL_IS_BORINGSSL) + + *err = "BoringSSL does not support this function"; + + return NGX_ERROR; + +#else + +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + ngx_http_upstream_t *u; + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + ngx_http_lua_ssl_ctx_t *cctx; + X509_STORE_CTX *x509_store; + + u = r->upstream; + if (u == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + c = u->peer.connection; + if (c == NULL || c->ssl == NULL) { + *err = "bad upstream connection"; + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + dd("get cctx session"); + + c = ngx_ssl_get_connection(ssl_conn); + + cctx = ngx_http_lua_ssl_get_ctx(c->ssl->connection); + if (cctx == NULL) { + *err = "bad lua context"; + return NGX_ERROR; + } + + x509_store = cctx->x509_store; + + return X509_STORE_CTX_get_error(x509_store); +#else + *err = "OpenSSL too old to support this function"; + + return NGX_ERROR; +#endif + +#endif +} + + +void +ngx_http_lua_ffi_proxy_ssl_free_verify_cert(void *cdata) +{ + X509 *cert = cdata; + + X509_free(cert); +} + + +void * +ngx_http_lua_ffi_proxy_ssl_get_verify_cert(ngx_http_request_t *r, char **err) +{ +#if defined(LIBRESSL_VERSION_NUMBER) + + *err = "LibreSSL does not support this function"; + + return NGX_ERROR; + +#elif defined(OPENSSL_IS_BORINGSSL) + + *err = "BoringSSL does not support this function"; + + return NGX_ERROR; + +#else + +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + ngx_http_upstream_t *u; + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + ngx_http_lua_ssl_ctx_t *cctx; + X509_STORE_CTX *x509_store; + X509 *x509; + + u = r->upstream; + if (u == NULL) { + *err = "bad request"; + return NULL; + } + + c = u->peer.connection; + if (c == NULL || c->ssl == NULL) { + *err = "bad upstream connection"; + return NULL; + } + + ssl_conn = c->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NULL; + } + + dd("get cctx session"); + + c = ngx_ssl_get_connection(ssl_conn); + + cctx = ngx_http_lua_ssl_get_ctx(c->ssl->connection); + if (cctx == NULL) { + *err = "bad lua context"; + return NULL; + } + + x509_store = cctx->x509_store; + + x509 = X509_STORE_CTX_get0_cert(x509_store); + + if (!X509_up_ref(x509)) { + *err = "get verify result failed"; + return NULL; + } + + return x509; +#else + *err = "OpenSSL too old to support this function"; + + return NULL; +#endif + +#endif +} + + +#else /* HAVE_PROXY_SSL_PATCH */ + + +int +ngx_http_lua_ffi_proxy_ssl_set_verify_result(ngx_http_request_t *r, + int verify_result, char **err) +{ + *err = "Does not have HAVE_PROXY_SSL_PATCH to support this function"; + + return NGX_ERROR; +} + + +int +ngx_http_lua_ffi_proxy_ssl_get_verify_result(ngx_http_request_t *r, char **err) +{ + *err = "Does not have HAVE_PROXY_SSL_PATCH to support this function"; + + return NGX_ERROR; +} + + +void +ngx_http_lua_ffi_proxy_ssl_free_verify_cert(void *cdata) +{ +} + + +void * +ngx_http_lua_ffi_proxy_ssl_get_verify_cert(ngx_http_request_t *r, char **err) +{ + *err = "Does not have HAVE_PROXY_SSL_PATCH to support this function"; + + return NULL; +} + +#endif /* HAVE_PROXY_SSL_PATCH */ +#endif /* NGX_HTTP_SSL */ diff --git a/src/ngx_http_lua_proxy_ssl_verifyby.h b/src/ngx_http_lua_proxy_ssl_verifyby.h new file mode 100644 index 0000000000..3e0b178dee --- /dev/null +++ b/src/ngx_http_lua_proxy_ssl_verifyby.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef _NGX_HTTP_LUA_PROXY_SSL_VERIFYBY_H_INCLUDED_ +#define _NGX_HTTP_LUA_PROXY_SSL_VERIFYBY_H_INCLUDED_ + + +#include "ngx_http_lua_common.h" + + +#if (NGX_HTTP_SSL) +#ifdef HAVE_PROXY_SSL_PATCH + +/* do not introduce ngx_http_proxy_module to pollute ngx_http_lua_module.c */ +extern ngx_module_t ngx_http_proxy_module; + +ngx_int_t ngx_http_lua_proxy_ssl_verify_handler_inline(ngx_http_request_t *r, + ngx_http_lua_loc_conf_t *llcf, lua_State *L); + +ngx_int_t ngx_http_lua_proxy_ssl_verify_handler_file(ngx_http_request_t *r, + ngx_http_lua_loc_conf_t *llcf, lua_State *L); + +char *ngx_http_lua_proxy_ssl_verify_by_lua_block(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); + +char *ngx_http_lua_proxy_ssl_verify_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +int ngx_http_lua_proxy_ssl_verify_handler(X509_STORE_CTX *x509_store, + void *arg); + +ngx_int_t ngx_http_lua_proxy_ssl_verify_set_callback(ngx_conf_t *cf); + +#endif /* HAVE_PROXY_SSL_PATCH */ +#endif /* NGX_HTTP_SSL */ + + +#endif /* _NGX_HTTP_LUA_PROXY_SSL_VERIFYBY_H_INCLUDED_ */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_regex.c b/src/ngx_http_lua_regex.c index 843d1e4e7a..35fe2a055c 100644 --- a/src/ngx_http_lua_regex.c +++ b/src/ngx_http_lua_regex.c @@ -9,27 +9,33 @@ #endif #include "ddebug.h" - #if (NGX_PCRE) -#include "ngx_http_lua_regex.h" #include "ngx_http_lua_pcrefix.h" #include "ngx_http_lua_script.h" -#include "ngx_http_lua_pcrefix.h" #include "ngx_http_lua_util.h" -#if (PCRE_MAJOR >= 6) +#if (PCRE_MAJOR >= 6 || NGX_PCRE2) # define LUA_HAVE_PCRE_DFA 1 #else # define LUA_HAVE_PCRE_DFA 0 #endif -#define NGX_LUA_RE_COMPILE_ONCE (1<<0) +#if (NGX_PCRE2) +static pcre2_compile_context *ngx_regex_compile_context; +static pcre2_match_context *ngx_regex_match_context; +static pcre2_match_data *ngx_regex_match_data; +static ngx_uint_t ngx_regex_match_data_size = 0; + +#define PCRE2_VERSION_SIZE 64 +static char ngx_pcre2_version[PCRE2_VERSION_SIZE]; +#endif + + #define NGX_LUA_RE_MODE_DFA (1<<1) #define NGX_LUA_RE_MODE_JIT (1<<2) -#define NGX_LUA_RE_MODE_DUPNAMES (1<<3) #define NGX_LUA_RE_NO_UTF8_CHECK (1<<4) #define NGX_LUA_RE_DFA_MODE_WORKSPACE_COUNT (100) @@ -38,25 +44,30 @@ typedef struct { -#ifndef NGX_LUA_NO_FFI_API ngx_pool_t *pool; u_char *name_table; int name_count; int name_entry_size; -#endif int ncaptures; int *captures; +#if (NGX_PCRE2) + pcre2_code *regex; + /* + * pcre2 doesn't use pcre_extra any more, + * just for keeping same memory layout in the lua ffi cdef + */ + void *regex_sd; +#else pcre *regex; pcre_extra *regex_sd; +#endif - ngx_http_lua_complex_value_t *replace; + ngx_http_lua_complex_value_t *replace; -#ifndef NGX_LUA_NO_FFI_API /* only for (stap) debugging, and may be an invalid pointer */ const u_char *pattern; -#endif } ngx_http_lua_regex_t; @@ -65,17 +76,24 @@ typedef struct { ngx_pool_t *pool; ngx_int_t options; +#if (NGX_PCRE2) + pcre2_code *regex; +#else pcre *regex; +#endif int captures; ngx_str_t err; } ngx_http_lua_regex_compile_t; typedef struct { - ngx_http_cleanup_pt *cleanup; ngx_http_request_t *request; +#if (NGX_PCRE2) + pcre2_code *regex; +#else pcre *regex; pcre_extra *regex_sd; +#endif int ncaptures; int *captures; int captures_len; @@ -83,24 +101,7 @@ typedef struct { } ngx_http_lua_regex_ctx_t; -static int ngx_http_lua_ngx_re_gmatch_iterator(lua_State *L); -static ngx_uint_t ngx_http_lua_ngx_re_parse_opts(lua_State *L, - ngx_http_lua_regex_compile_t *re, ngx_str_t *opts, int narg); -static int ngx_http_lua_ngx_re_sub_helper(lua_State *L, unsigned global); -static int ngx_http_lua_ngx_re_match_helper(lua_State *L, int wantcaps); -static int ngx_http_lua_ngx_re_find(lua_State *L); -static int ngx_http_lua_ngx_re_match(lua_State *L); -static int ngx_http_lua_ngx_re_gmatch(lua_State *L); -static int ngx_http_lua_ngx_re_sub(lua_State *L); -static int ngx_http_lua_ngx_re_gsub(lua_State *L); -static void ngx_http_lua_regex_free_study_data(ngx_pool_t *pool, - pcre_extra *sd); static ngx_int_t ngx_http_lua_regex_compile(ngx_http_lua_regex_compile_t *rc); -static void ngx_http_lua_ngx_re_gmatch_cleanup(void *data); -static int ngx_http_lua_ngx_re_gmatch_gc(lua_State *L); -static void ngx_http_lua_re_collect_named_captures(lua_State *L, - int res_tb_idx, u_char *name_table, int name_count, int name_entry_size, - unsigned flags, ngx_str_t *subj); #define ngx_http_lua_regex_exec(re, e, s, start, captures, size, opts) \ @@ -114,1848 +115,255 @@ static void ngx_http_lua_re_collect_named_captures(lua_State *L, captures, size, ws, wscount) -static int -ngx_http_lua_ngx_re_match(lua_State *L) -{ - return ngx_http_lua_ngx_re_match_helper(L, 1 /* want captures */); -} - - -static int -ngx_http_lua_ngx_re_find(lua_State *L) -{ - return ngx_http_lua_ngx_re_match_helper(L, 0 /* want captures */); -} - - -static int -ngx_http_lua_ngx_re_match_helper(lua_State *L, int wantcaps) +static void +ngx_http_lua_regex_free_study_data(ngx_pool_t *pool, ngx_http_lua_regex_t *re) { - /* u_char *p; */ - int res_tb_idx = 0; - ngx_http_request_t *r; - ngx_str_t subj; - ngx_str_t pat; - ngx_str_t opts; - ngx_http_lua_regex_t *re; - const char *msg; - ngx_int_t rc; - ngx_uint_t n; - int i; - ngx_int_t pos = 0; - int nargs; - int *cap = NULL; - int ovecsize; - int has_ctx = 0; - ngx_uint_t flags; - ngx_pool_t *pool, *old_pool; - ngx_http_lua_main_conf_t *lmcf; - u_char errstr[NGX_MAX_CONF_ERRSTR + 1]; - pcre_extra *sd = NULL; - int name_entry_size = 0, name_count; - u_char *name_table = NULL; - int exec_opts; - int group_id = 0; - - ngx_http_lua_regex_compile_t re_comp; - - nargs = lua_gettop(L); - - if (nargs != 2 && nargs != 3 && nargs != 4 && nargs != 5) { - return luaL_error(L, "expecting 2, 3, 4 or 5 arguments, " - "but got %d", nargs); - } - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } + ngx_pool_t *old_pool; - subj.data = (u_char *) luaL_checklstring(L, 1, &subj.len); - pat.data = (u_char *) luaL_checklstring(L, 2, &pat.len); - - ngx_memzero(&re_comp, sizeof(ngx_http_lua_regex_compile_t)); - - if (nargs >= 3) { - opts.data = (u_char *) luaL_checklstring(L, 3, &opts.len); - - if (nargs >= 4) { - if (!lua_isnil(L, 4)) { - luaL_checktype(L, 4, LUA_TTABLE); - has_ctx = 1; - - lua_getfield(L, 4, "pos"); - if (lua_isnumber(L, -1)) { - pos = (ngx_int_t) lua_tointeger(L, -1); - if (pos <= 0) { - pos = 0; - - } else { - pos--; /* 1-based on the Lua land */ - } - - } else if (lua_isnil(L, -1)) { - pos = 0; - - } else { - msg = lua_pushfstring(L, "bad pos field type in the ctx " - "table argument: %s", - luaL_typename(L, -1)); +#if (NGX_PCRE2) + if (re && re->regex) { + old_pool = ngx_http_lua_pcre_malloc_init(pool); - return luaL_argerror(L, 4, msg); - } + pcre2_code_free(re->regex); - lua_pop(L, 1); - } - } + ngx_http_lua_pcre_malloc_done(old_pool); - } else { - opts.data = (u_char *) ""; - opts.len = 0; + re->regex = NULL; } - - if (nargs == 5) { - if (wantcaps) { - luaL_checktype(L, 5, LUA_TTABLE); - res_tb_idx = 5; - -#if 0 - /* clear the Lua table */ - lua_pushnil(L); - while (lua_next(L, res_tb_idx) != 0) { - lua_pop(L, 1); - lua_pushvalue(L, -1); - lua_pushnil(L); - lua_rawset(L, res_tb_idx); - } +#else + if (re && re->regex_sd) { + old_pool = ngx_http_lua_pcre_malloc_init(pool); +#if LUA_HAVE_PCRE_JIT + pcre_free_study(re->regex_sd); +#else + pcre_free(re->regex_sd); #endif + ngx_http_lua_pcre_malloc_done(old_pool); - } else { - group_id = luaL_checkint(L, 5); - if (group_id < 0) { - group_id = 0; - } - } + re->regex_sd = NULL; } +#endif +} - re_comp.options = 0; - - flags = ngx_http_lua_ngx_re_parse_opts(L, &re_comp, &opts, 3); - - lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); - - if (flags & NGX_LUA_RE_COMPILE_ONCE) { - pool = lmcf->pool; - - dd("server pool %p", lmcf->pool); - - lua_pushlightuserdata(L, &ngx_http_lua_regex_cache_key); - lua_rawget(L, LUA_REGISTRYINDEX); /* table */ - - lua_pushliteral(L, "m"); - lua_pushvalue(L, 2); /* table regex */ - - dd("options size: %d", (int) sizeof(re_comp.options)); - - lua_pushlstring(L, (char *) &re_comp.options, sizeof(re_comp.options)); - /* table regex opts */ - - lua_concat(L, 3); /* table key */ - lua_pushvalue(L, -1); /* table key key */ - - dd("regex cache key: %.*s", (int) (pat.len + sizeof(re_comp.options)), - lua_tostring(L, -1)); - - lua_rawget(L, -3); /* table key re */ - re = lua_touserdata(L, -1); - - lua_pop(L, 1); /* table key */ - - if (re) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua regex cache hit for match regex \"%s\" with " - "options \"%s\"", pat.data, opts.data); - - lua_pop(L, 2); - dd("restoring regex %p, ncaptures %d, captures %p", re->regex, - re->ncaptures, re->captures); +#if (NGX_PCRE2) +static ngx_int_t +ngx_http_lua_regex_compile(ngx_http_lua_regex_compile_t *rc) +{ + int n, errcode; + char *p; + size_t erroff; + u_char errstr[128]; + pcre2_code *re; + ngx_pool_t *old_pool; + pcre2_general_context *gctx; + pcre2_compile_context *cctx; - re_comp.regex = re->regex; - sd = re->regex_sd; - re_comp.captures = re->ncaptures; - cap = re->captures; + ngx_http_lua_main_conf_t *lmcf; - if (flags & NGX_LUA_RE_MODE_DFA) { - ovecsize = 2; + if (ngx_regex_compile_context == NULL) { + /* + * Allocate a compile context if not yet allocated. This uses + * direct allocations from heap, so the result can be cached + * even at runtime. + */ - } else { - ovecsize = (re->ncaptures + 1) * 3; - } + old_pool = ngx_http_lua_pcre_malloc_init(NULL); - goto exec; + gctx = pcre2_general_context_create(ngx_http_lua_pcre_malloc, + ngx_http_lua_pcre_free, + NULL); + if (gctx == NULL) { + ngx_http_lua_pcre_malloc_done(old_pool); + goto nomem; } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua regex cache miss for match regex \"%s\" " - "with options \"%s\"", pat.data, opts.data); - - if (lmcf->regex_cache_entries >= lmcf->regex_cache_max_entries) { - - if (lmcf->regex_cache_entries == lmcf->regex_cache_max_entries) { - ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, - "lua exceeding regex cache max entries (%i)", - lmcf->regex_cache_max_entries); - - lmcf->regex_cache_entries++; - } - - pool = r->pool; - flags &= ~NGX_LUA_RE_COMPILE_ONCE; + cctx = pcre2_compile_context_create(gctx); + if (cctx == NULL) { + pcre2_general_context_free(gctx); + ngx_http_lua_pcre_malloc_done(old_pool); + goto nomem; } - } else { - pool = r->pool; - } - - dd("pool %p, r pool %p", pool, r->pool); - - re_comp.pattern = pat; - re_comp.err.len = NGX_MAX_CONF_ERRSTR; - re_comp.err.data = errstr; - re_comp.pool = pool; - - ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua compiling match regex \"%s\" with options \"%s\" " - "(compile once: %d) (dfa mode: %d) (jit mode: %d)", - pat.data, opts.data, - (flags & NGX_LUA_RE_COMPILE_ONCE) != 0, - (flags & NGX_LUA_RE_MODE_DFA) != 0, - (flags & NGX_LUA_RE_MODE_JIT) != 0); - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - - rc = ngx_http_lua_regex_compile(&re_comp); - - ngx_http_lua_pcre_malloc_done(old_pool); - - if (rc != NGX_OK) { - dd("compile failed"); + ngx_regex_compile_context = cctx; - lua_pushnil(L); - if (!wantcaps) { - lua_pushnil(L); + ngx_regex_match_context = pcre2_match_context_create(gctx); + if (ngx_regex_match_context == NULL) { + pcre2_general_context_free(gctx); + ngx_http_lua_pcre_malloc_done(old_pool); + goto nomem; } - lua_pushlstring(L, (char *) re_comp.err.data, re_comp.err.len); - return wantcaps ? 2 : 3; - } - -#if (LUA_HAVE_PCRE_JIT) - - if (flags & NGX_LUA_RE_MODE_JIT) { - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - - sd = pcre_study(re_comp.regex, PCRE_STUDY_JIT_COMPILE, &msg); - if (sd && lmcf->jit_stack) { - pcre_assign_jit_stack(sd, NULL, lmcf->jit_stack); + lmcf = ngx_http_cycle_get_module_main_conf(ngx_cycle, + ngx_http_lua_module); + if (lmcf && lmcf->regex_match_limit > 0) { + pcre2_set_match_limit(ngx_regex_match_context, + lmcf->regex_match_limit); } + pcre2_general_context_free(gctx); ngx_http_lua_pcre_malloc_done(old_pool); + } -# if (NGX_DEBUG) - dd("sd = %p", sd); - - if (msg != NULL) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "pcre study failed with PCRE_STUDY_JIT_COMPILE: " - "%s (%p)", msg, sd); - } - - if (sd != NULL) { - int jitted; - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - - pcre_fullinfo(re_comp.regex, sd, PCRE_INFO_JIT, &jitted); - - ngx_http_lua_pcre_malloc_done(old_pool); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "pcre JIT compiling result: %d", jitted); - } -# endif /* !(NGX_DEBUG) */ + old_pool = ngx_http_lua_pcre_malloc_init(rc->pool); - } else { - old_pool = ngx_http_lua_pcre_malloc_init(pool); + re = pcre2_compile(rc->pattern.data, + rc->pattern.len, rc->options, + &errcode, &erroff, ngx_regex_compile_context); - sd = pcre_study(re_comp.regex, 0, &msg); + ngx_http_lua_pcre_malloc_done(old_pool); - ngx_http_lua_pcre_malloc_done(old_pool); + if (re == NULL) { + pcre2_get_error_message(errcode, errstr, 128); -# if (NGX_DEBUG) - dd("sd = %p", sd); + if ((size_t) erroff == rc->pattern.len) { + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "pcre2_compile() failed: %s in \"%V\"", + errstr, &rc->pattern) + - rc->err.data; - if (msg != NULL) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "pcre_study failed with PCRE_STUDY_JIT_COMPILE: " - "%s (%p)", msg, sd); + } else { + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "pcre2_compile() failed: %s in " + "\"%V\" at \"%s\"", errstr, &rc->pattern, + rc->pattern.data + erroff) + - rc->err.data; } -# endif /* NGX_DEBUG */ - } -#else /* !(LUA_HAVE_PCRE_JIT) */ - - if (flags & NGX_LUA_RE_MODE_JIT) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "your pcre build does not have JIT support and " - "the \"j\" regex option is ignored"); + return NGX_ERROR; } -#endif /* LUA_HAVE_PCRE_JIT */ + rc->regex = re; - if (sd && lmcf->regex_match_limit > 0) { - sd->flags |= PCRE_EXTRA_MATCH_LIMIT; - sd->match_limit = lmcf->regex_match_limit; + n = pcre2_pattern_info(re, PCRE2_INFO_CAPTURECOUNT, &rc->captures); + if (n < 0) { + p = "pcre2_pattern_info(\"%V\", PCRE_INFO_CAPTURECOUNT) failed: %d"; + goto failed; } - dd("compile done, captures %d", (int) re_comp.captures); +#if (NGX_DEBUG) + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "pcre2_compile: pattern[%V], options 0x%08Xd, ncaptures %d", + &rc->pattern, rc->options, rc->captures); +#endif - if (flags & NGX_LUA_RE_MODE_DFA) { - ovecsize = 2; - re_comp.captures = 0; + return NGX_OK; - } else { - ovecsize = (re_comp.captures + 1) * 3; - } +failed: - dd("allocating cap with size: %d", (int) ovecsize); + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, p, &rc->pattern, n) + - rc->err.data; + return NGX_ERROR; - cap = ngx_palloc(pool, ovecsize * sizeof(int)); +nomem: - if (cap == NULL) { - flags &= ~NGX_LUA_RE_COMPILE_ONCE; - msg = "no memory"; - goto error; - } + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "regex \"%V\" compilation failed: no memory", + &rc->pattern) + - rc->err.data; + return NGX_ERROR; +} - if (flags & NGX_LUA_RE_COMPILE_ONCE) { +#else - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua saving compiled regex (%d captures) into the cache " - "(entries %i)", re_comp.captures, - lmcf->regex_cache_entries); +static ngx_int_t +ngx_http_lua_regex_compile(ngx_http_lua_regex_compile_t *rc) +{ + int n, erroff; + char *p; + const char *errstr; + pcre *re; + ngx_pool_t *old_pool; - re = ngx_palloc(pool, sizeof(ngx_http_lua_regex_t)); - if (re == NULL) { - msg = "no memory"; - goto error; - } + old_pool = ngx_http_lua_pcre_malloc_init(rc->pool); - dd("saving regex %p, ncaptures %d, captures %p", re_comp.regex, - re_comp.captures, cap); + re = pcre_compile((const char *) rc->pattern.data, (int) rc->options, + &errstr, &erroff, NULL); - re->regex = re_comp.regex; - re->regex_sd = sd; - re->ncaptures = re_comp.captures; - re->captures = cap; - re->replace = NULL; + ngx_http_lua_pcre_malloc_done(old_pool); - lua_pushlightuserdata(L, re); /* table key value */ - lua_rawset(L, -3); /* table */ - lua_pop(L, 1); + if (re == NULL) { + if ((size_t) erroff == rc->pattern.len) { + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "pcre_compile() failed: %s in \"%V\"", + errstr, &rc->pattern) + - rc->err.data; - if (lmcf) { - lmcf->regex_cache_entries++; + } else { + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "pcre_compile() failed: %s in \"%V\" " + "at \"%s\"", errstr, &rc->pattern, + rc->pattern.data + erroff) + - rc->err.data; } - } - -exec: - if (pcre_fullinfo(re_comp.regex, NULL, PCRE_INFO_NAMECOUNT, - &name_count) != 0) - { - msg = "cannot acquire named subpattern count"; - goto error; + return NGX_ERROR; } - if (name_count > 0) { - if (pcre_fullinfo(re_comp.regex, NULL, PCRE_INFO_NAMEENTRYSIZE, - &name_entry_size) != 0) - { - msg = "cannot acquire named subpattern entry size"; - goto error; - } + rc->regex = re; - if (pcre_fullinfo(re_comp.regex, NULL, PCRE_INFO_NAMETABLE, - &name_table) != 0) - { - msg = "cannot acquire named subpattern table"; - goto error; - } +#if 1 + n = pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &rc->captures); + if (n < 0) { + p = "pcre_fullinfo(\"%V\", PCRE_INFO_CAPTURECOUNT) failed: %d"; + goto failed; } +#endif - if (flags & NGX_LUA_RE_NO_UTF8_CHECK) { - exec_opts = PCRE_NO_UTF8_CHECK; + return NGX_OK; - } else { - exec_opts = 0; - } +failed: - if (flags & NGX_LUA_RE_MODE_DFA) { + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, p, &rc->pattern, n) + - rc->err.data; + return NGX_OK; +} +#endif -#if LUA_HAVE_PCRE_DFA - int ws[NGX_LUA_RE_DFA_MODE_WORKSPACE_COUNT]; - rc = ngx_http_lua_regex_dfa_exec(re_comp.regex, sd, &subj, - (int) pos, cap, ovecsize, ws, - sizeof(ws)/sizeof(ws[0]), exec_opts); +ngx_int_t +ngx_http_lua_ffi_set_jit_stack_size(int size, u_char *errstr, + size_t *errstr_size) +{ +#if (LUA_HAVE_PCRE_JIT) -#else /* LUA_HAVE_PCRE_DFA */ + ngx_http_lua_main_conf_t *lmcf; + ngx_pool_t *pool, *old_pool; - msg = "at least pcre 6.0 is required for the DFA mode"; - goto error; + lmcf = ngx_http_cycle_get_module_main_conf(ngx_cycle, + ngx_http_lua_module); -#endif /* LUA_HAVE_PCRE_DFA */ + ngx_http_lua_assert(lmcf != NULL); - } else { - rc = ngx_http_lua_regex_exec(re_comp.regex, sd, &subj, (int) pos, cap, - ovecsize, exec_opts); + if (size < NGX_LUA_RE_MIN_JIT_STACK_SIZE) { + size = NGX_LUA_RE_MIN_JIT_STACK_SIZE; } - if (rc == NGX_REGEX_NO_MATCHED) { - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "regex \"%V\" not matched on string \"%V\" starting " - "from %i", &pat, &subj, pos); - - if (!(flags & NGX_LUA_RE_COMPILE_ONCE)) { - if (sd) { - ngx_http_lua_regex_free_study_data(pool, sd); - } + pool = lmcf->pool; - ngx_pfree(pool, re_comp.regex); - ngx_pfree(pool, cap); - } + dd("server pool %p", lmcf->pool); - lua_pushnil(L); - return 1; - } - - if (rc < 0) { - msg = lua_pushfstring(L, ngx_regex_exec_n " failed: %d", (int) rc); - goto error; - } - - if (rc == 0) { - if (flags & NGX_LUA_RE_MODE_DFA) { - rc = 1; - - } else { - msg = "capture size too small"; - goto error; - } - } - - dd("rc = %d", (int) rc); - - if (has_ctx) { /* having ctx table */ - pos = cap[1]; - lua_pushinteger(L, (lua_Integer) (pos + 1)); - lua_setfield(L, 4, "pos"); - } - - if (!wantcaps) { - if (group_id > re_comp.captures) { - lua_pushnil(L); - lua_pushnil(L); - lua_pushliteral(L, "nth out of bound"); - return 3; - } - - if (group_id >= rc) { - lua_pushnil(L); - lua_pushnil(L); - return 2; - } - - { - int from, to; - - from = cap[group_id * 2] + 1; - to = cap[group_id * 2 + 1]; - if (from < 0 || to < 0) { - lua_pushnil(L); - lua_pushnil(L); - return 2; - } - - lua_pushinteger(L, from); - lua_pushinteger(L, to); - return 2; - } - } - - if (res_tb_idx == 0) { - lua_createtable(L, re_comp.captures || 1 /* narr */, - name_count /* nrec */); - res_tb_idx = lua_gettop(L); - } - - for (i = 0, n = 0; i <= re_comp.captures; i++, n += 2) { - dd("capture %d: %d %d", i, cap[n], cap[n + 1]); - if (i >= rc || cap[n] < 0) { - lua_pushboolean(L, 0); - - } else { - lua_pushlstring(L, (char *) &subj.data[cap[n]], - cap[n + 1] - cap[n]); - - dd("pushing capture %s at %d", lua_tostring(L, -1), (int) i); - } - - lua_rawseti(L, res_tb_idx, (int) i); - } - - if (name_count > 0) { - ngx_http_lua_re_collect_named_captures(L, res_tb_idx, name_table, - name_count, name_entry_size, - flags, &subj); - } - - if (!(flags & NGX_LUA_RE_COMPILE_ONCE)) { - - if (sd) { - ngx_http_lua_regex_free_study_data(pool, sd); - } - - ngx_pfree(pool, re_comp.regex); - ngx_pfree(pool, cap); - } - - return 1; - -error: - - if (!(flags & NGX_LUA_RE_COMPILE_ONCE)) { - if (sd) { - ngx_http_lua_regex_free_study_data(pool, sd); - } - - if (re_comp.regex) { - ngx_pfree(pool, re_comp.regex); - } - - if (cap) { - ngx_pfree(pool, cap); - } - } - - lua_pushnil(L); - if (!wantcaps) { - lua_pushnil(L); - } - lua_pushstring(L, msg); - return wantcaps ? 2 : 3; -} - - -static int -ngx_http_lua_ngx_re_gmatch(lua_State *L) -{ - ngx_http_lua_main_conf_t *lmcf; - ngx_http_request_t *r; - ngx_str_t subj; - ngx_str_t pat; - ngx_str_t opts; - int ovecsize; - ngx_http_lua_regex_t *re; - ngx_http_lua_regex_ctx_t *ctx; - const char *msg; - int nargs; - ngx_int_t flags; - int *cap = NULL; - ngx_int_t rc; - ngx_pool_t *pool, *old_pool; - u_char errstr[NGX_MAX_CONF_ERRSTR + 1]; - pcre_extra *sd = NULL; - ngx_http_cleanup_t *cln; - - ngx_http_lua_regex_compile_t re_comp; - - nargs = lua_gettop(L); - - if (nargs != 2 && nargs != 3) { - return luaL_error(L, "expecting two or three arguments, but got %d", - nargs); - } - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } - - subj.data = (u_char *) luaL_checklstring(L, 1, &subj.len); - pat.data = (u_char *) luaL_checklstring(L, 2, &pat.len); - - if (nargs == 3) { - opts.data = (u_char *) luaL_checklstring(L, 3, &opts.len); - lua_pop(L, 1); - - } else { - opts.data = (u_char *) ""; - opts.len = 0; - } - - /* stack: subj regex */ - - re_comp.options = 0; - - flags = ngx_http_lua_ngx_re_parse_opts(L, &re_comp, &opts, 3); - - lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); - - if (flags & NGX_LUA_RE_COMPILE_ONCE) { - pool = lmcf->pool; - - dd("server pool %p", lmcf->pool); - - lua_pushlightuserdata(L, &ngx_http_lua_regex_cache_key); - lua_rawget(L, LUA_REGISTRYINDEX); /* table */ - - lua_pushliteral(L, "m"); - lua_pushvalue(L, 2); /* table regex */ - - dd("options size: %d", (int) sizeof(re_comp.options)); - - lua_pushlstring(L, (char *) &re_comp.options, - sizeof(re_comp.options)); /* table regex opts */ - - lua_concat(L, 3); /* table key */ - lua_pushvalue(L, -1); /* table key key */ - - dd("regex cache key: %.*s", (int) (pat.len + sizeof(re_comp.options)), - lua_tostring(L, -1)); - - lua_rawget(L, -3); /* table key re */ - re = lua_touserdata(L, -1); - - lua_pop(L, 1); /* table key */ - - if (re) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua regex cache hit for match regex \"%s\" " - "with options \"%s\"", pat.data, opts.data); - - lua_pop(L, 2); - - dd("restoring regex %p, ncaptures %d, captures %p", re->regex, - re->ncaptures, re->captures); - - re_comp.regex = re->regex; - sd = re->regex_sd; - re_comp.captures = re->ncaptures; - cap = re->captures; - - if (flags & NGX_LUA_RE_MODE_DFA) { - ovecsize = 2; - - } else { - ovecsize = (re->ncaptures + 1) * 3; - } - - goto compiled; - } - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua regex cache miss for match regex \"%s\" " - "with options \"%s\"", pat.data, opts.data); - - if (lmcf->regex_cache_entries >= lmcf->regex_cache_max_entries) { - - if (lmcf->regex_cache_entries == lmcf->regex_cache_max_entries) { - ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, - "lua exceeding regex cache max entries (%i)", - lmcf->regex_cache_max_entries); - - lmcf->regex_cache_entries++; - } - - pool = r->pool; - flags &= ~NGX_LUA_RE_COMPILE_ONCE; - } - - } else { - pool = r->pool; - } - - re_comp.pattern = pat; - re_comp.err.len = NGX_MAX_CONF_ERRSTR; - re_comp.err.data = errstr; - re_comp.pool = pool; - - ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua compiling gmatch regex \"%s\" with options \"%s\" " - "(compile once: %d) (dfa mode: %d) (jit mode: %d)", - pat.data, opts.data, - (flags & NGX_LUA_RE_COMPILE_ONCE) != 0, - (flags & NGX_LUA_RE_MODE_DFA) != 0, - (flags & NGX_LUA_RE_MODE_JIT) != 0); - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - - rc = ngx_http_lua_regex_compile(&re_comp); - - ngx_http_lua_pcre_malloc_done(old_pool); - - if (rc != NGX_OK) { - dd("compile failed"); - - lua_pushnil(L); - lua_pushlstring(L, (char *) re_comp.err.data, re_comp.err.len); - return 2; - } - -#if LUA_HAVE_PCRE_JIT - - if (flags & NGX_LUA_RE_MODE_JIT) { - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - - sd = pcre_study(re_comp.regex, PCRE_STUDY_JIT_COMPILE, &msg); - - if (sd && lmcf->jit_stack) { - pcre_assign_jit_stack(sd, NULL, lmcf->jit_stack); - } - - ngx_http_lua_pcre_malloc_done(old_pool); - -# if (NGX_DEBUG) - dd("sd = %p", sd); - - if (msg != NULL) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "pcre_study failed with PCRE_STUDY_JIT_COMPILE: " - "%s (%p)", msg, sd); - } - - if (sd != NULL) { - int jitted; - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - - pcre_fullinfo(re_comp.regex, sd, PCRE_INFO_JIT, &jitted); - - ngx_http_lua_pcre_malloc_done(old_pool); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "pcre JIT compiling result: %d", jitted); - } -# endif /* NGX_DEBUG */ - - } else { - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - - sd = pcre_study(re_comp.regex, 0, &msg); - - ngx_http_lua_pcre_malloc_done(old_pool); - -# if (NGX_DEBUG) - dd("sd = %p", sd); - - if (msg != NULL) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "pcre study failed with PCRE_STUDY_JIT_COMPILE: " - "%s (%p)", msg, sd); - } -# endif /* NGX_DEBUG */ - } - -#else /* LUA_HAVE_PCRE_JIT */ - - if (flags & NGX_LUA_RE_MODE_JIT) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "your pcre build does not have JIT support and " - "the \"j\" regex option is ignored"); - } - -#endif /* LUA_HAVE_PCRE_JIT */ - - if (sd && lmcf->regex_match_limit > 0) { - sd->flags |= PCRE_EXTRA_MATCH_LIMIT; - sd->match_limit = lmcf->regex_match_limit; - } - - dd("compile done, captures %d", re_comp.captures); - - if (flags & NGX_LUA_RE_MODE_DFA) { - ovecsize = 2; - re_comp.captures = 0; - - } else { - ovecsize = (re_comp.captures + 1) * 3; - } - - cap = ngx_palloc(pool, ovecsize * sizeof(int)); - if (cap == NULL) { - flags &= ~NGX_LUA_RE_COMPILE_ONCE; - msg = "no memory"; - goto error; - } - - if (flags & NGX_LUA_RE_COMPILE_ONCE) { - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua saving compiled regex (%d captures) into the cache " - "(entries %i)", re_comp.captures, - lmcf->regex_cache_entries); - - re = ngx_palloc(pool, sizeof(ngx_http_lua_regex_t)); - if (re == NULL) { - msg = "no memory"; - goto error; - } - - dd("saving regex %p, ncaptures %d, captures %p", re_comp.regex, - re_comp.captures, cap); - - re->regex = re_comp.regex; - re->regex_sd = sd; - re->ncaptures = re_comp.captures; - re->captures = cap; - re->replace = NULL; - - lua_pushlightuserdata(L, re); /* table key value */ - lua_rawset(L, -3); /* table */ - lua_pop(L, 1); - - if (lmcf) { - lmcf->regex_cache_entries++; - } - } - -compiled: - - lua_settop(L, 1); - - ctx = lua_newuserdata(L, sizeof(ngx_http_lua_regex_ctx_t)); - - ctx->request = r; - ctx->regex = re_comp.regex; - ctx->regex_sd = sd; - ctx->ncaptures = re_comp.captures; - ctx->captures = cap; - ctx->captures_len = ovecsize; - ctx->flags = (uint8_t) flags; - - if (!(flags & NGX_LUA_RE_COMPILE_ONCE)) { - lua_createtable(L, 0 /* narr */, 1 /* nrec */); /* metatable */ - lua_pushcfunction(L, ngx_http_lua_ngx_re_gmatch_gc); - lua_setfield(L, -2, "__gc"); - lua_setmetatable(L, -2); - - cln = ngx_http_cleanup_add(r, 0); - if (cln == NULL) { - msg = "no memory"; - goto error; - } - - cln->handler = ngx_http_lua_ngx_re_gmatch_cleanup; - cln->data = ctx; - ctx->cleanup = &cln->handler; - - } else { - ctx->cleanup = NULL; - } - - lua_pushinteger(L, 0); - - /* upvalues in order: subj ctx offset */ - lua_pushcclosure(L, ngx_http_lua_ngx_re_gmatch_iterator, 3); - - return 1; - -error: - - if (!(flags & NGX_LUA_RE_COMPILE_ONCE)) { - if (sd) { - ngx_http_lua_regex_free_study_data(pool, sd); - } - - if (re_comp.regex) { - ngx_pfree(pool, re_comp.regex); - } - - if (cap) { - ngx_pfree(pool, cap); - } - } - - lua_pushnil(L); - lua_pushstring(L, msg); - return 2; -} - - -static int -ngx_http_lua_ngx_re_gmatch_iterator(lua_State *L) -{ - ngx_http_lua_regex_ctx_t *ctx; - ngx_http_request_t *r; - int *cap; - ngx_int_t rc; - ngx_uint_t n; - int i; - ngx_str_t subj; - int offset; - const char *msg = NULL; - int name_entry_size = 0, name_count; - u_char *name_table = NULL; - int exec_opts; - - /* upvalues in order: subj ctx offset */ - - subj.data = (u_char *) lua_tolstring(L, lua_upvalueindex(1), &subj.len); - ctx = (ngx_http_lua_regex_ctx_t *) lua_touserdata(L, lua_upvalueindex(2)); - offset = (int) lua_tointeger(L, lua_upvalueindex(3)); - - if (offset < 0) { - lua_pushnil(L); - return 1; - } - - cap = ctx->captures; - - dd("offset %d, r %p, subj %s", (int) offset, ctx->request, subj.data); - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } - - if (r != ctx->request || r->pool != ctx->request->pool) { - return luaL_error(L, "attempt to use ngx.re.gmatch iterator in a " - "request that did not create it"); - } - - dd("regex exec..."); - - if (pcre_fullinfo(ctx->regex, NULL, PCRE_INFO_NAMECOUNT, - &name_count) != 0) - { - msg = "cannot acquire named subpattern count"; - goto error; - } - - if (name_count > 0) { - if (pcre_fullinfo(ctx->regex, NULL, PCRE_INFO_NAMEENTRYSIZE, - &name_entry_size) != 0) - { - msg = "cannot acquire named subpattern entry size"; - goto error; - } - - if (pcre_fullinfo(ctx->regex, NULL, PCRE_INFO_NAMETABLE, - &name_table) != 0) - { - msg = "cannot acquire named subpattern table"; - goto error; - } - } - - if (ctx->flags & NGX_LUA_RE_NO_UTF8_CHECK) { - exec_opts = PCRE_NO_UTF8_CHECK; - - } else { - exec_opts = 0; - } - - if (ctx->flags & NGX_LUA_RE_MODE_DFA) { - -#if LUA_HAVE_PCRE_DFA - - int ws[NGX_LUA_RE_DFA_MODE_WORKSPACE_COUNT]; - - rc = ngx_http_lua_regex_dfa_exec(ctx->regex, ctx->regex_sd, &subj, - offset, cap, ctx->captures_len, ws, - sizeof(ws)/sizeof(ws[0]), exec_opts); - -#else /* LUA_HAVE_PCRE_DFA */ - msg = "at least pcre 6.0 is required for the DFA mode"; - goto error; - -#endif /* LUA_HAVE_PCRE_DFA */ - - } else { - rc = ngx_http_lua_regex_exec(ctx->regex, ctx->regex_sd, &subj, - offset, cap, ctx->captures_len, - exec_opts); - } - - if (rc == NGX_REGEX_NO_MATCHED) { - /* set upvalue "offset" to -1 */ - lua_pushinteger(L, -1); - lua_replace(L, lua_upvalueindex(3)); - - if (!(ctx->flags & NGX_LUA_RE_COMPILE_ONCE)) { - if (ctx->regex_sd) { - ngx_http_lua_regex_free_study_data(r->pool, ctx->regex_sd); - ctx->regex_sd = NULL; - } - - ngx_pfree(r->pool, cap); - } - - lua_pushnil(L); - return 1; - } - - if (rc < 0) { - msg = lua_pushfstring(L, ngx_regex_exec_n " failed: %d", (int) rc); - goto error; - } - - if (rc == 0) { - if (ctx->flags & NGX_LUA_RE_MODE_DFA) { - rc = 1; - - } else { - goto error; - } - } - - dd("rc = %d", (int) rc); - - lua_createtable(L, ctx->ncaptures || 1 /* narr */, name_count /* nrec */); - - for (i = 0, n = 0; i <= ctx->ncaptures; i++, n += 2) { - dd("capture %d: %d %d", i, cap[n], cap[n + 1]); - if (i >= rc || cap[n] < 0) { - lua_pushboolean(L, 0); - - } else { - lua_pushlstring(L, (char *) &subj.data[cap[n]], - cap[n + 1] - cap[n]); - - dd("pushing capture %s at %d", lua_tostring(L, -1), (int) i); - } - - lua_rawseti(L, -2, (int) i); - } - - if (name_count > 0) { - ngx_http_lua_re_collect_named_captures(L, lua_gettop(L), name_table, - name_count, name_entry_size, - ctx->flags, &subj); - } - - offset = cap[1]; - if (offset == cap[0]) { - offset++; - } - - if (offset > (ssize_t) subj.len) { - offset = -1; - - if (!(ctx->flags & NGX_LUA_RE_COMPILE_ONCE)) { - if (ctx->regex_sd) { - ngx_http_lua_regex_free_study_data(r->pool, ctx->regex_sd); - ctx->regex_sd = NULL; - } - - ngx_pfree(r->pool, cap); - } - } - - lua_pushinteger(L, offset); - lua_replace(L, lua_upvalueindex(3)); - - return 1; - -error: - - lua_pushinteger(L, -1); - lua_replace(L, lua_upvalueindex(3)); - - if (!(ctx->flags & NGX_LUA_RE_COMPILE_ONCE)) { - if (ctx->regex_sd) { - ngx_http_lua_regex_free_study_data(r->pool, ctx->regex_sd); - ctx->regex_sd = NULL; - } - - ngx_pfree(r->pool, cap); - } - - lua_pushnil(L); - lua_pushstring(L, msg); - return 2; -} - - -static ngx_uint_t -ngx_http_lua_ngx_re_parse_opts(lua_State *L, ngx_http_lua_regex_compile_t *re, - ngx_str_t *opts, int narg) -{ - u_char *p; - const char *msg; - ngx_uint_t flags; - - flags = 0; - p = opts->data; - - while (*p != '\0') { - switch (*p) { - case 'i': - re->options |= NGX_REGEX_CASELESS; - break; - - case 's': - re->options |= PCRE_DOTALL; - break; - - case 'm': - re->options |= PCRE_MULTILINE; - break; - - case 'u': - re->options |= PCRE_UTF8; - break; - - case 'U': - re->options |= PCRE_UTF8; - flags |= NGX_LUA_RE_NO_UTF8_CHECK; - break; - - case 'x': - re->options |= PCRE_EXTENDED; - break; - - case 'o': - flags |= NGX_LUA_RE_COMPILE_ONCE; - break; - - case 'j': - flags |= NGX_LUA_RE_MODE_JIT; - break; - - case 'd': - flags |= NGX_LUA_RE_MODE_DFA; - break; - - case 'a': - re->options |= PCRE_ANCHORED; - break; - -#if (PCRE_MAJOR > 8) || (PCRE_MAJOR == 8 && PCRE_MINOR >= 12) - case 'D': - re->options |= PCRE_DUPNAMES; - flags |= NGX_LUA_RE_MODE_DUPNAMES; - break; - - case 'J': - re->options |= PCRE_JAVASCRIPT_COMPAT; - break; -#endif - - default: - msg = lua_pushfstring(L, "unknown flag \"%c\" (flags \"%s\")", - *p, opts->data); - return luaL_argerror(L, narg, msg); - } - - p++; - } - - /* pcre does not support JIT for DFA mode yet, - * so if DFA mode is specified, we turn off JIT automatically - * */ - if ((flags & NGX_LUA_RE_MODE_JIT) && (flags & NGX_LUA_RE_MODE_DFA)) { - flags &= ~NGX_LUA_RE_MODE_JIT; - } - - return flags; -} - - -static int -ngx_http_lua_ngx_re_sub(lua_State *L) -{ - return ngx_http_lua_ngx_re_sub_helper(L, 0 /* global */); -} - - -static int -ngx_http_lua_ngx_re_gsub(lua_State *L) -{ - return ngx_http_lua_ngx_re_sub_helper(L, 1 /* global */); -} - - -static int -ngx_http_lua_ngx_re_sub_helper(lua_State *L, unsigned global) -{ - ngx_http_lua_regex_t *re; - ngx_http_request_t *r; - ngx_str_t subj; - ngx_str_t pat; - ngx_str_t opts; - ngx_str_t tpl; - ngx_http_lua_main_conf_t *lmcf; - ngx_pool_t *pool, *old_pool; - const char *msg; - ngx_int_t rc; - ngx_uint_t n; - ngx_int_t i; - int nargs; - int *cap = NULL; - int ovecsize; - int type; - unsigned func; - int offset; - int cp_offset; - size_t count; - luaL_Buffer luabuf; - ngx_int_t flags; - u_char *p; - u_char errstr[NGX_MAX_CONF_ERRSTR + 1]; - pcre_extra *sd = NULL; - int name_entry_size = 0, name_count; - u_char *name_table = NULL; - int exec_opts; - - ngx_http_lua_regex_compile_t re_comp; - ngx_http_lua_complex_value_t *ctpl = NULL; - ngx_http_lua_compile_complex_value_t ccv; - - nargs = lua_gettop(L); - - if (nargs != 3 && nargs != 4) { - return luaL_error(L, "expecting three or four arguments, but got %d", - nargs); - } - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } - - subj.data = (u_char *) luaL_checklstring(L, 1, &subj.len); - pat.data = (u_char *) luaL_checklstring(L, 2, &pat.len); - - func = 0; - - type = lua_type(L, 3); - switch (type) { - case LUA_TFUNCTION: - func = 1; - tpl.len = 0; - tpl.data = (u_char *) ""; - break; - - case LUA_TNUMBER: - case LUA_TSTRING: - tpl.data = (u_char *) lua_tolstring(L, 3, &tpl.len); - break; - - default: - msg = lua_pushfstring(L, "string, number, or function expected, " - "got %s", lua_typename(L, type)); - return luaL_argerror(L, 3, msg); - } - - ngx_memzero(&re_comp, sizeof(ngx_http_lua_regex_compile_t)); - - if (nargs == 4) { - opts.data = (u_char *) luaL_checklstring(L, 4, &opts.len); - lua_pop(L, 1); - - } else { /* nargs == 3 */ - opts.data = (u_char *) ""; - opts.len = 0; - } - - /* stack: subj regex repl */ - - re_comp.options = 0; - - flags = ngx_http_lua_ngx_re_parse_opts(L, &re_comp, &opts, 4); - - lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); - - if (flags & NGX_LUA_RE_COMPILE_ONCE) { - pool = lmcf->pool; - - dd("server pool %p", lmcf->pool); - - lua_pushlightuserdata(L, &ngx_http_lua_regex_cache_key); - lua_rawget(L, LUA_REGISTRYINDEX); /* table */ - - lua_pushliteral(L, "s"); - lua_pushinteger(L, tpl.len); - lua_pushliteral(L, ":"); - lua_pushvalue(L, 2); - - if (tpl.len != 0) { - lua_pushvalue(L, 3); - } - - dd("options size: %d", (int) sizeof(re_comp.options)); - - lua_pushlstring(L, (char *) &re_comp.options, sizeof(re_comp.options)); - /* table regex opts */ - - if (tpl.len == 0) { - lua_concat(L, 5); /* table key */ - - } else { - lua_concat(L, 6); /* table key */ - } - - lua_pushvalue(L, -1); /* table key key */ - - dd("regex cache key: %.*s", (int) (pat.len + sizeof(re_comp.options)), - lua_tostring(L, -1)); - - lua_rawget(L, -3); /* table key re */ - re = lua_touserdata(L, -1); - - lua_pop(L, 1); /* table key */ - - if (re) { - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua regex cache hit for sub regex \"%s\" with " - "options \"%s\" and replace \"%s\"", - pat.data, opts.data, - func ? (u_char *) "" : tpl.data); - - lua_pop(L, 2); - - dd("restoring regex %p, ncaptures %d, captures %p", re->regex, - re->ncaptures, re->captures); - - re_comp.regex = re->regex; - sd = re->regex_sd; - re_comp.captures = re->ncaptures; - cap = re->captures; - ctpl = re->replace; - - if (flags & NGX_LUA_RE_MODE_DFA) { - ovecsize = 2; - - } else { - ovecsize = (re->ncaptures + 1) * 3; - } - - goto exec; - } - - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua regex cache miss for %ssub regex \"%s\" with " - "options \"%s\" and replace \"%s\"", - global ? "g" : "", pat.data, opts.data, - func ? (u_char *) "" : tpl.data); - - if (lmcf->regex_cache_entries >= lmcf->regex_cache_max_entries) { - - if (lmcf->regex_cache_entries == lmcf->regex_cache_max_entries) { - ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, - "lua exceeding regex cache max entries (%i)", - lmcf->regex_cache_max_entries); - - lmcf->regex_cache_entries++; - } - - pool = r->pool; - flags &= ~NGX_LUA_RE_COMPILE_ONCE; - } - - } else { - pool = r->pool; - } - - re_comp.pattern = pat; - re_comp.err.len = NGX_MAX_CONF_ERRSTR; - re_comp.err.data = errstr; - re_comp.pool = pool; - - dd("compiling regex"); - - ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua compiling %ssub regex \"%s\" with options \"%s\" " - "(compile once: %d) (dfa mode: %d) (jit mode: %d)", - global ? "g" : "", pat.data, opts.data, - (flags & NGX_LUA_RE_COMPILE_ONCE) != 0, - (flags & NGX_LUA_RE_MODE_DFA) != 0, - (flags & NGX_LUA_RE_MODE_JIT) != 0); - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - - rc = ngx_http_lua_regex_compile(&re_comp); - - ngx_http_lua_pcre_malloc_done(old_pool); - - if (rc != NGX_OK) { - dd("compile failed"); - - lua_pushnil(L); - lua_pushnil(L); - lua_pushlstring(L, (char *) re_comp.err.data, re_comp.err.len); - return 3; - } - -#if LUA_HAVE_PCRE_JIT - - if (flags & NGX_LUA_RE_MODE_JIT) { - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - - sd = pcre_study(re_comp.regex, PCRE_STUDY_JIT_COMPILE, &msg); - - ngx_http_lua_pcre_malloc_done(old_pool); - -# if (NGX_DEBUG) - dd("sd = %p", sd); - - if (msg != NULL) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "pcre study failed with PCRE_STUDY_JIT_COMPILE: " - "%s (%p)", msg, sd); - } - - if (sd != NULL) { - int jitted; - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - - pcre_fullinfo(re_comp.regex, sd, PCRE_INFO_JIT, &jitted); - - ngx_http_lua_pcre_malloc_done(old_pool); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "pcre JIT compiling result: %d", jitted); - } -# endif /* NGX_DEBUG */ - - } else { - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - - sd = pcre_study(re_comp.regex, 0, &msg); - - ngx_http_lua_pcre_malloc_done(old_pool); - -# if (NGX_DEBUG) - dd("sd = %p", sd); - - if (msg != NULL) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "pcre_study failed with PCRE_STUDY_JIT_COMPILE: " - "%s (%p)", msg, sd); - } -# endif /* NGX_DEBUG */ - } - -#else /* LUA_HAVE_PCRE_JIT */ - - if (flags & NGX_LUA_RE_MODE_JIT) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "your pcre build does not have JIT support and " - "the \"j\" regex option is ignored"); - } - -#endif /* LUA_HAVE_PCRE_JIT */ - - if (sd && lmcf->regex_match_limit > 0) { - sd->flags |= PCRE_EXTRA_MATCH_LIMIT; - sd->match_limit = lmcf->regex_match_limit; - } - - dd("compile done, captures %d", re_comp.captures); - - if (flags & NGX_LUA_RE_MODE_DFA) { - ovecsize = 2; - re_comp.captures = 0; - - } else { - ovecsize = (re_comp.captures + 1) * 3; - } - - cap = ngx_palloc(pool, ovecsize * sizeof(int)); - if (cap == NULL) { - flags &= ~NGX_LUA_RE_COMPILE_ONCE; - msg = "no memory"; - goto error; - } - - if (func) { - ctpl = NULL; - - } else { - ctpl = ngx_palloc(pool, sizeof(ngx_http_lua_complex_value_t)); - if (ctpl == NULL) { - flags &= ~NGX_LUA_RE_COMPILE_ONCE; - msg = "no memory"; - goto error; - } - - if ((flags & NGX_LUA_RE_COMPILE_ONCE) && tpl.len != 0) { - /* copy the string buffer pointed to by tpl.data from Lua VM */ - p = ngx_palloc(pool, tpl.len + 1); - if (p == NULL) { - flags &= ~NGX_LUA_RE_COMPILE_ONCE; - msg = "no memory"; - goto error; - } - - ngx_memcpy(p, tpl.data, tpl.len); - p[tpl.len] = '\0'; - - tpl.data = p; - } - - ngx_memzero(&ccv, sizeof(ngx_http_lua_compile_complex_value_t)); - ccv.pool = pool; - ccv.log = r->connection->log; - ccv.value = &tpl; - ccv.complex_value = ctpl; - - if (ngx_http_lua_compile_complex_value(&ccv) != NGX_OK) { - ngx_pfree(pool, cap); - ngx_pfree(pool, ctpl); - - if ((flags & NGX_LUA_RE_COMPILE_ONCE) && tpl.len != 0) { - ngx_pfree(pool, tpl.data); - } - - if (sd) { - ngx_http_lua_regex_free_study_data(pool, sd); - } - - ngx_pfree(pool, re_comp.regex); - - lua_pushnil(L); - lua_pushnil(L); - lua_pushliteral(L, "failed to compile the replacement template"); - return 3; - } - } - - if (flags & NGX_LUA_RE_COMPILE_ONCE) { - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua saving compiled sub regex (%d captures) into " - "the cache (entries %i)", re_comp.captures, - lmcf->regex_cache_entries); - - re = ngx_palloc(pool, sizeof(ngx_http_lua_regex_t)); - if (re == NULL) { - msg = "no memory"; - goto error; - } - - dd("saving regex %p, ncaptures %d, captures %p", re_comp.regex, - re_comp.captures, cap); - - re->regex = re_comp.regex; - re->regex_sd = sd; - re->ncaptures = re_comp.captures; - re->captures = cap; - re->replace = ctpl; - - lua_pushlightuserdata(L, re); /* table key value */ - lua_rawset(L, -3); /* table */ - lua_pop(L, 1); - - if (lmcf) { - lmcf->regex_cache_entries++; - } - } - -exec: - - count = 0; - offset = 0; - cp_offset = 0; - - if (pcre_fullinfo(re_comp.regex, NULL, PCRE_INFO_NAMECOUNT, - &name_count) != 0) - { - msg = "cannot acquire named subpattern count"; - goto error; - } - - if (name_count > 0) { - if (pcre_fullinfo(re_comp.regex, NULL, PCRE_INFO_NAMEENTRYSIZE, - &name_entry_size) != 0) - { - msg = "cannot acquire named subpattern entry size"; - goto error; - } - - if (pcre_fullinfo(re_comp.regex, NULL, PCRE_INFO_NAMETABLE, - &name_table) != 0) - { - msg = "cannot acquire named subpattern table"; - goto error; - } - } - - if (flags & NGX_LUA_RE_NO_UTF8_CHECK) { - exec_opts = PCRE_NO_UTF8_CHECK; - - } else { - exec_opts = 0; - } - - for (;;) { - if (flags & NGX_LUA_RE_MODE_DFA) { - -#if LUA_HAVE_PCRE_DFA - - int ws[NGX_LUA_RE_DFA_MODE_WORKSPACE_COUNT]; - rc = ngx_http_lua_regex_dfa_exec(re_comp.regex, sd, &subj, - offset, cap, ovecsize, ws, - sizeof(ws)/sizeof(ws[0]), - exec_opts); - -#else /* LUA_HAVE_PCRE_DFA */ - - msg = "at least pcre 6.0 is required for the DFA mode"; - goto error; - -#endif /* LUA_HAVE_PCRE_DFA */ - - } else { - rc = ngx_http_lua_regex_exec(re_comp.regex, sd, &subj, offset, cap, - ovecsize, exec_opts); - } - - if (rc == NGX_REGEX_NO_MATCHED) { - break; - } - - if (rc < 0) { - msg = lua_pushfstring(L, ngx_regex_exec_n " failed: %d", - (int) rc); - goto error; - } - - if (rc == 0) { - if (flags & NGX_LUA_RE_MODE_DFA) { - rc = 1; - - } else { - msg = "capture size too small"; - goto error; - } - } - - dd("rc = %d", (int) rc); - - count++; - - if (count == 1) { - luaL_buffinit(L, &luabuf); - } - - if (func) { - lua_pushvalue(L, 3); - - lua_createtable(L, re_comp.captures || 1 /* narr */, - name_count /* nrec */); - - for (i = 0, n = 0; i <= re_comp.captures; i++, n += 2) { - dd("capture %d: %d %d", (int) i, cap[n], cap[n + 1]); - if (i >= rc || cap[n] < 0) { - lua_pushboolean(L, 0); - - } else { - lua_pushlstring(L, (char *) &subj.data[cap[n]], - cap[n + 1] - cap[n]); - - dd("pushing capture %s at %d", lua_tostring(L, -1), - (int) i); - } - - lua_rawseti(L, -2, (int) i); - } - - if (name_count > 0) { - ngx_http_lua_re_collect_named_captures(L, lua_gettop(L), - name_table, - name_count, - name_entry_size, - flags, &subj); - } - - dd("stack size at call: %d", lua_gettop(L)); - - lua_call(L, 1 /* nargs */, 1 /* nresults */); - type = lua_type(L, -1); - switch (type) { - case LUA_TNUMBER: - case LUA_TSTRING: - tpl.data = (u_char *) lua_tolstring(L, -1, &tpl.len); - break; - - default: - msg = lua_pushfstring(L, "string or number expected to be " - "returned by the replace " - "function, got %s", - lua_typename(L, type)); - return luaL_argerror(L, 3, msg); - } - - lua_insert(L, 1); - - luaL_addlstring(&luabuf, (char *) &subj.data[cp_offset], - cap[0] - cp_offset); - - luaL_addlstring(&luabuf, (char *) tpl.data, tpl.len); - - lua_remove(L, 1); - - cp_offset = cap[1]; - offset = cp_offset; - if (offset == cap[0]) { - offset++; - if (offset > (ssize_t) subj.len) { - break; - } - } - - if (global) { - continue; - } - - break; - } - - rc = ngx_http_lua_complex_value(r, &subj, cp_offset, rc, cap, ctpl, - &luabuf); - - if (rc != NGX_OK) { - msg = lua_pushfstring(L, "failed to eval the template for " - "replacement: \"%s\"", tpl.data); - goto error; - } - - cp_offset = cap[1]; - offset = cp_offset; - if (offset == cap[0]) { - offset++; - if (offset > (ssize_t) subj.len) { - break; - } - } - - if (global) { - continue; - } - - break; - } - - if (count == 0) { - dd("no match, just the original subject"); - lua_settop(L, 1); - - } else { - if (offset < (int) subj.len) { - dd("adding trailer: %s (len %d)", &subj.data[cp_offset], - (int) (subj.len - cp_offset)); - - - luaL_addlstring(&luabuf, (char *) &subj.data[cp_offset], - subj.len - cp_offset); - } - - luaL_pushresult(&luabuf); - - dd("the dst string: %s", lua_tostring(L, -1)); - } - - if (!(flags & NGX_LUA_RE_COMPILE_ONCE)) { - if (sd) { - ngx_http_lua_regex_free_study_data(pool, sd); - } - - if (re_comp.regex) { - ngx_pfree(pool, re_comp.regex); - } - - if (ctpl) { - ngx_pfree(pool, ctpl); - } - - if (cap) { - ngx_pfree(pool, cap); - } - } - - lua_pushinteger(L, count); - return 2; - -error: - - if (!(flags & NGX_LUA_RE_COMPILE_ONCE)) { - if (sd) { - ngx_http_lua_regex_free_study_data(pool, sd); - } - - if (re_comp.regex) { - ngx_pfree(pool, re_comp.regex); - } - - if (ctpl) { - ngx_pfree(pool, ctpl); - } - - if (cap) { - ngx_pfree(pool, cap); - } - } - - lua_pushnil(L); - lua_pushnil(L); - lua_pushstring(L, msg); - return 3; -} - - -ngx_int_t -ngx_http_lua_ffi_set_jit_stack_size(int size, u_char *errstr, - size_t *errstr_size) -{ -#if LUA_HAVE_PCRE_JIT - - ngx_http_lua_main_conf_t *lmcf; - ngx_pool_t *pool, *old_pool; - - lmcf = ngx_http_cycle_get_module_main_conf(ngx_cycle, - ngx_http_lua_module); - - if (size < NGX_LUA_RE_MIN_JIT_STACK_SIZE) { - size = NGX_LUA_RE_MIN_JIT_STACK_SIZE; - } - - pool = lmcf->pool; - - dd("server pool %p", lmcf->pool); - - if (lmcf->jit_stack) { - old_pool = ngx_http_lua_pcre_malloc_init(pool); + if (lmcf->jit_stack) { + old_pool = ngx_http_lua_pcre_malloc_init(pool); +#if (NGX_PCRE2) + pcre2_jit_stack_free(lmcf->jit_stack); +#else pcre_jit_stack_free(lmcf->jit_stack); +#endif ngx_http_lua_pcre_malloc_done(old_pool); } old_pool = ngx_http_lua_pcre_malloc_init(pool); +#if (NGX_PCRE2) + lmcf->jit_stack = pcre2_jit_stack_create(NGX_LUA_RE_MIN_JIT_STACK_SIZE, + size, NULL); +#else lmcf->jit_stack = pcre_jit_stack_alloc(NGX_LUA_RE_MIN_JIT_STACK_SIZE, size); +#endif ngx_http_lua_pcre_malloc_done(old_pool); @@ -1971,212 +379,159 @@ ngx_http_lua_ffi_set_jit_stack_size(int size, u_char *errstr, #else /* LUA_HAVE_PCRE_JIT */ *errstr_size = ngx_snprintf(errstr, *errstr_size, - "no pcre jit support found") - errstr; + "no pcre jit support found") + - errstr; return NGX_ERROR; -#endif /* LUA_HAVE_PCRE_JIT */ +#endif } -void -ngx_http_lua_inject_regex_api(lua_State *L) +#if (NGX_PCRE2) +static void +ngx_http_lua_regex_jit_compile(ngx_http_lua_regex_t *re, int flags, + ngx_pool_t *pool, ngx_http_lua_main_conf_t *lmcf, + ngx_http_lua_regex_compile_t *re_comp) { - /* ngx.re */ + ngx_int_t ret; + ngx_pool_t *old_pool; + + if (flags & NGX_LUA_RE_MODE_JIT) { + old_pool = ngx_http_lua_pcre_malloc_init(pool); + ret = pcre2_jit_compile(re_comp->regex, PCRE2_JIT_COMPLETE); - lua_createtable(L, 0, 5 /* nrec */); /* .re */ + if (ret != 0) { + ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0, + "pcre2_jit_compile() failed: %d in \"%V\", " + "ignored", + ret, &re_comp->pattern); - lua_pushcfunction(L, ngx_http_lua_ngx_re_find); - lua_setfield(L, -2, "find"); +#if (NGX_DEBUG) - lua_pushcfunction(L, ngx_http_lua_ngx_re_match); - lua_setfield(L, -2, "match"); + } else { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "pcre2 JIT compiled successfully"); +# endif /* !(NGX_DEBUG) */ + } - lua_pushcfunction(L, ngx_http_lua_ngx_re_gmatch); - lua_setfield(L, -2, "gmatch"); + ngx_http_lua_pcre_malloc_done(old_pool); - lua_pushcfunction(L, ngx_http_lua_ngx_re_sub); - lua_setfield(L, -2, "sub"); + } - lua_pushcfunction(L, ngx_http_lua_ngx_re_gsub); - lua_setfield(L, -2, "gsub"); + if (lmcf && lmcf->jit_stack) { + pcre2_jit_stack_assign(ngx_regex_match_context, NULL, + lmcf->jit_stack); + } - lua_setfield(L, -2, "re"); + return; } +#else static void -ngx_http_lua_regex_free_study_data(ngx_pool_t *pool, pcre_extra *sd) +ngx_http_lua_regex_jit_compile(ngx_http_lua_regex_t *re, int flags, + ngx_pool_t *pool, ngx_http_lua_main_conf_t *lmcf, + ngx_http_lua_regex_compile_t *re_comp) { - ngx_pool_t *old_pool; - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - -#if LUA_HAVE_PCRE_JIT - pcre_free_study(sd); -#else - pcre_free(sd); -#endif + const char *msg; + pcre_extra *sd = NULL; + ngx_pool_t *old_pool; - ngx_http_lua_pcre_malloc_done(old_pool); -} +#if (LUA_HAVE_PCRE_JIT) + if (flags & NGX_LUA_RE_MODE_JIT) { + old_pool = ngx_http_lua_pcre_malloc_init(pool); + sd = pcre_study(re_comp->regex, PCRE_STUDY_JIT_COMPILE, &msg); + ngx_http_lua_pcre_malloc_done(old_pool); -static ngx_int_t -ngx_http_lua_regex_compile(ngx_http_lua_regex_compile_t *rc) -{ - int n, erroff; - char *p; - const char *errstr; - pcre *re; - ngx_pool_t *old_pool; +# if (NGX_DEBUG) + if (msg != NULL) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "pcre study failed with PCRE_STUDY_JIT_COMPILE: " + "%s (%p)", msg, sd); + } - old_pool = ngx_http_lua_pcre_malloc_init(rc->pool); + if (sd != NULL) { + int jitted; - re = pcre_compile((const char *) rc->pattern.data, (int) rc->options, - &errstr, &erroff, NULL); + old_pool = ngx_http_lua_pcre_malloc_init(pool); - ngx_http_lua_pcre_malloc_done(old_pool); + pcre_fullinfo(re_comp->regex, sd, PCRE_INFO_JIT, &jitted); - if (re == NULL) { - if ((size_t) erroff == rc->pattern.len) { - rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, - "pcre_compile() failed: %s in \"%V\"", - errstr, &rc->pattern) - - rc->err.data; + ngx_http_lua_pcre_malloc_done(old_pool); - } else { - rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, - "pcre_compile() failed: %s in \"%V\" " - "at \"%s\"", errstr, &rc->pattern, - rc->pattern.data + erroff) - - rc->err.data; + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "pcre JIT compiling result: %d", jitted); } +# endif /* !(NGX_DEBUG) */ - return NGX_ERROR; + } else { + old_pool = ngx_http_lua_pcre_malloc_init(pool); + sd = pcre_study(re_comp->regex, 0, &msg); + ngx_http_lua_pcre_malloc_done(old_pool); } - rc->regex = re; - -#if 1 - n = pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &rc->captures); - if (n < 0) { - p = "pcre_fullinfo(\"%V\", PCRE_INFO_CAPTURECOUNT) failed: %d"; - goto failed; + if (sd && lmcf && lmcf->jit_stack) { + pcre_assign_jit_stack(sd, NULL, lmcf->jit_stack); } -#endif - return NGX_OK; + if (sd + && lmcf && lmcf->regex_match_limit > 0 + && !(flags & NGX_LUA_RE_MODE_DFA)) + { + sd->flags |= PCRE_EXTRA_MATCH_LIMIT; + sd->match_limit = lmcf->regex_match_limit; + } -failed: +#endif /* LUA_HAVE_PCRE_JIT */ - rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, p, &rc->pattern, n) - - rc->err.data; - return NGX_OK; + re->regex_sd = sd; } +#endif -static void -ngx_http_lua_ngx_re_gmatch_cleanup(void *data) +#if (NGX_PCRE2) +void +ngx_http_lua_regex_cleanup(void *data) { - ngx_http_lua_regex_ctx_t *ctx = data; + ngx_pool_t *old_pool; + ngx_http_lua_main_conf_t *lmcf; - if (ctx) { - if (ctx->regex_sd) { - ngx_http_lua_regex_free_study_data(ctx->request->pool, - ctx->regex_sd); - ctx->regex_sd = NULL; - } + lmcf = data; - if (ctx->cleanup) { - *ctx->cleanup = NULL; - ctx->cleanup = NULL; + if (ngx_regex_compile_context) { + old_pool = ngx_http_lua_pcre_malloc_init(NULL); + if (ngx_regex_match_context != NULL) { + pcre2_match_context_free(ngx_regex_match_context); + ngx_regex_match_context = NULL; } - ctx->request = NULL; + pcre2_compile_context_free(ngx_regex_compile_context); + ngx_regex_compile_context = NULL; + ngx_http_lua_pcre_malloc_done(old_pool); } - return; -} - - -static int -ngx_http_lua_ngx_re_gmatch_gc(lua_State *L) -{ - ngx_http_lua_regex_ctx_t *ctx; + if (lmcf && lmcf->jit_stack) { + old_pool = ngx_http_lua_pcre_malloc_init(NULL); - ctx = lua_touserdata(L, 1); + pcre2_jit_stack_free(lmcf->jit_stack); + lmcf->jit_stack = NULL; - if (ctx && ctx->cleanup) { - ngx_http_lua_ngx_re_gmatch_cleanup(ctx); + ngx_http_lua_pcre_malloc_done(old_pool); } - return 0; -} - - -static void -ngx_http_lua_re_collect_named_captures(lua_State *L, int res_tb_idx, - u_char *name_table, int name_count, int name_entry_size, unsigned flags, - ngx_str_t *subj) -{ - int i, n; - size_t len; - u_char *name_entry; - char *name; - - for (i = 0; i < name_count; i++) { - dd("top: %d", lua_gettop(L)); - - name_entry = &name_table[i * name_entry_size]; - n = (name_entry[0] << 8) | name_entry[1]; - name = (char *) &name_entry[2]; - - lua_rawgeti(L, -1, n); - if (lua_isnil(L, -1)) { - lua_pop(L, 1); - continue; - } - - if (flags & NGX_LUA_RE_MODE_DUPNAMES) { - /* unmatched groups are not stored in tables in DUPNAMES mode */ - if (!lua_toboolean(L, -1)) { - lua_pop(L, 1); - continue; - } - - lua_getfield(L, -2, name); /* big_tb cap small_tb */ - - if (lua_isnil(L, -1)) { - lua_pop(L, 1); - - /* assuming named submatches are usually unique */ - lua_createtable(L, 1 /* narr */, 0 /* nrec */); - lua_pushstring(L, name); - lua_pushvalue(L, -2); /* big_tb cap small_tb key small_tb */ - lua_rawset(L, res_tb_idx); /* big_tb cap small_tb */ - len = 0; - - } else { - len = lua_objlen(L, -1); - } - - lua_pushvalue(L, -2); /* big_tb cap small_tb cap */ - lua_rawseti(L, -2, (int) len + 1); /* big_tb cap small_tb */ - lua_pop(L, 2); - - } else { - lua_pushstring(L, name); /* big_tb cap key */ - lua_pushvalue(L, -2); /* big_tb cap key cap */ - lua_rawset(L, res_tb_idx); /* big_tb cap */ - lua_pop(L, 1); - } - - dd("top 2: %d", lua_gettop(L)); + if (ngx_regex_match_data) { + old_pool = ngx_http_lua_pcre_malloc_init(NULL); + pcre2_match_data_free(ngx_regex_match_data); + ngx_regex_match_data = NULL; + ngx_regex_match_data_size = 0; + ngx_http_lua_pcre_malloc_done(old_pool); } + } +#endif -#ifndef NGX_LUA_NO_FFI_API ngx_http_lua_regex_t * ngx_http_lua_ffi_compile_regex(const unsigned char *pat, size_t pat_len, int flags, int pcre_opts, u_char *errstr, @@ -2187,8 +542,7 @@ ngx_http_lua_ffi_compile_regex(const unsigned char *pat, size_t pat_len, ngx_int_t rc; const char *msg; ngx_pool_t *pool, *old_pool; - pcre_extra *sd = NULL; - ngx_http_lua_regex_t *re; + ngx_http_lua_regex_t *re = NULL; ngx_http_lua_main_conf_t *lmcf; ngx_http_lua_regex_compile_t re_comp; @@ -2210,6 +564,8 @@ ngx_http_lua_ffi_compile_regex(const unsigned char *pat, size_t pat_len, } re->pool = pool; + re->regex = NULL; + re->regex_sd = NULL; re_comp.options = pcre_opts; re_comp.pattern.data = (u_char *) pat; @@ -2231,58 +587,20 @@ ngx_http_lua_ffi_compile_regex(const unsigned char *pat, size_t pat_len, lmcf = ngx_http_cycle_get_module_main_conf(ngx_cycle, ngx_http_lua_module); -#if (LUA_HAVE_PCRE_JIT) - - if (flags & NGX_LUA_RE_MODE_JIT) { - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - sd = pcre_study(re_comp.regex, PCRE_STUDY_JIT_COMPILE, &msg); - ngx_http_lua_pcre_malloc_done(old_pool); - -# if (NGX_DEBUG) - if (msg != NULL) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, - "pcre study failed with PCRE_STUDY_JIT_COMPILE: " - "%s (%p)", msg, sd); - } - - if (sd != NULL) { - int jitted; - - old_pool = ngx_http_lua_pcre_malloc_init(pool); - - pcre_fullinfo(re_comp.regex, sd, PCRE_INFO_JIT, &jitted); - - ngx_http_lua_pcre_malloc_done(old_pool); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, - "pcre JIT compiling result: %d", jitted); - } -# endif /* !(NGX_DEBUG) */ - - } else { - old_pool = ngx_http_lua_pcre_malloc_init(pool); - sd = pcre_study(re_comp.regex, 0, &msg); - ngx_http_lua_pcre_malloc_done(old_pool); - } - - if (sd && lmcf->jit_stack) { - pcre_assign_jit_stack(sd, NULL, lmcf->jit_stack); - } - -#endif /* LUA_HAVE_PCRE_JIT */ + ngx_http_lua_assert(lmcf != NULL); - if (sd && lmcf && lmcf->regex_match_limit > 0) { - sd->flags |= PCRE_EXTRA_MATCH_LIMIT; - sd->match_limit = lmcf->regex_match_limit; - } + ngx_http_lua_regex_jit_compile(re, flags, pool, lmcf, &re_comp); if (flags & NGX_LUA_RE_MODE_DFA) { ovecsize = 2; re_comp.captures = 0; } else { +#if (NGX_PCRE2) + ovecsize = (re_comp.captures + 1) * 2; +#else ovecsize = (re_comp.captures + 1) * 3; +#endif } dd("allocating cap with size: %d", (int) ovecsize); @@ -2293,6 +611,31 @@ ngx_http_lua_ffi_compile_regex(const unsigned char *pat, size_t pat_len, goto error; } +#if (NGX_PCRE2) + if (pcre2_pattern_info(re_comp.regex, PCRE2_INFO_NAMECOUNT, + &re->name_count) < 0) + { + msg = "cannot acquire named subpattern count"; + goto error; + } + + if (re->name_count > 0) { + if (pcre2_pattern_info(re_comp.regex, PCRE2_INFO_NAMEENTRYSIZE, + &re->name_entry_size) != 0) + { + msg = "cannot acquire named subpattern entry size"; + goto error; + } + + if (pcre2_pattern_info(re_comp.regex, PCRE2_INFO_NAMETABLE, + &re->name_table) != 0) + { + msg = "cannot acquire named subpattern table"; + goto error; + } + } + +#else if (pcre_fullinfo(re_comp.regex, NULL, PCRE_INFO_NAMECOUNT, &re->name_count) != 0) { @@ -2315,9 +658,9 @@ ngx_http_lua_ffi_compile_regex(const unsigned char *pat, size_t pat_len, goto error; } } +#endif re->regex = re_comp.regex; - re->regex_sd = sd; re->ncaptures = re_comp.captures; re->captures = cap; re->replace = NULL; @@ -2333,9 +676,7 @@ ngx_http_lua_ffi_compile_regex(const unsigned char *pat, size_t pat_len, p = ngx_snprintf(errstr, errstr_size - 1, "%s", msg); *p = '\0'; - if (sd) { - ngx_http_lua_regex_free_study_data(pool, sd); - } + ngx_http_lua_regex_free_study_data(pool, re); if (pool) { ngx_destroy_pool(pool); @@ -2345,6 +686,103 @@ ngx_http_lua_ffi_compile_regex(const unsigned char *pat, size_t pat_len, } +#if (NGX_PCRE2) +int +ngx_http_lua_ffi_exec_regex(ngx_http_lua_regex_t *re, int flags, + const u_char *s, size_t len, int pos) +{ + int rc, exec_opts = 0; + size_t *ov; + ngx_uint_t ovecpair, n, i; + ngx_pool_t *old_pool; + + if (flags & NGX_LUA_RE_MODE_DFA) { + ovecpair = 1; + re->ncaptures = 0; + + } else { + ovecpair = re->ncaptures + 1; + } + + old_pool = ngx_http_lua_pcre_malloc_init(NULL); + + if (ngx_regex_match_data == NULL + || ovecpair > ngx_regex_match_data_size) + { + /* + * Allocate a match data if not yet allocated or smaller than + * needed. + */ + + if (ngx_regex_match_data) { + pcre2_match_data_free(ngx_regex_match_data); + } + + ngx_regex_match_data_size = ovecpair; + ngx_regex_match_data = pcre2_match_data_create(ovecpair, NULL); + + if (ngx_regex_match_data == NULL) { + rc = PCRE2_ERROR_NOMEMORY; + goto failed; + } + } + + if (flags & NGX_LUA_RE_NO_UTF8_CHECK) { + exec_opts = PCRE2_NO_UTF_CHECK; + + } else { + exec_opts = 0; + } + + if (flags & NGX_LUA_RE_MODE_DFA) { + int ws[NGX_LUA_RE_DFA_MODE_WORKSPACE_COUNT]; + rc = pcre2_dfa_match(re->regex, s, len, pos, exec_opts, + ngx_regex_match_data, ngx_regex_match_context, + ws, sizeof(ws) / sizeof(ws[0])); + + + } else { + rc = pcre2_match(re->regex, s, len, pos, exec_opts, + ngx_regex_match_data, ngx_regex_match_context); + } + + if (rc < 0) { +#if (NGX_DEBUG) + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "pcre2_match failed: flags 0x%05Xd, options 0x%08Xd, " + "rc %d, ovecpair %ui", flags, exec_opts, rc, ovecpair); +#endif + + goto failed; + } + + n = pcre2_get_ovector_count(ngx_regex_match_data); + ov = pcre2_get_ovector_pointer(ngx_regex_match_data); + +#if (NGX_DEBUG) + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "pcre2_match: flags 0x%05Xd, options 0x%08Xd, rc %d, " + "n %ui, ovecpair %ui", flags, exec_opts, rc, n, ovecpair); +#endif + + if (n > ovecpair) { + n = ovecpair; + } + + for (i = 0; i < n; i++) { + re->captures[i * 2] = ov[i * 2]; + re->captures[i * 2 + 1] = ov[i * 2 + 1]; + } + +failed: + + ngx_http_lua_pcre_malloc_done(old_pool); + + return rc; +} + +#else + int ngx_http_lua_ffi_exec_regex(ngx_http_lua_regex_t *re, int flags, const u_char *s, size_t len, int pos) @@ -2381,7 +819,8 @@ ngx_http_lua_ffi_exec_regex(ngx_http_lua_regex_t *re, int flags, int ws[NGX_LUA_RE_DFA_MODE_WORKSPACE_COUNT]; rc = ngx_http_lua_regex_dfa_exec(re->regex, sd, &subj, (int) pos, cap, ovecsize, ws, - sizeof(ws)/sizeof(ws[0]), exec_opts); + sizeof(ws) / sizeof(ws[0]), + exec_opts); #else @@ -2397,28 +836,19 @@ ngx_http_lua_ffi_exec_regex(ngx_http_lua_regex_t *re, int flags, return rc; } +#endif + void ngx_http_lua_ffi_destroy_regex(ngx_http_lua_regex_t *re) { - ngx_pool_t *old_pool; - dd("destroy regex called"); if (re == NULL || re->pool == NULL) { return; } - if (re->regex_sd) { - old_pool = ngx_http_lua_pcre_malloc_init(re->pool); -#if LUA_HAVE_PCRE_JIT - pcre_free_study(re->regex_sd); -#else - pcre_free(re->regex_sd); -#endif - ngx_http_lua_pcre_malloc_done(old_pool); - re->regex_sd = NULL; - } + ngx_http_lua_regex_free_study_data(re->pool, re); ngx_destroy_pool(re->pool); } @@ -2538,11 +968,25 @@ ngx_http_lua_ffi_max_regex_cache_size(void) if (lmcf == NULL) { return 0; } + return (uint32_t) lmcf->regex_cache_max_entries; } -#endif /* NGX_LUA_NO_FFI_API */ + + +const char * +ngx_http_lua_ffi_pcre_version(void) +{ +#if (NGX_PCRE2) + pcre2_config(PCRE2_CONFIG_VERSION, ngx_pcre2_version); + + return ngx_pcre2_version; +#else + return pcre_version(); +#endif +} #endif /* NGX_PCRE */ + /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_regex.h b/src/ngx_http_lua_regex.h deleted file mode 100644 index 03dffb80b9..0000000000 --- a/src/ngx_http_lua_regex.h +++ /dev/null @@ -1,24 +0,0 @@ - -/* - * Copyright (C) Yichun Zhang (agentzh) - */ - - -#ifndef _NGX_HTTP_LUA_REGEX_H_INCLUDED_ -#define _NGX_HTTP_LUA_REGEX_H_INCLUDED_ - - -#include "ngx_http_lua_common.h" -#include "ngx_http_lua_script.h" - - -#if (NGX_PCRE) -void ngx_http_lua_inject_regex_api(lua_State *L); -ngx_int_t ngx_http_lua_ffi_set_jit_stack_size(int size, u_char *errstr, - size_t *errstr_size); -#endif - - -#endif /* _NGX_HTTP_LUA_REGEX_H_INCLUDED_ */ - -/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_req_body.c b/src/ngx_http_lua_req_body.c index e6bf3c192e..5d69735cde 100644 --- a/src/ngx_http_lua_req_body.c +++ b/src/ngx_http_lua_req_body.c @@ -101,6 +101,7 @@ ngx_http_lua_ngx_req_read_body(lua_State *L) } ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE + | NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE | NGX_HTTP_LUA_CONTEXT_ACCESS | NGX_HTTP_LUA_CONTEXT_CONTENT); @@ -114,11 +115,6 @@ ngx_http_lua_ngx_req_read_body(lua_State *L) rc = ngx_http_read_client_request_body(r, ngx_http_lua_req_body_post_read); -#if (nginx_version < 1002006) || \ - (nginx_version >= 1003000 && nginx_version < 1003009) - r->main->count--; -#endif - if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { ctx->exit_code = rc; ctx->exited = 1; @@ -130,11 +126,8 @@ ngx_http_lua_ngx_req_read_body(lua_State *L) return lua_yield(L, 0); } -#if (nginx_version >= 1002006 && nginx_version < 1003000) || \ - nginx_version >= 1003009 r->main->count--; dd("decrement r->main->count: %d", (int) r->main->count); -#endif if (rc == NGX_AGAIN) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -236,15 +229,21 @@ ngx_http_lua_ngx_req_get_body_data(lua_State *L) { ngx_http_request_t *r; int n; - size_t len; + size_t len, max; + size_t size, rest; ngx_chain_t *cl; u_char *p; u_char *buf; n = lua_gettop(L); - if (n != 0) { - return luaL_error(L, "expecting 0 arguments but seen %d", n); + if (n != 0 && n != 1) { + return luaL_error(L, "expecting 0 or 1 arguments but seen %d", n); + } + + max = 0; + if (n == 1) { + max = (size_t) luaL_checknumber(L, 1); } r = ngx_http_lua_get_req(L); @@ -272,6 +271,7 @@ ngx_http_lua_ngx_req_get_body_data(lua_State *L) return 1; } + len = (max > 0 && len > max) ? max : len; lua_pushlstring(L, (char *) cl->buf->pos, len); return 1; } @@ -282,7 +282,13 @@ ngx_http_lua_ngx_req_get_body_data(lua_State *L) for (; cl; cl = cl->next) { dd("body chunk len: %d", (int) ngx_buf_size(cl->buf)); - len += cl->buf->last - cl->buf->pos; + size = cl->buf->last - cl->buf->pos; + if (max > 0 && (len + size > max)) { + len = max; + break; + } + + len += size; } if (len == 0) { @@ -293,8 +299,15 @@ ngx_http_lua_ngx_req_get_body_data(lua_State *L) buf = (u_char *) lua_newuserdata(L, len); p = buf; - for (cl = r->request_body->bufs; cl; cl = cl->next) { - p = ngx_copy(p, cl->buf->pos, cl->buf->last - cl->buf->pos); + rest = len; + for (cl = r->request_body->bufs; cl != NULL && rest > 0; cl = cl->next) { + size = ngx_buf_size(cl->buf); + if (size > rest) { /* reach limit*/ + size = rest; + } + + p = ngx_copy(p, cl->buf->pos, size); + rest -= size; } lua_pushlstring(L, (char *) buf, len); @@ -451,6 +464,7 @@ ngx_http_lua_ngx_req_set_body_data(lua_State *L) if (b->start == NULL) { return luaL_error(L, "no memory"); } + b->end = b->start + body.len; b->pos = b->start; @@ -462,6 +476,7 @@ ngx_http_lua_ngx_req_set_body_data(lua_State *L) if (rb->bufs == NULL) { return luaL_error(L, "no memory"); } + rb->bufs->next = NULL; b = ngx_create_temp_buf(r->pool, body.len); @@ -907,6 +922,7 @@ ngx_http_lua_ngx_req_set_body_file(lua_State *L) if (rb->bufs == NULL) { return luaL_error(L, "no memory"); } + rb->bufs->next = NULL; b = ngx_calloc_buf(r->pool); diff --git a/src/ngx_http_lua_req_method.c b/src/ngx_http_lua_req_method.c index 0a2a154ebe..a41e5af593 100644 --- a/src/ngx_http_lua_req_method.c +++ b/src/ngx_http_lua_req_method.c @@ -10,142 +10,9 @@ #include "ddebug.h" -#include "ngx_http_lua_req_method.h" #include "ngx_http_lua_subrequest.h" -#include "ngx_http_lua_util.h" -static int ngx_http_lua_ngx_req_get_method(lua_State *L); -static int ngx_http_lua_ngx_req_set_method(lua_State *L); - - -void -ngx_http_lua_inject_req_method_api(lua_State *L) -{ - lua_pushcfunction(L, ngx_http_lua_ngx_req_get_method); - lua_setfield(L, -2, "get_method"); - - lua_pushcfunction(L, ngx_http_lua_ngx_req_set_method); - lua_setfield(L, -2, "set_method"); -} - - -static int -ngx_http_lua_ngx_req_get_method(lua_State *L) -{ - int n; - ngx_http_request_t *r; - - n = lua_gettop(L); - if (n != 0) { - return luaL_error(L, "only one argument expected but got %d", n); - } - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "request object not found"); - } - - ngx_http_lua_check_fake_request(L, r); - - lua_pushlstring(L, (char *) r->method_name.data, r->method_name.len); - return 1; -} - - -static int -ngx_http_lua_ngx_req_set_method(lua_State *L) -{ - int n; - int method; - ngx_http_request_t *r; - - n = lua_gettop(L); - if (n != 1) { - return luaL_error(L, "only one argument expected but got %d", n); - } - - method = luaL_checkint(L, 1); - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "request object not found"); - } - - ngx_http_lua_check_fake_request(L, r); - - switch (method) { - case NGX_HTTP_GET: - r->method_name = ngx_http_lua_get_method; - break; - - case NGX_HTTP_POST: - r->method_name = ngx_http_lua_post_method; - break; - - case NGX_HTTP_PUT: - r->method_name = ngx_http_lua_put_method; - break; - - case NGX_HTTP_HEAD: - r->method_name = ngx_http_lua_head_method; - break; - - case NGX_HTTP_DELETE: - r->method_name = ngx_http_lua_delete_method; - break; - - case NGX_HTTP_OPTIONS: - r->method_name = ngx_http_lua_options_method; - break; - - case NGX_HTTP_MKCOL: - r->method_name = ngx_http_lua_mkcol_method; - break; - - case NGX_HTTP_COPY: - r->method_name = ngx_http_lua_copy_method; - break; - - case NGX_HTTP_MOVE: - r->method_name = ngx_http_lua_move_method; - break; - - case NGX_HTTP_PROPFIND: - r->method_name = ngx_http_lua_propfind_method; - break; - - case NGX_HTTP_PROPPATCH: - r->method_name = ngx_http_lua_proppatch_method; - break; - - case NGX_HTTP_LOCK: - r->method_name = ngx_http_lua_lock_method; - break; - - case NGX_HTTP_UNLOCK: - r->method_name = ngx_http_lua_unlock_method; - break; - - case NGX_HTTP_PATCH: - r->method_name = ngx_http_lua_patch_method; - break; - - case NGX_HTTP_TRACE: - r->method_name = ngx_http_lua_trace_method; - break; - - default: - return luaL_error(L, "unsupported HTTP method: %d", method); - } - - r->method = method; - - return 0; -} - - -#ifndef NGX_LUA_NO_FFI_API int ngx_http_lua_ffi_req_get_method(ngx_http_request_t *r) { @@ -158,15 +25,16 @@ ngx_http_lua_ffi_req_get_method(ngx_http_request_t *r) int -ngx_http_lua_ffi_req_get_method_name(ngx_http_request_t *r, char *buf, +ngx_http_lua_ffi_req_get_method_name(ngx_http_request_t *r, u_char **name, size_t *len) { if (r->connection->fd == (ngx_socket_t) -1) { return NGX_HTTP_LUA_FFI_BAD_CONTEXT; } - *len = ngx_min(r->method_name.len, *len); - ngx_memcpy(buf, r->method_name.data, *len); + *name = r->method_name.data; + *len = r->method_name.len; + return NGX_OK; } @@ -246,7 +114,6 @@ ngx_http_lua_ffi_req_set_method(ngx_http_request_t *r, int method) r->method = method; return NGX_OK; } -#endif /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_req_method.h b/src/ngx_http_lua_req_method.h deleted file mode 100644 index f8aa1af39a..0000000000 --- a/src/ngx_http_lua_req_method.h +++ /dev/null @@ -1,19 +0,0 @@ - -/* - * Copyright (C) Yichun Zhang (agentzh) - */ - - -#ifndef _NGX_HTTP_LUA_METHOD_H_INCLUDED_ -#define _NGX_HTTP_LUA_METHOD_H_INCLUDED_ - - -#include "ngx_http_lua_common.h" - - -void ngx_http_lua_inject_req_method_api(lua_State *L); - - -#endif /* _NGX_HTTP_LUA_METHOD_H_INCLUDED_ */ - -/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_rewriteby.c b/src/ngx_http_lua_rewriteby.c index 2fcf16f846..69302ba523 100644 --- a/src/ngx_http_lua_rewriteby.c +++ b/src/ngx_http_lua_rewriteby.c @@ -62,7 +62,7 @@ ngx_http_lua_rewrite_handler(ngx_http_request_t *r) #endif if (cur_ph < last_ph) { - dd("swaping the contents of cur_ph and last_ph..."); + dd("swapping the contents of cur_ph and last_ph..."); tmp = *cur_ph; @@ -107,10 +107,12 @@ ngx_http_lua_rewrite_handler(ngx_http_request_t *r) } if (rc == NGX_DECLINED) { - if (r->header_sent) { + if (r->header_sent + || (r->headers_out.status != 0 && ctx->out != NULL)) + { dd("header already sent"); - /* response header was already generated in access_by_lua*, + /* response header was already generated in rewrite_by_lua*, * so it is no longer safe to proceed to later phases * which may generate responses again */ @@ -126,6 +128,11 @@ ngx_http_lua_rewrite_handler(ngx_http_request_t *r) return NGX_HTTP_OK; } + + r->write_event_handler = ngx_http_core_run_phases; + ctx->entered_rewrite_phase = 0; + + return NGX_DECLINED; } return rc; @@ -144,11 +151,6 @@ ngx_http_lua_rewrite_handler(ngx_http_request_t *r) ngx_http_lua_generic_phase_post_read); if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { -#if (nginx_version < 1002006) || \ - (nginx_version >= 1003000 && nginx_version < 1003009) - r->main->count--; -#endif - return rc; } @@ -175,10 +177,15 @@ ngx_http_lua_rewrite_handler_inline(ngx_http_request_t *r) llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); L = ngx_http_lua_get_lua_vm(r, NULL); + if (!llcf->enable_code_cache) { + llcf->rewrite_src_ref = LUA_REFNIL; + } + /* load Lua inline script (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, llcf->rewrite_src.value.data, llcf->rewrite_src.value.len, + &llcf->rewrite_src_ref, llcf->rewrite_src_key, (const char *) llcf->rewrite_chunkname); @@ -214,8 +221,13 @@ ngx_http_lua_rewrite_handler_file(ngx_http_request_t *r) L = ngx_http_lua_get_lua_vm(r, NULL); + if (!llcf->enable_code_cache) { + llcf->rewrite_src_ref = LUA_REFNIL; + } + /* load Lua script file (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadfile(r->connection->log, L, script_path, + &llcf->rewrite_src_ref, llcf->rewrite_src_key); if (rc != NGX_OK) { if (rc < NGX_HTTP_SPECIAL_RESPONSE) { @@ -239,7 +251,7 @@ ngx_http_lua_rewrite_by_chunk(lua_State *L, ngx_http_request_t *r) ngx_event_t *rev; ngx_connection_t *c; ngx_http_lua_ctx_t *ctx; - ngx_http_cleanup_t *cln; + ngx_pool_cleanup_t *cln; ngx_http_lua_loc_conf_t *llcf; @@ -256,9 +268,11 @@ ngx_http_lua_rewrite_by_chunk(lua_State *L, ngx_http_request_t *r) /* move code closure to new coroutine */ lua_xmove(L, co, 1); +#ifndef OPENRESTY_LUAJIT /* set closure's env table to new coroutine's globals table */ ngx_http_lua_get_globals_table(co); lua_setfenv(co, -2); +#endif /* save nginx request in coroutine globals table */ ngx_http_lua_set_req(co, r); @@ -283,11 +297,13 @@ ngx_http_lua_rewrite_by_chunk(lua_State *L, ngx_http_request_t *r) ctx->cur_co_ctx->co_top = 1; #endif + ngx_http_lua_attach_co_ctx_to_L(co, ctx->cur_co_ctx); + /* }}} */ - /* {{{ register request cleanup hooks */ + /* {{{ register nginx pool cleanup hooks */ if (ctx->cleanup == NULL) { - cln = ngx_http_cleanup_add(r, 0); + cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -343,7 +359,9 @@ ngx_http_lua_rewrite_by_chunk(lua_State *L, ngx_http_request_t *r) } if (rc == NGX_OK || rc == NGX_DECLINED) { - if (r->header_sent) { + if (r->header_sent + || (r->headers_out.status != 0 && ctx->out != NULL)) + { dd("header already sent"); /* response header was already generated in rewrite_by_lua*, @@ -363,6 +381,9 @@ ngx_http_lua_rewrite_by_chunk(lua_State *L, ngx_http_request_t *r) return NGX_HTTP_OK; } + r->write_event_handler = ngx_http_core_run_phases; + ctx->entered_rewrite_phase = 0; + return NGX_DECLINED; } diff --git a/src/ngx_http_lua_script.c b/src/ngx_http_lua_script.c index 95ebadf64d..c675a162d4 100644 --- a/src/ngx_http_lua_script.c +++ b/src/ngx_http_lua_script.c @@ -329,7 +329,7 @@ ngx_http_lua_script_add_copy_code(ngx_http_lua_script_compile_t *sc, return NGX_ERROR; } - code->code = (ngx_http_lua_script_code_pt) + code->code = (ngx_http_lua_script_code_pt) (void *) ngx_http_lua_script_copy_len_code; code->len = len; @@ -399,7 +399,7 @@ ngx_http_lua_script_add_capture_code(ngx_http_lua_script_compile_t *sc, return NGX_ERROR; } - code->code = (ngx_http_lua_script_code_pt) + code->code = (ngx_http_lua_script_code_pt) (void *) ngx_http_lua_script_copy_capture_len_code; code->n = 2 * n; diff --git a/src/ngx_http_lua_semaphore.c b/src/ngx_http_lua_semaphore.c index 0b70aea722..435beaa29a 100644 --- a/src/ngx_http_lua_semaphore.c +++ b/src/ngx_http_lua_semaphore.c @@ -7,9 +7,6 @@ */ -#ifndef NGX_LUA_NO_FFI_API - - #ifndef DDEBUG #define DDEBUG 0 #endif @@ -40,7 +37,7 @@ void ngx_http_lua_ffi_sema_gc(ngx_http_lua_sema_t *sem); enum { SEMAPHORE_WAIT_SUCC = 0, - SEMAPHORE_WAIT_TIMEOUT = 1 + SEMAPHORE_WAIT_TIMEOUT = 1, }; @@ -86,6 +83,8 @@ ngx_http_lua_alloc_sema(void) lmcf = ngx_http_cycle_get_module_main_conf(ngx_cycle, ngx_http_lua_module); + ngx_http_lua_assert(lmcf != NULL); + mm = lmcf->sema_mm; if (!ngx_queue_empty(&mm->free_queue)) { @@ -172,6 +171,8 @@ ngx_http_lua_sema_mm_cleanup(void *data) sem = ngx_queue_data(q, ngx_http_lua_sema_t, chain); block = sem->block; + ngx_http_lua_assert(block != NULL); + if (block->used == 0) { iter = (ngx_http_lua_sema_t *) (block + 1); @@ -342,7 +343,7 @@ ngx_http_lua_ffi_sema_post(ngx_http_lua_sema_t *sem, int n) sem->resource_count += n; if (!ngx_queue_empty(&sem->wait_queue)) { - /* we need the extra paranthese around the first argument of + /* we need the extra parentheses around the first argument of * ngx_post_event() just to work around macro issues in nginx * cores older than nginx 1.7.12 (exclusive). */ @@ -378,11 +379,7 @@ ngx_http_lua_ffi_sema_wait(ngx_http_request_t *r, return NGX_ERROR; } - rc = ngx_http_lua_ffi_check_context(ctx, NGX_HTTP_LUA_CONTEXT_REWRITE - | NGX_HTTP_LUA_CONTEXT_ACCESS - | NGX_HTTP_LUA_CONTEXT_CONTENT - | NGX_HTTP_LUA_CONTEXT_TIMER - | NGX_HTTP_LUA_CONTEXT_SSL_CERT, + rc = ngx_http_lua_ffi_check_context(ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE, err, errlen); if (rc != NGX_OK) { @@ -469,8 +466,11 @@ ngx_http_lua_sema_handler(ngx_event_t *ev) sem = ev->data; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "semaphore handler: wait queue: %sempty, resource count: %d", + ngx_queue_empty(&sem->wait_queue) ? "" : "not ", + sem->resource_count); while (!ngx_queue_empty(&sem->wait_queue) && sem->resource_count > 0) { - q = ngx_queue_head(&sem->wait_queue); ngx_queue_remove(q); @@ -569,10 +569,12 @@ ngx_http_lua_ffi_sema_gc(ngx_http_lua_sema_t *sem) "destroyed", sem); } + if (sem->sem_event.posted) { + ngx_delete_posted_event(&sem->sem_event); + } + ngx_http_lua_free_sema(sem); } -#endif /* NGX_LUA_NO_FFI_API */ - /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_semaphore.h b/src/ngx_http_lua_semaphore.h index 65ba8fff34..826f86a3f6 100644 --- a/src/ngx_http_lua_semaphore.h +++ b/src/ngx_http_lua_semaphore.h @@ -41,11 +41,9 @@ typedef struct ngx_http_lua_sema_s { } ngx_http_lua_sema_t; -#ifndef NGX_LUA_NO_FFI_API void ngx_http_lua_sema_mm_cleanup(void *data); ngx_int_t ngx_http_lua_sema_mm_init(ngx_conf_t *cf, ngx_http_lua_main_conf_t *lmcf); -#endif #endif /* _NGX_HTTP_LUA_SEMAPHORE_H_INCLUDED_ */ diff --git a/src/ngx_http_lua_server_rewriteby.c b/src/ngx_http_lua_server_rewriteby.c new file mode 100644 index 0000000000..d012a8e341 --- /dev/null +++ b/src/ngx_http_lua_server_rewriteby.c @@ -0,0 +1,343 @@ + +/* + * Copyright (C) Xiaozhe Wang (chaoslawful) + * Copyright (C) Yichun Zhang (agentzh) + */ + + +#ifndef DDEBUG +#define DDEBUG 0 +#endif +#include "ddebug.h" + +#include +#include "ngx_http_lua_server_rewriteby.h" +#include "ngx_http_lua_util.h" +#include "ngx_http_lua_exception.h" +#include "ngx_http_lua_cache.h" + +static ngx_int_t ngx_http_lua_server_rewrite_by_chunk(lua_State *L, + ngx_http_request_t *r); + +ngx_int_t +ngx_http_lua_server_rewrite_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + lua_State *L; + ngx_http_lua_srv_conf_t *lscf; + ngx_http_lua_loc_conf_t *llcf; + ngx_http_lua_ctx_t *ctx; + + /* XXX we need to take into account ngx_rewrite's location dump */ + if (r->uri_changed) { + return NGX_DECLINED; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua server rewrite handler, uri:\"%V\" c:%ud", &r->uri, + r->main->count); + + lscf = ngx_http_get_module_srv_conf(r, ngx_http_lua_module); + llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); + L = ngx_http_lua_get_lua_vm(r, NULL); + + if (lscf->srv.server_rewrite_handler == NULL) { + dd("no rewrite handler found"); + return NGX_DECLINED; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + + dd("ctx = %p", ctx); + + if (ctx == NULL) { + ctx = ngx_http_lua_create_ctx(r); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + } + + dd("entered? %d", (int) ctx->entered_server_rewrite_phase); + + if (ctx->entered_server_rewrite_phase) { + dd("rewriteby: calling wev handler"); + rc = ctx->resume_handler(r); + dd("rewriteby: wev handler returns %d", (int) rc); + + if (rc == NGX_OK) { + rc = NGX_DECLINED; + } + + if (rc == NGX_DECLINED) { + if (r->header_sent + || (r->headers_out.status != 0 && ctx->out != NULL)) + { + dd("header already sent"); + + /* response header was already generated in rewrite_by_lua*, + * so it is no longer safe to proceed to later phases + * which may generate responses again */ + + if (!ctx->eof) { + dd("eof not yet sent"); + + rc = ngx_http_lua_send_chain_link(r, ctx, NULL + /* indicate last_buf */); + if (rc == NGX_ERROR || rc > NGX_OK) { + return rc; + } + } + + return NGX_HTTP_OK; + } + + r->write_event_handler = ngx_http_core_run_phases; + ctx->entered_server_rewrite_phase = 0; + + return NGX_DECLINED; + } + + return rc; + } + + if (ctx->waiting_more_body) { + return NGX_DONE; + } + + /* TODO: lscf do not have force_read_body */ + if (llcf->force_read_body && !ctx->read_body_done) { + r->request_body_in_single_buf = 1; + r->request_body_in_persistent_file = 1; + r->request_body_in_clean_file = 1; + + rc = ngx_http_read_client_request_body(r, + ngx_http_lua_generic_phase_post_read); + + if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + + if (rc == NGX_AGAIN) { + ctx->waiting_more_body = 1; + return NGX_DONE; + } + } + + dd("calling server rewrite handler"); + return lscf->srv.server_rewrite_handler(r, lscf, L); +} + + +ngx_int_t +ngx_http_lua_server_rewrite_handler_inline(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L) +{ + ngx_int_t rc; + + dd("server_rewrite by lua inline"); + + + /* load Lua inline script (w/ cache) sp = 1 */ + rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, + lscf->srv.server_rewrite_src.value.data, + lscf->srv.server_rewrite_src.value.len, + &lscf->srv.server_rewrite_src_ref, + lscf->srv.server_rewrite_src_key, + (const char *) + lscf->srv.server_rewrite_chunkname); + if (rc != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return ngx_http_lua_server_rewrite_by_chunk(L, r); +} + + +ngx_int_t +ngx_http_lua_server_rewrite_handler_file(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L) +{ + ngx_int_t rc; + u_char *script_path; + ngx_str_t eval_src; + + + if (ngx_http_complex_value(r, &lscf->srv.server_rewrite_src, + &eval_src) != NGX_OK) + { + return NGX_ERROR; + } + + script_path = ngx_http_lua_rebase_path(r->pool, eval_src.data, + eval_src.len); + + if (script_path == NULL) { + return NGX_ERROR; + } + + /* load Lua script file (w/ cache) sp = 1 */ + rc = ngx_http_lua_cache_loadfile(r->connection->log, L, script_path, + &lscf->srv.server_rewrite_src_ref, + lscf->srv.server_rewrite_src_key); + if (rc != NGX_OK) { + if (rc < NGX_HTTP_SPECIAL_RESPONSE) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return rc; + } + + return ngx_http_lua_server_rewrite_by_chunk(L, r); +} + + +static ngx_int_t +ngx_http_lua_server_rewrite_by_chunk(lua_State *L, ngx_http_request_t *r) +{ + int co_ref; + lua_State *co; + ngx_int_t rc; + ngx_uint_t nreqs; + ngx_event_t *rev; + ngx_connection_t *c; + ngx_http_lua_ctx_t *ctx; + ngx_pool_cleanup_t *cln; + + ngx_http_lua_loc_conf_t *llcf; + + /* {{{ new coroutine to handle request */ + co = ngx_http_lua_new_thread(r, L, &co_ref); + + if (co == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "lua: failed to create new coroutine to handle request"); + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* move code closure to new coroutine */ + lua_xmove(L, co, 1); + +#ifndef OPENRESTY_LUAJIT + /* set closure's env table to new coroutine's globals table */ + ngx_http_lua_get_globals_table(co); + lua_setfenv(co, -2); +#endif + + /* save nginx request in coroutine globals table */ + ngx_http_lua_set_req(co, r); + + /* {{{ initialize request context */ + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + + dd("ctx = %p", ctx); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_http_lua_reset_ctx(r, L, ctx); + + ctx->entered_server_rewrite_phase = 1; + + ctx->cur_co_ctx = &ctx->entry_co_ctx; + ctx->cur_co_ctx->co = co; + ctx->cur_co_ctx->co_ref = co_ref; +#ifdef NGX_LUA_USE_ASSERT + ctx->cur_co_ctx->co_top = 1; +#endif + + ngx_http_lua_attach_co_ctx_to_L(co, ctx->cur_co_ctx); + + /* }}} */ + + /* {{{ register request cleanup hooks */ + if (ctx->cleanup == NULL) { + cln = ngx_pool_cleanup_add(r->pool, 0); + if (cln == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + cln->handler = ngx_http_lua_request_cleanup_handler; + cln->data = ctx; + ctx->cleanup = &cln->handler; + } + /* }}} */ + + ctx->context = NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE; + + llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); + + if (llcf->check_client_abort) { + r->read_event_handler = ngx_http_lua_rd_check_broken_connection; + +#if (NGX_HTTP_V2) + if (!r->stream) { +#endif + + rev = r->connection->read; + + if (!rev->active) { + if (ngx_add_event(rev, NGX_READ_EVENT, 0) != NGX_OK) { + return NGX_ERROR; + } + } + +#if (NGX_HTTP_V2) + } +#endif + + } else { + r->read_event_handler = ngx_http_block_reading; + } + + c = r->connection; + nreqs = c->requests; + + rc = ngx_http_lua_run_thread(L, r, ctx, 0); + + if (rc == NGX_ERROR || rc > NGX_OK) { + return rc; + } + + if (rc == NGX_AGAIN) { + rc = ngx_http_lua_run_posted_threads(c, L, r, ctx, nreqs); + + } else if (rc == NGX_DONE) { + ngx_http_lua_finalize_request(r, NGX_DONE); + rc = ngx_http_lua_run_posted_threads(c, L, r, ctx, nreqs); + } + + if (rc == NGX_OK || rc == NGX_DECLINED) { + if (r->header_sent + || (r->headers_out.status != 0 && ctx->out != NULL)) + { + dd("header already sent"); + + /* response header was already generated in rewrite_by_lua*, + * so it is no longer safe to proceed to later phases + * which may generate responses again */ + + if (!ctx->eof) { + dd("eof not yet sent"); + + rc = ngx_http_lua_send_chain_link(r, ctx, NULL + /* indicate last_buf */); + if (rc == NGX_ERROR || rc > NGX_OK) { + return rc; + } + } + + return NGX_HTTP_OK; + } + + r->write_event_handler = ngx_http_core_run_phases; + ctx->entered_server_rewrite_phase = 0; + + return NGX_DECLINED; + } + + return rc; +} + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_server_rewriteby.h b/src/ngx_http_lua_server_rewriteby.h new file mode 100644 index 0000000000..a35ebd2b72 --- /dev/null +++ b/src/ngx_http_lua_server_rewriteby.h @@ -0,0 +1,20 @@ + +/* + * Copyright (C) Xiaozhe Wang (chaoslawful) + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef _NGX_HTTP_LUA_SERVER_REWRITEBY_H_INCLUDED_ +#define _NGX_HTTP_LUA_SERVER_REWRITEBY_H_INCLUDED_ + +#include "ngx_http_lua_common.h" + +ngx_int_t ngx_http_lua_server_rewrite_handler(ngx_http_request_t *r); +ngx_int_t ngx_http_lua_server_rewrite_handler_inline(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L); +ngx_int_t ngx_http_lua_server_rewrite_handler_file(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L); + +#endif /* _NGX_HTTP_LUA_SERVER_REWRITEBY_H_INCLUDED_ */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_setby.c b/src/ngx_http_lua_setby.c index 2e8762a29d..98e8d47f5a 100644 --- a/src/ngx_http_lua_setby.c +++ b/src/ngx_http_lua_setby.c @@ -15,10 +15,7 @@ #include "ngx_http_lua_exception.h" #include "ngx_http_lua_util.h" #include "ngx_http_lua_pcrefix.h" -#include "ngx_http_lua_time.h" #include "ngx_http_lua_log.h" -#include "ngx_http_lua_regex.h" -#include "ngx_http_lua_variable.h" #include "ngx_http_lua_string.h" #include "ngx_http_lua_misc.h" #include "ngx_http_lua_consts.h" @@ -30,13 +27,6 @@ static void ngx_http_lua_set_by_lua_env(lua_State *L, ngx_http_request_t *r, size_t nargs, ngx_http_variable_value_t *args); -/* keys in Lua thread for fetching args and nargs in set_by_lua* */ - -#define ngx_http_lua_nargs_key "__ngx_nargs" - -#define ngx_http_lua_args_key "__ngx_args" - - ngx_int_t ngx_http_lua_set_by_chunk(lua_State *L, ngx_http_request_t *r, ngx_str_t *val, ngx_http_variable_value_t *args, size_t nargs, ngx_str_t *script) @@ -133,33 +123,32 @@ ngx_http_lua_set_by_chunk(lua_State *L, ngx_http_request_t *r, ngx_str_t *val, } -int -ngx_http_lua_setby_param_get(lua_State *L) +void +ngx_http_lua_ffi_get_setby_param(ngx_http_request_t *r, int idx, + u_char **data_p, size_t *len_p) { - int idx; int n; ngx_http_variable_value_t *v; + ngx_http_lua_main_conf_t *lmcf; - idx = luaL_checkint(L, 2); idx--; - /* get number of args from globals */ - lua_getglobal(L, ngx_http_lua_nargs_key); - n = (int) lua_tointeger(L, -1); + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); - /* get args from globals */ - lua_getglobal(L, ngx_http_lua_args_key); - v = lua_touserdata(L, -1); + /* get number of args from lmcf */ + n = lmcf->setby_nargs; + + /* get args from lmcf */ + v = lmcf->setby_args; if (idx < 0 || idx > n - 1) { - lua_pushnil(L); + *len_p = 0; } else { - lua_pushlstring(L, (const char *) (v[idx].data), v[idx].len); + *data_p = v[idx].data; + *len_p = v[idx].len; } - - return 1; } @@ -178,15 +167,16 @@ static void ngx_http_lua_set_by_lua_env(lua_State *L, ngx_http_request_t *r, size_t nargs, ngx_http_variable_value_t *args) { - /* set nginx request pointer to current lua thread's globals table */ + ngx_http_lua_main_conf_t *lmcf; + ngx_http_lua_set_req(L, r); - lua_pushinteger(L, nargs); - lua_setglobal(L, ngx_http_lua_nargs_key); + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); - lua_pushlightuserdata(L, args); - lua_setglobal(L, ngx_http_lua_args_key); + lmcf->setby_nargs = nargs; + lmcf->setby_args = args; +#ifndef OPENRESTY_LUAJIT /** * we want to create empty environment for current script * @@ -211,6 +201,7 @@ ngx_http_lua_set_by_lua_env(lua_State *L, ngx_http_request_t *r, size_t nargs, /* }}} */ lua_setfenv(L, -2); /* set new running env for the code closure */ +#endif } /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_setby.h b/src/ngx_http_lua_setby.h index da71753eaa..8fd1e2c7b3 100644 --- a/src/ngx_http_lua_setby.h +++ b/src/ngx_http_lua_setby.h @@ -7,7 +7,6 @@ ngx_int_t ngx_http_lua_set_by_chunk(lua_State *L, ngx_http_request_t *r, ngx_str_t *val, ngx_http_variable_value_t *args, size_t nargs, ngx_str_t *script); -int ngx_http_lua_setby_param_get(lua_State *L); #endif /* _NGX_HTTP_LUA_SET_BY_H_INCLUDED_ */ diff --git a/src/ngx_http_lua_shdict.c b/src/ngx_http_lua_shdict.c index 5b48eb4b0d..146974d954 100644 --- a/src/ngx_http_lua_shdict.c +++ b/src/ngx_http_lua_shdict.c @@ -15,23 +15,11 @@ #include "ngx_http_lua_api.h" -static int ngx_http_lua_shdict_set(lua_State *L); -static int ngx_http_lua_shdict_safe_set(lua_State *L); -static int ngx_http_lua_shdict_get(lua_State *L); -static int ngx_http_lua_shdict_get_stale(lua_State *L); -static int ngx_http_lua_shdict_get_helper(lua_State *L, int get_stale); static int ngx_http_lua_shdict_expire(ngx_http_lua_shdict_ctx_t *ctx, ngx_uint_t n); static ngx_int_t ngx_http_lua_shdict_lookup(ngx_shm_zone_t *shm_zone, ngx_uint_t hash, u_char *kdata, size_t klen, ngx_http_lua_shdict_node_t **sdp); -static int ngx_http_lua_shdict_set_helper(lua_State *L, int flags); -static int ngx_http_lua_shdict_add(lua_State *L); -static int ngx_http_lua_shdict_safe_add(lua_State *L); -static int ngx_http_lua_shdict_replace(lua_State *L); -static int ngx_http_lua_shdict_incr(lua_State *L); -static int ngx_http_lua_shdict_delete(lua_State *L); -static int ngx_http_lua_shdict_flush_all(lua_State *L); static int ngx_http_lua_shdict_flush_expired(lua_State *L); static int ngx_http_lua_shdict_get_keys(lua_State *L); static int ngx_http_lua_shdict_lpush(lua_State *L); @@ -44,7 +32,7 @@ static int ngx_http_lua_shdict_llen(lua_State *L); static ngx_inline ngx_shm_zone_t *ngx_http_lua_shdict_get_zone(lua_State *L, - int index); + int index); #define NGX_HTTP_LUA_SHDICT_ADD 0x0001 @@ -127,9 +115,7 @@ ngx_http_lua_shdict_init_zone(ngx_shm_zone_t *shm_zone, void *data) ngx_sprintf(ctx->shpool->log_ctx, " in lua_shared_dict zone \"%V\"%Z", &shm_zone->shm.name); -#if defined(nginx_version) && nginx_version >= 1005013 ctx->shpool->log_nomem = 0; -#endif return NGX_OK; } @@ -212,9 +198,6 @@ ngx_http_lua_shdict_lookup(ngx_shm_zone_t *shm_zone, ngx_uint_t hash, rc = ngx_memn2cmp(kdata, sd->data, klen, (size_t) sd->key_len); if (rc == 0) { - ngx_queue_remove(&sd->queue); - ngx_queue_insert_head(&ctx->sh->lru_queue, &sd->queue); - *sdp = sd; dd("node expires: %lld", (long long) sd->expires); @@ -227,12 +210,15 @@ ngx_http_lua_shdict_lookup(ngx_shm_zone_t *shm_zone, ngx_uint_t hash, dd("time to live: %lld", (long long) ms); - if (ms < 0) { + if (ms <= 0) { dd("node already expired"); return NGX_DONE; } } + ngx_queue_remove(&sd->queue); + ngx_queue_insert_head(&ctx->sh->lru_queue, &sd->queue); + return NGX_OK; } @@ -325,39 +311,13 @@ ngx_http_lua_inject_shdict_api(ngx_http_lua_main_conf_t *lmcf, lua_State *L) ngx_http_lua_shdict_ctx_t *ctx; ngx_uint_t i; ngx_shm_zone_t **zone; + ngx_shm_zone_t **zone_udata; if (lmcf->shdict_zones != NULL) { lua_createtable(L, 0, lmcf->shdict_zones->nelts /* nrec */); /* ngx.shared */ - lua_createtable(L, 0 /* narr */, 18 /* nrec */); /* shared mt */ - - lua_pushcfunction(L, ngx_http_lua_shdict_get); - lua_setfield(L, -2, "get"); - - lua_pushcfunction(L, ngx_http_lua_shdict_get_stale); - lua_setfield(L, -2, "get_stale"); - - lua_pushcfunction(L, ngx_http_lua_shdict_set); - lua_setfield(L, -2, "set"); - - lua_pushcfunction(L, ngx_http_lua_shdict_safe_set); - lua_setfield(L, -2, "safe_set"); - - lua_pushcfunction(L, ngx_http_lua_shdict_add); - lua_setfield(L, -2, "add"); - - lua_pushcfunction(L, ngx_http_lua_shdict_safe_add); - lua_setfield(L, -2, "safe_add"); - - lua_pushcfunction(L, ngx_http_lua_shdict_replace); - lua_setfield(L, -2, "replace"); - - lua_pushcfunction(L, ngx_http_lua_shdict_incr); - lua_setfield(L, -2, "incr"); - - lua_pushcfunction(L, ngx_http_lua_shdict_delete); - lua_setfield(L, -2, "delete"); + lua_createtable(L, 0 /* narr */, 22 /* nrec */); /* shared mt */ lua_pushcfunction(L, ngx_http_lua_shdict_lpush); lua_setfield(L, -2, "lpush"); @@ -374,9 +334,6 @@ ngx_http_lua_inject_shdict_api(ngx_http_lua_main_conf_t *lmcf, lua_State *L) lua_pushcfunction(L, ngx_http_lua_shdict_llen); lua_setfield(L, -2, "llen"); - lua_pushcfunction(L, ngx_http_lua_shdict_flush_all); - lua_setfield(L, -2, "flush_all"); - lua_pushcfunction(L, ngx_http_lua_shdict_flush_expired); lua_setfield(L, -2, "flush_expired"); @@ -396,7 +353,9 @@ ngx_http_lua_inject_shdict_api(ngx_http_lua_main_conf_t *lmcf, lua_State *L) lua_createtable(L, 1 /* narr */, 0 /* nrec */); /* table of zone[i] */ - lua_pushlightuserdata(L, zone[i]); /* shared mt key ud */ + zone_udata = lua_newuserdata(L, sizeof(ngx_shm_zone_t *)); + /* shared mt key ud */ + *zone_udata = zone[i]; lua_rawseti(L, -2, SHDICT_USERDATA_INDEX); /* {zone[i]} */ lua_pushvalue(L, -3); /* shared mt key ud mt */ lua_setmetatable(L, -2); /* shared mt key ud */ @@ -413,265 +372,22 @@ ngx_http_lua_inject_shdict_api(ngx_http_lua_main_conf_t *lmcf, lua_State *L) } -static int -ngx_http_lua_shdict_get(lua_State *L) -{ - return ngx_http_lua_shdict_get_helper(L, 0 /* stale */); -} - - -static int -ngx_http_lua_shdict_get_stale(lua_State *L) -{ - return ngx_http_lua_shdict_get_helper(L, 1 /* stale */); -} - - static ngx_inline ngx_shm_zone_t * ngx_http_lua_shdict_get_zone(lua_State *L, int index) { ngx_shm_zone_t *zone; + ngx_shm_zone_t **zone_udata; lua_rawgeti(L, index, SHDICT_USERDATA_INDEX); - zone = lua_touserdata(L, -1); + zone_udata = lua_touserdata(L, -1); lua_pop(L, 1); - return zone; -} - - -static int -ngx_http_lua_shdict_get_helper(lua_State *L, int get_stale) -{ - int n; - ngx_str_t name; - ngx_str_t key; - uint32_t hash; - ngx_int_t rc; - ngx_http_lua_shdict_ctx_t *ctx; - ngx_http_lua_shdict_node_t *sd; - ngx_str_t value; - int value_type; - double num; - u_char c; - ngx_shm_zone_t *zone; - uint32_t user_flags = 0; - - n = lua_gettop(L); - - if (n != 2) { - return luaL_error(L, "expecting exactly two arguments, " - "but only seen %d", n); - } - - if (lua_type(L, 1) != LUA_TTABLE) { - return luaL_error(L, "bad \"zone\" argument"); - } - - zone = ngx_http_lua_shdict_get_zone(L, 1); - if (zone == NULL) { - return luaL_error(L, "bad \"zone\" argument"); - } - - ctx = zone->data; - name = ctx->name; - - if (lua_isnil(L, 2)) { - lua_pushnil(L); - lua_pushliteral(L, "nil key"); - return 2; - } - - key.data = (u_char *) luaL_checklstring(L, 2, &key.len); - - if (key.len == 0) { - lua_pushnil(L); - lua_pushliteral(L, "empty key"); - return 2; - } - - if (key.len > 65535) { - lua_pushnil(L); - lua_pushliteral(L, "key too long"); - return 2; - } - - hash = ngx_crc32_short(key.data, key.len); - -#if (NGX_DEBUG) - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->log, 0, - "fetching key \"%V\" in shared dict \"%V\"", &key, &name); -#endif /* NGX_DEBUG */ - - ngx_shmtx_lock(&ctx->shpool->mutex); - -#if 1 - if (!get_stale) { - ngx_http_lua_shdict_expire(ctx, 1); - } -#endif - - rc = ngx_http_lua_shdict_lookup(zone, hash, key.data, key.len, &sd); - - dd("shdict lookup returns %d", (int) rc); - - if (rc == NGX_DECLINED || (rc == NGX_DONE && !get_stale)) { - ngx_shmtx_unlock(&ctx->shpool->mutex); - lua_pushnil(L); - return 1; - } - - /* rc == NGX_OK || (rc == NGX_DONE && get_stale) */ - - value_type = sd->value_type; - - dd("data: %p", sd->data); - dd("key len: %d", (int) sd->key_len); - - value.data = sd->data + sd->key_len; - value.len = (size_t) sd->value_len; - - switch (value_type) { - - case SHDICT_TSTRING: - - lua_pushlstring(L, (char *) value.data, value.len); - break; - - case SHDICT_TNUMBER: - - if (value.len != sizeof(double)) { - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - return luaL_error(L, "bad lua number value size found for key %s " - "in shared_dict %s: %lu", key.data, name.data, - (unsigned long) value.len); - } - - ngx_memcpy(&num, value.data, sizeof(double)); - - lua_pushnumber(L, num); - break; - - case SHDICT_TBOOLEAN: - - if (value.len != sizeof(u_char)) { - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - return luaL_error(L, "bad lua boolean value size found for key %s " - "in shared_dict %s: %lu", key.data, name.data, - (unsigned long) value.len); - } - - c = *value.data; - - lua_pushboolean(L, c ? 1 : 0); - break; - - case SHDICT_TLIST: - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - lua_pushnil(L); - lua_pushliteral(L, "value is a list"); - return 2; - - default: - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - return luaL_error(L, "bad value type found for key %s in " - "shared_dict %s: %d", key.data, name.data, - value_type); - } - - user_flags = sd->user_flags; - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - if (get_stale) { - - /* always return value, flags, stale */ - - if (user_flags) { - lua_pushinteger(L, (lua_Integer) user_flags); - - } else { - lua_pushnil(L); - } - - lua_pushboolean(L, rc == NGX_DONE); - return 3; - } - - if (user_flags) { - lua_pushinteger(L, (lua_Integer) user_flags); - return 2; - } - - return 1; -} - - -static int -ngx_http_lua_shdict_delete(lua_State *L) -{ - int n; - - n = lua_gettop(L); - - if (n != 2) { - return luaL_error(L, "expecting 2 arguments, " - "but only seen %d", n); - } - - lua_pushnil(L); - - return ngx_http_lua_shdict_set_helper(L, 0); -} - - -static int -ngx_http_lua_shdict_flush_all(lua_State *L) -{ - ngx_queue_t *q; - ngx_http_lua_shdict_node_t *sd; - int n; - ngx_http_lua_shdict_ctx_t *ctx; - ngx_shm_zone_t *zone; - - n = lua_gettop(L); - - if (n != 1) { - return luaL_error(L, "expecting 1 argument, but seen %d", n); - } - - luaL_checktype(L, 1, LUA_TTABLE); - - zone = ngx_http_lua_shdict_get_zone(L, 1); - if (zone == NULL) { - return luaL_error(L, "bad user data for the ngx_shm_zone_t pointer"); - } - - ctx = zone->data; - - ngx_shmtx_lock(&ctx->shpool->mutex); - - for (q = ngx_queue_head(&ctx->sh->lru_queue); - q != ngx_queue_sentinel(&ctx->sh->lru_queue); - q = ngx_queue_next(q)) - { - sd = ngx_queue_data(q, ngx_http_lua_shdict_node_t, queue); - sd->expires = 1; + if (zone_udata == NULL) { + return NULL; } - ngx_http_lua_shdict_expire(ctx, 0); - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - return 0; + zone = *zone_udata; + return zone; } @@ -686,813 +402,183 @@ ngx_http_lua_shdict_flush_expired(lua_State *L) int freed = 0; int attempts = 0; ngx_rbtree_node_t *node; - uint64_t now; - int n; - ngx_http_lua_shdict_list_node_t *lnode; - - n = lua_gettop(L); - - if (n != 1 && n != 2) { - return luaL_error(L, "expecting 1 or 2 argument(s), but saw %d", n); - } - - luaL_checktype(L, 1, LUA_TTABLE); - - zone = ngx_http_lua_shdict_get_zone(L, 1); - if (zone == NULL) { - return luaL_error(L, "bad user data for the ngx_shm_zone_t pointer"); - } - - if (n == 2) { - attempts = luaL_checkint(L, 2); - } - - ctx = zone->data; - - ngx_shmtx_lock(&ctx->shpool->mutex); - - if (ngx_queue_empty(&ctx->sh->lru_queue)) { - ngx_shmtx_unlock(&ctx->shpool->mutex); - lua_pushnumber(L, 0); - return 1; - } - - tp = ngx_timeofday(); - - now = (uint64_t) tp->sec * 1000 + tp->msec; - - q = ngx_queue_last(&ctx->sh->lru_queue); - - while (q != ngx_queue_sentinel(&ctx->sh->lru_queue)) { - prev = ngx_queue_prev(q); - - sd = ngx_queue_data(q, ngx_http_lua_shdict_node_t, queue); - - if (sd->expires != 0 && sd->expires <= now) { - - if (sd->value_type == SHDICT_TLIST) { - list_queue = ngx_http_lua_shdict_get_list_head(sd, sd->key_len); - - for (lq = ngx_queue_head(list_queue); - lq != ngx_queue_sentinel(list_queue); - lq = ngx_queue_next(lq)) - { - lnode = ngx_queue_data(lq, ngx_http_lua_shdict_list_node_t, - queue); - - ngx_slab_free_locked(ctx->shpool, lnode); - } - } - - ngx_queue_remove(q); - - node = (ngx_rbtree_node_t *) - ((u_char *) sd - offsetof(ngx_rbtree_node_t, color)); - - ngx_rbtree_delete(&ctx->sh->rbtree, node); - ngx_slab_free_locked(ctx->shpool, node); - freed++; - - if (attempts && freed == attempts) { - break; - } - } - - q = prev; - } - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - lua_pushnumber(L, freed); - return 1; -} - - -/* - * This trades CPU for memory. This is potentially slow. O(2n) - */ - -static int -ngx_http_lua_shdict_get_keys(lua_State *L) -{ - ngx_queue_t *q, *prev; - ngx_http_lua_shdict_node_t *sd; - ngx_http_lua_shdict_ctx_t *ctx; - ngx_shm_zone_t *zone; - ngx_time_t *tp; - int total = 0; - int attempts = 1024; - uint64_t now; - int n; - - n = lua_gettop(L); - - if (n != 1 && n != 2) { - return luaL_error(L, "expecting 1 or 2 argument(s), " - "but saw %d", n); - } - - luaL_checktype(L, 1, LUA_TTABLE); - - zone = ngx_http_lua_shdict_get_zone(L, 1); - if (zone == NULL) { - return luaL_error(L, "bad user data for the ngx_shm_zone_t pointer"); - } - - if (n == 2) { - attempts = luaL_checkint(L, 2); - } - - ctx = zone->data; - - ngx_shmtx_lock(&ctx->shpool->mutex); - - if (ngx_queue_empty(&ctx->sh->lru_queue)) { - ngx_shmtx_unlock(&ctx->shpool->mutex); - lua_createtable(L, 0, 0); - return 1; - } - - tp = ngx_timeofday(); - - now = (uint64_t) tp->sec * 1000 + tp->msec; - - /* first run through: get total number of elements we need to allocate */ - - q = ngx_queue_last(&ctx->sh->lru_queue); - - while (q != ngx_queue_sentinel(&ctx->sh->lru_queue)) { - prev = ngx_queue_prev(q); - - sd = ngx_queue_data(q, ngx_http_lua_shdict_node_t, queue); - - if (sd->expires == 0 || sd->expires > now) { - total++; - if (attempts && total == attempts) { - break; - } - } - - q = prev; - } - - lua_createtable(L, total, 0); - - /* second run through: add keys to table */ - - total = 0; - q = ngx_queue_last(&ctx->sh->lru_queue); - - while (q != ngx_queue_sentinel(&ctx->sh->lru_queue)) { - prev = ngx_queue_prev(q); - - sd = ngx_queue_data(q, ngx_http_lua_shdict_node_t, queue); - - if (sd->expires == 0 || sd->expires > now) { - lua_pushlstring(L, (char *) sd->data, sd->key_len); - lua_rawseti(L, -2, ++total); - if (attempts && total == attempts) { - break; - } - } - - q = prev; - } - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - /* table is at top of stack */ - return 1; -} - - -static int -ngx_http_lua_shdict_add(lua_State *L) -{ - return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_ADD); -} - - -static int -ngx_http_lua_shdict_safe_add(lua_State *L) -{ - return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_ADD - |NGX_HTTP_LUA_SHDICT_SAFE_STORE); -} - - -static int -ngx_http_lua_shdict_replace(lua_State *L) -{ - return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_REPLACE); -} - - -static int -ngx_http_lua_shdict_set(lua_State *L) -{ - return ngx_http_lua_shdict_set_helper(L, 0); -} - - -static int -ngx_http_lua_shdict_safe_set(lua_State *L) -{ - return ngx_http_lua_shdict_set_helper(L, NGX_HTTP_LUA_SHDICT_SAFE_STORE); -} - - -static int -ngx_http_lua_shdict_set_helper(lua_State *L, int flags) -{ - int i, n; - ngx_str_t key; - uint32_t hash; - ngx_int_t rc; - ngx_http_lua_shdict_ctx_t *ctx; - ngx_http_lua_shdict_node_t *sd; - ngx_str_t value; - int value_type; - double num; - u_char c; - lua_Number exptime = 0; - u_char *p; - ngx_rbtree_node_t *node; - ngx_time_t *tp; - ngx_shm_zone_t *zone; - int forcible = 0; - /* indicates whether to foricibly override other - * valid entries */ - int32_t user_flags = 0; - ngx_queue_t *queue, *q; - - n = lua_gettop(L); - - if (n != 3 && n != 4 && n != 5) { - return luaL_error(L, "expecting 3, 4 or 5 arguments, " - "but only seen %d", n); - } - - if (lua_type(L, 1) != LUA_TTABLE) { - return luaL_error(L, "bad \"zone\" argument"); - } - - zone = ngx_http_lua_shdict_get_zone(L, 1); - if (zone == NULL) { - return luaL_error(L, "bad \"zone\" argument"); - } - - ctx = zone->data; - - if (lua_isnil(L, 2)) { - lua_pushnil(L); - lua_pushliteral(L, "nil key"); - return 2; - } - - key.data = (u_char *) luaL_checklstring(L, 2, &key.len); - - if (key.len == 0) { - lua_pushnil(L); - lua_pushliteral(L, "empty key"); - return 2; - } - - if (key.len > 65535) { - lua_pushnil(L); - lua_pushliteral(L, "key too long"); - return 2; - } - - hash = ngx_crc32_short(key.data, key.len); - - value_type = lua_type(L, 3); - - switch (value_type) { - - case SHDICT_TSTRING: - value.data = (u_char *) lua_tolstring(L, 3, &value.len); - break; - - case SHDICT_TNUMBER: - value.len = sizeof(double); - num = lua_tonumber(L, 3); - value.data = (u_char *) # - break; - - case SHDICT_TBOOLEAN: - value.len = sizeof(u_char); - c = lua_toboolean(L, 3) ? 1 : 0; - value.data = &c; - break; - - case LUA_TNIL: - if (flags & (NGX_HTTP_LUA_SHDICT_ADD|NGX_HTTP_LUA_SHDICT_REPLACE)) { - lua_pushnil(L); - lua_pushliteral(L, "attempt to add or replace nil values"); - return 2; - } - - ngx_str_null(&value); - break; - - default: - lua_pushnil(L); - lua_pushliteral(L, "bad value type"); - return 2; - } - - if (n >= 4) { - exptime = luaL_checknumber(L, 4); - if (exptime < 0) { - return luaL_error(L, "bad \"exptime\" argument"); - } - } - - if (n == 5) { - user_flags = (uint32_t) luaL_checkinteger(L, 5); - } - - ngx_shmtx_lock(&ctx->shpool->mutex); - -#if 1 - ngx_http_lua_shdict_expire(ctx, 1); -#endif - - rc = ngx_http_lua_shdict_lookup(zone, hash, key.data, key.len, &sd); - - dd("shdict lookup returned %d", (int) rc); - - if (flags & NGX_HTTP_LUA_SHDICT_REPLACE) { - - if (rc == NGX_DECLINED || rc == NGX_DONE) { - ngx_shmtx_unlock(&ctx->shpool->mutex); - - lua_pushboolean(L, 0); - lua_pushliteral(L, "not found"); - lua_pushboolean(L, forcible); - return 3; - } - - /* rc == NGX_OK */ - - goto replace; - } - - if (flags & NGX_HTTP_LUA_SHDICT_ADD) { - - if (rc == NGX_OK) { - ngx_shmtx_unlock(&ctx->shpool->mutex); - - lua_pushboolean(L, 0); - lua_pushliteral(L, "exists"); - lua_pushboolean(L, forcible); - return 3; - } - - if (rc == NGX_DONE) { - /* exists but expired */ - - dd("go to replace"); - goto replace; - } - - /* rc == NGX_DECLINED */ - - dd("go to insert"); - goto insert; - } - - if (rc == NGX_OK || rc == NGX_DONE) { - - if (value_type == LUA_TNIL) { - goto remove; - } - -replace: - - if (value.data - && value.len == (size_t) sd->value_len - && sd->value_type != SHDICT_TLIST) - { - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0, - "lua shared dict set: found old entry and value " - "size matched, reusing it"); - - ngx_queue_remove(&sd->queue); - ngx_queue_insert_head(&ctx->sh->lru_queue, &sd->queue); - - sd->key_len = (u_short) key.len; - - if (exptime > 0) { - tp = ngx_timeofday(); - sd->expires = (uint64_t) tp->sec * 1000 + tp->msec - + (uint64_t) (exptime * 1000); - - } else { - sd->expires = 0; - } - - sd->user_flags = user_flags; - - sd->value_len = (uint32_t) value.len; - - dd("setting value type to %d", value_type); - - sd->value_type = (uint8_t) value_type; - - p = ngx_copy(sd->data, key.data, key.len); - ngx_memcpy(p, value.data, value.len); - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - lua_pushboolean(L, 1); - lua_pushnil(L); - lua_pushboolean(L, forcible); - return 3; - } - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0, - "lua shared dict set: found old entry but value size " - "NOT matched, removing it first"); - -remove: - - if (sd->value_type == SHDICT_TLIST) { - queue = ngx_http_lua_shdict_get_list_head(sd, key.len); - - for (q = ngx_queue_head(queue); - q != ngx_queue_sentinel(queue); - q = ngx_queue_next(q)) - { - p = (u_char *) ngx_queue_data(q, - ngx_http_lua_shdict_list_node_t, - queue); - - ngx_slab_free_locked(ctx->shpool, p); - } - } - - ngx_queue_remove(&sd->queue); - - node = (ngx_rbtree_node_t *) - ((u_char *) sd - offsetof(ngx_rbtree_node_t, color)); - - ngx_rbtree_delete(&ctx->sh->rbtree, node); - - ngx_slab_free_locked(ctx->shpool, node); - - } - -insert: - - /* rc == NGX_DECLINED or value size unmatch */ - - if (value.data == NULL) { - ngx_shmtx_unlock(&ctx->shpool->mutex); - - lua_pushboolean(L, 1); - lua_pushnil(L); - lua_pushboolean(L, 0); - return 3; - } - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0, - "lua shared dict set: creating a new entry"); - - n = offsetof(ngx_rbtree_node_t, color) - + offsetof(ngx_http_lua_shdict_node_t, data) - + key.len - + value.len; - - dd("overhead = %d", (int) (offsetof(ngx_rbtree_node_t, color) - + offsetof(ngx_http_lua_shdict_node_t, data))); - - node = ngx_slab_alloc_locked(ctx->shpool, n); - - if (node == NULL) { - - if (flags & NGX_HTTP_LUA_SHDICT_SAFE_STORE) { - ngx_shmtx_unlock(&ctx->shpool->mutex); - - lua_pushboolean(L, 0); - lua_pushliteral(L, "no memory"); - return 2; - } - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, - "lua shared dict set: overriding non-expired items " - "due to memory shortage for entry \"%V\"", &key); - - for (i = 0; i < 30; i++) { - if (ngx_http_lua_shdict_expire(ctx, 0) == 0) { - break; - } - - forcible = 1; - - node = ngx_slab_alloc_locked(ctx->shpool, n); - if (node != NULL) { - goto allocated; - } - } - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - lua_pushboolean(L, 0); - lua_pushliteral(L, "no memory"); - lua_pushboolean(L, forcible); - return 3; - } - -allocated: - - sd = (ngx_http_lua_shdict_node_t *) &node->color; - - node->key = hash; - sd->key_len = (u_short) key.len; - - if (exptime > 0) { - tp = ngx_timeofday(); - sd->expires = (uint64_t) tp->sec * 1000 + tp->msec - + (uint64_t) (exptime * 1000); - - } else { - sd->expires = 0; - } - - sd->user_flags = user_flags; - - sd->value_len = (uint32_t) value.len; - - dd("setting value type to %d", value_type); - - sd->value_type = (uint8_t) value_type; - - p = ngx_copy(sd->data, key.data, key.len); - ngx_memcpy(p, value.data, value.len); - - ngx_rbtree_insert(&ctx->sh->rbtree, node); - - ngx_queue_insert_head(&ctx->sh->lru_queue, &sd->queue); - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - lua_pushboolean(L, 1); - lua_pushnil(L); - lua_pushboolean(L, forcible); - return 3; -} - - -static int -ngx_http_lua_shdict_incr(lua_State *L) -{ - int i, n; - ngx_str_t key; - uint32_t hash; - ngx_int_t rc; - ngx_http_lua_shdict_ctx_t *ctx; - ngx_http_lua_shdict_node_t *sd; - double num; - double init = 0; - u_char *p; - ngx_shm_zone_t *zone; - double value; - ngx_rbtree_node_t *node; - /* indicates whether to foricibly override other - * valid entries */ - int forcible = 0; - ngx_queue_t *queue, *q; - - n = lua_gettop(L); - - if (n != 3 && n != 4) { - return luaL_error(L, "expecting 3 or 4 arguments, but only seen %d", n); - } - - if (lua_type(L, 1) != LUA_TTABLE) { - return luaL_error(L, "bad \"zone\" argument"); - } - - zone = ngx_http_lua_shdict_get_zone(L, 1); - if (zone == NULL) { - return luaL_error(L, "bad user data for the ngx_shm_zone_t pointer"); - } - - ctx = zone->data; - - if (lua_isnil(L, 2)) { - lua_pushnil(L); - lua_pushliteral(L, "nil key"); - return 2; - } - - key.data = (u_char *) luaL_checklstring(L, 2, &key.len); - - if (key.len == 0) { - lua_pushnil(L); - lua_pushliteral(L, "empty key"); - return 2; - } - - if (key.len > 65535) { - lua_pushnil(L); - lua_pushliteral(L, "key too long"); - return 2; - } - - hash = ngx_crc32_short(key.data, key.len); - - value = luaL_checknumber(L, 3); - - if (n == 4) { - init = luaL_checknumber(L, 4); - } - - dd("looking up key %.*s in shared dict %.*s", (int) key.len, key.data, - (int) ctx->name.len, ctx->name.data); - - ngx_shmtx_lock(&ctx->shpool->mutex); - -#if 1 - ngx_http_lua_shdict_expire(ctx, 1); -#endif - - rc = ngx_http_lua_shdict_lookup(zone, hash, key.data, key.len, &sd); - - dd("shdict lookup returned %d", (int) rc); - - if (rc == NGX_DECLINED || rc == NGX_DONE) { - - if (n == 3) { - ngx_shmtx_unlock(&ctx->shpool->mutex); - - lua_pushnil(L); - lua_pushliteral(L, "not found"); - return 2; - } - - /* add value */ - num = value + init; - - if (rc == NGX_DONE) { - - /* found an expired item */ + uint64_t now; + int n; + ngx_http_lua_shdict_list_node_t *lnode; - if ((size_t) sd->value_len == sizeof(double) - && sd->value_type != SHDICT_TLIST) - { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0, - "lua shared dict incr: found old entry and " - "value size matched, reusing it"); + n = lua_gettop(L); - ngx_queue_remove(&sd->queue); - ngx_queue_insert_head(&ctx->sh->lru_queue, &sd->queue); + if (n != 1 && n != 2) { + return luaL_error(L, "expecting 1 or 2 argument(s), but saw %d", n); + } - dd("go to setvalue"); - goto setvalue; - } + luaL_checktype(L, 1, LUA_TTABLE); - dd("go to remove"); - goto remove; - } + zone = ngx_http_lua_shdict_get_zone(L, 1); + if (zone == NULL) { + return luaL_error(L, "bad user data for the ngx_shm_zone_t pointer"); + } - dd("go to insert"); - goto insert; + if (n == 2) { + attempts = luaL_checkint(L, 2); } - /* rc == NGX_OK */ + ctx = zone->data; - if (sd->value_type != SHDICT_TNUMBER || sd->value_len != sizeof(double)) { - ngx_shmtx_unlock(&ctx->shpool->mutex); + ngx_shmtx_lock(&ctx->shpool->mutex); - lua_pushnil(L); - lua_pushliteral(L, "not a number"); - return 2; + if (ngx_queue_empty(&ctx->sh->lru_queue)) { + ngx_shmtx_unlock(&ctx->shpool->mutex); + lua_pushnumber(L, 0); + return 1; } - ngx_queue_remove(&sd->queue); - ngx_queue_insert_head(&ctx->sh->lru_queue, &sd->queue); + tp = ngx_timeofday(); - dd("setting value type to %d", (int) sd->value_type); + now = (uint64_t) tp->sec * 1000 + tp->msec; - p = sd->data + key.len; + q = ngx_queue_last(&ctx->sh->lru_queue); - ngx_memcpy(&num, p, sizeof(double)); - num += value; + while (q != ngx_queue_sentinel(&ctx->sh->lru_queue)) { + prev = ngx_queue_prev(q); - ngx_memcpy(p, (double *) &num, sizeof(double)); + sd = ngx_queue_data(q, ngx_http_lua_shdict_node_t, queue); - ngx_shmtx_unlock(&ctx->shpool->mutex); + if (sd->expires != 0 && sd->expires <= now) { - lua_pushnumber(L, num); - lua_pushnil(L); - return 2; + if (sd->value_type == SHDICT_TLIST) { + list_queue = ngx_http_lua_shdict_get_list_head(sd, sd->key_len); -remove: + for (lq = ngx_queue_head(list_queue); + lq != ngx_queue_sentinel(list_queue); + lq = ngx_queue_next(lq)) + { + lnode = ngx_queue_data(lq, ngx_http_lua_shdict_list_node_t, + queue); - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0, - "lua shared dict incr: found old entry but value size " - "NOT matched, removing it first"); + ngx_slab_free_locked(ctx->shpool, lnode); + } + } - if (sd->value_type == SHDICT_TLIST) { - queue = ngx_http_lua_shdict_get_list_head(sd, key.len); + ngx_queue_remove(q); - for (q = ngx_queue_head(queue); - q != ngx_queue_sentinel(queue); - q = ngx_queue_next(q)) - { - p = (u_char *) ngx_queue_data(q, - ngx_http_lua_shdict_list_node_t, - queue); + node = (ngx_rbtree_node_t *) + ((u_char *) sd - offsetof(ngx_rbtree_node_t, color)); - ngx_slab_free_locked(ctx->shpool, p); + ngx_rbtree_delete(&ctx->sh->rbtree, node); + ngx_slab_free_locked(ctx->shpool, node); + freed++; + + if (attempts && freed == attempts) { + break; + } } - } - ngx_queue_remove(&sd->queue); + q = prev; + } - node = (ngx_rbtree_node_t *) - ((u_char *) sd - offsetof(ngx_rbtree_node_t, color)); + ngx_shmtx_unlock(&ctx->shpool->mutex); - ngx_rbtree_delete(&ctx->sh->rbtree, node); + lua_pushnumber(L, freed); + return 1; +} - ngx_slab_free_locked(ctx->shpool, node); -insert: +/* + * This trades CPU for memory. This is potentially slow. O(2n) + */ - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0, - "lua shared dict incr: creating a new entry"); +static int +ngx_http_lua_shdict_get_keys(lua_State *L) +{ + ngx_queue_t *q, *prev; + ngx_http_lua_shdict_node_t *sd; + ngx_http_lua_shdict_ctx_t *ctx; + ngx_shm_zone_t *zone; + ngx_time_t *tp; + int total = 0; + int attempts = 1024; + uint64_t now; + int n; - n = offsetof(ngx_rbtree_node_t, color) - + offsetof(ngx_http_lua_shdict_node_t, data) - + key.len - + sizeof(double); + n = lua_gettop(L); - node = ngx_slab_alloc_locked(ctx->shpool, n); + if (n != 1 && n != 2) { + return luaL_error(L, "expecting 1 or 2 argument(s), " + "but saw %d", n); + } - if (node == NULL) { + luaL_checktype(L, 1, LUA_TTABLE); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0, - "lua shared dict incr: overriding non-expired items " - "due to memory shortage for entry \"%V\"", &key); + zone = ngx_http_lua_shdict_get_zone(L, 1); + if (zone == NULL) { + return luaL_error(L, "bad user data for the ngx_shm_zone_t pointer"); + } - for (i = 0; i < 30; i++) { - if (ngx_http_lua_shdict_expire(ctx, 0) == 0) { - break; - } + if (n == 2) { + attempts = luaL_checkint(L, 2); + } - forcible = 1; + ctx = zone->data; - node = ngx_slab_alloc_locked(ctx->shpool, n); - if (node != NULL) { - goto allocated; - } - } + ngx_shmtx_lock(&ctx->shpool->mutex); + if (ngx_queue_empty(&ctx->sh->lru_queue)) { ngx_shmtx_unlock(&ctx->shpool->mutex); - - lua_pushboolean(L, 0); - lua_pushliteral(L, "no memory"); - lua_pushboolean(L, forcible); - return 3; + lua_createtable(L, 0, 0); + return 1; } -allocated: + tp = ngx_timeofday(); - sd = (ngx_http_lua_shdict_node_t *) &node->color; + now = (uint64_t) tp->sec * 1000 + tp->msec; - node->key = hash; + /* first run through: get total number of elements we need to allocate */ - sd->key_len = (u_short) key.len; + q = ngx_queue_last(&ctx->sh->lru_queue); - sd->value_len = (uint32_t) sizeof(double); + while (q != ngx_queue_sentinel(&ctx->sh->lru_queue)) { + prev = ngx_queue_prev(q); - ngx_rbtree_insert(&ctx->sh->rbtree, node); + sd = ngx_queue_data(q, ngx_http_lua_shdict_node_t, queue); - ngx_queue_insert_head(&ctx->sh->lru_queue, &sd->queue); + if (sd->expires == 0 || sd->expires > now) { + total++; + if (attempts && total == attempts) { + break; + } + } -setvalue: + q = prev; + } - sd->user_flags = 0; + lua_createtable(L, total, 0); - sd->expires = 0; + /* second run through: add keys to table */ - dd("setting value type to %d", LUA_TNUMBER); + total = 0; + q = ngx_queue_last(&ctx->sh->lru_queue); - sd->value_type = (uint8_t) LUA_TNUMBER; + while (q != ngx_queue_sentinel(&ctx->sh->lru_queue)) { + prev = ngx_queue_prev(q); - p = ngx_copy(sd->data, key.data, key.len); - ngx_memcpy(p, (double *) &num, sizeof(double)); + sd = ngx_queue_data(q, ngx_http_lua_shdict_node_t, queue); + + if (sd->expires == 0 || sd->expires > now) { + lua_pushlstring(L, (char *) sd->data, sd->key_len); + lua_rawseti(L, -2, ++total); + if (attempts && total == attempts) { + break; + } + } + + q = prev; + } ngx_shmtx_unlock(&ctx->shpool->mutex); - lua_pushnumber(L, num); - lua_pushnil(L); - lua_pushboolean(L, forcible); - return 3; + /* table is at top of stack */ + return 1; } @@ -1568,7 +654,7 @@ ngx_http_lua_shared_dict_get(ngx_shm_zone_t *zone, u_char *key_data, return NGX_ERROR; } - ngx_memcpy(&value->value.b, data, len); + ngx_memcpy(&value->value.n, data, len); break; case SHDICT_TBOOLEAN: @@ -1729,7 +815,7 @@ ngx_http_lua_shdict_push_helper(lua_State *L, int flags) "type matched, reusing it"); sd->expires = 0; - + sd->value_len = 0; /* free list nodes */ queue = ngx_http_lua_shdict_get_list_head(sd, key.len); @@ -1857,7 +943,7 @@ ngx_http_lua_shdict_push_helper(lua_State *L, int flags) ngx_shmtx_unlock(&ctx->shpool->mutex); - lua_pushboolean(L, 0); + lua_pushnil(L); lua_pushliteral(L, "no memory"); return 2; } @@ -2208,11 +1294,21 @@ ngx_http_lua_find_zone(u_char *name_data, size_t name_len) } -#ifndef NGX_LUA_NO_FFI_API +ngx_shm_zone_t * +ngx_http_lua_ffi_shdict_udata_to_zone(void *zone_udata) +{ + if (zone_udata == NULL) { + return NULL; + } + + return *(ngx_shm_zone_t **) zone_udata; +} + + int ngx_http_lua_ffi_shdict_store(ngx_shm_zone_t *zone, int op, u_char *key, size_t key_len, int value_type, u_char *str_value_buf, - size_t str_value_len, double num_value, int exptime, int user_flags, + size_t str_value_len, double num_value, long exptime, int user_flags, char **errmsg, int *forcible) { int i, n; @@ -2225,11 +1321,7 @@ ngx_http_lua_ffi_shdict_store(ngx_shm_zone_t *zone, int op, u_char *key, ngx_http_lua_shdict_ctx_t *ctx; ngx_http_lua_shdict_node_t *sd; - if (zone == NULL) { - return NGX_ERROR; - } - - dd("exptime: %d", exptime); + dd("exptime: %ld", exptime); ctx = zone->data; @@ -2334,8 +1426,6 @@ ngx_http_lua_ffi_shdict_store(ngx_shm_zone_t *zone, int op, u_char *key, ngx_queue_remove(&sd->queue); ngx_queue_insert_head(&ctx->sh->lru_queue, &sd->queue); - sd->key_len = (u_short) key_len; - if (exptime > 0) { tp = ngx_timeofday(); sd->expires = (uint64_t) tp->sec * 1000 + tp->msec @@ -2347,14 +1437,11 @@ ngx_http_lua_ffi_shdict_store(ngx_shm_zone_t *zone, int op, u_char *key, sd->user_flags = user_flags; - sd->value_len = (uint32_t) str_value_len; - dd("setting value type to %d", value_type); sd->value_type = (uint8_t) value_type; - p = ngx_copy(sd->data, key, key_len); - ngx_memcpy(p, str_value_buf, str_value_len); + ngx_memcpy(sd->data + key_len, str_value_buf, str_value_len); ngx_shmtx_unlock(&ctx->shpool->mutex); @@ -2490,10 +1577,6 @@ ngx_http_lua_ffi_shdict_get(ngx_shm_zone_t *zone, u_char *key, ngx_http_lua_shdict_node_t *sd; ngx_str_t value; - if (zone == NULL) { - return NGX_ERROR; - } - *err = NULL; ctx = zone->data; @@ -2623,11 +1706,12 @@ ngx_http_lua_ffi_shdict_get(ngx_shm_zone_t *zone, u_char *key, int ngx_http_lua_ffi_shdict_incr(ngx_shm_zone_t *zone, u_char *key, size_t key_len, double *value, char **err, int has_init, double init, - int *forcible) + long init_ttl, int *forcible) { int i, n; uint32_t hash; ngx_int_t rc; + ngx_time_t *tp = NULL; ngx_http_lua_shdict_ctx_t *ctx; ngx_http_lua_shdict_node_t *sd; double num; @@ -2635,8 +1719,8 @@ ngx_http_lua_ffi_shdict_incr(ngx_shm_zone_t *zone, u_char *key, u_char *p; ngx_queue_t *queue, *q; - if (zone == NULL) { - return NGX_ERROR; + if (init_ttl > 0) { + tp = ngx_timeofday(); } ctx = zone->data; @@ -2802,7 +1886,13 @@ ngx_http_lua_ffi_shdict_incr(ngx_shm_zone_t *zone, u_char *key, sd->user_flags = 0; - sd->expires = 0; + if (init_ttl > 0) { + sd->expires = (uint64_t) tp->sec * 1000 + tp->msec + + (uint64_t) init_ttl; + + } else { + sd->expires = 0; + } dd("setting value type to %d", LUA_TNUMBER); @@ -2892,7 +1982,7 @@ ngx_http_lua_shdict_peek(ngx_shm_zone_t *shm_zone, ngx_uint_t hash, } -int +long ngx_http_lua_ffi_shdict_get_ttl(ngx_shm_zone_t *zone, u_char *key, size_t key_len) { @@ -2904,10 +1994,6 @@ ngx_http_lua_ffi_shdict_get_ttl(ngx_shm_zone_t *zone, u_char *key, ngx_http_lua_shdict_ctx_t *ctx; ngx_http_lua_shdict_node_t *sd; - if (zone == NULL) { - return NGX_ERROR; - } - ctx = zone->data; hash = ngx_crc32_short(key, key_len); @@ -2940,7 +2026,7 @@ ngx_http_lua_ffi_shdict_get_ttl(ngx_shm_zone_t *zone, u_char *key, int ngx_http_lua_ffi_shdict_set_expire(ngx_shm_zone_t *zone, u_char *key, - size_t key_len, int exptime) + size_t key_len, long exptime) { uint32_t hash; ngx_int_t rc; @@ -2948,10 +2034,6 @@ ngx_http_lua_ffi_shdict_set_expire(ngx_shm_zone_t *zone, u_char *key, ngx_http_lua_shdict_ctx_t *ctx; ngx_http_lua_shdict_node_t *sd; - if (zone == NULL) { - return NGX_ERROR; - } - if (exptime > 0) { tp = ngx_timeofday(); } @@ -2992,7 +2074,7 @@ ngx_http_lua_ffi_shdict_capacity(ngx_shm_zone_t *zone) } -# if nginx_version >= 1011007 +#if (nginx_version >= 1011007) size_t ngx_http_lua_ffi_shdict_free_space(ngx_shm_zone_t *zone) { @@ -3007,10 +2089,44 @@ ngx_http_lua_ffi_shdict_free_space(ngx_shm_zone_t *zone) return bytes; } -# endif /* nginx_version >= 1011007 */ +#endif + + +#if (NGX_DARWIN) +int +ngx_http_lua_ffi_shdict_get_macos(ngx_http_lua_shdict_get_params_t *p) +{ + return ngx_http_lua_ffi_shdict_get(p->zone, + (u_char *) p->key, p->key_len, + p->value_type, p->str_value_buf, + p->str_value_len, p->num_value, + p->user_flags, p->get_stale, + p->is_stale, p->errmsg); +} + + +int +ngx_http_lua_ffi_shdict_store_macos(ngx_http_lua_shdict_store_params_t *p) +{ + return ngx_http_lua_ffi_shdict_store(p->zone, p->op, + (u_char *) p->key, p->key_len, + p->value_type, + (u_char *) p->str_value_buf, + p->str_value_len, p->num_value, + p->exptime, p->user_flags, + p->errmsg, p->forcible); +} -#endif /* NGX_LUA_NO_FFI_API */ +int +ngx_http_lua_ffi_shdict_incr_macos(ngx_http_lua_shdict_incr_params_t *p) +{ + return ngx_http_lua_ffi_shdict_incr(p->zone, (u_char *) p->key, p->key_len, + p->num_value, p->errmsg, + p->has_init, p->init, p->init_ttl, + p->forcible); +} +#endif /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_shdict.h b/src/ngx_http_lua_shdict.h index 90a0099f5d..67dd4d4eb0 100644 --- a/src/ngx_http_lua_shdict.h +++ b/src/ngx_http_lua_shdict.h @@ -55,6 +55,52 @@ typedef struct { } ngx_http_lua_shm_zone_ctx_t; +#if (NGX_DARWIN) +typedef struct { + void *zone; + const unsigned char *key; + size_t key_len; + int *value_type; + unsigned char **str_value_buf; + size_t *str_value_len; + double *num_value; + int *user_flags; + int get_stale; + int *is_stale; + char **errmsg; +} ngx_http_lua_shdict_get_params_t; + + +typedef struct { + void *zone; + int op; + const unsigned char *key; + size_t key_len; + int value_type; + const unsigned char *str_value_buf; + size_t str_value_len; + double num_value; + long exptime; + int user_flags; + char **errmsg; + int *forcible; +} ngx_http_lua_shdict_store_params_t; + + +typedef struct { + void *zone; + const unsigned char *key; + size_t key_len; + double *num_value; + char **errmsg; + int has_init; + double init; + long init_ttl; + int *forcible; +} ngx_http_lua_shdict_incr_params_t; +#endif + + ngx_int_t ngx_http_lua_shdict_init_zone(ngx_shm_zone_t *shm_zone, void *data); void ngx_http_lua_shdict_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); diff --git a/src/ngx_http_lua_sleep.c b/src/ngx_http_lua_sleep.c index 09ea0f6d40..770ce9beac 100644 --- a/src/ngx_http_lua_sleep.c +++ b/src/ngx_http_lua_sleep.c @@ -52,12 +52,7 @@ ngx_http_lua_ngx_sleep(lua_State *L) return luaL_error(L, "no request ctx found"); } - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE - | NGX_HTTP_LUA_CONTEXT_ACCESS - | NGX_HTTP_LUA_CONTEXT_CONTENT - | NGX_HTTP_LUA_CONTEXT_TIMER - | NGX_HTTP_LUA_CONTEXT_SSL_CERT - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH); + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); coctx = ctx->cur_co_ctx; if (coctx == NULL) { @@ -164,11 +159,20 @@ ngx_http_lua_sleep_cleanup(void *data) } #ifdef HAVE_POSTED_DELAYED_EVENTS_PATCH +#if (nginx_version >= 1007005) if (coctx->sleep.posted) { +#else + if (coctx->sleep.prev) { +#endif ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "lua clean up the posted event for pending ngx.sleep"); - ngx_delete_posted_event(&coctx->sleep); + /* + * We need the extra parentheses around the argument + * of ngx_delete_posted_event() just to work around macro issues in + * nginx cores older than 1.7.5 (exclusive). + */ + ngx_delete_posted_event((&coctx->sleep)); } #endif } diff --git a/src/ngx_http_lua_socket_tcp.c b/src/ngx_http_lua_socket_tcp.c index c34a24e5c3..85cf49069e 100644 --- a/src/ngx_http_lua_socket_tcp.c +++ b/src/ngx_http_lua_socket_tcp.c @@ -11,6 +11,7 @@ #include "ngx_http_lua_socket_tcp.h" +#include "ngx_http_lua_input_filters.h" #include "ngx_http_lua_util.h" #include "ngx_http_lua_uthread.h" #include "ngx_http_lua_output.h" @@ -19,14 +20,17 @@ static int ngx_http_lua_socket_tcp(lua_State *L); +static int ngx_http_lua_socket_tcp_bind(lua_State *L); static int ngx_http_lua_socket_tcp_connect(lua_State *L); #if (NGX_HTTP_SSL) -static int ngx_http_lua_socket_tcp_sslhandshake(lua_State *L); +static void ngx_http_lua_ssl_handshake_handler(ngx_connection_t *c); +static int ngx_http_lua_ssl_handshake_retval_handler(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L); #endif static int ngx_http_lua_socket_tcp_receive(lua_State *L); +static int ngx_http_lua_socket_tcp_receiveany(lua_State *L); static int ngx_http_lua_socket_tcp_send(lua_State *L); static int ngx_http_lua_socket_tcp_close(lua_State *L); -static int ngx_http_lua_socket_tcp_setoption(lua_State *L); static int ngx_http_lua_socket_tcp_settimeout(lua_State *L); static int ngx_http_lua_socket_tcp_settimeouts(lua_State *L); static void ngx_http_lua_socket_tcp_handler(ngx_event_t *ev); @@ -69,6 +73,10 @@ static int ngx_http_lua_socket_tcp_conn_retval_handler(ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L); static void ngx_http_lua_socket_dummy_handler(ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u); +static int ngx_http_lua_socket_tcp_receive_helper(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L); +static void ngx_http_lua_socket_tcp_read_prepare(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, void *data, lua_State *L); static ngx_int_t ngx_http_lua_socket_tcp_read(ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u); static int ngx_http_lua_socket_tcp_receive_retval_handler(ngx_http_request_t *r, @@ -86,6 +94,7 @@ static int ngx_http_lua_socket_write_error_retval_handler(ngx_http_request_t *r, static ngx_int_t ngx_http_lua_socket_read_all(void *data, ssize_t bytes); static ngx_int_t ngx_http_lua_socket_read_until(void *data, ssize_t bytes); static ngx_int_t ngx_http_lua_socket_read_chunk(void *data, ssize_t bytes); +static ngx_int_t ngx_http_lua_socket_read_any(void *data, ssize_t bytes); static int ngx_http_lua_socket_tcp_receiveuntil(lua_State *L); static int ngx_http_lua_socket_receiveuntil_iterator(lua_State *L); static ngx_int_t ngx_http_lua_socket_compile_pattern(u_char *data, size_t len, @@ -95,15 +104,31 @@ static int ngx_http_lua_req_socket(lua_State *L); static void ngx_http_lua_req_socket_rev_handler(ngx_http_request_t *r); static int ngx_http_lua_socket_tcp_getreusedtimes(lua_State *L); static int ngx_http_lua_socket_tcp_setkeepalive(lua_State *L); +static void ngx_http_lua_socket_tcp_create_socket_pool(lua_State *L, + ngx_http_request_t *r, ngx_str_t key, ngx_int_t pool_size, + ngx_int_t backlog, ngx_http_lua_socket_pool_t **spool); static ngx_int_t ngx_http_lua_get_keepalive_peer(ngx_http_request_t *r, - lua_State *L, int key_index, ngx_http_lua_socket_tcp_upstream_t *u); static void ngx_http_lua_socket_keepalive_dummy_handler(ngx_event_t *ev); +static int ngx_http_lua_socket_tcp_connect_helper(lua_State *L, + ngx_http_lua_socket_tcp_upstream_t *u, ngx_http_request_t *r, + ngx_http_lua_ctx_t *ctx, u_char *host_ref, size_t host_len, in_port_t port, + unsigned resuming); +static void ngx_http_lua_socket_tcp_conn_op_timeout_handler( + ngx_event_t *ev); +static int ngx_http_lua_socket_tcp_conn_op_timeout_retval_handler( + ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L); +static void ngx_http_lua_socket_tcp_resume_conn_op( + ngx_http_lua_socket_pool_t *spool); +static void ngx_http_lua_socket_tcp_conn_op_ctx_cleanup(void *data); +static void ngx_http_lua_socket_tcp_conn_op_resume_handler(ngx_event_t *ev); static ngx_int_t ngx_http_lua_socket_keepalive_close_handler(ngx_event_t *ev); static void ngx_http_lua_socket_keepalive_rev_handler(ngx_event_t *ev); +static int ngx_http_lua_socket_tcp_conn_op_resume_retval_handler( + ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L); static int ngx_http_lua_socket_tcp_upstream_destroy(lua_State *L); static int ngx_http_lua_socket_downstream_destroy(lua_State *L); -static ngx_int_t ngx_http_lua_socket_push_input_data(ngx_http_request_t *r, +static void ngx_http_lua_socket_push_input_data(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx, ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L); static ngx_int_t ngx_http_lua_socket_add_pending_data(ngx_http_request_t *r, @@ -113,11 +138,13 @@ static ngx_int_t ngx_http_lua_socket_add_input_buffer(ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u); static ngx_int_t ngx_http_lua_socket_insert_buffer(ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u, u_char *pat, size_t prefix); +static ngx_int_t ngx_http_lua_socket_tcp_conn_op_resume(ngx_http_request_t *r); static ngx_int_t ngx_http_lua_socket_tcp_conn_resume(ngx_http_request_t *r); static ngx_int_t ngx_http_lua_socket_tcp_read_resume(ngx_http_request_t *r); static ngx_int_t ngx_http_lua_socket_tcp_write_resume(ngx_http_request_t *r); static ngx_int_t ngx_http_lua_socket_tcp_resume_helper(ngx_http_request_t *r, int socket_op); +static void ngx_http_lua_tcp_queue_conn_op_cleanup(void *data); static void ngx_http_lua_tcp_resolve_cleanup(void *data); static void ngx_http_lua_coctx_cleanup(void *data); static void ngx_http_lua_socket_free_pool(ngx_log_t *log, @@ -125,16 +152,8 @@ static void ngx_http_lua_socket_free_pool(ngx_log_t *log, static int ngx_http_lua_socket_shutdown_pool(lua_State *L); static void ngx_http_lua_socket_shutdown_pool_helper( ngx_http_lua_socket_pool_t *spool); -static void - ngx_http_lua_socket_empty_resolve_handler(ngx_resolver_ctx_t *ctx); static int ngx_http_lua_socket_prepare_error_retvals(ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L, ngx_uint_t ft_type); -#if (NGX_HTTP_SSL) -static int ngx_http_lua_ssl_handshake_retval_handler(ngx_http_request_t *r, - ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L); -static void ngx_http_lua_ssl_handshake_handler(ngx_connection_t *c); -static int ngx_http_lua_ssl_free_session(lua_State *L); -#endif static void ngx_http_lua_socket_tcp_close_connection(ngx_connection_t *c); @@ -144,13 +163,26 @@ enum { SOCKET_CONNECT_TIMEOUT_INDEX = 2, SOCKET_SEND_TIMEOUT_INDEX = 4, SOCKET_READ_TIMEOUT_INDEX = 5, + SOCKET_CLIENT_CERT_INDEX = 6 , + SOCKET_CLIENT_PKEY_INDEX = 7 , + SOCKET_BIND_INDEX = 8 /* only in upstream cosocket */ +}; + + +enum { + SOCKET_OP_CONNECT = 0x01, + SOCKET_OP_READ = 0x02, + SOCKET_OP_WRITE = 0x04, + SOCKET_OP_RESUME_CONN = 0x08, }; enum { - SOCKET_OP_CONNECT, - SOCKET_OP_READ, - SOCKET_OP_WRITE + NGX_HTTP_LUA_SOCKOPT_KEEPALIVE = 1, + NGX_HTTP_LUA_SOCKOPT_REUSEADDR, + NGX_HTTP_LUA_SOCKOPT_TCP_NODELAY, + NGX_HTTP_LUA_SOCKOPT_SNDBUF, + NGX_HTTP_LUA_SOCKOPT_RCVBUF, }; @@ -192,9 +224,13 @@ static char ngx_http_lua_upstream_udata_metatable_key; static char ngx_http_lua_downstream_udata_metatable_key; static char ngx_http_lua_pool_udata_metatable_key; static char ngx_http_lua_pattern_udata_metatable_key; -#if (NGX_HTTP_SSL) -static char ngx_http_lua_ssl_session_metatable_key; -#endif + + +#define ngx_http_lua_tcp_socket_metatable_literal_key "__tcp_cosocket_mt" +#define ngx_http_lua_tcp_req_socket_metatable_literal_key \ + "__tcp_req_cosocket_mt" +#define ngx_http_lua_tcp_raw_req_socket_metatable_literal_key \ + "__tcp_raw_req_cosocket_mt" void @@ -229,12 +265,16 @@ ngx_http_lua_inject_socket_tcp_api(ngx_log_t *log, lua_State *L) lua_setfield(L, -2, "socket"); /* {{{req socket object metatable */ - lua_pushlightuserdata(L, &ngx_http_lua_req_socket_metatable_key); - lua_createtable(L, 0 /* narr */, 5 /* nrec */); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + req_socket_metatable_key)); + lua_createtable(L, 0 /* narr */, 6 /* nrec */); lua_pushcfunction(L, ngx_http_lua_socket_tcp_receive); lua_setfield(L, -2, "receive"); + lua_pushcfunction(L, ngx_http_lua_socket_tcp_receiveany); + lua_setfield(L, -2, "receiveany"); + lua_pushcfunction(L, ngx_http_lua_socket_tcp_receiveuntil); lua_setfield(L, -2, "receiveuntil"); @@ -247,16 +287,26 @@ ngx_http_lua_inject_socket_tcp_api(ngx_log_t *log, lua_State *L) lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); + lua_rawset(L, LUA_REGISTRYINDEX); + + lua_pushliteral(L, ngx_http_lua_tcp_req_socket_metatable_literal_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + req_socket_metatable_key)); + lua_rawget(L, LUA_REGISTRYINDEX); lua_rawset(L, LUA_REGISTRYINDEX); /* }}} */ /* {{{raw req socket object metatable */ - lua_pushlightuserdata(L, &ngx_http_lua_raw_req_socket_metatable_key); - lua_createtable(L, 0 /* narr */, 6 /* nrec */); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + raw_req_socket_metatable_key)); + lua_createtable(L, 0 /* narr */, 7 /* nrec */); lua_pushcfunction(L, ngx_http_lua_socket_tcp_receive); lua_setfield(L, -2, "receive"); + lua_pushcfunction(L, ngx_http_lua_socket_tcp_receiveany); + lua_setfield(L, -2, "receiveany"); + lua_pushcfunction(L, ngx_http_lua_socket_tcp_receiveuntil); lua_setfield(L, -2, "receiveuntil"); @@ -272,26 +322,32 @@ ngx_http_lua_inject_socket_tcp_api(ngx_log_t *log, lua_State *L) lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); + lua_rawset(L, LUA_REGISTRYINDEX); + + lua_pushliteral(L, ngx_http_lua_tcp_raw_req_socket_metatable_literal_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + raw_req_socket_metatable_key)); + lua_rawget(L, LUA_REGISTRYINDEX); lua_rawset(L, LUA_REGISTRYINDEX); /* }}} */ /* {{{tcp object metatable */ - lua_pushlightuserdata(L, &ngx_http_lua_tcp_socket_metatable_key); - lua_createtable(L, 0 /* narr */, 12 /* nrec */); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + tcp_socket_metatable_key)); + lua_createtable(L, 0 /* narr */, 16 /* nrec */); + + lua_pushcfunction(L, ngx_http_lua_socket_tcp_bind); + lua_setfield(L, -2, "bind"); lua_pushcfunction(L, ngx_http_lua_socket_tcp_connect); lua_setfield(L, -2, "connect"); -#if (NGX_HTTP_SSL) - - lua_pushcfunction(L, ngx_http_lua_socket_tcp_sslhandshake); - lua_setfield(L, -2, "sslhandshake"); - -#endif - lua_pushcfunction(L, ngx_http_lua_socket_tcp_receive); lua_setfield(L, -2, "receive"); + lua_pushcfunction(L, ngx_http_lua_socket_tcp_receiveany); + lua_setfield(L, -2, "receiveany"); + lua_pushcfunction(L, ngx_http_lua_socket_tcp_receiveuntil); lua_setfield(L, -2, "receiveuntil"); @@ -301,9 +357,6 @@ ngx_http_lua_inject_socket_tcp_api(ngx_log_t *log, lua_State *L) lua_pushcfunction(L, ngx_http_lua_socket_tcp_close); lua_setfield(L, -2, "close"); - lua_pushcfunction(L, ngx_http_lua_socket_tcp_setoption); - lua_setfield(L, -2, "setoption"); - lua_pushcfunction(L, ngx_http_lua_socket_tcp_settimeout); lua_setfield(L, -2, "settimeout"); /* ngx socket mt */ @@ -319,10 +372,17 @@ ngx_http_lua_inject_socket_tcp_api(ngx_log_t *log, lua_State *L) lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); lua_rawset(L, LUA_REGISTRYINDEX); + + lua_pushliteral(L, ngx_http_lua_tcp_socket_metatable_literal_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + tcp_socket_metatable_key)); + lua_rawget(L, LUA_REGISTRYINDEX); + lua_rawset(L, LUA_REGISTRYINDEX); /* }}} */ /* {{{upstream userdata metatable */ - lua_pushlightuserdata(L, &ngx_http_lua_upstream_udata_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + upstream_udata_metatable_key)); lua_createtable(L, 0 /* narr */, 1 /* nrec */); /* metatable */ lua_pushcfunction(L, ngx_http_lua_socket_tcp_upstream_destroy); lua_setfield(L, -2, "__gc"); @@ -330,7 +390,8 @@ ngx_http_lua_inject_socket_tcp_api(ngx_log_t *log, lua_State *L) /* }}} */ /* {{{downstream userdata metatable */ - lua_pushlightuserdata(L, &ngx_http_lua_downstream_udata_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + downstream_udata_metatable_key)); lua_createtable(L, 0 /* narr */, 1 /* nrec */); /* metatable */ lua_pushcfunction(L, ngx_http_lua_socket_downstream_destroy); lua_setfield(L, -2, "__gc"); @@ -338,7 +399,8 @@ ngx_http_lua_inject_socket_tcp_api(ngx_log_t *log, lua_State *L) /* }}} */ /* {{{socket pool userdata metatable */ - lua_pushlightuserdata(L, &ngx_http_lua_pool_udata_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + pool_udata_metatable_key)); lua_createtable(L, 0, 1); /* metatable */ lua_pushcfunction(L, ngx_http_lua_socket_shutdown_pool); lua_setfield(L, -2, "__gc"); @@ -346,24 +408,13 @@ ngx_http_lua_inject_socket_tcp_api(ngx_log_t *log, lua_State *L) /* }}} */ /* {{{socket compiled pattern userdata metatable */ - lua_pushlightuserdata(L, &ngx_http_lua_pattern_udata_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + pattern_udata_metatable_key)); lua_createtable(L, 0 /* narr */, 1 /* nrec */); /* metatable */ lua_pushcfunction(L, ngx_http_lua_socket_cleanup_compiled_pattern); lua_setfield(L, -2, "__gc"); lua_rawset(L, LUA_REGISTRYINDEX); /* }}} */ - -#if (NGX_HTTP_SSL) - - /* {{{ssl session userdata metatable */ - lua_pushlightuserdata(L, &ngx_http_lua_ssl_session_metatable_key); - lua_createtable(L, 0 /* narr */, 1 /* nrec */); /* metatable */ - lua_pushcfunction(L, ngx_http_lua_ssl_free_session); - lua_setfield(L, -2, "__gc"); - lua_rawset(L, LUA_REGISTRYINDEX); - /* }}} */ - -#endif } @@ -396,15 +447,11 @@ ngx_http_lua_socket_tcp(lua_State *L) return luaL_error(L, "no ctx found"); } - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE - | NGX_HTTP_LUA_CONTEXT_ACCESS - | NGX_HTTP_LUA_CONTEXT_CONTENT - | NGX_HTTP_LUA_CONTEXT_TIMER - | NGX_HTTP_LUA_CONTEXT_SSL_CERT - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH); + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); - lua_createtable(L, 5 /* narr */, 1 /* nrec */); - lua_pushlightuserdata(L, &ngx_http_lua_tcp_socket_metatable_key); + lua_createtable(L, 7 /* narr */, 1 /* nrec */); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + tcp_socket_metatable_key)); lua_rawget(L, LUA_REGISTRYINDEX); lua_setmetatable(L, -2); @@ -414,266 +461,243 @@ ngx_http_lua_socket_tcp(lua_State *L) } -static int -ngx_http_lua_socket_tcp_connect(lua_State *L) +static void +ngx_http_lua_socket_tcp_create_socket_pool(lua_State *L, ngx_http_request_t *r, + ngx_str_t key, ngx_int_t pool_size, ngx_int_t backlog, + ngx_http_lua_socket_pool_t **spool) { - ngx_http_request_t *r; - ngx_http_lua_ctx_t *ctx; - ngx_str_t host; - int port; - ngx_resolver_ctx_t *rctx, temp; - ngx_http_core_loc_conf_t *clcf; - int saved_top; - int n; - u_char *p; - size_t len; - ngx_url_t url; - ngx_int_t rc; - ngx_http_lua_loc_conf_t *llcf; - ngx_peer_connection_t *pc; - int connect_timeout, send_timeout, read_timeout; - unsigned custom_pool; - int key_index; - const char *msg; - ngx_http_lua_co_ctx_t *coctx; + u_char *p; + size_t size, key_len; + ngx_int_t i; + ngx_http_lua_socket_pool_t *sp; + ngx_http_lua_socket_pool_item_t *items; - ngx_http_lua_socket_tcp_upstream_t *u; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tcp socket connection pool size: %i, backlog: %i", + pool_size, backlog); - n = lua_gettop(L); - if (n != 2 && n != 3 && n != 4) { - return luaL_error(L, "ngx.socket connect: expecting 2, 3, or 4 " - "arguments (including the object), but seen %d", n); - } + key_len = ngx_align(key.len + 1, sizeof(void *)); - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request found"); - } + size = sizeof(ngx_http_lua_socket_pool_t) - 1 + key_len + + sizeof(ngx_http_lua_socket_pool_item_t) * pool_size; - ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); - if (ctx == NULL) { - return luaL_error(L, "no ctx found"); + /* before calling this function, the Lua stack is: + * -1 key + * -2 pools + */ + sp = lua_newuserdata(L, size); + if (sp == NULL) { + luaL_error(L, "no memory"); + return; } - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE - | NGX_HTTP_LUA_CONTEXT_ACCESS - | NGX_HTTP_LUA_CONTEXT_CONTENT - | NGX_HTTP_LUA_CONTEXT_TIMER - | NGX_HTTP_LUA_CONTEXT_SSL_CERT - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH); - - luaL_checktype(L, 1, LUA_TTABLE); - - p = (u_char *) luaL_checklstring(L, 2, &len); - - key_index = 2; - custom_pool = 0; - - if (lua_type(L, n) == LUA_TTABLE) { - - /* found the last optional option table */ - - lua_getfield(L, n, "pool"); - - switch (lua_type(L, -1)) { - case LUA_TNUMBER: - lua_tostring(L, -1); - /* FALLTHROUGH */ - - case LUA_TSTRING: - custom_pool = 1; - - lua_pushvalue(L, -1); - lua_rawseti(L, 1, SOCKET_KEY_INDEX); - - key_index = n + 1; - - break; - - case LUA_TNIL: - lua_pop(L, 2); - break; - - default: - msg = lua_pushfstring(L, "bad \"pool\" option type: %s", - luaL_typename(L, -1)); - luaL_argerror(L, n, msg); - break; - } + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + pool_udata_metatable_key)); + lua_rawget(L, LUA_REGISTRYINDEX); + lua_setmetatable(L, -2); - n--; - } + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tcp socket keepalive create connection pool for key" + " \"%V\"", &key); + + /* a new socket pool with metatable is push to the stack, so now we have: + * -1 sp + * -2 key + * -3 pools + * + * it is time to set pools[key] to sp. + */ + lua_rawset(L, -3); - /* the fourth argument is not a table */ - if (n == 4) { - lua_pop(L, 1); - n--; - } + /* clean up the stack for consistency's sake */ + lua_pop(L, 1); - if (n == 3) { - port = luaL_checkinteger(L, 3); + sp->backlog = backlog; + sp->size = pool_size; + sp->connections = 0; + sp->lua_vm = ngx_http_lua_get_lua_vm(r, NULL); - if (port < 0 || port > 65535) { - lua_pushnil(L); - lua_pushfstring(L, "bad port number: %d", port); - return 2; - } + ngx_queue_init(&sp->cache_connect_op); + ngx_queue_init(&sp->wait_connect_op); + ngx_queue_init(&sp->cache); + ngx_queue_init(&sp->free); - if (!custom_pool) { - lua_pushliteral(L, ":"); - lua_insert(L, 3); - lua_concat(L, 3); - } + p = ngx_copy(sp->key, key.data, key.len); + *p++ = '\0'; - dd("socket key: %s", lua_tostring(L, -1)); + items = (ngx_http_lua_socket_pool_item_t *) (sp->key + key_len); - } else { /* n == 2 */ - port = 0; - } + dd("items: %p", items); - if (!custom_pool) { - /* the key's index is 2 */ + ngx_http_lua_assert((void *) items == ngx_align_ptr(items, sizeof(void *))); - lua_pushvalue(L, 2); - lua_rawseti(L, 1, SOCKET_KEY_INDEX); + for (i = 0; i < pool_size; i++) { + ngx_queue_insert_head(&sp->free, &items[i].queue); + items[i].socket_pool = sp; } - lua_rawgeti(L, 1, SOCKET_CTX_INDEX); - u = lua_touserdata(L, -1); - lua_pop(L, 1); - - if (u) { - if (u->request && u->request != r) { - return luaL_error(L, "bad request"); - } - - ngx_http_lua_socket_check_busy_connecting(r, u, L); - ngx_http_lua_socket_check_busy_reading(r, u, L); - ngx_http_lua_socket_check_busy_writing(r, u, L); - - if (u->body_downstream || u->raw_downstream) { - return luaL_error(L, "attempt to re-connect a request socket"); - } - - if (u->peer.connection) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua tcp socket reconnect without shutting down"); + *spool = sp; +} - ngx_http_lua_socket_tcp_finalize(r, u); - } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua reuse socket upstream ctx"); +static int +ngx_http_lua_socket_tcp_connect_helper(lua_State *L, + ngx_http_lua_socket_tcp_upstream_t *u, ngx_http_request_t *r, + ngx_http_lua_ctx_t *ctx, u_char *host_ref, size_t host_len, in_port_t port, + unsigned resuming) +{ + int n; + int host_size; + int saved_top; + ngx_int_t rc; + ngx_str_t host; + ngx_str_t *conn_op_host; + ngx_url_t url; + ngx_queue_t *q; + ngx_resolver_ctx_t *rctx, temp; + ngx_http_lua_co_ctx_t *coctx; + ngx_http_core_loc_conf_t *clcf; + ngx_http_lua_socket_pool_t *spool; + ngx_http_lua_socket_tcp_conn_op_ctx_t *conn_op_ctx; + + spool = u->socket_pool; + if (spool != NULL) { + rc = ngx_http_lua_get_keepalive_peer(r, u); - } else { - u = lua_newuserdata(L, sizeof(ngx_http_lua_socket_tcp_upstream_t)); - if (u == NULL) { - return luaL_error(L, "no memory"); + if (rc == NGX_OK) { + lua_pushinteger(L, 1); + return 1; } -#if 1 - lua_pushlightuserdata(L, &ngx_http_lua_upstream_udata_metatable_key); - lua_rawget(L, LUA_REGISTRYINDEX); - lua_setmetatable(L, -2); -#endif - - lua_rawseti(L, 1, SOCKET_CTX_INDEX); - } - - ngx_memzero(u, sizeof(ngx_http_lua_socket_tcp_upstream_t)); - - coctx = ctx->cur_co_ctx; - - u->request = r; /* set the controlling request */ - - llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); + /* rc == NGX_DECLINED */ - u->conf = llcf; + spool->connections++; - pc = &u->peer; + /* check if backlog is enabled and + * don't queue resuming connection operation */ + if (spool->backlog >= 0 && !resuming) { - pc->log = r->connection->log; - pc->log_error = NGX_ERROR_ERR; + dd("lua tcp socket %s connections %ld", + spool->key, spool->connections); - dd("lua peer connection log: %p", pc->log); + if (spool->connections > spool->size + spool->backlog) { + spool->connections--; + lua_pushnil(L); + lua_pushliteral(L, "too many waiting connect operations"); + return 2; + } - lua_rawgeti(L, 1, SOCKET_CONNECT_TIMEOUT_INDEX); - lua_rawgeti(L, 1, SOCKET_SEND_TIMEOUT_INDEX); - lua_rawgeti(L, 1, SOCKET_READ_TIMEOUT_INDEX); + if (spool->connections > spool->size) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, u->peer.log, 0, + "lua tcp socket queue connect operation for " + "connection pool \"%s\", connections: %i", + spool->key, spool->connections); + + host_size = sizeof(u_char) * + (ngx_max(host_len, NGX_INET_ADDRSTRLEN) + 1); + + if (!ngx_queue_empty(&spool->cache_connect_op)) { + q = ngx_queue_last(&spool->cache_connect_op); + ngx_queue_remove(q); + conn_op_ctx = ngx_queue_data( + q, ngx_http_lua_socket_tcp_conn_op_ctx_t, queue); + + conn_op_host = &conn_op_ctx->host; + if (host_len > conn_op_host->len + && host_len > NGX_INET_ADDRSTRLEN) + { + ngx_free(conn_op_host->data); + conn_op_host->data = ngx_alloc(host_size, + ngx_cycle->log); + if (conn_op_host->data == NULL) { + ngx_free(conn_op_ctx); + goto no_memory_and_not_resuming; + } + } - read_timeout = (ngx_int_t) lua_tointeger(L, -1); - send_timeout = (ngx_int_t) lua_tointeger(L, -2); - connect_timeout = (ngx_int_t) lua_tointeger(L, -3); + } else { + conn_op_ctx = ngx_alloc( + sizeof(ngx_http_lua_socket_tcp_conn_op_ctx_t), + ngx_cycle->log); + if (conn_op_ctx == NULL) { + goto no_memory_and_not_resuming; + } - lua_pop(L, 3); + conn_op_host = &conn_op_ctx->host; + conn_op_host->data = ngx_alloc(host_size, ngx_cycle->log); + if (conn_op_host->data == NULL) { + ngx_free(conn_op_ctx); + goto no_memory_and_not_resuming; + } + } - if (connect_timeout > 0) { - u->connect_timeout = (ngx_msec_t) connect_timeout; + conn_op_ctx->cleanup = NULL; - } else { - u->connect_timeout = u->conf->connect_timeout; - } + ngx_memcpy(conn_op_host->data, host_ref, host_len); + conn_op_host->data[host_len] = '\0'; + conn_op_host->len = host_len; - if (send_timeout > 0) { - u->send_timeout = (ngx_msec_t) send_timeout; + conn_op_ctx->port = port; - } else { - u->send_timeout = u->conf->send_timeout; - } + u->write_co_ctx = ctx->cur_co_ctx; - if (read_timeout > 0) { - u->read_timeout = (ngx_msec_t) read_timeout; + conn_op_ctx->u = u; + ctx->cur_co_ctx->cleanup = + ngx_http_lua_tcp_queue_conn_op_cleanup; + ctx->cur_co_ctx->data = conn_op_ctx; - } else { - u->read_timeout = u->conf->read_timeout; - } + ngx_memzero(&conn_op_ctx->event, sizeof(ngx_event_t)); + conn_op_ctx->event.handler = + ngx_http_lua_socket_tcp_conn_op_timeout_handler; + conn_op_ctx->event.data = conn_op_ctx; + conn_op_ctx->event.log = ngx_cycle->log; - rc = ngx_http_lua_get_keepalive_peer(r, L, key_index, u); + ngx_add_timer(&conn_op_ctx->event, u->connect_timeout); - if (rc == NGX_OK) { - lua_pushinteger(L, 1); - return 1; - } + ngx_queue_insert_tail(&spool->wait_connect_op, + &conn_op_ctx->queue); - if (rc == NGX_ERROR) { - lua_pushnil(L); - lua_pushliteral(L, "error in get keepalive peer"); - return 2; - } + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua tcp socket queued connect operation for " + "%d(ms), u: %p, ctx: %p", + u->connect_timeout, conn_op_ctx->u, conn_op_ctx); - /* rc == NGX_DECLINED */ + return lua_yield(L, 0); + } + } - /* TODO: we should avoid this in-pool allocation */ + } /* end spool != NULL */ - host.data = ngx_palloc(r->pool, len + 1); + host.data = ngx_palloc(r->pool, host_len + 1); if (host.data == NULL) { return luaL_error(L, "no memory"); } - host.len = len; + host.len = host_len; - ngx_memcpy(host.data, p, len); - host.data[len] = '\0'; + ngx_memcpy(host.data, host_ref, host_len); + host.data[host_len] = '\0'; ngx_memzero(&url, sizeof(ngx_url_t)); - - url.url.len = host.len; - url.url.data = host.data; - url.default_port = (in_port_t) port; + url.url = host; + url.default_port = port; url.no_resolve = 1; + coctx = ctx->cur_co_ctx; + if (ngx_parse_url(r->pool, &url) != NGX_OK) { lua_pushnil(L); if (url.err) { lua_pushfstring(L, "failed to parse host name \"%s\": %s", - host.data, url.err); + url.url.data, url.err); } else { - lua_pushfstring(L, "failed to parse host name \"%s\"", host.data); + lua_pushfstring(L, "failed to parse host name \"%s\"", + url.url.data); } - return 2; + goto failed; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -681,8 +705,14 @@ ngx_http_lua_socket_tcp_connect(lua_State *L) u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t)); if (u->resolved == NULL) { - return luaL_error(L, "no memory"); - } + if (resuming) { + lua_pushnil(L); + lua_pushliteral(L, "no memory"); + goto failed; + } + + goto no_memory_and_not_resuming; + } if (url.addrs && url.addrs[0].sockaddr) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -695,15 +725,19 @@ ngx_http_lua_socket_tcp_connect(lua_State *L) } else { u->resolved->host = host; - u->resolved->port = (in_port_t) port; + u->resolved->port = url.default_port; } if (u->resolved->sockaddr) { rc = ngx_http_lua_socket_resolve_retval_handler(r, u, L); - if (rc == NGX_AGAIN) { + if (rc == NGX_AGAIN && !resuming) { return lua_yield(L, 0); } + if (rc > 1) { + goto failed; + } + return rc; } @@ -715,20 +749,17 @@ ngx_http_lua_socket_tcp_connect(lua_State *L) u->ft_type |= NGX_HTTP_LUA_SOCKET_FT_RESOLVER; lua_pushnil(L); lua_pushliteral(L, "failed to start the resolver"); - return 2; + goto failed; } if (rctx == NGX_NO_RESOLVER) { u->ft_type |= NGX_HTTP_LUA_SOCKET_FT_RESOLVER; lua_pushnil(L); lua_pushfstring(L, "no resolver defined to resolve \"%s\"", host.data); - return 2; + goto failed; } rctx->name = host; -#if !defined(nginx_version) || nginx_version < 1005008 - rctx->type = NGX_RESOLVE_A; -#endif rctx->handler = ngx_http_lua_socket_resolve_handler; rctx->data = u; rctx->timeout = clcf->resolver_timeout; @@ -754,12 +785,16 @@ ngx_http_lua_socket_tcp_connect(lua_State *L) u->resolved->ctx = NULL; lua_pushnil(L); lua_pushfstring(L, "%s could not be resolved", host.data); - - return 2; + goto failed; } if (u->conn_waiting) { dd("resolved and already connecting"); + + if (resuming) { + return NGX_AGAIN; + } + return lua_yield(L, 0); } @@ -767,6 +802,11 @@ ngx_http_lua_socket_tcp_connect(lua_State *L) if (n) { dd("errors occurred during resolving or connecting" "or already connected"); + + if (n > 1) { + goto failed; + } + return n; } @@ -784,14 +824,416 @@ ngx_http_lua_socket_tcp_connect(lua_State *L) r->write_event_handler = ngx_http_core_run_phases; } + if (resuming) { + return NGX_AGAIN; + } + return lua_yield(L, 0); + +failed: + + if (spool != NULL) { + spool->connections--; + ngx_http_lua_socket_tcp_resume_conn_op(spool); + } + + return 2; + +no_memory_and_not_resuming: + + if (spool != NULL) { + spool->connections--; + ngx_http_lua_socket_tcp_resume_conn_op(spool); + } + + return luaL_error(L, "no memory"); } -static void -ngx_http_lua_socket_empty_resolve_handler(ngx_resolver_ctx_t *ctx) +static int +ngx_http_lua_socket_tcp_bind(lua_State *L) +{ + int port; + ngx_http_request_t *r; + ngx_http_lua_ctx_t *ctx; + int n; + u_char *text; + size_t len; + ngx_addr_t *local; + + n = lua_gettop(L); + + /* Correct the parameter check and allow 2 or 3 parameters */ + if (n != 2 && n != 3) { + return luaL_error(L, "expecting 2 or 3 arguments, but got %d", + lua_gettop(L)); + } + + r = ngx_http_lua_get_req(L); + if (r == NULL) { + return luaL_error(L, "no request found"); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + return luaL_error(L, "no ctx found"); + } + + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); + + luaL_checktype(L, 1, LUA_TTABLE); + + port = 0; + /* handle case: host:port */ + /* Hit the following parameter combination: + * sock:bind("127.0.0.1", port) */ + if (n == 3) { + if (!lua_isnumber(L, 3)) { + lua_pushnil(L); + lua_pushfstring(L, "expecting port to be a" + "number but got type: %s", luaL_typename(L, 3)); + return 2; + } + + port = (int) lua_tointeger(L, 3); + if (port < 0 || port > 65535) { + lua_pushnil(L); + lua_pushfstring(L, "bad port number: %d", port); + return 2; + } + } + + text = (u_char *) luaL_checklstring(L, 2, &len); + + local = ngx_http_lua_parse_addr(L, text, len); + if (local == NULL) { + lua_pushnil(L); + lua_pushfstring(L, "bad address"); + return 2; + } + + if (port > 0) { + ngx_inet_set_port(local->sockaddr, (in_port_t) port); + } + /* TODO: we may reuse the userdata here */ + lua_rawseti(L, 1, SOCKET_BIND_INDEX); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tcp socket bind ip: %V", &local->name); + + lua_pushboolean(L, 1); + return 1; +} + + +static int +ngx_http_lua_socket_tcp_connect(lua_State *L) { - /* do nothing */ + ngx_http_request_t *r; + ngx_http_lua_ctx_t *ctx; + int port; + int n; + u_char *p; + size_t len; + ngx_http_lua_loc_conf_t *llcf; + ngx_peer_connection_t *pc; + ngx_addr_t *local; + int connect_timeout, send_timeout, read_timeout; + unsigned custom_pool; + int key_index; + ngx_int_t backlog; + ngx_int_t pool_size; + ngx_str_t key; + const char *msg; + + ngx_http_lua_socket_tcp_upstream_t *u; + + ngx_http_lua_socket_pool_t *spool; + + n = lua_gettop(L); + if (n != 2 && n != 3 && n != 4) { + return luaL_error(L, "ngx.socket connect: expecting 2, 3, or 4 " + "arguments (including the object), but seen %d", n); + } + + r = ngx_http_lua_get_req(L); + if (r == NULL) { + return luaL_error(L, "no request found"); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + return luaL_error(L, "no ctx found"); + } + + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); + + luaL_checktype(L, 1, LUA_TTABLE); + + p = (u_char *) luaL_checklstring(L, 2, &len); + + backlog = -1; + key_index = 2; + pool_size = 0; + custom_pool = 0; + llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); + + if (lua_type(L, n) == LUA_TTABLE) { + + /* found the last optional option table */ + + lua_getfield(L, n, "pool_size"); + + if (lua_isnumber(L, -1)) { + pool_size = (ngx_int_t) lua_tointeger(L, -1); + + if (pool_size <= 0) { + msg = lua_pushfstring(L, "bad \"pool_size\" option value: %d", + pool_size); + return luaL_argerror(L, n, msg); + } + + } else if (!lua_isnil(L, -1)) { + msg = lua_pushfstring(L, "bad \"pool_size\" option type: %s", + lua_typename(L, lua_type(L, -1))); + return luaL_argerror(L, n, msg); + } + + lua_pop(L, 1); + + lua_getfield(L, n, "backlog"); + + if (lua_isnumber(L, -1)) { + backlog = (ngx_int_t) lua_tointeger(L, -1); + + if (backlog < 0) { + msg = lua_pushfstring(L, "bad \"backlog\" option value: %d", + backlog); + return luaL_argerror(L, n, msg); + } + + /* use default value for pool size if only backlog specified */ + if (pool_size == 0) { + pool_size = llcf->pool_size; + } + } + + lua_pop(L, 1); + + lua_getfield(L, n, "pool"); + + switch (lua_type(L, -1)) { + case LUA_TNUMBER: + lua_tostring(L, -1); + /* FALLTHROUGH */ + + case LUA_TSTRING: + custom_pool = 1; + + lua_pushvalue(L, -1); + lua_rawseti(L, 1, SOCKET_KEY_INDEX); + + key_index = n + 1; + + break; + + case LUA_TNIL: + lua_pop(L, 2); + break; + + default: + msg = lua_pushfstring(L, "bad \"pool\" option type: %s", + luaL_typename(L, -1)); + luaL_argerror(L, n, msg); + break; + } + + n--; + } + + /* the fourth argument is not a table */ + if (n == 4) { + lua_pop(L, 1); + n--; + } + + /* most popular suit: host:port */ + if (n == 3 && lua_isnumber(L, 3)) { + + /* Hit the following parameter combination: + * sock:connect("127.0.0.1", port) + * sock:connect("127.0.0.1", port, opts) + * sock:connect("unix:/path", port) + * sock:connect("unix:/path", port, opts) */ + + port = (int) lua_tointeger(L, 3); + + if (port < 0 || port > 65535) { + lua_pushnil(L); + lua_pushfstring(L, "bad port number: %d", port); + return 2; + } + + if (!custom_pool) { + lua_pushliteral(L, ":"); + lua_insert(L, 3); + lua_concat(L, 3); + } + + dd("socket key: %s", lua_tostring(L, -1)); + + } else if (len >= 5 && ngx_strncasecmp(p, (u_char *) "unix:", 5) == 0) { + + /* Hit the following parameter combination: + * sock:connect("unix:/path") + * sock:connect("unix:/path", nil) + * sock:connect("unix:/path", opts) + * sock:connect("unix:/path", nil, opts) */ + + port = 0; + + } else { + + /* Ban the following parameter combination: + * sock:connect("127.0.0.1") + * sock:connect("127.0.0.1", nil) + * sock:connect("127.0.0.1", opts) + * sock:connect("127.0.0.1", nil, opts) */ + + lua_pushnil(L); + lua_pushfstring(L, "missing the port number"); + return 2; + } + + if (!custom_pool) { + /* the key's index is 2 */ + + lua_pushvalue(L, 2); + lua_rawseti(L, 1, SOCKET_KEY_INDEX); + } + + lua_rawgeti(L, 1, SOCKET_CTX_INDEX); + u = lua_touserdata(L, -1); + lua_pop(L, 1); + + if (u) { + if (u->request && u->request != r) { + return luaL_error(L, "bad request"); + } + + ngx_http_lua_socket_check_busy_connecting(r, u, L); + ngx_http_lua_socket_check_busy_reading(r, u, L); + ngx_http_lua_socket_check_busy_writing(r, u, L); + + if (u->body_downstream || u->raw_downstream) { + return luaL_error(L, "attempt to re-connect a request socket"); + } + + if (u->peer.connection) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tcp socket reconnect without shutting down"); + + ngx_http_lua_socket_tcp_finalize(r, u); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua reuse socket upstream ctx"); + + } else { + u = lua_newuserdata(L, sizeof(ngx_http_lua_socket_tcp_upstream_t)); + if (u == NULL) { + return luaL_error(L, "no memory"); + } + +#if 1 + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + upstream_udata_metatable_key)); + lua_rawget(L, LUA_REGISTRYINDEX); + lua_setmetatable(L, -2); +#endif + + lua_rawseti(L, 1, SOCKET_CTX_INDEX); + } + + ngx_memzero(u, sizeof(ngx_http_lua_socket_tcp_upstream_t)); + + u->request = r; /* set the controlling request */ + + u->conf = llcf; + + pc = &u->peer; + + pc->log = r->connection->log; + pc->log_error = NGX_ERROR_ERR; + + dd("lua peer connection log: %p", pc->log); + + lua_rawgeti(L, 1, SOCKET_BIND_INDEX); + local = lua_touserdata(L, -1); + + if (local != NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tcp socket sock:connect ip: %V", &local->name); + } + + lua_pop(L, 1); + + if (local) { + u->peer.local = local; + } + + lua_rawgeti(L, 1, SOCKET_CONNECT_TIMEOUT_INDEX); + lua_rawgeti(L, 1, SOCKET_SEND_TIMEOUT_INDEX); + lua_rawgeti(L, 1, SOCKET_READ_TIMEOUT_INDEX); + + read_timeout = (ngx_int_t) lua_tointeger(L, -1); + send_timeout = (ngx_int_t) lua_tointeger(L, -2); + connect_timeout = (ngx_int_t) lua_tointeger(L, -3); + + lua_pop(L, 3); + + if (connect_timeout > 0) { + u->connect_timeout = (ngx_msec_t) connect_timeout; + + } else { + u->connect_timeout = u->conf->connect_timeout; + } + + if (send_timeout > 0) { + u->send_timeout = (ngx_msec_t) send_timeout; + + } else { + u->send_timeout = u->conf->send_timeout; + } + + if (read_timeout > 0) { + u->read_timeout = (ngx_msec_t) read_timeout; + + } else { + u->read_timeout = u->conf->read_timeout; + } + + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask(socket_pool_key)); + lua_rawget(L, LUA_REGISTRYINDEX); /* table */ + lua_pushvalue(L, key_index); /* key */ + + lua_rawget(L, -2); + spool = lua_touserdata(L, -1); + lua_pop(L, 1); + + if (spool != NULL) { + u->socket_pool = spool; + + } else if (pool_size > 0) { + lua_pushvalue(L, key_index); + key.data = (u_char *) lua_tolstring(L, -1, &key.len); + + ngx_http_lua_socket_tcp_create_socket_pool(L, r, key, pool_size, + backlog, &spool); + u->socket_pool = spool; + } + + return ngx_http_lua_socket_tcp_connect_helper(L, u, r, ctx, p, + len, port, 0); } @@ -806,12 +1248,8 @@ ngx_http_lua_socket_resolve_handler(ngx_resolver_ctx_t *ctx) ngx_http_lua_socket_tcp_upstream_t *u; u_char *p; size_t len; -#if defined(nginx_version) && nginx_version >= 1005008 socklen_t socklen; struct sockaddr *sockaddr; -#else - struct sockaddr_in *sin; -#endif ngx_uint_t i; unsigned waiting; @@ -866,36 +1304,20 @@ ngx_http_lua_socket_resolve_handler(ngx_resolver_ctx_t *ctx) #if (NGX_DEBUG) { -# if defined(nginx_version) && nginx_version >= 1005008 - u_char text[NGX_SOCKADDR_STRLEN]; - ngx_str_t addr; -# else - in_addr_t addr; -# endif - ngx_uint_t i; + u_char text[NGX_SOCKADDR_STRLEN]; + ngx_str_t addr; + ngx_uint_t i; -# if defined(nginx_version) && nginx_version >= 1005008 - addr.data = text; + addr.data = text; - for (i = 0; i < ctx->naddrs; i++) { - addr.len = ngx_sock_ntop(ur->addrs[i].sockaddr, ur->addrs[i].socklen, - text, NGX_SOCKADDR_STRLEN, 0); + for (i = 0; i < ctx->naddrs; i++) { + addr.len = ngx_sock_ntop(ur->addrs[i].sockaddr, + ur->addrs[i].socklen, text, + NGX_SOCKADDR_STRLEN, 0); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "name was resolved to %V", &addr); - } -# else - for (i = 0; i < ctx->naddrs; i++) { - dd("addr i: %d %p", (int) i, &ctx->addrs[i]); - - addr = ntohl(ctx->addrs[i]); - - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, - "name was resolved to %ud.%ud.%ud.%ud", - (addr >> 24) & 0xff, (addr >> 16) & 0xff, - (addr >> 8) & 0xff, addr & 0xff); - } -# endif + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "name was resolved to %V", &addr); + } } #endif @@ -910,7 +1332,6 @@ ngx_http_lua_socket_resolve_handler(ngx_resolver_ctx_t *ctx) dd("selected addr index: %d", (int) i); -#if defined(nginx_version) && nginx_version >= 1005008 socklen = ur->addrs[i].socklen; sockaddr = ngx_palloc(r->pool, socklen); @@ -939,30 +1360,6 @@ ngx_http_lua_socket_resolve_handler(ngx_resolver_ctx_t *ctx) ur->sockaddr = sockaddr; ur->socklen = socklen; -#else - /* for nginx older than 1.5.8 */ - - len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1; - - p = ngx_pnalloc(r->pool, len + sizeof(struct sockaddr_in)); - if (p == NULL) { - goto nomem; - } - - sin = (struct sockaddr_in *) &p[len]; - ngx_memzero(sin, sizeof(struct sockaddr_in)); - - len = ngx_inet_ntop(AF_INET, &ur->addrs[i], p, NGX_INET_ADDRSTRLEN); - len = ngx_sprintf(&p[len], ":%d", ur->port) - p; - - sin->sin_family = AF_INET; - sin->sin_port = htons(ur->port); - sin->sin_addr.s_addr = ur->addrs[i]; - - ur->sockaddr = (struct sockaddr *) sin; - ur->socklen = sizeof(struct sockaddr_in); -#endif - ur->host.data = p; ur->host.len = len; ur->naddrs = 1; @@ -1250,64 +1647,73 @@ ngx_http_lua_socket_conn_error_retval_handler(ngx_http_request_t *r, #if (NGX_HTTP_SSL) -static int -ngx_http_lua_socket_tcp_sslhandshake(lua_State *L) +static const char * +ngx_http_lua_socket_tcp_check_busy(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, unsigned int ops) { - int n, top; - ngx_int_t rc; - ngx_str_t name = ngx_null_string; - ngx_connection_t *c; - ngx_ssl_session_t **psession; - ngx_http_request_t *r; - ngx_http_lua_ctx_t *ctx; - ngx_http_lua_co_ctx_t *coctx; - - ngx_http_lua_socket_tcp_upstream_t *u; - - /* Lua function arguments: self [,session] [,host] [,verify] - [,send_status_req] */ + if ((ops & SOCKET_OP_CONNECT) && u->conn_waiting) { + return "socket busy connecting"; + } - n = lua_gettop(L); - if (n < 1 || n > 5) { - return luaL_error(L, "ngx.socket sslhandshake: expecting 1 ~ 5 " - "arguments (including the object), but seen %d", n); + if ((ops & SOCKET_OP_READ) && u->read_waiting) { + return "socket busy reading"; } - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request found"); + if ((ops & SOCKET_OP_WRITE) + && (u->write_waiting + || (u->raw_downstream + && (r->connection->buffered & NGX_HTTP_LOWLEVEL_BUFFERED)))) + { + return "socket busy writing"; } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua tcp socket ssl handshake"); + return NULL; +} - luaL_checktype(L, 1, LUA_TTABLE); - lua_rawgeti(L, 1, SOCKET_CTX_INDEX); - u = lua_touserdata(L, -1); +int +ngx_http_lua_ffi_socket_tcp_sslhandshake(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, ngx_ssl_session_t *sess, + int enable_session_reuse, ngx_str_t *server_name, int verify, + int ocsp_status_req, STACK_OF(X509) *chain, EVP_PKEY *pkey, + const char **errmsg) +{ + ngx_int_t rc, i; + ngx_connection_t *c; + ngx_http_lua_ctx_t *ctx; + ngx_http_lua_co_ctx_t *coctx; + const char *busy_msg; + ngx_ssl_conn_t *ssl_conn; + X509 *x509; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tcp socket ssl handshake"); if (u == NULL || u->peer.connection == NULL || u->read_closed || u->write_closed) { - lua_pushnil(L); - lua_pushliteral(L, "closed"); - return 2; + *errmsg = "closed"; + return NGX_ERROR; } if (u->request != r) { - return luaL_error(L, "bad request"); + *errmsg = "bad request"; + return NGX_ERROR; } - ngx_http_lua_socket_check_busy_connecting(r, u, L); - ngx_http_lua_socket_check_busy_reading(r, u, L); - ngx_http_lua_socket_check_busy_writing(r, u, L); + busy_msg = ngx_http_lua_socket_tcp_check_busy(r, u, SOCKET_OP_CONNECT + | SOCKET_OP_READ + | SOCKET_OP_WRITE); + if (busy_msg != NULL) { + *errmsg = busy_msg; + return NGX_ERROR; + } if (u->raw_downstream || u->body_downstream) { - lua_pushnil(L); - lua_pushliteral(L, "not supported for downstream"); - return 2; + *errmsg = "not supported for downstream sockets"; + return NGX_ERROR; } c = u->peer.connection; @@ -1315,122 +1721,140 @@ ngx_http_lua_socket_tcp_sslhandshake(lua_State *L) u->ssl_session_reuse = 1; if (c->ssl && c->ssl->handshaked) { - switch (lua_type(L, 2)) { - case LUA_TUSERDATA: - lua_pushvalue(L, 2); - break; + if (sess != NULL) { + return NGX_DONE; + } - case LUA_TBOOLEAN: - if (!lua_toboolean(L, 2)) { - /* avoid generating the ssl session */ - lua_pushboolean(L, 1); - break; - } - /* fall through */ + u->ssl_session_reuse = enable_session_reuse; - default: - ngx_http_lua_ssl_handshake_retval_handler(r, u, L); - break; - } + (void) ngx_http_lua_ssl_handshake_retval_handler(r, u, NULL); - return 1; + return NGX_OK; } if (ngx_ssl_create_connection(u->conf->ssl, c, NGX_SSL_BUFFER|NGX_SSL_CLIENT) != NGX_OK) { - lua_pushnil(L); - lua_pushliteral(L, "failed to create ssl connection"); - return 2; + *errmsg = "failed to create ssl connection"; + return NGX_ERROR; } + ssl_conn = c->ssl->connection; + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); if (ctx == NULL) { - return luaL_error(L, "no ctx found"); + return NGX_HTTP_LUA_FFI_NO_REQ_CTX; } coctx = ctx->cur_co_ctx; c->sendfile = 0; - if (n >= 2) { - if (lua_type(L, 2) == LUA_TBOOLEAN) { - u->ssl_session_reuse = lua_toboolean(L, 2); + if (sess != NULL) { + if (ngx_ssl_set_session(c, sess) != NGX_OK) { + *errmsg = "ssl set session failed"; + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "lua ssl set session: %p", sess); - } else { - psession = lua_touserdata(L, 2); + } else { + u->ssl_session_reuse = enable_session_reuse; + } - if (psession != NULL && *psession != NULL) { - if (ngx_ssl_set_session(c, *psession) != NGX_OK) { - lua_pushnil(L); - lua_pushliteral(L, "lua ssl set session failed"); - return 2; - } + if (chain != NULL) { + ngx_http_lua_assert(pkey != NULL); /* ensured by resty.core */ - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "lua ssl set session: %p", *psession); - } + if (sk_X509_num(chain) < 1) { + ERR_clear_error(); + *errmsg = "invalid client certificate chain"; + return NGX_ERROR; } - if (n >= 3) { - name.data = (u_char *) lua_tolstring(L, 3, &name.len); + x509 = sk_X509_value(chain, 0); + if (x509 == NULL) { + ERR_clear_error(); + *errmsg = "ssl fetch client certificate from chain failed"; + return NGX_ERROR; + } - if (name.data) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua ssl server name: \"%*s\"", name.len, - name.data); + if (SSL_use_certificate(ssl_conn, x509) == 0) { + ERR_clear_error(); + *errmsg = "ssl set client certificate failed"; + return NGX_ERROR; + } -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + /* read rest of the chain */ - if (SSL_set_tlsext_host_name(c->ssl->connection, - (char *) name.data) - == 0) - { - lua_pushnil(L); - lua_pushliteral(L, "SSL_set_tlsext_host_name failed"); - return 2; - } + for (i = 1; i < (ngx_int_t) sk_X509_num(chain); i++) { + x509 = sk_X509_value(chain, i); + if (x509 == NULL) { + ERR_clear_error(); + *errmsg = "ssl fetch client intermediate certificate from " + "chain failed"; + return NGX_ERROR; + } -#else + if (SSL_add1_chain_cert(ssl_conn, x509) == 0) { + ERR_clear_error(); + *errmsg = "ssl set client intermediate certificate failed"; + return NGX_ERROR; + } + } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "lua socket SNI disabled because the current " - "version of OpenSSL lacks the support"); + if (SSL_use_PrivateKey(ssl_conn, pkey) == 0) { + ERR_clear_error(); + *errmsg = "ssl set client private key failed"; + return NGX_ERROR; + } + } + if (server_name != NULL && server_name->data != NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua ssl server name: \"%V\"", server_name); + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + if (SSL_set_tlsext_host_name(c->ssl->connection, + (char *) server_name->data) + == 0) + { + *errmsg = "SSL_set_tlsext_host_name failed"; + return NGX_ERROR; + } + +#else + *errmsg = "no TLS extension support"; + return NGX_ERROR; #endif - } + } - if (n >= 4) { - u->ssl_verify = lua_toboolean(L, 4); + u->ssl_verify = verify; - if (n >= 5) { - if (lua_toboolean(L, 5)) { + if (ocsp_status_req) { #ifdef NGX_HTTP_LUA_USE_OCSP - SSL_set_tlsext_status_type(c->ssl->connection, - TLSEXT_STATUSTYPE_ocsp); + SSL_set_tlsext_status_type(c->ssl->connection, + TLSEXT_STATUSTYPE_ocsp); + #else - return luaL_error(L, "no OCSP support"); + *errmsg = "no OCSP support"; + return NGX_ERROR; #endif - } - } - } - } } - dd("found sni name: %.*s %p", (int) name.len, name.data, name.data); - - if (name.len == 0) { + if (server_name == NULL || server_name->len == 0) { u->ssl_name.len = 0; } else { if (u->ssl_name.data) { /* buffer already allocated */ - if (u->ssl_name.len >= name.len) { + if (u->ssl_name.len >= server_name->len) { /* reuse it */ - ngx_memcpy(u->ssl_name.data, name.data, name.len); - u->ssl_name.len = name.len; + ngx_memcpy(u->ssl_name.data, server_name->data, + server_name->len); + u->ssl_name.len = server_name->len; } else { ngx_free(u->ssl_name.data); @@ -1441,17 +1865,15 @@ ngx_http_lua_socket_tcp_sslhandshake(lua_State *L) new_ssl_name: - u->ssl_name.data = ngx_alloc(name.len, ngx_cycle->log); + u->ssl_name.data = ngx_alloc(server_name->len, ngx_cycle->log); if (u->ssl_name.data == NULL) { u->ssl_name.len = 0; - - lua_pushnil(L); - lua_pushliteral(L, "no memory"); - return 2; + *errmsg = "no memory"; + return NGX_ERROR; } - ngx_memcpy(u->ssl_name.data, name.data, name.len); - u->ssl_name.len = name.len; + ngx_memcpy(u->ssl_name.data, server_name->data, server_name->len); + u->ssl_name.len = server_name->len; } } @@ -1465,7 +1887,8 @@ ngx_http_lua_socket_tcp_sslhandshake(lua_State *L) rc = ngx_ssl_handshake(c); - dd("ngx_ssl_handshake returned %d", (int) rc); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "ngx_ssl_handshake returned: %d", rc); if (rc == NGX_AGAIN) { if (c->write->timer_set) { @@ -1490,21 +1913,24 @@ ngx_http_lua_socket_tcp_sslhandshake(lua_State *L) r->write_event_handler = ngx_http_core_run_phases; } - return lua_yield(L, 0); + return NGX_AGAIN; } - top = lua_gettop(L); ngx_http_lua_ssl_handshake_handler(c); - return lua_gettop(L) - top; + + if (rc == NGX_ERROR) { + *errmsg = u->error_ret; + return NGX_ERROR; + } + + return NGX_OK; } static void ngx_http_lua_ssl_handshake_handler(ngx_connection_t *c) { - const char *err; int waiting; - lua_State *L; ngx_int_t rc; ngx_connection_t *dc; /* downstream connection */ ngx_http_request_t *r; @@ -1527,11 +1953,9 @@ ngx_http_lua_ssl_handshake_handler(ngx_connection_t *c) waiting = u->conn_waiting; dc = r->connection; - L = u->write_co_ctx->co; if (c->read->timedout) { - lua_pushnil(L); - lua_pushliteral(L, "timeout"); + u->error_ret = "timeout"; goto failed; } @@ -1540,31 +1964,29 @@ ngx_http_lua_ssl_handshake_handler(ngx_connection_t *c) } if (c->ssl->handshaked) { - if (u->ssl_verify) { rc = SSL_get_verify_result(c->ssl->connection); if (rc != X509_V_OK) { - lua_pushnil(L); - err = lua_pushfstring(L, "%d: %s", (int) rc, - X509_verify_cert_error_string(rc)); + u->error_ret = X509_verify_cert_error_string(rc); + u->openssl_error_code_ret = rc; llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); if (llcf->log_socket_errors) { ngx_log_error(NGX_LOG_ERR, dc->log, 0, "lua ssl " - "certificate verify error: (%s)", err); + "certificate verify error: (%d: %s)", + rc, u->error_ret); } goto failed; } -#if defined(nginx_version) && nginx_version >= 1007000 +#if (nginx_version >= 1007000) if (u->ssl_name.len && ngx_ssl_check_host(c, &u->ssl_name) != NGX_OK) { - lua_pushnil(L); - lua_pushliteral(L, "certificate host mismatch"); + u->error_ret = "certificate host mismatch"; llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); if (llcf->log_socket_errors) { @@ -1583,7 +2005,7 @@ ngx_http_lua_ssl_handshake_handler(ngx_connection_t *c) ngx_http_lua_socket_handle_conn_success(r, u); } else { - (void) ngx_http_lua_ssl_handshake_retval_handler(r, u, L); + (void) ngx_http_lua_ssl_handshake_retval_handler(r, u, NULL); } if (waiting) { @@ -1593,59 +2015,83 @@ ngx_http_lua_ssl_handshake_handler(ngx_connection_t *c) return; } - lua_pushnil(L); - lua_pushliteral(L, "handshake failed"); + u->error_ret = "handshake failed"; failed: if (waiting) { u->write_prepare_retvals = - ngx_http_lua_socket_conn_error_retval_handler; - ngx_http_lua_socket_handle_conn_error(r, u, - NGX_HTTP_LUA_SOCKET_FT_SSL); + ngx_http_lua_socket_conn_error_retval_handler; + ngx_http_lua_socket_handle_conn_error(r, u, NGX_HTTP_LUA_SOCKET_FT_SSL); ngx_http_run_posted_requests(dc); } else { - (void) ngx_http_lua_socket_conn_error_retval_handler(r, u, L); + u->ft_type |= NGX_HTTP_LUA_SOCKET_FT_SSL; + + (void) ngx_http_lua_socket_conn_error_retval_handler(r, u, NULL); } } +int +ngx_http_lua_ffi_socket_tcp_get_sslhandshake_result(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, ngx_ssl_session_t **sess, + const char **errmsg, int *openssl_error_code) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua cosocket get SSL handshake result for upstream: %p", u); + + if (u->error_ret != NULL) { + *errmsg = u->error_ret; + *openssl_error_code = u->openssl_error_code_ret; + + return NGX_ERROR; + } + + *sess = u->ssl_session_ret; + + return NGX_OK; +} + + static int ngx_http_lua_ssl_handshake_retval_handler(ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L) { ngx_connection_t *c; - ngx_ssl_session_t *ssl_session, **ud; + ngx_ssl_session_t *ssl_session; if (!u->ssl_session_reuse) { - lua_pushboolean(L, 1); - return 1; + return 0; } - ud = lua_newuserdata(L, sizeof(ngx_ssl_session_t *)); - c = u->peer.connection; ssl_session = ngx_ssl_get_session(c); if (ssl_session == NULL) { - *ud = NULL; + u->ssl_session_ret = NULL; } else { - *ud = ssl_session; + u->ssl_session_ret = ssl_session; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "lua ssl save session: %p", ssl_session); - - /* set up the __gc metamethod */ - lua_pushlightuserdata(L, &ngx_http_lua_ssl_session_metatable_key); - lua_rawget(L, LUA_REGISTRYINDEX); - lua_setmetatable(L, -2); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "lua ssl save session: %p", ssl_session); } - return 1; + return 0; +} + + +void +ngx_http_lua_ffi_ssl_free_session(ngx_ssl_session_t *sess) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua ssl free session: %p", sess); + + ngx_ssl_free_session(sess); } + #endif /* NGX_HTTP_SSL */ @@ -1698,12 +2144,14 @@ ngx_http_lua_socket_prepare_error_retvals(ngx_http_request_t *r, u_char errstr[NGX_MAX_ERROR_STR]; u_char *p; - if (ft_type & (NGX_HTTP_LUA_SOCKET_FT_RESOLVER - | NGX_HTTP_LUA_SOCKET_FT_SSL)) - { + if (ft_type & NGX_HTTP_LUA_SOCKET_FT_RESOLVER) { return 2; } + if (ft_type & NGX_HTTP_LUA_SOCKET_FT_SSL) { + return 0; + } + lua_pushnil(L); if (ft_type & NGX_HTTP_LUA_SOCKET_FT_TIMEOUT) { @@ -1724,11 +2172,7 @@ ngx_http_lua_socket_prepare_error_retvals(ngx_http_request_t *r, } else { if (u->socket_errno) { -#if defined(nginx_version) && nginx_version >= 9000 p = ngx_strerror(u->socket_errno, errstr, sizeof(errstr)); -#else - p = ngx_strerror_r(u->socket_errno, errstr, sizeof(errstr)); -#endif /* for compatibility with LuaSocket */ ngx_strlow(errstr, errstr, p - errstr); lua_pushlstring(L, (char *) errstr, p - errstr); @@ -1755,20 +2199,173 @@ ngx_http_lua_socket_tcp_conn_retval_handler(ngx_http_request_t *r, } +static int +ngx_http_lua_socket_tcp_receive_helper(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L) +{ + ngx_int_t rc; + ngx_http_lua_ctx_t *ctx; + ngx_http_lua_co_ctx_t *coctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + + if (u->bufs_in == NULL) { + u->bufs_in = + ngx_http_lua_chain_get_free_buf(r->connection->log, r->pool, + &ctx->free_recv_bufs, + u->conf->buffer_size); + + if (u->bufs_in == NULL) { + return luaL_error(L, "no memory"); + } + + u->buf_in = u->bufs_in; + u->buffer = *u->buf_in->buf; + } + + dd("tcp receive: buf_in: %p, bufs_in: %p", u->buf_in, u->bufs_in); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tcp socket read timeout: %M", u->read_timeout); + + if (u->raw_downstream || u->body_downstream) { + r->read_event_handler = ngx_http_lua_req_socket_rev_handler; + } + + u->read_waiting = 0; + u->read_co_ctx = NULL; + + ngx_http_lua_socket_tcp_read_prepare(r, u, u, L); + + rc = ngx_http_lua_socket_tcp_read(r, u); + + if (rc == NGX_ERROR) { + dd("read failed: %d", (int) u->ft_type); + rc = ngx_http_lua_socket_tcp_receive_retval_handler(r, u, L); + dd("tcp receive retval returned: %d", (int) rc); + return rc; + } + + if (rc == NGX_OK) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tcp socket receive done in a single run"); + + return ngx_http_lua_socket_tcp_receive_retval_handler(r, u, L); + } + + /* rc == NGX_AGAIN */ + + u->read_event_handler = ngx_http_lua_socket_read_handler; + + coctx = ctx->cur_co_ctx; + + ngx_http_lua_cleanup_pending_operation(coctx); + coctx->cleanup = ngx_http_lua_coctx_cleanup; + coctx->data = u; + + if (ctx->entered_content_phase) { + r->write_event_handler = ngx_http_lua_content_wev_handler; + + } else { + r->write_event_handler = ngx_http_core_run_phases; + } + + u->read_co_ctx = coctx; + u->read_waiting = 1; + u->read_prepare_retvals = ngx_http_lua_socket_tcp_receive_retval_handler; + + dd("setting data to %p, coctx:%p", u, coctx); + + if (u->raw_downstream || u->body_downstream) { + ctx->downstream = u; + } + + return lua_yield(L, 0); +} + + +static int +ngx_http_lua_socket_tcp_receiveany(lua_State *L) +{ + int n; + lua_Integer bytes; + ngx_http_request_t *r; + ngx_http_lua_loc_conf_t *llcf; + ngx_http_lua_socket_tcp_upstream_t *u; + + n = lua_gettop(L); + if (n != 2) { + return luaL_error(L, "expecting 2 arguments " + "(including the object), but got %d", n); + } + + r = ngx_http_lua_get_req(L); + if (r == NULL) { + return luaL_error(L, "no request found"); + } + + luaL_checktype(L, 1, LUA_TTABLE); + + lua_rawgeti(L, 1, SOCKET_CTX_INDEX); + u = lua_touserdata(L, -1); + + if (u == NULL || u->peer.connection == NULL || u->read_closed) { + + llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); + + if (llcf->log_socket_errors) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "attempt to receive data on a closed socket: u:%p, " + "c:%p, ft:%d eof:%d", + u, u ? u->peer.connection : NULL, + u ? (int) u->ft_type : 0, u ? (int) u->eof : 0); + } + + lua_pushnil(L); + lua_pushliteral(L, "closed"); + return 2; + } + + if (u->request != r) { + return luaL_error(L, "bad request"); + } + + ngx_http_lua_socket_check_busy_connecting(r, u, L); + ngx_http_lua_socket_check_busy_reading(r, u, L); + + if (!lua_isnumber(L, 2)) { + return luaL_argerror(L, 2, "bad max argument"); + } + + bytes = lua_tointeger(L, 2); + if (bytes <= 0) { + return luaL_argerror(L, 2, "bad max argument"); + } + + u->input_filter = ngx_http_lua_socket_read_any; + u->rest = (size_t) bytes; + u->length = u->rest; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tcp socket calling receiveany() method to read at " + "most %uz bytes", u->rest); + + return ngx_http_lua_socket_tcp_receive_helper(r, u, L); +} + + static int ngx_http_lua_socket_tcp_receive(lua_State *L) { ngx_http_request_t *r; ngx_http_lua_socket_tcp_upstream_t *u; - ngx_int_t rc; - ngx_http_lua_ctx_t *ctx; int n; ngx_str_t pat; lua_Integer bytes; char *p; int typ; ngx_http_lua_loc_conf_t *llcf; - ngx_http_lua_co_ctx_t *coctx; n = lua_gettop(L); if (n != 1 && n != 2) { @@ -1856,7 +2453,7 @@ ngx_http_lua_socket_tcp_receive(lua_State *L) case LUA_TNUMBER: bytes = lua_tointeger(L, 2); if (bytes < 0) { - return luaL_argerror(L, 2, "bad pattern argument"); + return luaL_argerror(L, 2, "bad number argument"); } #if 1 @@ -1873,7 +2470,7 @@ ngx_http_lua_socket_tcp_receive(lua_State *L) break; default: - return luaL_argerror(L, 2, "bad pattern argument"); + return luaL_argerror(L, 2, "bad argument"); break; } @@ -1883,221 +2480,162 @@ ngx_http_lua_socket_tcp_receive(lua_State *L) u->rest = 0; } - u->input_filter_ctx = u; - - ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); - - if (u->bufs_in == NULL) { - u->bufs_in = - ngx_http_lua_chain_get_free_buf(r->connection->log, r->pool, - &ctx->free_recv_bufs, - u->conf->buffer_size); - - if (u->bufs_in == NULL) { - return luaL_error(L, "no memory"); - } - - u->buf_in = u->bufs_in; - u->buffer = *u->buf_in->buf; - } - - dd("tcp receive: buf_in: %p, bufs_in: %p", u->buf_in, u->bufs_in); + return ngx_http_lua_socket_tcp_receive_helper(r, u, L); +} - if (u->raw_downstream || u->body_downstream) { - r->read_event_handler = ngx_http_lua_req_socket_rev_handler; - } - u->read_waiting = 0; - u->read_co_ctx = NULL; +static ngx_int_t +ngx_http_lua_socket_read_chunk(void *data, ssize_t bytes) +{ + ngx_int_t rc; + ngx_http_lua_socket_tcp_upstream_t *u = data; - rc = ngx_http_lua_socket_tcp_read(r, u); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, + "lua tcp socket read chunk %z", bytes); + rc = ngx_http_lua_read_bytes(&u->buffer, u->buf_in, &u->rest, + bytes, u->request->connection->log); if (rc == NGX_ERROR) { - dd("read failed: %d", (int) u->ft_type); - rc = ngx_http_lua_socket_tcp_receive_retval_handler(r, u, L); - dd("tcp receive retval returned: %d", (int) rc); - return rc; - } - - if (rc == NGX_OK) { - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua tcp socket receive done in a single run"); - - return ngx_http_lua_socket_tcp_receive_retval_handler(r, u, L); - } - - /* rc == NGX_AGAIN */ - - u->read_event_handler = ngx_http_lua_socket_read_handler; - - coctx = ctx->cur_co_ctx; - - ngx_http_lua_cleanup_pending_operation(coctx); - coctx->cleanup = ngx_http_lua_coctx_cleanup; - coctx->data = u; - - if (ctx->entered_content_phase) { - r->write_event_handler = ngx_http_lua_content_wev_handler; - - } else { - r->write_event_handler = ngx_http_core_run_phases; + u->ft_type |= NGX_HTTP_LUA_SOCKET_FT_CLOSED; + return NGX_ERROR; } - u->read_co_ctx = coctx; - u->read_waiting = 1; - u->read_prepare_retvals = ngx_http_lua_socket_tcp_receive_retval_handler; + return rc; +} - dd("setting data to %p, coctx:%p", u, coctx); - if (u->raw_downstream || u->body_downstream) { - ctx->downstream = u; - } +static ngx_int_t +ngx_http_lua_socket_read_all(void *data, ssize_t bytes) +{ + ngx_http_lua_socket_tcp_upstream_t *u = data; - return lua_yield(L, 0); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, + "lua tcp socket read all"); + return ngx_http_lua_read_all(&u->buffer, u->buf_in, bytes, + u->request->connection->log); } static ngx_int_t -ngx_http_lua_socket_read_chunk(void *data, ssize_t bytes) +ngx_http_lua_socket_read_line(void *data, ssize_t bytes) { ngx_http_lua_socket_tcp_upstream_t *u = data; - ngx_buf_t *b; -#if (NGX_DEBUG) - ngx_http_request_t *r; - - r = u->request; -#endif + ngx_int_t rc; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua tcp socket read chunk %z", bytes); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, + "lua tcp socket read line"); - if (bytes == 0) { + rc = ngx_http_lua_read_line(&u->buffer, u->buf_in, bytes, + u->request->connection->log); + if (rc == NGX_ERROR) { u->ft_type |= NGX_HTTP_LUA_SOCKET_FT_CLOSED; return NGX_ERROR; } - b = &u->buffer; - - if (bytes >= (ssize_t) u->rest) { - - u->buf_in->buf->last += u->rest; - b->pos += u->rest; - u->rest = 0; - - return NGX_OK; - } - - /* bytes < u->rest */ - - u->buf_in->buf->last += bytes; - b->pos += bytes; - u->rest -= bytes; - - return NGX_AGAIN; + return rc; } static ngx_int_t -ngx_http_lua_socket_read_all(void *data, ssize_t bytes) +ngx_http_lua_socket_read_any(void *data, ssize_t bytes) { ngx_http_lua_socket_tcp_upstream_t *u = data; - ngx_buf_t *b; -#if (NGX_DEBUG) - ngx_http_request_t *r; - - r = u->request; -#endif + ngx_int_t rc; - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua tcp socket read all"); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, + "lua tcp socket read any"); - if (bytes == 0) { - return NGX_OK; + rc = ngx_http_lua_read_any(&u->buffer, u->buf_in, &u->rest, bytes, + u->request->connection->log); + if (rc == NGX_ERROR) { + u->ft_type |= NGX_HTTP_LUA_SOCKET_FT_CLOSED; + return NGX_ERROR; } - b = &u->buffer; - - u->buf_in->buf->last += bytes; - b->pos += bytes; - - return NGX_AGAIN; + return rc; } -static ngx_int_t -ngx_http_lua_socket_read_line(void *data, ssize_t bytes) +static void +ngx_http_lua_socket_tcp_read_prepare(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, void *data, lua_State *L) { - ngx_http_lua_socket_tcp_upstream_t *u = data; + ngx_http_lua_ctx_t *ctx; + ngx_chain_t *new_cl; + ngx_buf_t *b; + off_t size; - ngx_buf_t *b; - u_char *dst; - u_char c; -#if (NGX_DEBUG) - u_char *begin; -#endif + ngx_http_lua_socket_compiled_pattern_t *cp; - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, - "lua tcp socket read line"); + /* input_filter_ctx doesn't change, no need recovering */ + if (u->input_filter_ctx == data) { + return; + } - if (bytes == 0) { - u->ft_type |= NGX_HTTP_LUA_SOCKET_FT_CLOSED; - return NGX_ERROR; + /* last input_filter_ctx is null or upstream, no data pending */ + if (u->input_filter_ctx == NULL || u->input_filter_ctx == u) { + u->input_filter_ctx = data; + return; } - b = &u->buffer; + /* compiled pattern may be with data pending */ -#if (NGX_DEBUG) - begin = b->pos; -#endif + cp = u->input_filter_ctx; + u->input_filter_ctx = data; + + cp->upstream = NULL; - dd("already read: %p: %.*s", u->buf_in, - (int) (u->buf_in->buf->last - u->buf_in->buf->pos), - u->buf_in->buf->pos); + /* no data pending */ + if (cp->state <= 0) { + return; + } - dd("data read: %.*s", (int) bytes, b->pos); + b = &u->buffer; - dst = u->buf_in->buf->last; + if (b->pos - b->start >= cp->state) { + dd("pending data in one buffer"); - while (bytes--) { + b->pos -= cp->state; - c = *b->pos++; + u->buf_in->buf->pos = b->pos; + u->buf_in->buf->last = b->pos; - switch (c) { - case '\n': - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, - "lua tcp socket read the final line part: \"%*s\"", - b->pos - 1 - begin, begin); + /* reset dfa state for future matching */ + cp->state = 0; + return; + } - u->buf_in->buf->last = dst; + dd("pending data in multiple buffers"); - dd("read a line: %p: %.*s", u->buf_in, - (int) (u->buf_in->buf->last - u->buf_in->buf->pos), - u->buf_in->buf->pos); + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); - return NGX_OK; + size = ngx_buf_size(b); - case '\r': - /* ignore it */ - break; + new_cl = + ngx_http_lua_chain_get_free_buf(r->connection->log, r->pool, + &ctx->free_recv_bufs, + cp->state + size); - default: - *dst++ = c; - break; - } + if (new_cl == NULL) { + luaL_error(L, "no memory"); + return; } -#if (NGX_DEBUG) - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, - "lua tcp socket read partial line data: %*s", - dst - begin, begin); -#endif + ngx_memcpy(b, new_cl->buf, sizeof(ngx_buf_t)); - u->buf_in->buf->last = dst; + b->last = ngx_copy(b->last, cp->pattern.data, cp->state); + b->last = ngx_copy(b->last, u->buf_in->buf->pos, size); - return NGX_AGAIN; + u->buf_in->next = ctx->free_recv_bufs; + ctx->free_recv_bufs = u->buf_in; + + u->bufs_in = new_cl; + u->buf_in = new_cl; + + /* reset dfa state for future matching */ + cp->state = 0; } @@ -2109,7 +2647,7 @@ ngx_http_lua_socket_tcp_read(ngx_http_request_t *r, ngx_connection_t *c; ngx_buf_t *b; ngx_event_t *rev; - size_t size; + off_t size; ssize_t n; unsigned read; off_t preread = 0; @@ -2122,6 +2660,14 @@ ngx_http_lua_socket_tcp_read(ngx_http_request_t *r, "lua tcp socket read data: wait:%d", (int) u->read_waiting); + /* ngx_shutdown_timer_handler will set c->close and c->error on timeout + * when worker_shutdown_timeout is configured. + * The rev->ready is false at that time, so we need to set u->eof. + */ + if (c->close && c->error) { + u->eof = 1; + } + b = &u->buffer; read = 0; @@ -2216,7 +2762,7 @@ ngx_http_lua_socket_tcp_read(ngx_http_request_t *r, } b = &u->buffer; - size = (size_t) (b->end - b->last); + size = b->end - b->last; } if (u->raw_downstream) { @@ -2224,8 +2770,8 @@ ngx_http_lua_socket_tcp_read(ngx_http_request_t *r, if (preread) { - if ((off_t) size > preread) { - size = (size_t) preread; + if (size > preread) { + size = preread; } ngx_http_lua_probe_req_socket_consume_preread(r, @@ -2266,8 +2812,8 @@ ngx_http_lua_socket_tcp_read(ngx_http_request_t *r, preread = r->request_body->rest; } - if ((off_t) size > preread) { - size = (size_t) preread; + if (size > preread) { + size = preread; } ngx_http_lua_probe_req_socket_consume_preread(r, @@ -2286,8 +2832,8 @@ ngx_http_lua_socket_tcp_read(ngx_http_request_t *r, continue; } - if (size > (size_t) r->request_body->rest) { - size = (size_t) r->request_body->rest; + if (size > r->request_body->rest) { + size = r->request_body->rest; } } @@ -2299,7 +2845,7 @@ ngx_http_lua_socket_tcp_read(ngx_http_request_t *r, #endif ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua tcp socket try to recv data %uz: \"%V?%V\"", + "lua tcp socket try to recv data %O: \"%V?%V\"", size, &r->uri, &r->args); n = c->recv(c, b->last, size); @@ -2454,14 +3000,32 @@ ngx_http_lua_socket_tcp_send(lua_State *L) type = lua_type(L, 2); switch (type) { case LUA_TNUMBER: + len = ngx_http_lua_get_num_len(L, 2); + break; + case LUA_TSTRING: lua_tolstring(L, 2, &len); break; case LUA_TTABLE: + /* The maximum possible length, not the actual length */ len = ngx_http_lua_calc_strlen_in_table(L, 2, 2, 1 /* strict */); break; + case LUA_TNIL: + len = sizeof("nil") - 1; + break; + + case LUA_TBOOLEAN: + if (lua_toboolean(L, 2)) { + len = sizeof("true") - 1; + + } else { + len = sizeof("false") - 1; + } + + break; + default: msg = lua_pushfstring(L, "string, number, boolean, nil, " "or array table expected, got %s", @@ -2488,13 +3052,39 @@ ngx_http_lua_socket_tcp_send(lua_State *L) switch (type) { case LUA_TNUMBER: + b->last = ngx_http_lua_write_num(L, 2, b->last); + break; + case LUA_TSTRING: - p = (u_char *) lua_tolstring(L, -1, &len); + p = (u_char *) lua_tolstring(L, 2, &len); b->last = ngx_copy(b->last, (u_char *) p, len); break; case LUA_TTABLE: - b->last = ngx_http_lua_copy_str_in_table(L, -1, b->last); + b->last = ngx_http_lua_copy_str_in_table(L, 2, b->last); + break; + + case LUA_TNIL: + *b->last++ = 'n'; + *b->last++ = 'i'; + *b->last++ = 'l'; + break; + + case LUA_TBOOLEAN: + if (lua_toboolean(L, 2)) { + *b->last++ = 't'; + *b->last++ = 'r'; + *b->last++ = 'u'; + *b->last++ = 'e'; + + } else { + *b->last++ = 'f'; + *b->last++ = 'a'; + *b->last++ = 'l'; + *b->last++ = 's'; + *b->last++ = 'e'; + } + break; default: @@ -2503,6 +3093,10 @@ ngx_http_lua_socket_tcp_send(lua_State *L) u->request_bufs = cl; + lua_assert(b->last - b->start <= len); + + len = b->last - b->start; + u->request_len = len; /* mimic ngx_http_upstream_init_request here */ @@ -2605,7 +3199,6 @@ ngx_http_lua_socket_tcp_receive_retval_handler(ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L) { int n; - ngx_int_t rc; ngx_http_lua_ctx_t *ctx; ngx_event_t *ev; @@ -2652,12 +3245,7 @@ ngx_http_lua_socket_tcp_receive_retval_handler(ngx_http_request_t *r, dd("u->bufs_in: %p", u->bufs_in); if (u->bufs_in) { - rc = ngx_http_lua_socket_push_input_data(r, ctx, u, L); - if (rc == NGX_ERROR) { - lua_pushnil(L); - lua_pushliteral(L, "no memory"); - return 2; - } + ngx_http_lua_socket_push_input_data(r, ctx, u, L); (void) ngx_http_lua_socket_read_error_retval_handler(r, u, L); @@ -2671,12 +3259,7 @@ ngx_http_lua_socket_tcp_receive_retval_handler(ngx_http_request_t *r, return n + 1; } - rc = ngx_http_lua_socket_push_input_data(r, ctx, u, L); - if (rc == NGX_ERROR) { - lua_pushnil(L); - lua_pushliteral(L, "no memory"); - return 2; - } + ngx_http_lua_socket_push_input_data(r, ctx, u, L); return 1; } @@ -2734,14 +3317,6 @@ ngx_http_lua_socket_tcp_close(lua_State *L) } -static int -ngx_http_lua_socket_tcp_setoption(lua_State *L) -{ - /* TODO */ - return 0; -} - - static int ngx_http_lua_socket_tcp_settimeout(lua_State *L) { @@ -2753,7 +3328,7 @@ ngx_http_lua_socket_tcp_settimeout(lua_State *L) n = lua_gettop(L); if (n != 2) { - return luaL_error(L, "ngx.socket settimout: expecting 2 arguments " + return luaL_error(L, "ngx.socket settimeout: expecting 2 arguments " "(including the object) but seen %d", lua_gettop(L)); } @@ -2800,7 +3375,7 @@ ngx_http_lua_socket_tcp_settimeouts(lua_State *L) n = lua_gettop(L); if (n != 4) { - return luaL_error(L, "ngx.socket settimout: expecting 4 arguments " + return luaL_error(L, "ngx.socket settimeouts: expecting 4 arguments " "(including the object) but seen %d", lua_gettop(L)); } @@ -2890,7 +3465,7 @@ ngx_http_lua_socket_tcp_handler(ngx_event_t *ev) static ngx_int_t ngx_http_lua_socket_tcp_get_peer(ngx_peer_connection_t *pc, void *data) { - /* empty */ + pc->type = SOCK_STREAM; return NGX_OK; } @@ -3003,12 +3578,8 @@ ngx_http_lua_socket_send(ngx_http_request_t *r, } -#if defined(nginx_version) && nginx_version >= 1001004 ngx_chain_update_chains(r->pool, -#else - ngx_chain_update_chains( -#endif - &ctx->free_bufs, &ctx->busy_bufs, + &ctx->free_bufs, &u->busy_bufs, &u->request_bufs, (ngx_buf_tag_t) &ngx_http_lua_module); @@ -3400,105 +3971,368 @@ ngx_http_lua_socket_tcp_finalize_read_part(ngx_http_request_t *r, if (ctx && u->bufs_in) { - ll = &u->bufs_in; - for (cl = u->bufs_in; cl; cl = cl->next) { - dd("bufs_in chain: %p, next %p", cl, cl->next); - cl->buf->pos = cl->buf->last; - ll = &cl->next; - } + ll = &u->bufs_in; + for (cl = u->bufs_in; cl; cl = cl->next) { + dd("bufs_in chain: %p, next %p", cl, cl->next); + cl->buf->pos = cl->buf->last; + ll = &cl->next; + } + + dd("ctx: %p", ctx); + dd("free recv bufs: %p", ctx->free_recv_bufs); + *ll = ctx->free_recv_bufs; + ctx->free_recv_bufs = u->bufs_in; + u->bufs_in = NULL; + u->buf_in = NULL; + ngx_memzero(&u->buffer, sizeof(ngx_buf_t)); + } + + if (u->raw_downstream || u->body_downstream) { + if (r->connection->read->timer_set) { + ngx_del_timer(r->connection->read); + } + return; + } + + c = u->peer.connection; + + if (c) { + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (c->read->active || c->read->disabled) { + ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT); + } + +#if (nginx_version >= 1007005) + if (c->read->posted) { +#else + if (c->read->prev) { +#endif + ngx_delete_posted_event(c->read); + } + + c->read->closed = 1; + + /* TODO: shutdown the reading part of the connection */ + } +} + + +static void +ngx_http_lua_socket_tcp_finalize_write_part(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u) +{ + ngx_connection_t *c; + ngx_http_lua_ctx_t *ctx; + + if (u->write_closed) { + return; + } + + u->write_closed = 1; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + + if (u->raw_downstream || u->body_downstream) { + if (ctx && ctx->writing_raw_req_socket) { + ctx->writing_raw_req_socket = 0; + if (r->connection->write->timer_set) { + ngx_del_timer(r->connection->write); + } + + r->connection->write->error = 1; + } + return; + } + + c = u->peer.connection; + + if (c) { + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + if (c->write->active || c->write->disabled) { + ngx_del_event(c->write, NGX_WRITE_EVENT, NGX_CLOSE_EVENT); + } + +#if (nginx_version >= 1007005) + if (c->write->posted) { +#else + if (c->write->prev) { +#endif + ngx_delete_posted_event(c->write); + } + + c->write->closed = 1; + + /* TODO: shutdown the writing part of the connection */ + } +} + + +static void +ngx_http_lua_socket_tcp_conn_op_timeout_handler(ngx_event_t *ev) +{ + ngx_http_lua_socket_tcp_upstream_t *u; + ngx_http_lua_ctx_t *ctx; + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_lua_co_ctx_t *coctx; + ngx_http_lua_loc_conf_t *llcf; + ngx_http_lua_socket_tcp_conn_op_ctx_t *conn_op_ctx; + + conn_op_ctx = ev->data; + ngx_queue_remove(&conn_op_ctx->queue); + + u = conn_op_ctx->u; + r = u->request; + + coctx = u->write_co_ctx; + coctx->cleanup = NULL; + /* note that we store conn_op_ctx in coctx->data instead of u */ + coctx->data = conn_op_ctx; + u->write_co_ctx = NULL; + + llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); + + if (llcf->log_socket_errors) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "lua tcp socket queued connect timed out," + " when trying to connect to %V:%ud", + &conn_op_ctx->host, conn_op_ctx->port); + } + + ngx_queue_insert_head(&u->socket_pool->cache_connect_op, + &conn_op_ctx->queue); + u->socket_pool->connections--; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + return; + } + + ctx->cur_co_ctx = coctx; + + ngx_http_lua_assert(coctx && (!ngx_http_lua_is_thread(ctx) + || coctx->co_ref >= 0)); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tcp socket waking up the current request"); + + u->write_prepare_retvals = + ngx_http_lua_socket_tcp_conn_op_timeout_retval_handler; + + c = r->connection; + + if (ctx->entered_content_phase) { + (void) ngx_http_lua_socket_tcp_conn_op_resume(r); + + } else { + ctx->resume_handler = ngx_http_lua_socket_tcp_conn_op_resume; + ngx_http_core_run_phases(r); + } + + ngx_http_run_posted_requests(c); +} + + +static int +ngx_http_lua_socket_tcp_conn_op_timeout_retval_handler(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L) +{ + lua_pushnil(L); + lua_pushliteral(L, "timeout"); + return 2; +} + + +static void +ngx_http_lua_socket_tcp_resume_conn_op(ngx_http_lua_socket_pool_t *spool) +{ + ngx_queue_t *q; + ngx_http_lua_socket_tcp_conn_op_ctx_t *conn_op_ctx; + +#if (NGX_DEBUG) + ngx_http_lua_assert(spool->connections >= 0); - dd("ctx: %p", ctx); - dd("free recv bufs: %p", ctx->free_recv_bufs); - *ll = ctx->free_recv_bufs; - ctx->free_recv_bufs = u->bufs_in; - u->bufs_in = NULL; - u->buf_in = NULL; - ngx_memzero(&u->buffer, sizeof(ngx_buf_t)); +#else + if (spool->connections < 0) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "lua tcp socket connections count mismatched for " + "connection pool \"%s\", connections: %i, size: %i", + spool->key, spool->connections, spool->size); + spool->connections = 0; } +#endif - if (u->raw_downstream || u->body_downstream) { - if (r->connection->read->timer_set) { - ngx_del_timer(r->connection->read); - } + /* we manually destroy wait_connect_op before triggering connect + * operation resumption, so that there is no resumption happens when Nginx + * is exiting. + */ + if (ngx_queue_empty(&spool->wait_connect_op)) { return; } - c = u->peer.connection; + q = ngx_queue_head(&spool->wait_connect_op); + conn_op_ctx = ngx_queue_data(q, ngx_http_lua_socket_tcp_conn_op_ctx_t, + queue); + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua tcp socket post connect operation resumption " + "u: %p, ctx: %p for connection pool \"%s\", " + "connections: %i", + conn_op_ctx->u, conn_op_ctx, spool->key, spool->connections); - if (c) { - if (c->read->timer_set) { - ngx_del_timer(c->read); - } + if (conn_op_ctx->event.timer_set) { + ngx_del_timer(&conn_op_ctx->event); + } - if (c->read->active || c->read->disabled) { - ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT); - } + conn_op_ctx->event.handler = + ngx_http_lua_socket_tcp_conn_op_resume_handler; -#if defined(nginx_version) && nginx_version >= 1007005 - if (c->read->posted) { -#else - if (c->read->prev) { -#endif - ngx_delete_posted_event(c->read); - } + ngx_post_event((&conn_op_ctx->event), &ngx_posted_events); +} - c->read->closed = 1; - /* TODO: shutdown the reading part of the connection */ - } +static void +ngx_http_lua_socket_tcp_conn_op_ctx_cleanup(void *data) +{ + ngx_http_lua_socket_tcp_upstream_t *u; + ngx_http_lua_socket_tcp_conn_op_ctx_t *conn_op_ctx = data; + + u = conn_op_ctx->u; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, + "cleanup lua tcp socket conn_op_ctx: %p, u: %p, " + "request: \"%V\"", + conn_op_ctx, u, &u->request->uri); + + ngx_queue_insert_head(&u->socket_pool->cache_connect_op, + &conn_op_ctx->queue); } static void -ngx_http_lua_socket_tcp_finalize_write_part(ngx_http_request_t *r, - ngx_http_lua_socket_tcp_upstream_t *u) +ngx_http_lua_socket_tcp_conn_op_resume_handler(ngx_event_t *ev) { - ngx_connection_t *c; - ngx_http_lua_ctx_t *ctx; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_http_lua_ctx_t *ctx; + ngx_http_request_t *r; + ngx_http_cleanup_t *cln; + ngx_http_lua_co_ctx_t *coctx; + ngx_http_lua_socket_pool_t *spool; + ngx_http_lua_socket_tcp_upstream_t *u; + ngx_http_lua_socket_tcp_conn_op_ctx_t *conn_op_ctx; + + conn_op_ctx = ev->data; + u = conn_op_ctx->u; + r = u->request; + spool = u->socket_pool; + + if (ngx_queue_empty(&spool->wait_connect_op)) { +#if (NGX_DEBUG) + ngx_http_lua_assert(!(spool->backlog >= 0 + && spool->connections > spool->size)); + +#else + if (spool->backlog >= 0 && spool->connections > spool->size) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "lua tcp socket connections count mismatched for " + "connection pool \"%s\", connections: %i, size: %i", + spool->key, spool->connections, spool->size); + spool->connections = spool->size; + } +#endif - if (u->write_closed) { return; } - u->write_closed = 1; - - ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + q = ngx_queue_head(&spool->wait_connect_op); + ngx_queue_remove(q); - if (u->raw_downstream || u->body_downstream) { - if (ctx && ctx->writing_raw_req_socket) { - ctx->writing_raw_req_socket = 0; - if (r->connection->write->timer_set) { - ngx_del_timer(r->connection->write); - } + coctx = u->write_co_ctx; + coctx->cleanup = NULL; + /* note that we store conn_op_ctx in coctx->data instead of u */ + coctx->data = conn_op_ctx; + /* clear ngx_http_lua_tcp_queue_conn_op_cleanup */ + u->write_co_ctx = NULL; - r->connection->write->error = 1; - } + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + ngx_queue_insert_head(&spool->cache_connect_op, + &conn_op_ctx->queue); return; } - c = u->peer.connection; + ctx->cur_co_ctx = coctx; - if (c) { - if (c->write->timer_set) { - ngx_del_timer(c->write); - } + ngx_http_lua_assert(coctx && (!ngx_http_lua_is_thread(ctx) + || coctx->co_ref >= 0)); - if (c->write->active || c->write->disabled) { - ngx_del_event(c->write, NGX_WRITE_EVENT, NGX_CLOSE_EVENT); - } + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tcp socket waking up the current request"); -#if defined(nginx_version) && nginx_version >= 1007005 - if (c->write->posted) { -#else - if (c->write->prev) { -#endif - ngx_delete_posted_event(c->write); + u->write_prepare_retvals = + ngx_http_lua_socket_tcp_conn_op_resume_retval_handler; + + c = r->connection; + + if (ctx->entered_content_phase) { + (void) ngx_http_lua_socket_tcp_conn_op_resume(r); + + } else { + cln = ngx_http_lua_cleanup_add(r, 0); + if (cln != NULL) { + cln->handler = ngx_http_lua_socket_tcp_conn_op_ctx_cleanup; + cln->data = conn_op_ctx; + conn_op_ctx->cleanup = &cln->handler; } - c->write->closed = 1; + ctx->resume_handler = ngx_http_lua_socket_tcp_conn_op_resume; + ngx_http_core_run_phases(r); + } - /* TODO: shutdown the writing part of the connection */ + ngx_http_run_posted_requests(c); +} + + +static int +ngx_http_lua_socket_tcp_conn_op_resume_retval_handler(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L) +{ + int nret; + ngx_http_lua_ctx_t *ctx; + ngx_http_lua_co_ctx_t *coctx; + ngx_http_lua_socket_tcp_conn_op_ctx_t *conn_op_ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + return NGX_ERROR; + } + + coctx = ctx->cur_co_ctx; + dd("coctx: %p", coctx); + conn_op_ctx = coctx->data; + if (conn_op_ctx->cleanup != NULL) { + *conn_op_ctx->cleanup = NULL; + ngx_http_lua_cleanup_free(r, conn_op_ctx->cleanup); + conn_op_ctx->cleanup = NULL; } + + /* decrease pending connect operation counter */ + u->socket_pool->connections--; + + nret = ngx_http_lua_socket_tcp_connect_helper(L, u, r, ctx, + conn_op_ctx->host.data, + conn_op_ctx->host.len, + conn_op_ctx->port, 1); + ngx_queue_insert_head(&u->socket_pool->cache_connect_op, + &conn_op_ctx->queue); + + return nret; } @@ -3523,6 +4357,11 @@ ngx_http_lua_socket_tcp_finalize(ngx_http_request_t *r, ngx_http_lua_socket_tcp_finalize_read_part(r, u); ngx_http_lua_socket_tcp_finalize_write_part(r, u); + if (u->input_filter_ctx != NULL && u->input_filter_ctx != u) { + ((ngx_http_lua_socket_compiled_pattern_t *) + u->input_filter_ctx)->upstream = NULL; + } + if (u->raw_downstream || u->body_downstream) { u->peer.connection = NULL; return; @@ -3552,21 +4391,21 @@ ngx_http_lua_socket_tcp_finalize(ngx_http_request_t *r, ngx_http_lua_socket_tcp_close_connection(c); u->peer.connection = NULL; - - if (!u->reused) { - return; - } + u->conn_closed = 1; spool = u->socket_pool; if (spool == NULL) { return; } - spool->active_connections--; + spool->connections--; - if (spool->active_connections == 0) { + if (spool->connections == 0) { ngx_http_lua_socket_free_pool(r->connection->log, spool); + return; } + + ngx_http_lua_socket_tcp_resume_conn_op(spool); } } @@ -3738,7 +4577,8 @@ ngx_http_lua_socket_tcp_receiveuntil(lua_State *L) return luaL_error(L, "no memory"); } - lua_pushlightuserdata(L, &ngx_http_lua_pattern_udata_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + pattern_udata_metatable_key)); lua_rawget(L, LUA_REGISTRYINDEX); lua_setmetatable(L, -2); @@ -3775,7 +4615,7 @@ ngx_http_lua_socket_receiveuntil_iterator(lua_State *L) n = lua_gettop(L); if (n > 1) { - return luaL_error(L, "expecting 0 or 1 arguments, " + return luaL_error(L, "expecting 0 or 1 argument, " "but seen %d", n); } @@ -3838,8 +4678,6 @@ ngx_http_lua_socket_receiveuntil_iterator(lua_State *L) (u_char *) lua_tolstring(L, lua_upvalueindex(2), &cp->pattern.len); - u->input_filter_ctx = cp; - ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); if (u->bufs_in == NULL) { @@ -3866,6 +4704,8 @@ ngx_http_lua_socket_receiveuntil_iterator(lua_State *L) u->read_waiting = 0; u->read_co_ctx = NULL; + ngx_http_lua_socket_tcp_read_prepare(r, u, cp, L); + rc = ngx_http_lua_socket_tcp_read(r, u); if (rc == NGX_ERROR) { @@ -4023,6 +4863,7 @@ ngx_http_lua_socket_read_until(void *data, ssize_t bytes) u_char c; u_char *pat; size_t pat_len; + size_t pending_len; int i; int state; int old_state = 0; /* just to make old @@ -4151,11 +4992,12 @@ ngx_http_lua_socket_read_until(void *data, ssize_t bytes) /* matched */ - dd("adding pending data: %.*s", (int) (old_state + 1 - state), - (char *) pat); + pending_len = old_state + 1 - state; + + dd("adding pending data: %.*s", (int) pending_len, (char *) pat); rc = ngx_http_lua_socket_add_pending_data(r, u, b->pos, i, pat, - old_state + 1 - state, + pending_len, old_state); if (rc != NGX_OK) { @@ -4166,14 +5008,14 @@ ngx_http_lua_socket_read_until(void *data, ssize_t bytes) i++; if (u->length) { - if (u->rest <= (size_t) state) { + if (u->rest <= pending_len) { u->rest = 0; cp->state = state; b->pos += i; return NGX_OK; } else { - u->rest -= state; + u->rest -= pending_len; } } @@ -4192,13 +5034,24 @@ ngx_http_lua_socket_cleanup_compiled_pattern(lua_State *L) { ngx_http_lua_socket_compiled_pattern_t *cp; - ngx_http_lua_dfa_edge_t *edge, *p; - unsigned i; + ngx_http_lua_socket_tcp_upstream_t *u; + ngx_http_lua_dfa_edge_t *edge, *p; + unsigned i; dd("cleanup compiled pattern"); cp = lua_touserdata(L, 1); - if (cp == NULL || cp->recovering == NULL) { + if (cp == NULL) { + return 0; + } + + u = cp->upstream; + if (u != NULL) { + ngx_http_lua_socket_tcp_read_prepare(u->request, u, NULL, L); + u->input_filter_ctx = NULL; + } + + if (cp->recovering == NULL) { return 0; } @@ -4252,7 +5105,7 @@ ngx_http_lua_req_socket(lua_State *L) lua_pop(L, 1); } else { - return luaL_error(L, "expecting zero arguments, but got %d", + return luaL_error(L, "expecting 0 or 1 argument, but got %d", lua_gettop(L)); } @@ -4275,13 +5128,17 @@ ngx_http_lua_req_socket(lua_State *L) } #endif -#if nginx_version >= 1003009 +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + return luaL_error(L, "http v3 not supported yet"); + } +#endif + if (!raw && r->headers_in.chunked) { lua_pushnil(L); lua_pushliteral(L, "chunked request bodies not supported yet"); return 2; } -#endif ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); if (ctx == NULL) { @@ -4289,17 +5146,13 @@ ngx_http_lua_req_socket(lua_State *L) } ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE + | NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE | NGX_HTTP_LUA_CONTEXT_ACCESS | NGX_HTTP_LUA_CONTEXT_CONTENT); c = r->connection; if (raw) { -#if !defined(nginx_version) || nginx_version < 1003013 - lua_pushnil(L); - lua_pushliteral(L, "nginx version too old"); - return 2; -#else if (r->request_body) { if (r->request_body->rest > 0) { lua_pushnil(L); @@ -4313,6 +5166,7 @@ ngx_http_lua_req_socket(lua_State *L) if (rb == NULL) { return luaL_error(L, "no memory"); } + r->request_body = rb; } @@ -4347,7 +5201,6 @@ ngx_http_lua_req_socket(lua_State *L) ctx->acquired_raw_req_socket = 1; r->keepalive = 0; r->lingering_close = 1; -#endif } else { /* request body reader */ @@ -4390,13 +5243,15 @@ ngx_http_lua_req_socket(lua_State *L) r->request_body = rb; } - lua_createtable(L, 3 /* narr */, 1 /* nrec */); /* the object */ + lua_createtable(L, 2 /* narr */, 3 /* nrec */); /* the object */ if (raw) { - lua_pushlightuserdata(L, &ngx_http_lua_raw_req_socket_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + raw_req_socket_metatable_key)); } else { - lua_pushlightuserdata(L, &ngx_http_lua_req_socket_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + req_socket_metatable_key)); } lua_rawget(L, LUA_REGISTRYINDEX); @@ -4408,7 +5263,8 @@ ngx_http_lua_req_socket(lua_State *L) } #if 1 - lua_pushlightuserdata(L, &ngx_http_lua_downstream_udata_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + downstream_udata_metatable_key)); lua_rawget(L, LUA_REGISTRYINDEX); lua_setmetatable(L, -2); #endif @@ -4499,6 +5355,7 @@ ngx_http_lua_req_socket_rev_handler(ngx_http_request_t *r) u->read_event_handler(r, u); } + static int ngx_http_lua_socket_tcp_getreusedtimes(lua_State *L) { @@ -4535,20 +5392,18 @@ ngx_http_lua_socket_tcp_setkeepalive(lua_State *L) ngx_http_lua_socket_tcp_upstream_t *u; ngx_connection_t *c; ngx_http_lua_socket_pool_t *spool; - size_t size, key_len; ngx_str_t key; - ngx_uint_t i; ngx_queue_t *q; ngx_peer_connection_t *pc; - u_char *p; ngx_http_request_t *r; ngx_msec_t timeout; - ngx_uint_t pool_size; + ngx_int_t pool_size; int n; ngx_int_t rc; ngx_buf_t *b; + const char *msg; - ngx_http_lua_socket_pool_item_t *items, *item; + ngx_http_lua_socket_pool_item_t *item; n = lua_gettop(L); @@ -4559,15 +5414,32 @@ ngx_http_lua_socket_tcp_setkeepalive(lua_State *L) luaL_checktype(L, 1, LUA_TTABLE); - lua_pushlightuserdata(L, &ngx_http_lua_socket_pool_key); - lua_rawget(L, LUA_REGISTRYINDEX); + r = ngx_http_lua_get_req(L); + if (r == NULL) { + return luaL_error(L, "no request found"); + } - lua_rawgeti(L, 1, SOCKET_KEY_INDEX); - key.data = (u_char *) lua_tolstring(L, -1, &key.len); - if (key.data == NULL) { - lua_pushnil(L); - lua_pushliteral(L, "key not found"); - return 2; + llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); + + /* luaL_checkinteger will throw error if the argument is not a number. + * e.g.: bad argument \#2 to '?' (number expected, got string) + * + * We should check the argument in advance; otherwise, + * throwing an exception in the middle can compromise data integrity. + * e.g.: set pc->connection to NULL without following cleanup. + */ + if (n >= 2 && !lua_isnil(L, 2)) { + timeout = (ngx_msec_t) luaL_checkinteger(L, 2); + + } else { + timeout = llcf->keepalive_timeout; + } + + if (n >= 3 && !lua_isnil(L, 3)) { + pool_size = luaL_checkinteger(L, 3); + + } else { + pool_size = llcf->pool_size; } lua_rawgeti(L, 1, SOCKET_CTX_INDEX); @@ -4580,22 +5452,22 @@ ngx_http_lua_socket_tcp_setkeepalive(lua_State *L) return 2; } - /* stack: obj cache key */ + /* stack: obj timeout? size? */ pc = &u->peer; c = pc->connection; - if (c == NULL || u->read_closed || u->write_closed) { + /* When the server closes the connection, + * epoll will return EPOLLRDHUP event and nginx will set pending_eof. + */ + if (c == NULL || u->read_closed || u->write_closed + || c->read->eof || c->read->pending_eof) + { lua_pushnil(L); lua_pushliteral(L, "closed"); return 2; } - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request found"); - } - if (u->request != r) { return luaL_error(L, "bad request"); } @@ -4615,8 +5487,7 @@ ngx_http_lua_socket_tcp_setkeepalive(lua_State *L) return 2; } - if (c->read->eof - || c->read->error + if (c->read->error || c->read->timedout || c->write->error || c->write->timedout) @@ -4632,9 +5503,32 @@ ngx_http_lua_socket_tcp_setkeepalive(lua_State *L) return 2; } + if (ngx_terminate || ngx_exiting) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "lua tcp socket set keepalive while process exiting, " + "closing connection %p", c); + + ngx_http_lua_socket_tcp_finalize(r, u); + lua_pushinteger(L, 1); + return 1; + } + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, "lua tcp socket set keepalive: saving connection %p", c); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask(socket_pool_key)); + lua_rawget(L, LUA_REGISTRYINDEX); + + /* stack: obj timeout? size? pools */ + + lua_rawgeti(L, 1, SOCKET_KEY_INDEX); + key.data = (u_char *) lua_tolstring(L, -1, &key.len); + if (key.data == NULL) { + lua_pushnil(L); + lua_pushliteral(L, "key not found"); + return 2; + } + dd("saving connection to key %s", lua_tostring(L, -1)); lua_pushvalue(L, -1); @@ -4642,96 +5536,53 @@ ngx_http_lua_socket_tcp_setkeepalive(lua_State *L) spool = lua_touserdata(L, -1); lua_pop(L, 1); - /* stack: obj timeout? size? cache key */ - - llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); + /* stack: obj timeout? size? pools cache_key */ if (spool == NULL) { /* create a new socket pool for the current peer key */ - - if (n >= 3 && !lua_isnil(L, 3)) { - pool_size = luaL_checkinteger(L, 3); - - } else { - pool_size = llcf->pool_size; - } - - if (pool_size == 0) { - lua_pushnil(L); - lua_pushliteral(L, "zero pool size"); - return 2; - } - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua tcp socket connection pool size: %ui", pool_size); - - key_len = ngx_align(key.len + 1, sizeof(void *)); - - size = sizeof(ngx_http_lua_socket_pool_t) + key_len - 1 - + sizeof(ngx_http_lua_socket_pool_item_t) - * pool_size; - - spool = lua_newuserdata(L, size); - if (spool == NULL) { - return luaL_error(L, "no memory"); + if (pool_size <= 0) { + msg = lua_pushfstring(L, "bad \"pool_size\" option value: %d", + pool_size); + return luaL_argerror(L, n, msg); } - lua_pushlightuserdata(L, &ngx_http_lua_pool_udata_metatable_key); - lua_rawget(L, LUA_REGISTRYINDEX); - lua_setmetatable(L, -2); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, - "lua tcp socket keepalive create connection pool for key" - " \"%s\"", lua_tostring(L, -2)); - - lua_rawset(L, -3); - - spool->active_connections = 0; - spool->lua_vm = ngx_http_lua_get_lua_vm(r, NULL); - - ngx_queue_init(&spool->cache); - ngx_queue_init(&spool->free); - - p = ngx_copy(spool->key, key.data, key.len); - *p++ = '\0'; - - items = (ngx_http_lua_socket_pool_item_t *) (spool->key + key_len); - - dd("items: %p", items); - - ngx_http_lua_assert((void *) items == ngx_align_ptr(items, - sizeof(void *))); - - for (i = 0; i < pool_size; i++) { - ngx_queue_insert_head(&spool->free, &items[i].queue); - items[i].socket_pool = spool; - } + ngx_http_lua_socket_tcp_create_socket_pool(L, r, key, pool_size, -1, + &spool); } if (ngx_queue_empty(&spool->free)) { q = ngx_queue_last(&spool->cache); ngx_queue_remove(q); - spool->active_connections--; item = ngx_queue_data(q, ngx_http_lua_socket_pool_item_t, queue); ngx_http_lua_socket_tcp_close_connection(item->connection); + /* only decrease the counter for connections which were counted */ + if (u->socket_pool != NULL) { + u->socket_pool->connections--; + } + } else { q = ngx_queue_head(&spool->free); ngx_queue_remove(q); item = ngx_queue_data(q, ngx_http_lua_socket_pool_item_t, queue); + + /* we should always increase connections after getting connected, + * and decrease connections after getting closed. + * however, we don't create connection pool in previous connect method. + * so we increase connections here for backward compatibility. + */ + if (u->socket_pool == NULL) { + spool->connections++; + } } item->connection = c; ngx_queue_insert_head(&spool->cache, q); - if (!u->reused) { - spool->active_connections++; - } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "lua tcp socket clear current socket connection"); @@ -4752,13 +5603,6 @@ ngx_http_lua_socket_tcp_setkeepalive(lua_State *L) ngx_del_timer(c->write); } - if (n >= 2 && !lua_isnil(L, 2)) { - timeout = (ngx_msec_t) luaL_checkinteger(L, 2); - - } else { - timeout = llcf->keepalive_timeout; - } - #if (NGX_DEBUG) if (timeout == 0) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -4786,10 +5630,13 @@ ngx_http_lua_socket_tcp_setkeepalive(lua_State *L) item->socklen = pc->socklen; ngx_memcpy(&item->sockaddr, pc->sockaddr, pc->socklen); item->reused = u->reused; + item->udata_queue = u->udata_queue; + u->udata_queue = NULL; if (c->read->ready) { rc = ngx_http_lua_socket_keepalive_close_handler(c->read); if (rc != NGX_OK) { + ngx_http_lua_socket_tcp_finalize(r, u); lua_pushnil(L); lua_pushliteral(L, "connection in dubious state"); return 2; @@ -4800,48 +5647,33 @@ ngx_http_lua_socket_tcp_setkeepalive(lua_State *L) ngx_http_lua_socket_tcp_finalize(r, u); #endif + /* since we set u->peer->connection to NULL previously, the connect + * operation won't be resumed in the ngx_http_lua_socket_tcp_finalize. + * Therefore we need to resume it here. + */ + ngx_http_lua_socket_tcp_resume_conn_op(spool); + lua_pushinteger(L, 1); return 1; } static ngx_int_t -ngx_http_lua_get_keepalive_peer(ngx_http_request_t *r, lua_State *L, - int key_index, ngx_http_lua_socket_tcp_upstream_t *u) +ngx_http_lua_get_keepalive_peer(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u) { ngx_http_lua_socket_pool_item_t *item; ngx_http_lua_socket_pool_t *spool; ngx_http_cleanup_t *cln; ngx_queue_t *q; - int top; ngx_peer_connection_t *pc; ngx_connection_t *c; - top = lua_gettop(L); - - if (key_index < 0) { - key_index = top + key_index + 1; - } - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua tcp socket pool get keepalive peer"); - - pc = &u->peer; - - lua_pushlightuserdata(L, &ngx_http_lua_socket_pool_key); - lua_rawget(L, LUA_REGISTRYINDEX); /* table */ - lua_pushvalue(L, key_index); /* key */ - lua_rawget(L, -2); - - spool = lua_touserdata(L, -1); - if (spool == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, - "lua tcp socket keepalive connection pool not found"); - lua_settop(L, top); - return NGX_DECLINED; - } + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tcp socket pool get keepalive peer"); - u->socket_pool = spool; + pc = &u->peer; + spool = u->socket_pool; if (!ngx_queue_empty(&spool->cache)) { q = ngx_queue_head(&spool->cache); @@ -4876,6 +5708,8 @@ ngx_http_lua_get_keepalive_peer(ngx_http_request_t *r, lua_State *L, pc->cached = 1; u->reused = item->reused + 1; + u->udata_queue = item->udata_queue; + item->udata_queue = NULL; #if 1 u->write_event_handler = ngx_http_lua_socket_dummy_handler; @@ -4886,7 +5720,6 @@ ngx_http_lua_get_keepalive_peer(ngx_http_request_t *r, lua_State *L, cln = ngx_http_lua_cleanup_add(r, 0); if (cln == NULL) { u->ft_type |= NGX_HTTP_LUA_SOCKET_FT_ERROR; - lua_settop(L, top); return NGX_ERROR; } @@ -4895,16 +5728,12 @@ ngx_http_lua_get_keepalive_peer(ngx_http_request_t *r, lua_State *L, u->cleanup = &cln->handler; } - lua_settop(L, top); - return NGX_OK; } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "lua tcp socket keepalive: connection pool empty"); - lua_settop(L, top); - return NGX_DECLINED; } @@ -4931,7 +5760,7 @@ ngx_http_lua_socket_keepalive_close_handler(ngx_event_t *ev) ngx_http_lua_socket_pool_t *spool; int n; - char buf[1]; + unsigned char buf[1]; ngx_connection_t *c; c = ev->data; @@ -4952,9 +5781,10 @@ ngx_http_lua_socket_keepalive_close_handler(ngx_event_t *ev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, "lua tcp socket keepalive close handler check stale events"); - n = recv(c->fd, buf, 1, MSG_PEEK); + /* consume the possible ssl-layer data implicitly */ + n = c->recv(c, buf, 1); - if (n == -1 && ngx_socket_errno == NGX_EAGAIN) { + if (n == NGX_AGAIN) { /* stale event */ if (ngx_handle_read_event(c->read, 0) != NGX_OK) { @@ -4976,13 +5806,15 @@ ngx_http_lua_socket_keepalive_close_handler(ngx_event_t *ev) ngx_queue_remove(&item->queue); ngx_queue_insert_head(&spool->free, &item->queue); - spool->active_connections--; + spool->connections--; - dd("keepalive: active connections: %u", - (unsigned) spool->active_connections); + dd("keepalive: connections: %u", (unsigned) spool->connections); - if (spool->active_connections == 0) { + if (spool->connections == 0) { ngx_http_lua_socket_free_pool(ev->log, spool); + + } else { + ngx_http_lua_socket_tcp_resume_conn_op(spool); } return NGX_DECLINED; @@ -5000,7 +5832,7 @@ ngx_http_lua_socket_free_pool(ngx_log_t *log, ngx_http_lua_socket_pool_t *spool) L = spool->lua_vm; - lua_pushlightuserdata(L, &ngx_http_lua_socket_pool_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask(socket_pool_key)); lua_rawget(L, LUA_REGISTRYINDEX); lua_pushstring(L, (char *) spool->key); lua_pushnil(L); @@ -5012,9 +5844,10 @@ ngx_http_lua_socket_free_pool(ngx_log_t *log, ngx_http_lua_socket_pool_t *spool) static void ngx_http_lua_socket_shutdown_pool_helper(ngx_http_lua_socket_pool_t *spool) { - ngx_queue_t *q; - ngx_connection_t *c; - ngx_http_lua_socket_pool_item_t *item; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_http_lua_socket_pool_item_t *item; + ngx_http_lua_socket_tcp_conn_op_ctx_t *conn_op_ctx; while (!ngx_queue_empty(&spool->cache)) { q = ngx_queue_head(&spool->cache); @@ -5028,7 +5861,29 @@ ngx_http_lua_socket_shutdown_pool_helper(ngx_http_lua_socket_pool_t *spool) ngx_queue_insert_head(&spool->free, q); } - spool->active_connections = 0; + while (!ngx_queue_empty(&spool->cache_connect_op)) { + q = ngx_queue_head(&spool->cache_connect_op); + ngx_queue_remove(q); + conn_op_ctx = ngx_queue_data(q, ngx_http_lua_socket_tcp_conn_op_ctx_t, + queue); + ngx_http_lua_socket_tcp_free_conn_op_ctx(conn_op_ctx); + } + + while (!ngx_queue_empty(&spool->wait_connect_op)) { + q = ngx_queue_head(&spool->wait_connect_op); + ngx_queue_remove(q); + conn_op_ctx = ngx_queue_data(q, ngx_http_lua_socket_tcp_conn_op_ctx_t, + queue); + + if (conn_op_ctx->event.timer_set) { + ngx_del_timer(&conn_op_ctx->event); + } + + ngx_http_lua_socket_tcp_free_conn_op_ctx(conn_op_ctx); + } + + /* spool->connections will be decreased down to zero in + * ngx_http_lua_socket_tcp_finalize */ } @@ -5088,7 +5943,7 @@ ngx_http_lua_socket_downstream_destroy(lua_State *L) } -static ngx_int_t +static void ngx_http_lua_socket_push_input_data(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx, ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L) @@ -5160,8 +6015,6 @@ ngx_http_lua_socket_push_input_data(ngx_http_request_t *r, u->buf_in->buf->last = u->buffer.pos; u->buf_in->buf->pos = u->buffer.pos; } - - return NGX_OK; } @@ -5282,6 +6135,13 @@ static ngx_int_t ngx_http_lua_socket_insert_buffer(ngx_http_request_t *r, } +static ngx_int_t +ngx_http_lua_socket_tcp_conn_op_resume(ngx_http_request_t *r) +{ + return ngx_http_lua_socket_tcp_resume_helper(r, SOCKET_OP_RESUME_CONN); +} + + static ngx_int_t ngx_http_lua_socket_tcp_conn_resume(ngx_http_request_t *r) { @@ -5306,13 +6166,14 @@ ngx_http_lua_socket_tcp_write_resume(ngx_http_request_t *r) static ngx_int_t ngx_http_lua_socket_tcp_resume_helper(ngx_http_request_t *r, int socket_op) { - int nret; - lua_State *vm; - ngx_int_t rc; - ngx_uint_t nreqs; - ngx_connection_t *c; - ngx_http_lua_ctx_t *ctx; - ngx_http_lua_co_ctx_t *coctx; + int nret; + lua_State *vm; + ngx_int_t rc; + ngx_uint_t nreqs; + ngx_connection_t *c; + ngx_http_lua_ctx_t *ctx; + ngx_http_lua_co_ctx_t *coctx; + ngx_http_lua_socket_tcp_conn_op_ctx_t *conn_op_ctx; ngx_http_lua_socket_tcp_retval_handler prepare_retvals; @@ -5332,15 +6193,22 @@ ngx_http_lua_socket_tcp_resume_helper(ngx_http_request_t *r, int socket_op) dd("coctx: %p", coctx); - u = coctx->data; - switch (socket_op) { + + case SOCKET_OP_RESUME_CONN: + conn_op_ctx = coctx->data; + u = conn_op_ctx->u; + prepare_retvals = u->write_prepare_retvals; + break; + case SOCKET_OP_CONNECT: case SOCKET_OP_WRITE: + u = coctx->data; prepare_retvals = u->write_prepare_retvals; break; case SOCKET_OP_READ: + u = coctx->data; prepare_retvals = u->read_prepare_retvals; break; @@ -5354,6 +6222,15 @@ ngx_http_lua_socket_tcp_resume_helper(ngx_http_request_t *r, int socket_op) "u:%p", prepare_retvals, u); nret = prepare_retvals(r, u, ctx->cur_co_ctx->co); + if (socket_op == SOCKET_OP_CONNECT + && nret > 1 + && !u->conn_closed + && u->socket_pool != NULL) + { + u->socket_pool->connections--; + ngx_http_lua_socket_tcp_resume_conn_op(u->socket_pool); + } + if (nret == NGX_AGAIN) { return NGX_DONE; } @@ -5385,6 +6262,45 @@ ngx_http_lua_socket_tcp_resume_helper(ngx_http_request_t *r, int socket_op) } +static void +ngx_http_lua_tcp_queue_conn_op_cleanup(void *data) +{ + ngx_http_lua_co_ctx_t *coctx = data; + ngx_http_lua_socket_tcp_upstream_t *u; + ngx_http_lua_socket_tcp_conn_op_ctx_t *conn_op_ctx; + + conn_op_ctx = coctx->data; + u = conn_op_ctx->u; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua tcp socket abort queueing, conn_op_ctx: %p, u: %p", + conn_op_ctx, u); + +#if (nginx_version >= 1007005) + if (conn_op_ctx->event.posted) { +#else + if (conn_op_ctx->event.prev) { +#endif + /* + * We need the extra parentheses around the argument + * of ngx_delete_posted_event() just to work around macro issues in + * nginx cores older than 1.7.5 (exclusive). + */ + ngx_delete_posted_event((&conn_op_ctx->event)); + + } else if (conn_op_ctx->event.timer_set) { + ngx_del_timer(&conn_op_ctx->event); + } + + ngx_queue_remove(&conn_op_ctx->queue); + ngx_queue_insert_head(&u->socket_pool->cache_connect_op, + &conn_op_ctx->queue); + + u->socket_pool->connections--; + ngx_http_lua_socket_tcp_resume_conn_op(u->socket_pool); +} + + static void ngx_http_lua_tcp_resolve_cleanup(void *data) { @@ -5400,15 +6316,18 @@ ngx_http_lua_tcp_resolve_cleanup(void *data) return; } + if (u->socket_pool != NULL) { + u->socket_pool->connections--; + ngx_http_lua_socket_tcp_resume_conn_op(u->socket_pool); + } + rctx = u->resolved->ctx; if (rctx == NULL) { return; } - /* just to be safer */ - rctx->handler = ngx_http_lua_socket_empty_resolve_handler; - - ngx_resolve_name_done(rctx); + /* postpone free the rctx in the handler */ + rctx->handler = ngx_resolve_name_done; } @@ -5433,33 +6352,13 @@ ngx_http_lua_coctx_cleanup(void *data) } -#if (NGX_HTTP_SSL) - -static int -ngx_http_lua_ssl_free_session(lua_State *L) -{ - ngx_ssl_session_t **psession; - - psession = lua_touserdata(L, 1); - if (psession && *psession != NULL) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, - "lua ssl free session: %p", *psession); - - ngx_ssl_free_session(*psession); - } - - return 0; -} - -#endif /* NGX_HTTP_SSL */ - - void ngx_http_lua_cleanup_conn_pools(lua_State *L) { ngx_http_lua_socket_pool_t *spool; - lua_pushlightuserdata(L, &ngx_http_lua_socket_pool_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + socket_pool_key)); lua_rawget(L, LUA_REGISTRYINDEX); /* table */ lua_pushnil(L); /* first key */ @@ -5481,4 +6380,405 @@ ngx_http_lua_cleanup_conn_pools(lua_State *L) lua_pop(L, 1); } + +int +ngx_http_lua_ffi_socket_tcp_init_udata_queue( + ngx_http_lua_socket_tcp_upstream_t *u, int capacity, char **err_msg) +{ + int i, max_size; + ngx_pool_t *pool; + ngx_http_lua_socket_udata_queue_t *udata_queue; + ngx_http_lua_socket_node_t *node; + + pool = u->peer.connection->pool; + + if (u->udata_queue == NULL) { + max_size = capacity; + if (max_size == 0) { + max_size = 4; + } + + udata_queue = ngx_palloc(pool, + sizeof(ngx_http_lua_socket_udata_queue_t) + + sizeof(ngx_http_lua_socket_node_t) * max_size); + + if (udata_queue == NULL) { + *err_msg = "no memory"; + return NGX_ERROR; + } + + udata_queue->pool = pool; + udata_queue->capacity = capacity; + udata_queue->len = 0; + ngx_queue_init(&udata_queue->queue); + ngx_queue_init(&udata_queue->free); + + node = (ngx_http_lua_socket_node_t *) (udata_queue + 1); + + for (i = 0; i < max_size; i++) { + ngx_queue_insert_head(&udata_queue->free, &node->queue); + node++; + } + + u->udata_queue = udata_queue; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, + "init udata_queue %uD, cosocket %p udata %p", + capacity, u, udata_queue); + } + + return NGX_OK; +} + + +int +ngx_http_lua_ffi_socket_tcp_count_udata(ngx_http_lua_socket_tcp_upstream_t *u) +{ + /* return NGX_ERROR (-1) for missing udata_queue to + * distinguish it from empty udata_queue */ + if (u->udata_queue == NULL) { + return NGX_ERROR; + } + + return u->udata_queue->len; +} + + +int +ngx_http_lua_ffi_socket_tcp_add_udata(ngx_http_lua_socket_tcp_upstream_t *u, + uint64_t key, uint64_t value, uint64_t *evicted_key, + uint64_t *evicted_value, char **err_msg) +{ + int evicted = 0; + ngx_pool_t *pool; + ngx_http_lua_socket_node_t *node = NULL; + ngx_queue_t *q, *uqueue; + + pool = u->peer.connection->pool; + + if (u->udata_queue == NULL) { + *err_msg = "no udata queue"; + return NGX_ERROR; + } + + uqueue = &u->udata_queue->queue; + + for (q = ngx_queue_head(uqueue); + q != ngx_queue_sentinel(uqueue); + q = ngx_queue_next(q)) + { + node = ngx_queue_data(q, ngx_http_lua_socket_node_t, queue); + + if (node->key == key) { + /* key exists */ + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, + "found %uD, cosocket %p udata %p", + key, u, u->udata_queue); + ngx_queue_remove(q); + node->value = value; + + break; + } + } + + if (q == ngx_queue_sentinel(uqueue)) { + + if (u->udata_queue->capacity + && u->udata_queue->capacity == u->udata_queue->len) + { + /* evict key */ + q = ngx_queue_last(uqueue); + node = ngx_queue_data(q, ngx_http_lua_socket_node_t, queue); + ngx_queue_remove(q); + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, + "evict %uD for %uD, cosocket %p udata %p", + node->key, key, u, u->udata_queue); + *evicted_key = node->key; + *evicted_value = node->value; + evicted = 1; + + } else { + /* insert key */ + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, + "insert %uD, cosocket %p udata %p", + key, u, u->udata_queue); + + if (!ngx_queue_empty(&u->udata_queue->free)) { + q = ngx_queue_head(&u->udata_queue->free); + node = ngx_queue_data(q, ngx_http_lua_socket_node_t, queue); + ngx_queue_remove(q); + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, u->request->connection->log, + 0, "reuse free node %p, cosocket %p udata %p", + node, u, u->udata_queue); + + } else { + node = ngx_palloc(pool, sizeof(ngx_http_lua_socket_node_t)); + if (node == NULL) { + goto nomem; + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, u->request->connection->log, + 0, "allocate new node %p, cosocket %p udata %p", + node, u, u->udata_queue); + } + + u->udata_queue->len++; + } + + node->key = key; + node->value = value; + } + + ngx_queue_insert_head(uqueue, &node->queue); + return evicted ? NGX_DONE : NGX_OK; + +nomem: + + *err_msg = "no memory"; + return NGX_ERROR; +} + + +int +ngx_http_lua_ffi_socket_tcp_get_udata(ngx_http_lua_socket_tcp_upstream_t *u, + uint64_t key, uint64_t *value, char **err_msg) +{ + ngx_http_lua_socket_node_t *node; + ngx_queue_t *q, *uqueue; + + if (u->udata_queue == NULL) { + *err_msg = "no udata queue"; + return NGX_ERROR; + } + + uqueue = &u->udata_queue->queue; + + for (q = ngx_queue_head(uqueue); + q != ngx_queue_sentinel(uqueue); + q = ngx_queue_next(q)) + { + node = ngx_queue_data(q, ngx_http_lua_socket_node_t, queue); + + if (node->key == key) { + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, + "found %uD, cosocket %p udata %p", + key, u, u->udata_queue); + ngx_queue_remove(q); + ngx_queue_insert_head(uqueue, &node->queue); + *value = node->value; + return NGX_OK; + } + } + + *err_msg = "not found"; + return NGX_ERROR; +} + + +int +ngx_http_lua_ffi_socket_tcp_del_udata(ngx_http_lua_socket_tcp_upstream_t *u, + uint64_t key, char **err_msg) +{ + ngx_http_lua_socket_node_t *node; + ngx_queue_t *q, *uqueue; + + if (u->udata_queue == NULL) { + *err_msg = "no udata queue"; + return NGX_ERROR; + } + + uqueue = &u->udata_queue->queue; + + for (q = ngx_queue_head(uqueue); + q != ngx_queue_sentinel(uqueue); + q = ngx_queue_next(q)) + { + node = ngx_queue_data(q, ngx_http_lua_socket_node_t, queue); + + if (node->key == key) { + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, u->request->connection->log, 0, + "delete %uD, cosocket %p udata %p", + key, u, u->udata_queue); + ngx_queue_remove(q); + ngx_queue_insert_head(&u->udata_queue->free, &node->queue); + u->udata_queue->len--; + return NGX_OK; + } + } + + *err_msg = "not found"; + return NGX_ERROR; +} + + +int +ngx_http_lua_ffi_socket_tcp_getoption(ngx_http_lua_socket_tcp_upstream_t *u, + int option, int *val, u_char *err, size_t *errlen) +{ + socklen_t len; + int fd, rc; + + if (u == NULL || u->peer.connection == NULL) { + *errlen = ngx_snprintf(err, *errlen, "closed") - err; + return NGX_ERROR; + } + + fd = u->peer.connection->fd; + + if (fd == (int) -1) { + *errlen = ngx_snprintf(err, *errlen, "invalid socket fd") - err; + return NGX_ERROR; + } + + len = sizeof(int); + + switch (option) { + case NGX_HTTP_LUA_SOCKOPT_KEEPALIVE: + rc = getsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *) val, &len); + break; + + case NGX_HTTP_LUA_SOCKOPT_REUSEADDR: + rc = getsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) val, &len); + break; + + case NGX_HTTP_LUA_SOCKOPT_TCP_NODELAY: + rc = getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *) val, &len); + break; + + case NGX_HTTP_LUA_SOCKOPT_SNDBUF: + rc = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *) val, &len); + break; + + case NGX_HTTP_LUA_SOCKOPT_RCVBUF: + rc = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *) val, &len); + break; + + default: + *errlen = ngx_snprintf(err, *errlen, "unsupported option %d", option) + - err; + return NGX_ERROR; + } + + if (rc == -1) { + *errlen = ngx_strerror(ngx_errno, err, NGX_MAX_ERROR_STR) - err; + return NGX_ERROR; + } + + return NGX_OK; +} + + +int +ngx_http_lua_ffi_socket_tcp_setoption(ngx_http_lua_socket_tcp_upstream_t *u, + int option, int val, u_char *err, size_t *errlen) +{ + socklen_t len; + int fd, rc; + + if (u == NULL || u->peer.connection == NULL) { + *errlen = ngx_snprintf(err, *errlen, "closed") - err; + return NGX_ERROR; + } + + fd = u->peer.connection->fd; + + if (fd == (int) -1) { + *errlen = ngx_snprintf(err, *errlen, "invalid socket fd") - err; + return NGX_ERROR; + } + + len = sizeof(int); + + switch (option) { + case NGX_HTTP_LUA_SOCKOPT_KEEPALIVE: + rc = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, + (const void *) &val, len); + break; + + case NGX_HTTP_LUA_SOCKOPT_REUSEADDR: + rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, + (const void *) &val, len); + break; + + case NGX_HTTP_LUA_SOCKOPT_TCP_NODELAY: + rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &val, len); + break; + + case NGX_HTTP_LUA_SOCKOPT_SNDBUF: + rc = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, + (const void *) &val, len); + break; + + case NGX_HTTP_LUA_SOCKOPT_RCVBUF: + rc = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, + (const void *) &val, len); + break; + + default: + *errlen = ngx_snprintf(err, *errlen, "unsupported option: %d", option) + - err; + return NGX_ERROR; + } + + if (rc == -1) { + *errlen = ngx_strerror(ngx_errno, err, NGX_MAX_ERROR_STR) - err; + return NGX_ERROR; + } + + return NGX_OK; +} + + +int +ngx_http_lua_ffi_socket_tcp_getfd(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, const char **errmsg) +{ + int fd; + + *errmsg = NULL; + + if (u == NULL || u->peer.connection == NULL) { + *errmsg = "closed"; + return -1; + } + + fd = u->peer.connection->fd; + if (fd == -1) { + *errmsg = "faked connection"; + } + + return fd; +} + + +/* just hack the fd for testing bad case, it will also return the original fd */ +int +ngx_http_lua_ffi_socket_tcp_hack_fd(ngx_http_lua_socket_tcp_upstream_t *u, + int fd, u_char *err, size_t *errlen) +{ + int rc; + + if (u == NULL || u->peer.connection == NULL) { + *errlen = ngx_snprintf(err, *errlen, "closed") - err; + return -1; + } + + rc = u->peer.connection->fd; + if (rc == (int) -1) { + *errlen = ngx_snprintf(err, *errlen, "invalid socket fd") - err; + return -1; + } + + /* return the original fd value directly when the new fd is invalid */ + if (fd < 0) { + return rc; + } + + u->peer.connection->fd = fd; + + return rc; +} + + /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_socket_tcp.h b/src/ngx_http_lua_socket_tcp.h index dbdee41c6e..0cc6641538 100644 --- a/src/ngx_http_lua_socket_tcp.h +++ b/src/ngx_http_lua_socket_tcp.h @@ -26,6 +26,10 @@ typedef struct ngx_http_lua_socket_tcp_upstream_s ngx_http_lua_socket_tcp_upstream_t; +typedef struct ngx_http_lua_socket_udata_queue_s + ngx_http_lua_socket_udata_queue_t; + + typedef int (*ngx_http_lua_socket_tcp_retval_handler)(ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L); @@ -35,17 +39,39 @@ typedef void (*ngx_http_lua_socket_tcp_upstream_handler_pt) (ngx_http_request_t *r, ngx_http_lua_socket_tcp_upstream_t *u); +typedef struct { + ngx_event_t event; + ngx_queue_t queue; + ngx_str_t host; + ngx_http_cleanup_pt *cleanup; + ngx_http_lua_socket_tcp_upstream_t *u; + in_port_t port; +} ngx_http_lua_socket_tcp_conn_op_ctx_t; + + +#define ngx_http_lua_socket_tcp_free_conn_op_ctx(conn_op_ctx) \ + ngx_free(conn_op_ctx->host.data); \ + ngx_free(conn_op_ctx) + + typedef struct { lua_State *lua_vm; - /* active connections == out-of-pool reused connections - * + in-pool connections */ - ngx_uint_t active_connections; + ngx_int_t size; + ngx_queue_t cache_connect_op; + ngx_queue_t wait_connect_op; + + /* connections == active connections + pending connect operations, + * while active connections == out-of-pool reused connections + * + in-pool connections */ + ngx_int_t connections; /* queues of ngx_http_lua_socket_pool_item_t: */ ngx_queue_t cache; ngx_queue_t free; + ngx_int_t backlog; + u_char key[1]; } ngx_http_lua_socket_pool_t; @@ -57,6 +83,8 @@ struct ngx_http_lua_socket_tcp_upstream_s { ngx_http_lua_socket_tcp_upstream_handler_pt read_event_handler; ngx_http_lua_socket_tcp_upstream_handler_pt write_event_handler; + ngx_http_lua_socket_udata_queue_t *udata_queue; + ngx_http_lua_socket_pool_t *socket_pool; ngx_http_lua_loc_conf_t *conf; @@ -92,8 +120,13 @@ struct ngx_http_lua_socket_tcp_upstream_s { #if (NGX_HTTP_SSL) ngx_str_t ssl_name; + ngx_ssl_session_t *ssl_session_ret; + const char *error_ret; + int openssl_error_code_ret; #endif + ngx_chain_t *busy_bufs; + unsigned ft_type:16; unsigned no_close:1; unsigned conn_waiting:1; @@ -104,6 +137,7 @@ struct ngx_http_lua_socket_tcp_upstream_s { unsigned raw_downstream:1; unsigned read_closed:1; unsigned write_closed:1; + unsigned conn_closed:1; #if (NGX_HTTP_SSL) unsigned ssl_verify:1; unsigned ssl_session_reuse:1; @@ -143,9 +177,26 @@ typedef struct { ngx_uint_t reused; + ngx_http_lua_socket_udata_queue_t *udata_queue; } ngx_http_lua_socket_pool_item_t; +struct ngx_http_lua_socket_udata_queue_s { + ngx_pool_t *pool; + ngx_queue_t queue; + ngx_queue_t free; + int len; + int capacity; +}; + + +typedef struct { + ngx_queue_t queue; + uint64_t key; + uint64_t value; +} ngx_http_lua_socket_node_t; + + void ngx_http_lua_inject_socket_tcp_api(ngx_log_t *log, lua_State *L); void ngx_http_lua_inject_req_socket_api(lua_State *L); void ngx_http_lua_cleanup_conn_pools(lua_State *L); diff --git a/src/ngx_http_lua_socket_udp.c b/src/ngx_http_lua_socket_udp.c index 15cadf3100..d57a55e123 100644 --- a/src/ngx_http_lua_socket_udp.c +++ b/src/ngx_http_lua_socket_udp.c @@ -24,7 +24,7 @@ #endif -#define UDP_MAX_DATAGRAM_SIZE 8192 +#define UDP_MAX_DATAGRAM_SIZE 65536 static int ngx_http_lua_socket_udp(lua_State *L); @@ -54,16 +54,19 @@ static void ngx_http_lua_socket_udp_read_handler(ngx_http_request_t *r, ngx_http_lua_socket_udp_upstream_t *u); static void ngx_http_lua_socket_udp_handle_success(ngx_http_request_t *r, ngx_http_lua_socket_udp_upstream_t *u); -static ngx_int_t ngx_http_lua_udp_connect(ngx_http_lua_udp_connection_t *uc); +static ngx_int_t ngx_http_lua_udp_connect(ngx_http_lua_udp_connection_t *uc, + ngx_addr_t *local); static int ngx_http_lua_socket_udp_close(lua_State *L); static ngx_int_t ngx_http_lua_socket_udp_resume(ngx_http_request_t *r); static void ngx_http_lua_udp_resolve_cleanup(void *data); static void ngx_http_lua_udp_socket_cleanup(void *data); +static int ngx_http_lua_socket_udp_bind(lua_State *L); enum { SOCKET_CTX_INDEX = 1, - SOCKET_TIMEOUT_INDEX = 2 + SOCKET_TIMEOUT_INDEX = 2, + SOCKET_BIND_INDEX = 3, }; @@ -81,7 +84,8 @@ ngx_http_lua_inject_socket_udp_api(ngx_log_t *log, lua_State *L) lua_setfield(L, -2, "udp"); /* ngx socket */ /* udp socket object metatable */ - lua_pushlightuserdata(L, &ngx_http_lua_socket_udp_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + socket_udp_metatable_key)); lua_createtable(L, 0 /* narr */, 6 /* nrec */); lua_pushcfunction(L, ngx_http_lua_socket_udp_setpeername); @@ -99,13 +103,17 @@ ngx_http_lua_inject_socket_udp_api(ngx_log_t *log, lua_State *L) lua_pushcfunction(L, ngx_http_lua_socket_udp_close); lua_setfield(L, -2, "close"); /* ngx socket mt */ + lua_pushcfunction(L, ngx_http_lua_socket_udp_bind); + lua_setfield(L, -2, "bind"); + lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); lua_rawset(L, LUA_REGISTRYINDEX); /* }}} */ /* udp socket object metatable */ - lua_pushlightuserdata(L, &ngx_http_lua_udp_udata_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + udp_udata_metatable_key)); lua_createtable(L, 0 /* narr */, 1 /* nrec */); /* metatable */ lua_pushcfunction(L, ngx_http_lua_socket_udp_upstream_destroy); lua_setfield(L, -2, "__gc"); @@ -137,14 +145,11 @@ ngx_http_lua_socket_udp(lua_State *L) return luaL_error(L, "no ctx found"); } - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE - | NGX_HTTP_LUA_CONTEXT_ACCESS - | NGX_HTTP_LUA_CONTEXT_CONTENT - | NGX_HTTP_LUA_CONTEXT_TIMER - | NGX_HTTP_LUA_CONTEXT_SSL_CERT); + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); lua_createtable(L, 3 /* narr */, 1 /* nrec */); - lua_pushlightuserdata(L, &ngx_http_lua_socket_udp_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + socket_udp_metatable_key)); lua_rawget(L, LUA_REGISTRYINDEX); lua_setmetatable(L, -2); @@ -160,6 +165,7 @@ ngx_http_lua_socket_udp_setpeername(lua_State *L) ngx_http_request_t *r; ngx_http_lua_ctx_t *ctx; ngx_str_t host; + ngx_addr_t *local; int port; ngx_resolver_ctx_t *rctx, temp; ngx_http_core_loc_conf_t *clcf; @@ -198,11 +204,7 @@ ngx_http_lua_socket_udp_setpeername(lua_State *L) return luaL_error(L, "no ctx found"); } - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE - | NGX_HTTP_LUA_CONTEXT_ACCESS - | NGX_HTTP_LUA_CONTEXT_CONTENT - | NGX_HTTP_LUA_CONTEXT_TIMER - | NGX_HTTP_LUA_CONTEXT_SSL_CERT); + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); luaL_checktype(L, 1, LUA_TTABLE); @@ -263,7 +265,8 @@ ngx_http_lua_socket_udp_setpeername(lua_State *L) } #if 1 - lua_pushlightuserdata(L, &ngx_http_lua_udp_udata_metatable_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + udp_udata_metatable_key)); lua_rawget(L, LUA_REGISTRYINDEX); lua_setmetatable(L, -2); #endif @@ -295,6 +298,13 @@ ngx_http_lua_socket_udp_setpeername(lua_State *L) u->read_timeout = u->conf->read_timeout; } + lua_rawgeti(L, 1, SOCKET_BIND_INDEX); + local = lua_touserdata(L, -1); + lua_pop(L, 1); + if (local != NULL) { + u->local = local; + } + ngx_memzero(&url, sizeof(ngx_url_t)); url.url.len = host.len; @@ -363,9 +373,6 @@ ngx_http_lua_socket_udp_setpeername(lua_State *L) } rctx->name = host; -#if !defined(nginx_version) || nginx_version < 1005008 - rctx->type = NGX_RESOLVE_A; -#endif rctx->handler = ngx_http_lua_socket_resolve_handler; rctx->data = u; rctx->timeout = clcf->resolver_timeout; @@ -433,12 +440,8 @@ ngx_http_lua_socket_resolve_handler(ngx_resolver_ctx_t *ctx) ngx_http_lua_socket_udp_upstream_t *u; u_char *p; size_t len; -#if defined(nginx_version) && nginx_version >= 1005008 socklen_t socklen; struct sockaddr *sockaddr; -#else - struct sockaddr_in *sin; -#endif ngx_uint_t i; unsigned waiting; @@ -498,36 +501,20 @@ ngx_http_lua_socket_resolve_handler(ngx_resolver_ctx_t *ctx) #if (NGX_DEBUG) { -# if defined(nginx_version) && nginx_version >= 1005008 - u_char text[NGX_SOCKADDR_STRLEN]; - ngx_str_t addr; -# else - in_addr_t addr; -# endif - ngx_uint_t i; + u_char text[NGX_SOCKADDR_STRLEN]; + ngx_str_t addr; + ngx_uint_t i; -# if defined(nginx_version) && nginx_version >= 1005008 - addr.data = text; + addr.data = text; - for (i = 0; i < ctx->naddrs; i++) { - addr.len = ngx_sock_ntop(ur->addrs[i].sockaddr, ur->addrs[i].socklen, - text, NGX_SOCKADDR_STRLEN, 0); + for (i = 0; i < ctx->naddrs; i++) { + addr.len = ngx_sock_ntop(ur->addrs[i].sockaddr, + ur->addrs[i].socklen, text, + NGX_SOCKADDR_STRLEN, 0); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "name was resolved to %V", &addr); - } -# else - for (i = 0; i < ctx->naddrs; i++) { - dd("addr i: %d %p", (int) i, &ctx->addrs[i]); - - addr = ntohl(ctx->addrs[i]); - - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, - "name was resolved to %ud.%ud.%ud.%ud", - (addr >> 24) & 0xff, (addr >> 16) & 0xff, - (addr >> 8) & 0xff, addr & 0xff); - } -# endif + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "name was resolved to %V", &addr); + } } #endif @@ -542,7 +529,6 @@ ngx_http_lua_socket_resolve_handler(ngx_resolver_ctx_t *ctx) dd("selected addr index: %d", (int) i); -#if defined(nginx_version) && nginx_version >= 1005008 socklen = ur->addrs[i].socklen; sockaddr = ngx_palloc(r->pool, socklen); @@ -571,30 +557,6 @@ ngx_http_lua_socket_resolve_handler(ngx_resolver_ctx_t *ctx) ur->sockaddr = sockaddr; ur->socklen = socklen; -#else - /* for nginx older than 1.5.8 */ - - len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1; - - p = ngx_pnalloc(r->pool, len + sizeof(struct sockaddr_in)); - if (p == NULL) { - goto nomem; - } - - sin = (struct sockaddr_in *) &p[len]; - ngx_memzero(sin, sizeof(struct sockaddr_in)); - - len = ngx_inet_ntop(AF_INET, &ur->addrs[i], p, NGX_INET_ADDRSTRLEN); - len = ngx_sprintf(&p[len], ":%d", ur->port) - p; - - sin->sin_family = AF_INET; - sin->sin_port = htons(ur->port); - sin->sin_addr.s_addr = ur->addrs[i]; - - ur->sockaddr = (struct sockaddr *) sin; - ur->socklen = sizeof(struct sockaddr_in); -#endif - ur->host.data = p; ur->host.len = len; ur->naddrs = 1; @@ -643,7 +605,7 @@ ngx_http_lua_socket_resolve_retval_handler(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx; ngx_http_lua_co_ctx_t *coctx; ngx_connection_t *c; - ngx_http_cleanup_t *cln; + ngx_pool_cleanup_t *cln; ngx_http_upstream_resolved_t *ur; ngx_int_t rc; ngx_http_lua_udp_connection_t *uc; @@ -670,14 +632,14 @@ ngx_http_lua_socket_resolve_retval_handler(ngx_http_request_t *r, return 2; } - rc = ngx_http_lua_udp_connect(uc); + rc = ngx_http_lua_udp_connect(uc, u->local); if (rc != NGX_OK) { u->socket_errno = ngx_socket_errno; } if (u->cleanup == NULL) { - cln = ngx_http_cleanup_add(r, 0); + cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { u->ft_type |= NGX_HTTP_LUA_SOCKET_FT_ERROR; lua_pushnil(L); @@ -759,11 +721,7 @@ ngx_http_lua_socket_error_retval_handler(ngx_http_request_t *r, } else { if (u->socket_errno) { -#if defined(nginx_version) && nginx_version >= 9000 p = ngx_strerror(u->socket_errno, errstr, sizeof(errstr)); -#else - p = ngx_strerror_r(u->socket_errno, errstr, sizeof(errstr)); -#endif /* for compatibility with LuaSocket */ ngx_strlow(errstr, errstr, p - errstr); lua_pushlstring(L, (char *) errstr, p - errstr); @@ -777,12 +735,63 @@ ngx_http_lua_socket_error_retval_handler(ngx_http_request_t *r, } +static int +ngx_http_lua_socket_udp_bind(lua_State *L) +{ + ngx_http_request_t *r; + ngx_http_lua_ctx_t *ctx; + int n; + u_char *text; + size_t len; + ngx_addr_t *local; + + n = lua_gettop(L); + + if (n != 2) { + return luaL_error(L, "expecting 2 arguments, but got %d", + lua_gettop(L)); + } + + r = ngx_http_lua_get_req(L); + if (r == NULL) { + return luaL_error(L, "no request found"); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + return luaL_error(L, "no ctx found"); + } + + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); + + luaL_checktype(L, 1, LUA_TTABLE); + + text = (u_char *) luaL_checklstring(L, 2, &len); + + local = ngx_http_lua_parse_addr(L, text, len); + if (local == NULL) { + lua_pushnil(L); + lua_pushfstring(L, "bad address"); + return 2; + } + + lua_rawseti(L, 1, SOCKET_BIND_INDEX); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua udp socket bind ip: %V", &local->name); + + lua_pushinteger(L, 1); + return 1; +} + + static int ngx_http_lua_socket_udp_send(lua_State *L) { ssize_t n; ngx_http_request_t *r; u_char *p; + u_char *str; size_t len; ngx_http_lua_socket_udp_upstream_t *u; int type; @@ -837,14 +846,32 @@ ngx_http_lua_socket_udp_send(lua_State *L) type = lua_type(L, 2); switch (type) { case LUA_TNUMBER: + len = ngx_http_lua_get_num_len(L, 2); + break; + case LUA_TSTRING: lua_tolstring(L, 2, &len); break; case LUA_TTABLE: + /* The maximum possible length, not the actual length */ len = ngx_http_lua_calc_strlen_in_table(L, 2, 2, 1 /* strict */); break; + case LUA_TNIL: + len = sizeof("nil") - 1; + break; + + case LUA_TBOOLEAN: + if (lua_toboolean(L, 2)) { + len = sizeof("true") - 1; + + } else { + len = sizeof("false") - 1; + } + + break; + default: msg = lua_pushfstring(L, "string, number, boolean, nil, " "or array table expected, got %s", @@ -854,23 +881,52 @@ ngx_http_lua_socket_udp_send(lua_State *L) } query.data = lua_newuserdata(L, len); - query.len = len; + p = query.data; switch (type) { case LUA_TNUMBER: + p = ngx_http_lua_write_num(L, 2, p); + break; + case LUA_TSTRING: - p = (u_char *) lua_tolstring(L, 2, &len); - ngx_memcpy(query.data, (u_char *) p, len); + str = (u_char *) lua_tolstring(L, 2, &len); + p = ngx_cpymem(p, (u_char *) str, len); break; case LUA_TTABLE: - (void) ngx_http_lua_copy_str_in_table(L, 2, query.data); + p = ngx_http_lua_copy_str_in_table(L, 2, p); + break; + + case LUA_TNIL: + *p++ = 'n'; + *p++ = 'i'; + *p++ = 'l'; + break; + + case LUA_TBOOLEAN: + if (lua_toboolean(L, 2)) { + *p++ = 't'; + *p++ = 'r'; + *p++ = 'u'; + *p++ = 'e'; + + } else { + *p++ = 'f'; + *p++ = 'a'; + *p++ = 'l'; + *p++ = 's'; + *p++ = 'e'; + } + break; default: return luaL_error(L, "impossible to reach here"); } + query.len = p - query.data; + ngx_http_lua_assert(query.len <= len); + u->ft_type = 0; /* mimic ngx_http_upstream_init_request here */ @@ -1052,7 +1108,7 @@ ngx_http_lua_socket_udp_settimeout(lua_State *L) n = lua_gettop(L); if (n != 2) { - return luaL_error(L, "ngx.socket settimout: expecting at least 2 " + return luaL_error(L, "ngx.socket settimeout: expecting at least 2 " "arguments (including the object) but seen %d", lua_gettop(L)); } @@ -1151,7 +1207,7 @@ ngx_http_lua_socket_udp_read(ngx_http_request_t *r, ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "lua udp socket read data: waiting: %d", (int) u->waiting); - n = ngx_udp_recv(u->udp_connection.connection, + n = ngx_udp_recv(c, ngx_http_lua_socket_udp_buffer, u->recv_buf_size); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -1348,7 +1404,7 @@ ngx_http_lua_socket_udp_handle_success(ngx_http_request_t *r, static ngx_int_t -ngx_http_lua_udp_connect(ngx_http_lua_udp_connection_t *uc) +ngx_http_lua_udp_connect(ngx_http_lua_udp_connection_t *uc, ngx_addr_t *local) { int rc; ngx_int_t event; @@ -1421,8 +1477,18 @@ ngx_http_lua_udp_connect(ngx_http_lua_udp_connection_t *uc) return NGX_ERROR; } } + #endif + if (local != NULL) { + if (bind(s, local->sockaddr, local->socklen) == -1) { + ngx_log_error(NGX_LOG_CRIT, &uc->log, ngx_socket_errno, + "bind(%V) failed", &local->name); + + return NGX_ERROR; + } + } + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, &uc->log, 0, "connect to %V, fd:%d #%d", &uc->server, s, c->number); @@ -1592,7 +1658,8 @@ ngx_http_lua_udp_resolve_cleanup(void *data) return; } - ngx_resolve_name_done(rctx); + /* postpone free the rctx in the handler */ + rctx->handler = ngx_resolve_name_done; } diff --git a/src/ngx_http_lua_socket_udp.h b/src/ngx_http_lua_socket_udp.h index 83333461db..61245b3647 100644 --- a/src/ngx_http_lua_socket_udp.h +++ b/src/ngx_http_lua_socket_udp.h @@ -42,6 +42,8 @@ struct ngx_http_lua_socket_udp_upstream_s { ngx_http_request_t *request; ngx_http_lua_udp_connection_t udp_connection; + ngx_addr_t *local; + ngx_msec_t read_timeout; ngx_http_upstream_resolved_t *resolved; @@ -53,7 +55,7 @@ struct ngx_http_lua_socket_udp_upstream_s { ngx_http_lua_co_ctx_t *co_ctx; - unsigned waiting; /* :1 */ + unsigned waiting:1; /* :1 */ }; diff --git a/src/ngx_http_lua_ssl.c b/src/ngx_http_lua_ssl.c index 8ed7b95417..2e066ffd82 100644 --- a/src/ngx_http_lua_ssl.c +++ b/src/ngx_http_lua_ssl.c @@ -9,11 +9,13 @@ #endif #include "ddebug.h" - #if (NGX_HTTP_SSL) +#include "ngx_http_lua_ssl.h" + int ngx_http_lua_ssl_ctx_index = -1; +int ngx_http_lua_ssl_key_log_index = -1; ngx_int_t @@ -30,8 +32,51 @@ ngx_http_lua_ssl_init(ngx_log_t *log) } } + if (ngx_http_lua_ssl_key_log_index == -1) { + ngx_http_lua_ssl_key_log_index = SSL_get_ex_new_index(0, NULL, NULL, + NULL, NULL); + + if (ngx_http_lua_ssl_key_log_index == -1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, + "lua: SSL_get_ex_new_index() for key log failed"); + return NGX_ERROR; + } + } + return NGX_OK; } +ngx_ssl_conn_t * +ngx_http_lua_ffi_get_upstream_ssl_pointer(ngx_http_request_t *r, + const char **err) +{ + ngx_connection_t *c; + + if (r == NULL) { + *err = "bad request"; + return NULL; + } + + if (r->upstream == NULL) { + *err = "bad upstream"; + return NULL; + } + + if (r->upstream->peer.connection == NULL) { + *err = "bad peer connection"; + return NULL; + } + + c = r->upstream->peer.connection; + + if (c->ssl == NULL || c->ssl->connection == NULL) { + *err = "not ssl connection"; + return NULL; + } + + return c->ssl->connection; +} + + #endif /* NGX_HTTP_SSL */ diff --git a/src/ngx_http_lua_ssl.h b/src/ngx_http_lua_ssl.h index acb8c4b16c..f709e6530f 100644 --- a/src/ngx_http_lua_ssl.h +++ b/src/ngx_http_lua_ssl.h @@ -16,29 +16,56 @@ typedef struct { ngx_connection_t *connection; /* original true connection */ - ngx_http_request_t *request; /* fake request */ + ngx_http_request_t *request; ngx_pool_cleanup_pt *cleanup; - ngx_ssl_session_t *session; /* retrurn value for openssl's + ngx_ssl_session_t *session; /* return value for openssl's * session_get_cb */ ngx_str_t session_id; +#ifdef HAVE_PROXY_SSL_PATCH + X509_STORE_CTX *x509_store; + ngx_pool_t *pool; +#endif + int exit_code; /* exit code for openssl's - set_cert_cb callback */ + set_client_hello_cb or + set_cert_cb callback or + SSL_CTX_set_cert_verify_callback */ + int ctx_ref; /* reference to anchor + request ctx data in lua + registry */ + +#ifdef HAVE_PROXY_SSL_PATCH + /* same size as count field of ngx_http_request_t */ + unsigned original_request_count:16; +#endif unsigned done:1; unsigned aborted:1; + unsigned entered_client_hello_handler:1; unsigned entered_cert_handler:1; unsigned entered_sess_fetch_handler:1; +#ifdef HAVE_PROXY_SSL_PATCH + unsigned entered_proxy_ssl_verify_handler:1; +#endif } ngx_http_lua_ssl_ctx_t; +typedef struct { + ngx_ssl_t *ssl; + ngx_fd_t fd; + ngx_str_t name; +} ngx_http_lua_ssl_key_log_t; + + ngx_int_t ngx_http_lua_ssl_init(ngx_log_t *log); extern int ngx_http_lua_ssl_ctx_index; +extern int ngx_http_lua_ssl_key_log_index; #endif diff --git a/src/ngx_http_lua_ssl_certby.c b/src/ngx_http_lua_ssl_certby.c index 020b341873..5d16834212 100644 --- a/src/ngx_http_lua_ssl_certby.c +++ b/src/ngx_http_lua_ssl_certby.c @@ -26,7 +26,7 @@ enum { NGX_HTTP_LUA_ADDR_TYPE_UNIX = 0, NGX_HTTP_LUA_ADDR_TYPE_INET = 1, - NGX_HTTP_LUA_ADDR_TYPE_INET6 = 2 + NGX_HTTP_LUA_ADDR_TYPE_INET6 = 2, }; @@ -37,6 +37,10 @@ static u_char *ngx_http_lua_log_ssl_cert_error(ngx_log_t *log, u_char *buf, static ngx_int_t ngx_http_lua_ssl_cert_by_chunk(lua_State *L, ngx_http_request_t *r); +#ifndef OPENSSL_IS_BORINGSSL +static int ngx_http_lua_is_grease_cipher(uint16_t cipher_id); +#endif + ngx_int_t ngx_http_lua_ssl_cert_handler_file(ngx_http_request_t *r, @@ -46,6 +50,7 @@ ngx_http_lua_ssl_cert_handler_file(ngx_http_request_t *r, rc = ngx_http_lua_cache_loadfile(r->connection->log, L, lscf->srv.ssl_cert_src.data, + &lscf->srv.ssl_cert_src_ref, lscf->srv.ssl_cert_src_key); if (rc != NGX_OK) { return rc; @@ -67,8 +72,10 @@ ngx_http_lua_ssl_cert_handler_inline(ngx_http_request_t *r, rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, lscf->srv.ssl_cert_src.data, lscf->srv.ssl_cert_src.len, + &lscf->srv.ssl_cert_src_ref, lscf->srv.ssl_cert_src_key, - "=ssl_certificate_by_lua"); + (const char *) + lscf->srv.ssl_cert_chunkname); if (rc != NGX_OK) { return rc; } @@ -113,10 +120,12 @@ ngx_http_lua_ssl_cert_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, #else - u_char *p; + size_t chunkname_len; + u_char *chunkname; + u_char *cache_key = NULL; u_char *name; ngx_str_t *value; - ngx_http_lua_srv_conf_t *lscf = conf; + ngx_http_lua_srv_conf_t *lscf = conf; /* must specify a concrete handler */ if (cmd->post == NULL) { @@ -137,44 +146,43 @@ ngx_http_lua_ssl_cert_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, if (cmd->post == ngx_http_lua_ssl_cert_handler_file) { /* Lua code in an external file */ - name = ngx_http_lua_rebase_path(cf->pool, value[1].data, value[1].len); if (name == NULL) { return NGX_CONF_ERROR; } - lscf->srv.ssl_cert_src.data = name; - lscf->srv.ssl_cert_src.len = ngx_strlen(name); - - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_FILE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - lscf->srv.ssl_cert_src_key = p; - - p = ngx_copy(p, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; + lscf->srv.ssl_cert_src.data = name; + lscf->srv.ssl_cert_src.len = ngx_strlen(name); } else { - /* inlined Lua code */ - - lscf->srv.ssl_cert_src = value[1]; - - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_INLINE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, + "ssl_certificate_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - lscf->srv.ssl_cert_src_key = p; + chunkname = ngx_http_lua_gen_chunk_name(cf, "ssl_certificate_by_lua", + sizeof("ssl_certificate_by_lua") - 1, &chunkname_len); + if (chunkname == NULL) { + return NGX_CONF_ERROR; + } - p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; + /* Don't eval nginx variables for inline lua code */ + lscf->srv.ssl_cert_src = value[1]; + lscf->srv.ssl_cert_chunkname = chunkname; } + lscf->srv.ssl_cert_src_key = cache_key; + return NGX_CONF_OK; #endif /* OPENSSL_VERSION_NUMBER < 0x1000205fL */ @@ -209,7 +217,7 @@ ngx_http_lua_ssl_cert_handler(ngx_ssl_conn_t *ssl_conn, void *data) if (cctx->done) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "lua_certificate_by_lua: cert cb exit code: %d", + "ssl_certificate_by_lua: cert cb exit code: %d", cctx->exit_code); dd("lua ssl cert done, finally"); @@ -221,7 +229,9 @@ ngx_http_lua_ssl_cert_handler(ngx_ssl_conn_t *ssl_conn, void *data) dd("first time"); +#if (nginx_version < 1017009) ngx_reusable_connection(c, 0); +#endif hc = c->data; @@ -251,26 +261,11 @@ ngx_http_lua_ssl_cert_handler(ngx_ssl_conn_t *ssl_conn, void *data) clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); -#if defined(nginx_version) && nginx_version >= 1003014 - -# if nginx_version >= 1009000 - +#if (nginx_version >= 1009000) ngx_set_connection_log(fc, clcf->error_log); -# else - - ngx_http_set_connection_log(fc, clcf->error_log); - -# endif - #else - - fc->log->file = clcf->error_log->file; - - if (!(fc->log->log_level & NGX_LOG_DEBUG_CONNECTION)) { - fc->log->log_level = clcf->error_log->log_level; - } - + ngx_http_set_connection_log(fc, clcf->error_log); #endif if (cctx == NULL) { @@ -278,6 +273,8 @@ ngx_http_lua_ssl_cert_handler(ngx_ssl_conn_t *ssl_conn, void *data) if (cctx == NULL) { goto failed; /* error */ } + + cctx->ctx_ref = LUA_NOREF; } cctx->exit_code = 1; /* successful by default */ @@ -322,7 +319,7 @@ ngx_http_lua_ssl_cert_handler(ngx_ssl_conn_t *ssl_conn, void *data) } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "lua_certificate_by_lua: handler return value: %i, " + "ssl_certificate_by_lua: handler return value: %i, " "cert cb exit code: %d", rc, cctx->exit_code); c->log->action = "SSL handshaking"; @@ -353,7 +350,6 @@ ngx_http_lua_ssl_cert_handler(ngx_ssl_conn_t *ssl_conn, void *data) return -1; -#if 1 failed: if (r && r->pool) { @@ -365,15 +361,14 @@ ngx_http_lua_ssl_cert_handler(ngx_ssl_conn_t *ssl_conn, void *data) } return 0; -#endif } static void ngx_http_lua_ssl_cert_done(void *data) { - ngx_connection_t *c; - ngx_http_lua_ssl_ctx_t *cctx = data; + ngx_connection_t *c; + ngx_http_lua_ssl_ctx_t *cctx = data; dd("lua ssl cert done"); @@ -394,6 +389,14 @@ ngx_http_lua_ssl_cert_done(void *data) c->log->action = "SSL handshaking"; ngx_post_event(c->write, &ngx_posted_events); + +#if (HAVE_QUIC_SSL_LUA_YIELD_PATCH && NGX_HTTP_V3) +# if OPENSSL_VERSION_NUMBER >= 0x1000205fL +# if (NGX_QUIC_OPENSSL_COMPAT || NGX_QUIC_OPENSSL_API) + ngx_http_lua_resume_quic_ssl_handshake(c); +# endif +# endif +#endif } @@ -402,7 +405,7 @@ ngx_http_lua_ssl_cert_aborted(void *data) { ngx_http_lua_ssl_ctx_t *cctx = data; - dd("lua ssl cert done"); + dd("lua ssl cert aborted"); if (cctx->done) { /* completed successfully already */ @@ -410,7 +413,7 @@ ngx_http_lua_ssl_cert_aborted(void *data) } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cctx->connection->log, 0, - "lua_certificate_by_lua: cert cb aborted"); + "ssl_certificate_by_lua: cert cb aborted"); cctx->aborted = 1; cctx->request->connection->ssl = NULL; @@ -437,7 +440,7 @@ ngx_http_lua_log_ssl_cert_error(ngx_log_t *log, u_char *buf, size_t len) c = log->data; - if (c->addr_text.len) { + if (c && c->addr_text.len) { p = ngx_snprintf(buf, len, ", client: %V", &c->addr_text); len -= p - buf; buf = p; @@ -453,6 +456,18 @@ ngx_http_lua_log_ssl_cert_error(ngx_log_t *log, u_char *buf, size_t len) } +#ifndef OPENSSL_IS_BORINGSSL +static int +ngx_http_lua_is_grease_cipher(uint16_t cipher_id) +{ + /* GREASE values follow pattern: 0x?A?A where ? can be any hex digit */ + /* and both ? must be the same */ + /* Check if both bytes follow ?A pattern and high nibbles match */ + return (cipher_id & 0x0F0F) == 0x0A0A; +} +#endif + + static ngx_int_t ngx_http_lua_ssl_cert_by_chunk(lua_State *L, ngx_http_request_t *r) { @@ -460,7 +475,7 @@ ngx_http_lua_ssl_cert_by_chunk(lua_State *L, ngx_http_request_t *r) ngx_int_t rc; lua_State *co; ngx_http_lua_ctx_t *ctx; - ngx_http_cleanup_t *cln; + ngx_pool_cleanup_t *cln; ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); @@ -494,9 +509,11 @@ ngx_http_lua_ssl_cert_by_chunk(lua_State *L, ngx_http_request_t *r) /* move code closure to new coroutine */ lua_xmove(L, co, 1); +#ifndef OPENRESTY_LUAJIT /* set closure's env table to new coroutine's globals table */ ngx_http_lua_get_globals_table(co); lua_setfenv(co, -2); +#endif /* save nginx request in coroutine globals table */ ngx_http_lua_set_req(co, r); @@ -508,9 +525,11 @@ ngx_http_lua_ssl_cert_by_chunk(lua_State *L, ngx_http_request_t *r) ctx->cur_co_ctx->co_top = 1; #endif + ngx_http_lua_attach_co_ctx_to_L(co, ctx->cur_co_ctx); + /* register request cleanup hooks */ if (ctx->cleanup == NULL) { - cln = ngx_http_cleanup_add(r, 0); + cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { rc = NGX_ERROR; ngx_http_lua_finalize_request(r, rc); @@ -544,18 +563,9 @@ ngx_http_lua_ssl_cert_by_chunk(lua_State *L, ngx_http_request_t *r) } -#ifndef NGX_LUA_NO_FFI_API - int ngx_http_lua_ffi_ssl_get_tls1_version(ngx_http_request_t *r, char **err) { -#ifndef TLS1_get_version - - *err = "no TLS1 support"; - return NGX_ERROR; - -#else - ngx_ssl_conn_t *ssl_conn; if (r->connection == NULL || r->connection->ssl == NULL) { @@ -569,11 +579,9 @@ ngx_http_lua_ffi_ssl_get_tls1_version(ngx_http_request_t *r, char **err) return NGX_ERROR; } - dd("tls1 ver: %d", (int) TLS1_get_version(ssl_conn)); - - return (int) TLS1_get_version(ssl_conn); + dd("tls1 ver: %d", SSL_version(ssl_conn)); -#endif + return SSL_version(ssl_conn); } @@ -747,7 +755,7 @@ ngx_http_lua_ffi_ssl_set_der_private_key(ngx_http_request_t *r, } if (SSL_use_PrivateKey(ssl_conn, pkey) == 0) { - *err = "SSL_CTX_use_PrivateKey() failed"; + *err = "SSL_use_PrivateKey() failed"; goto failed; } @@ -847,6 +855,70 @@ ngx_http_lua_ffi_ssl_raw_server_addr(ngx_http_request_t *r, char **addr, } +int +ngx_http_lua_ffi_req_shared_ssl_ciphers(ngx_http_request_t *r, + uint16_t *ciphers, uint16_t *nciphers, int filter_grease, char **err) +{ +#ifdef OPENSSL_IS_BORINGSSL + + *err = "BoringSSL is not supported for SSL cipher operations"; + return NGX_ERROR; + +#else + ngx_ssl_conn_t *ssl_conn; + STACK_OF(SSL_CIPHER) *sk, *ck; + int sn, cn, i, n; + uint16_t cipher; + + if (r == NULL || r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + sk = SSL_get1_supported_ciphers(ssl_conn); + ck = SSL_get_client_ciphers(ssl_conn); + sn = sk_SSL_CIPHER_num(sk); + cn = sk_SSL_CIPHER_num(ck); + + if (sn > *nciphers) { + *err = "buffer too small"; + *nciphers = 0; + sk_SSL_CIPHER_free(sk); + return NGX_ERROR; + } + + for (*nciphers = 0, i = 0; i < sn; i++) { + cipher = SSL_CIPHER_get_protocol_id(sk_SSL_CIPHER_value(sk, i)); + + /* Skip GREASE ciphers if filtering is enabled */ + if (filter_grease && ngx_http_lua_is_grease_cipher(cipher)) { + continue; + } + + for (n = 0; n < cn; n++) { + if (SSL_CIPHER_get_protocol_id(sk_SSL_CIPHER_value(ck, n)) + == cipher) + { + ciphers[(*nciphers)++] = cipher; + break; + } + } + } + + sk_SSL_CIPHER_free(sk); + + return NGX_OK; +#endif + +} + + int ngx_http_lua_ffi_ssl_server_name(ngx_http_request_t *r, char **name, size_t *namelen, char **err) @@ -884,6 +956,39 @@ ngx_http_lua_ffi_ssl_server_name(ngx_http_request_t *r, char **name, } +int +ngx_http_lua_ffi_ssl_server_port(ngx_http_request_t *r, + unsigned short *server_port, char **err) +{ + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + c = ngx_ssl_get_connection(ssl_conn); + + switch (c->local_sockaddr->sa_family) { + + case AF_UNIX: + *err = "unix domain has no port"; + return NGX_ERROR; + + default: + *server_port = (unsigned short) ngx_inet_get_port(c->local_sockaddr); + return NGX_OK; + } +} + + int ngx_http_lua_ffi_ssl_raw_client_addr(ngx_http_request_t *r, char **addr, size_t *addrlen, int *addrtype, char **err) @@ -1033,7 +1138,7 @@ ngx_http_lua_ffi_cert_pem_to_der(const u_char *pem, size_t pem_len, u_char *der, int ngx_http_lua_ffi_priv_key_pem_to_der(const u_char *pem, size_t pem_len, - u_char *der, char **err) + const u_char *passphrase, u_char *der, char **err) { int len; BIO *in; @@ -1046,7 +1151,7 @@ ngx_http_lua_ffi_priv_key_pem_to_der(const u_char *pem, size_t pem_len, return NGX_ERROR; } - pkey = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL); + pkey = PEM_read_bio_PrivateKey(in, NULL, NULL, (void *) passphrase); if (pkey == NULL) { BIO_free(in); *err = "PEM_read_bio_PrivateKey() failed"; @@ -1153,6 +1258,81 @@ ngx_http_lua_ffi_parse_pem_cert(const u_char *pem, size_t pem_len, } +void * +ngx_http_lua_ffi_parse_der_cert(const char *data, size_t len, + char **err) +{ + BIO *bio; + X509 *x509; + STACK_OF(X509) *chain; + + if (data == NULL || len == 0) { + *err = "invalid argument"; + ERR_clear_error(); + return NULL; + } + + bio = BIO_new_mem_buf((char *) data, len); + if (bio == NULL) { + *err = "BIO_new_mem_buf() failed"; + ERR_clear_error(); + return NULL; + } + + x509 = d2i_X509_bio(bio, NULL); + if (x509 == NULL) { + *err = "d2i_X509_bio() failed"; + BIO_free(bio); + ERR_clear_error(); + return NULL; + } + + chain = sk_X509_new_null(); + if (chain == NULL) { + *err = "sk_X509_new_null() failed"; + X509_free(x509); + BIO_free(bio); + ERR_clear_error(); + return NULL; + } + + if (sk_X509_push(chain, x509) == 0) { + *err = "sk_X509_push() failed"; + sk_X509_free(chain); + X509_free(x509); + BIO_free(bio); + ERR_clear_error(); + return NULL; + } + + /* read rest of the chain */ + + while (!BIO_eof(bio)) { + x509 = d2i_X509_bio(bio, NULL); + if (x509 == NULL) { + *err = "d2i_X509_bio() failed in rest of chain"; + sk_X509_pop_free(chain, X509_free); + BIO_free(bio); + ERR_clear_error(); + return NULL; + } + + if (sk_X509_push(chain, x509) == 0) { + *err = "sk_X509_push() failed in rest of chain"; + sk_X509_pop_free(chain, X509_free); + X509_free(x509); + BIO_free(bio); + ERR_clear_error(); + return NULL; + } + } + + BIO_free(bio); + + return chain; +} + + void ngx_http_lua_ffi_free_cert(void *cdata) { @@ -1190,6 +1370,40 @@ ngx_http_lua_ffi_parse_pem_priv_key(const u_char *pem, size_t pem_len, } +void * +ngx_http_lua_ffi_parse_der_priv_key(const char *data, size_t len, + char **err) +{ + BIO *bio = NULL; + EVP_PKEY *pkey = NULL; + + if (data == NULL || len == 0) { + *err = "invalid argument"; + ERR_clear_error(); + return NULL; + } + + bio = BIO_new_mem_buf((char *) data, len); + if (bio == NULL) { + *err = "BIO_new_mem_buf() failed"; + ERR_clear_error(); + return NULL; + } + + pkey = d2i_PrivateKey_bio(bio, NULL); + if (pkey == NULL) { + *err = "d2i_PrivateKey_bio() failed"; + BIO_free(bio); + ERR_clear_error(); + return NULL; + } + + BIO_free(bio); + + return pkey; +} + + void ngx_http_lua_ffi_free_priv_key(void *cdata) { @@ -1305,7 +1519,7 @@ ngx_http_lua_ffi_set_priv_key(ngx_http_request_t *r, pkey = cdata; if (pkey == NULL) { - *err = "invalid private key failed"; + *err = "invalid private key"; goto failed; } @@ -1324,7 +1538,212 @@ ngx_http_lua_ffi_set_priv_key(ngx_http_request_t *r, } -#endif /* NGX_LUA_NO_FFI_API */ +#ifndef LIBRESSL_VERSION_NUMBER +static int +ngx_http_lua_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store) +{ + /* + * we never terminate handshake here and user can later use + * $ssl_client_verify to check verification result. + * + * this is consistent with Nginx behavior. + */ + return 1; +} +#endif + + +int +ngx_http_lua_ffi_ssl_verify_client(ngx_http_request_t *r, void *client_certs, + void *trusted_certs, int depth, char **err) +{ +#ifdef LIBRESSL_VERSION_NUMBER + + *err = "LibreSSL not supported"; + return NGX_ERROR; + +#else + + ngx_http_lua_ctx_t *ctx; + ngx_ssl_conn_t *ssl_conn; + ngx_http_ssl_srv_conf_t *sscf; + STACK_OF(X509) *client_chain = client_certs; + STACK_OF(X509) *trusted_chain = trusted_certs; + STACK_OF(X509_NAME) *name_chain = NULL; + X509 *x509 = NULL; + X509_NAME *subject = NULL; + X509_STORE *ca_store = NULL; +#ifdef OPENSSL_IS_BORINGSSL + size_t i; +#else + int i; +#endif + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + *err = "no request ctx found"; + return NGX_ERROR; + } + + if (!(ctx->context & NGX_HTTP_LUA_CONTEXT_SSL_CERT)) { + *err = "API disabled in the current context"; + return NGX_ERROR; + } + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + /* enable verify */ + + SSL_set_verify(ssl_conn, SSL_VERIFY_PEER, ngx_http_lua_ssl_verify_callback); + + /* set depth */ + + if (depth < 0) { + sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module); + if (sscf != NULL) { + depth = sscf->verify_depth; + + } else { + /* same as the default value of ssl_verify_depth */ + depth = 1; + } + } + + SSL_set_verify_depth(ssl_conn, depth); + + /* set CA chain */ + + if (client_chain != NULL || trusted_chain != NULL) { + + ca_store = X509_STORE_new(); + if (ca_store == NULL) { + *err = "X509_STORE_new() failed"; + return NGX_ERROR; + } + + if (client_chain != NULL) { + + /* construct name chain */ + name_chain = sk_X509_NAME_new_null(); + if (name_chain == NULL) { + *err = "sk_X509_NAME_new_null() failed"; + goto failed; + } + + for (i = 0; i < sk_X509_num(client_chain); i++) { + x509 = sk_X509_value(client_chain, i); + if (x509 == NULL) { + *err = "sk_X509_value() failed"; + goto failed; + } + + /* add subject to name chain, which will be sent to client */ + subject = X509_NAME_dup(X509_get_subject_name(x509)); + if (subject == NULL) { + *err = "X509_get_subject_name() failed"; + goto failed; + } + + if (!sk_X509_NAME_push(name_chain, subject)) { + *err = "sk_X509_NAME_push() failed"; + X509_NAME_free(subject); + goto failed; + } + + /* add to trusted CA store */ + if (X509_STORE_add_cert(ca_store, x509) == 0) { + *err = "X509_STORE_add_cert() failed"; + goto failed; + } + } + + /* clean subject name list, and set it for send to client */ + SSL_set_client_CA_list(ssl_conn, name_chain); + } + + if (trusted_chain != NULL) { + for (i = 0; i < sk_X509_num(trusted_chain); i++) { + x509 = sk_X509_value(trusted_chain, i); + if (x509 == NULL) { + *err = "sk_X509_value() failed"; + goto failed; + } + + /* add to trusted CA store */ + if (X509_STORE_add_cert(ca_store, x509) == 0) { + *err = "X509_STORE_add_cert() failed"; + goto failed; + } + } + } + + /* clean ca_store, and store new ca_store */ + if (SSL_set0_verify_cert_store(ssl_conn, ca_store) == 0) { + *err = "SSL_set0_verify_cert_store() failed"; + goto failed; + } + } + + return NGX_OK; + +failed: + + sk_X509_NAME_free(name_chain); + + X509_STORE_free(ca_store); + + return NGX_ERROR; +#endif +} + + +ngx_ssl_conn_t * +ngx_http_lua_ffi_get_req_ssl_pointer(ngx_http_request_t *r, const char **err) +{ + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NULL; + } + + if (r->connection->ssl->connection == NULL) { + *err = "bad ssl connection"; + return NULL; + } + + return r->connection->ssl->connection; +} + + +int +ngx_http_lua_ffi_ssl_client_random(ngx_http_request_t *r, + unsigned char *out, size_t *outlen, char **err) +{ + ngx_ssl_conn_t *ssl_conn; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + *outlen = SSL_get_client_random(ssl_conn, out, *outlen); + + return NGX_OK; +} #endif /* NGX_HTTP_SSL */ diff --git a/src/ngx_http_lua_ssl_client_helloby.c b/src/ngx_http_lua_ssl_client_helloby.c new file mode 100644 index 0000000000..283d1017f0 --- /dev/null +++ b/src/ngx_http_lua_ssl_client_helloby.c @@ -0,0 +1,833 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef DDEBUG +#define DDEBUG 0 +#endif +#include "ddebug.h" + + +#if (NGX_HTTP_SSL) + + +#include "ngx_http_lua_cache.h" +#include "ngx_http_lua_initworkerby.h" +#include "ngx_http_lua_util.h" +#include "ngx_http_ssl_module.h" +#include "ngx_http_lua_contentby.h" +#include "ngx_http_lua_ssl_client_helloby.h" +#include "ngx_http_lua_directive.h" +#include "ngx_http_lua_ssl.h" + + +static void ngx_http_lua_ssl_client_hello_done(void *data); +static void ngx_http_lua_ssl_client_hello_aborted(void *data); +static u_char *ngx_http_lua_log_ssl_client_hello_error(ngx_log_t *log, + u_char *buf, size_t len); +static ngx_int_t ngx_http_lua_ssl_client_hello_by_chunk(lua_State *L, + ngx_http_request_t *r); + + +ngx_int_t +ngx_http_lua_ssl_client_hello_handler_file(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L) +{ + ngx_int_t rc; + + rc = ngx_http_lua_cache_loadfile(r->connection->log, L, + lscf->srv.ssl_client_hello_src.data, + &lscf->srv.ssl_client_hello_src_ref, + lscf->srv.ssl_client_hello_src_key); + if (rc != NGX_OK) { + return rc; + } + + /* make sure we have a valid code chunk */ + ngx_http_lua_assert(lua_isfunction(L, -1)); + + return ngx_http_lua_ssl_client_hello_by_chunk(L, r); +} + + +ngx_int_t +ngx_http_lua_ssl_client_hello_handler_inline(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L) +{ + ngx_int_t rc; + + rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, + lscf->srv.ssl_client_hello_src.data, + lscf->srv.ssl_client_hello_src.len, + &lscf->srv.ssl_client_hello_src_ref, + lscf->srv.ssl_client_hello_src_key, + (const char *) lscf->srv.ssl_client_hello_chunkname); + if (rc != NGX_OK) { + return rc; + } + + /* make sure we have a valid code chunk */ + ngx_http_lua_assert(lua_isfunction(L, -1)); + + return ngx_http_lua_ssl_client_hello_by_chunk(L, r); +} + + +char * +ngx_http_lua_ssl_client_hello_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + char *rv; + ngx_conf_t save; + + save = *cf; + cf->handler = ngx_http_lua_ssl_client_hello_by_lua; + cf->handler_conf = conf; + + rv = ngx_http_lua_conf_lua_block_parse(cf, cmd); + + *cf = save; + + return rv; +} + + +char * +ngx_http_lua_ssl_client_hello_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ +#ifndef SSL_ERROR_WANT_CLIENT_HELLO_CB + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "at least OpenSSL 1.1.1 required but found " + OPENSSL_VERSION_TEXT); + + return NGX_CONF_ERROR; + +#else + + size_t chunkname_len; + u_char *chunkname; + u_char *cache_key = NULL; + u_char *name; + ngx_str_t *value; + ngx_http_lua_srv_conf_t *lscf = conf; + + /* must specify a concrete handler */ + if (cmd->post == NULL) { + return NGX_CONF_ERROR; + } + + if (lscf->srv.ssl_client_hello_handler) { + return "is duplicate"; + } + + if (ngx_http_lua_ssl_init(cf->log) != NGX_OK) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + lscf->srv.ssl_client_hello_handler = + (ngx_http_lua_srv_conf_handler_pt) cmd->post; + + if (cmd->post == ngx_http_lua_ssl_client_hello_handler_file) { + /* Lua code in an external file */ + + name = ngx_http_lua_rebase_path(cf->pool, value[1].data, + value[1].len); + if (name == NULL) { + return NGX_CONF_ERROR; + } + + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } + + lscf->srv.ssl_client_hello_src.data = name; + lscf->srv.ssl_client_hello_src.len = ngx_strlen(name); + + } else { + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, + "ssl_client_hello_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } + + chunkname = ngx_http_lua_gen_chunk_name(cf, "ssl_client_hello_by_lua", + sizeof("ssl_client_hello_by_lua") - 1, + &chunkname_len); + if (chunkname == NULL) { + return NGX_CONF_ERROR; + } + + /* Don't eval nginx variables for inline lua code */ + lscf->srv.ssl_client_hello_src = value[1]; + lscf->srv.ssl_client_hello_chunkname = chunkname; + } + + lscf->srv.ssl_client_hello_src_key = cache_key; + + return NGX_CONF_OK; + +#endif /* NO SSL_ERROR_WANT_CLIENT_HELLO_CB */ +} + + +int +ngx_http_lua_ssl_client_hello_handler(ngx_ssl_conn_t *ssl_conn, + int *al, void *arg) +{ + lua_State *L; + ngx_int_t rc; + ngx_connection_t *c, *fc; + ngx_http_request_t *r = NULL; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_lua_srv_conf_t *lscf; + ngx_http_core_loc_conf_t *clcf; + ngx_http_lua_ssl_ctx_t *cctx; + ngx_http_core_srv_conf_t *cscf; + + c = ngx_ssl_get_connection(ssl_conn); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl client hello: connection reusable: %ud", c->reusable); + + cctx = ngx_http_lua_ssl_get_ctx(c->ssl->connection); + + dd("ssl client hello handler, client-hello-ctx=%p", cctx); + + if (cctx && cctx->entered_client_hello_handler) { + /* not the first time */ + + if (cctx->done) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl_client_hello_by_lua: " + "client hello cb exit code: %d", + cctx->exit_code); + + dd("lua ssl client hello done, finally"); + return cctx->exit_code; + } + + return -1; + } + + dd("first time"); + +#if (nginx_version > 1029001) + /* see commit 0373fe5d98c1515640 for more details */ + rc = ngx_ssl_client_hello_callback(ssl_conn, al, arg); + + if (rc == 0) { + return rc; + } +#endif + +#if (nginx_version < 1017009) + ngx_reusable_connection(c, 0); +#endif + + hc = c->data; + + fc = ngx_http_lua_create_fake_connection(NULL); + if (fc == NULL) { + goto failed; + } + + fc->log->handler = ngx_http_lua_log_ssl_client_hello_error; + fc->log->data = fc; + + fc->addr_text = c->addr_text; + fc->listening = c->listening; + + r = ngx_http_lua_create_fake_request(fc); + if (r == NULL) { + goto failed; + } + + r->main_conf = hc->conf_ctx->main_conf; + r->srv_conf = hc->conf_ctx->srv_conf; + r->loc_conf = hc->conf_ctx->loc_conf; + + fc->log->file = c->log->file; + fc->log->log_level = c->log->log_level; + fc->ssl = c->ssl; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + +#if nginx_version >= 1009000 + ngx_set_connection_log(fc, clcf->error_log); +#else + ngx_http_set_connection_log(fc, clcf->error_log); +#endif + + if (cctx == NULL) { + cctx = ngx_pcalloc(c->pool, sizeof(ngx_http_lua_ssl_ctx_t)); + if (cctx == NULL) { + goto failed; /* error */ + } + + cctx->ctx_ref = LUA_NOREF; + } + + cctx->exit_code = 1; /* successful by default */ + cctx->connection = c; + cctx->request = r; + cctx->entered_client_hello_handler = 1; + cctx->done = 0; + + dd("setting cctx"); + + if (SSL_set_ex_data(c->ssl->connection, ngx_http_lua_ssl_ctx_index, cctx) + == 0) + { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_ex_data() failed"); + goto failed; + } + + lscf = ngx_http_get_module_srv_conf(r, ngx_http_lua_module); + + /* TODO honor lua_code_cache off */ + L = ngx_http_lua_get_lua_vm(r, NULL); + + c->log->action = "loading SSL client hello by lua"; + + if (lscf->srv.ssl_client_hello_handler == NULL) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "no ssl_client_hello_by_lua* defined in " + "server %V", &cscf->server_name); + + goto failed; + } + + rc = lscf->srv.ssl_client_hello_handler(r, lscf, L); + + if (rc >= NGX_OK || rc == NGX_ERROR) { + cctx->done = 1; + + if (cctx->cleanup) { + *cctx->cleanup = NULL; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl_client_hello_by_lua: handler return value: %i, " + "client hello cb exit code: %d", rc, cctx->exit_code); + + c->log->action = "SSL handshaking"; + return cctx->exit_code; + } + + /* rc == NGX_DONE */ + + cln = ngx_pool_cleanup_add(fc->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->handler = ngx_http_lua_ssl_client_hello_done; + cln->data = cctx; + + if (cctx->cleanup == NULL) { + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->data = cctx; + cctx->cleanup = &cln->handler; + } + + *cctx->cleanup = ngx_http_lua_ssl_client_hello_aborted; + + return -1; + +failed: + + if (r && r->pool) { + ngx_http_lua_free_fake_request(r); + } + + if (fc) { + ngx_http_lua_close_fake_connection(fc); + } + + return 0; +} + + +static void +ngx_http_lua_ssl_client_hello_done(void *data) +{ + ngx_connection_t *c; + ngx_http_lua_ssl_ctx_t *cctx = data; + + dd("lua ssl client hello done"); + + if (cctx->aborted) { + return; + } + + ngx_http_lua_assert(cctx->done == 0); + + cctx->done = 1; + + if (cctx->cleanup) { + *cctx->cleanup = NULL; + } + + c = cctx->connection; + + c->log->action = "SSL handshaking"; + + ngx_post_event(c->write, &ngx_posted_events); + +#if (HAVE_QUIC_SSL_LUA_YIELD_PATCH && NGX_HTTP_V3) +# if defined(SSL_ERROR_WANT_CLIENT_HELLO_CB) +# if (NGX_QUIC_OPENSSL_COMPAT || NGX_QUIC_OPENSSL_API) + ngx_http_lua_resume_quic_ssl_handshake(c); +# endif +# endif +#endif +} + + +static void +ngx_http_lua_ssl_client_hello_aborted(void *data) +{ + ngx_http_lua_ssl_ctx_t *cctx = data; + + dd("lua ssl client hello aborted"); + + if (cctx->done) { + /* completed successfully already */ + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cctx->connection->log, 0, + "ssl_client_hello_by_lua: client hello cb aborted"); + + cctx->aborted = 1; + cctx->request->connection->ssl = NULL; + + ngx_http_lua_finalize_fake_request(cctx->request, NGX_ERROR); +} + + +static u_char * +ngx_http_lua_log_ssl_client_hello_error(ngx_log_t *log, + u_char *buf, size_t len) +{ + u_char *p; + ngx_connection_t *c; + + if (log->action) { + p = ngx_snprintf(buf, len, " while %s", log->action); + len -= p - buf; + buf = p; + } + + p = ngx_snprintf(buf, len, ", context: ssl_client_hello_by_lua*"); + len -= p - buf; + buf = p; + + c = log->data; + + if (c && c->addr_text.len) { + p = ngx_snprintf(buf, len, ", client: %V", &c->addr_text); + len -= p - buf; + buf = p; + } + + if (c && c->listening && c->listening->addr_text.len) { + p = ngx_snprintf(buf, len, ", server: %V", &c->listening->addr_text); + /* len -= p - buf; */ + buf = p; + } + + return buf; +} + + +static ngx_int_t +ngx_http_lua_ssl_client_hello_by_chunk(lua_State *L, ngx_http_request_t *r) +{ + int co_ref; + ngx_int_t rc; + lua_State *co; + ngx_http_lua_ctx_t *ctx; + ngx_pool_cleanup_t *cln; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + + if (ctx == NULL) { + ctx = ngx_http_lua_create_ctx(r); + if (ctx == NULL) { + rc = NGX_ERROR; + ngx_http_lua_finalize_request(r, rc); + return rc; + } + + } else { + dd("reset ctx"); + ngx_http_lua_reset_ctx(r, L, ctx); + } + + ctx->entered_content_phase = 1; + + /* {{{ new coroutine to handle request */ + co = ngx_http_lua_new_thread(r, L, &co_ref); + + if (co == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "lua: failed to create new coroutine to handle request"); + + rc = NGX_ERROR; + ngx_http_lua_finalize_request(r, rc); + return rc; + } + + /* move code closure to new coroutine */ + lua_xmove(L, co, 1); + +#ifndef OPENRESTY_LUAJIT + /* set closure's env table to new coroutine's globals table */ + ngx_http_lua_get_globals_table(co); + lua_setfenv(co, -2); +#endif + + /* save nginx request in coroutine globals table */ + ngx_http_lua_set_req(co, r); + + ctx->cur_co_ctx = &ctx->entry_co_ctx; + ctx->cur_co_ctx->co = co; + ctx->cur_co_ctx->co_ref = co_ref; +#ifdef NGX_LUA_USE_ASSERT + ctx->cur_co_ctx->co_top = 1; +#endif + + ngx_http_lua_attach_co_ctx_to_L(co, ctx->cur_co_ctx); + + /* register request cleanup hooks */ + if (ctx->cleanup == NULL) { + cln = ngx_pool_cleanup_add(r->pool, 0); + if (cln == NULL) { + rc = NGX_ERROR; + ngx_http_lua_finalize_request(r, rc); + return rc; + } + + cln->handler = ngx_http_lua_request_cleanup_handler; + cln->data = ctx; + ctx->cleanup = &cln->handler; + } + + ctx->context = NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO; + + rc = ngx_http_lua_run_thread(L, r, ctx, 0); + + if (rc == NGX_ERROR || rc >= NGX_OK) { + /* do nothing */ + + } else if (rc == NGX_AGAIN) { + rc = ngx_http_lua_content_run_posted_threads(L, r, ctx, 0); + + } else if (rc == NGX_DONE) { + rc = ngx_http_lua_content_run_posted_threads(L, r, ctx, 1); + + } else { + rc = NGX_OK; + } + + ngx_http_lua_finalize_request(r, rc); + return rc; +} + + +int +ngx_http_lua_ffi_ssl_get_client_hello_server_name(ngx_http_request_t *r, + const char **name, size_t *namelen, char **err) +{ +#ifdef LIBRESSL_VERSION_NUMBER + *err = "LibreSSL does not support by ssl_client_hello_by_lua*"; + return NGX_ERROR; +#else + ngx_ssl_conn_t *ssl_conn; +#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB + const unsigned char *p; + size_t remaining, len; +#endif + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + +#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB + remaining = 0; + + /* This code block is taken from OpenSSL's client_hello_select_server_ctx() + * */ + if (!SSL_client_hello_get0_ext(ssl_conn, TLSEXT_TYPE_server_name, &p, + &remaining)) + { + return NGX_DECLINED; + } + + if (remaining <= 2) { + *err = "Bad SSL Client Hello Extension"; + return NGX_ERROR; + } + + len = (*(p++) << 8); + len += *(p++); + if (len + 2 != remaining) { + *err = "Bad SSL Client Hello Extension"; + return NGX_ERROR; + } + + remaining = len; + if (remaining == 0 || *p++ != TLSEXT_NAMETYPE_host_name) { + *err = "Bad SSL Client Hello Extension"; + return NGX_ERROR; + } + + remaining--; + if (remaining <= 2) { + *err = "Bad SSL Client Hello Extension"; + return NGX_ERROR; + } + + len = (*(p++) << 8); + len += *(p++); + if (len + 2 > remaining) { + *err = "Bad SSL Client Hello Extension"; + return NGX_ERROR; + } + + remaining = len; + *name = (const char *) p; + *namelen = len; + + return NGX_OK; + +#else + *err = "OpenSSL too old to support this function"; + return NGX_ERROR; + +#endif + +#else + + *err = "no TLS extension support"; + return NGX_ERROR; +#endif +#endif /* LIBRESSL_VERSION_NUMBER */ +} + + +int +ngx_http_lua_ffi_ssl_get_client_hello_ext(ngx_http_request_t *r, + unsigned int type, const unsigned char **out, size_t *outlen, char **err) +{ +#ifdef LIBRESSL_VERSION_NUMBER + *err = "LibreSSL does not support by ssl_client_hello_by_lua*"; + return NGX_ERROR; +#else + ngx_ssl_conn_t *ssl_conn; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + +#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB + if (SSL_client_hello_get0_ext(ssl_conn, type, out, outlen) == 0) { + return NGX_DECLINED; + } + + return NGX_OK; +#else + *err = "OpenSSL too old to support this function"; + return NGX_ERROR; +#endif +#endif /* LIBRESSL_VERSION_NUMBER */ +} + + +int +ngx_http_lua_ffi_ssl_get_client_hello_ext_present(ngx_http_request_t *r, + int **extensions, size_t *extensions_len, char **err) +{ +#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB + ngx_ssl_conn_t *ssl_conn; + int got_extensions; + size_t ext_len; + int *ext_out; + /* OPENSSL will allocate memory for us and make the ext_out point to it */ + + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + got_extensions = SSL_client_hello_get1_extensions_present(ssl_conn, + &ext_out, &ext_len); + if (!got_extensions || !ext_out || !ext_len) { + *err = "failed SSL_client_hello_get1_extensions_present()"; + return NGX_DECLINED; + } + + *extensions = ngx_palloc(r->connection->pool, sizeof(int) * ext_len); + if (*extensions != NULL) { + ngx_memcpy(*extensions, ext_out, sizeof(int) * ext_len); + *extensions_len = ext_len; + } + + OPENSSL_free(ext_out); + return NGX_OK; +#else + *err = "OpenSSL too old to support this function"; + return NGX_ERROR; +#endif +} + + +int ngx_http_lua_ffi_ssl_get_client_hello_ciphers(ngx_http_request_t *r, + unsigned short *ciphers, size_t ciphers_size, char **err) +{ +#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB + int i; + size_t ciphers_cnt; + size_t ciphersuites_bytes; + ngx_ssl_conn_t *ssl_conn; + const unsigned char *ciphers_raw; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + ciphersuites_bytes = SSL_client_hello_get0_ciphers(ssl_conn, &ciphers_raw); + + if (ciphersuites_bytes == 0) { + *err = "failed SSL_client_hello_get0_ciphers()"; + return NGX_DECLINED; + } + + if (ciphersuites_bytes % 2 != 0) { + *err = "SSL_client_hello_get0_ciphers() odd ciphersuites_bytes"; + return NGX_DECLINED; + } + + ciphers_cnt = ciphersuites_bytes / 2; + ciphers_cnt = ciphers_cnt > ciphers_size ? ciphers_size : ciphers_cnt; + + for (i = 0 ; i < (int) ciphers_cnt ; i++) { + ciphers[i] = (ciphers_raw[i * 2] << 8) | ciphers_raw[i * 2 + 1]; + } + + return ciphers_cnt; +#else + *err = "OpenSSL too old to support this function"; + return NGX_ERROR; +#endif +} + + +int +ngx_http_lua_ffi_ssl_set_protocols(ngx_http_request_t *r, + int protocols, char **err) +{ + + ngx_ssl_conn_t *ssl_conn; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + +#if OPENSSL_VERSION_NUMBER >= 0x009080dfL + /* only in 0.9.8m+ */ + SSL_clear_options(ssl_conn, + SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1); +#endif + + if (!(protocols & NGX_SSL_SSLv2)) { + SSL_set_options(ssl_conn, SSL_OP_NO_SSLv2); + } + + if (!(protocols & NGX_SSL_SSLv3)) { + SSL_set_options(ssl_conn, SSL_OP_NO_SSLv3); + } + + if (!(protocols & NGX_SSL_TLSv1)) { + SSL_set_options(ssl_conn, SSL_OP_NO_TLSv1); + } + +#ifdef SSL_OP_NO_TLSv1_1 + SSL_clear_options(ssl_conn, SSL_OP_NO_TLSv1_1); + if (!(protocols & NGX_SSL_TLSv1_1)) { + SSL_set_options(ssl_conn, SSL_OP_NO_TLSv1_1); + } +#endif + +#ifdef SSL_OP_NO_TLSv1_2 + SSL_clear_options(ssl_conn, SSL_OP_NO_TLSv1_2); + if (!(protocols & NGX_SSL_TLSv1_2)) { + SSL_set_options(ssl_conn, SSL_OP_NO_TLSv1_2); + } +#endif + +#if defined(NGX_SSL_TLSv1_3) && defined( SSL_OP_NO_TLSv1_3) + SSL_clear_options(ssl_conn, SSL_OP_NO_TLSv1_3); + if (!(protocols & NGX_SSL_TLSv1_3)) { + SSL_set_options(ssl_conn, SSL_OP_NO_TLSv1_3); + } +#endif + + return NGX_OK; +} + +#endif /* NGX_HTTP_SSL */ diff --git a/src/ngx_http_lua_ssl_client_helloby.h b/src/ngx_http_lua_ssl_client_helloby.h new file mode 100644 index 0000000000..682a7cf292 --- /dev/null +++ b/src/ngx_http_lua_ssl_client_helloby.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef _NGX_HTTP_LUA_SSL_CLIENT_HELLOBY_H_INCLUDED_ +#define _NGX_HTTP_LUA_SSL_CLIENT_HELLOBY_H_INCLUDED_ + + +#include "ngx_http_lua_common.h" + + +#if (NGX_HTTP_SSL) + +ngx_int_t ngx_http_lua_ssl_client_hello_handler_inline(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L); + +ngx_int_t ngx_http_lua_ssl_client_hello_handler_file(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L); + +char *ngx_http_lua_ssl_client_hello_by_lua_block(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); + +char *ngx_http_lua_ssl_client_hello_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +int ngx_http_lua_ssl_client_hello_handler(ngx_ssl_conn_t *ssl_conn, + int *al, void *arg); + + +#endif /* NGX_HTTP_SSL */ + + +#endif /* _NGX_HTTP_LUA_SSL_CLIENT_HELLOBY_H_INCLUDED_ */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_ssl_export_keying_material.c b/src/ngx_http_lua_ssl_export_keying_material.c new file mode 100644 index 0000000000..ec64c049a9 --- /dev/null +++ b/src/ngx_http_lua_ssl_export_keying_material.c @@ -0,0 +1,125 @@ + +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + + +#ifndef DDEBUG +#define DDEBUG 0 +#endif + + +#include "ddebug.h" + +#if (NGX_HTTP_SSL) + +#include + +#include "ngx_http_lua_cache.h" +#include "ngx_http_lua_initworkerby.h" +#include "ngx_http_lua_util.h" +#include "ngx_http_ssl_module.h" +#include "ngx_http_lua_contentby.h" +#include "ngx_http_lua_ssl_session_fetchby.h" +#include "ngx_http_lua_ssl.h" +#include "ngx_http_lua_directive.h" +#include "ngx_http_lua_ssl_export_keying_material.h" + + +ngx_int_t +ngx_http_lua_ffi_ssl_export_keying_material(ngx_http_request_t *r, + u_char *out, size_t out_size, const char *label, size_t llen, + const u_char *context, size_t ctxlen, int use_ctx, char **err) +{ +#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER < 0x10101000L + *err = "BoringSSL does not support SSL_export_keying_material"; + return NGX_ERROR; +#elif defined(LIBRESSL_VERSION_NUMBER) + *err = "LibreSSL does not support SSL_export_keying_material"; + return NGX_ERROR; +#elif OPENSSL_VERSION_NUMBER < 0x10101000L + *err = "OpenSSL too old"; + return NGX_ERROR; +#else + ngx_connection_t *c; + ngx_ssl_conn_t *ssl_conn; + int rc; + + c = r->connection; + if (c == NULL || c->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl connection"; + return NGX_ERROR; + } + + rc = SSL_export_keying_material(ssl_conn, out, out_size, label, llen, + context, ctxlen, use_ctx); + if (rc == 1) { + return NGX_OK; + } + + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "SSL_export_keying_material rc: %d, error: %s", + rc, ERR_error_string(ERR_get_error(), NULL)); + + *err = "SSL_export_keying_material() failed"; + + return NGX_ERROR; +#endif +} + + +ngx_int_t +ngx_http_lua_ffi_ssl_export_keying_material_early(ngx_http_request_t *r, + u_char *out, size_t out_size, const char *label, size_t llen, + const u_char *context, size_t ctxlen, char **err) +{ +#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER < 0x10101000L + *err = "BoringSSL does not support SSL_export_keying_material"; + return NGX_ERROR; +#elif defined(LIBRESSL_VERSION_NUMBER) + *err = "LibreSSL does not support SSL_export_keying_material"; + return NGX_ERROR; +#elif OPENSSL_VERSION_NUMBER < 0x10101000L + *err = "OpenSSL too old"; + return NGX_ERROR; +#else + int rc; + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + + c = r->connection; + if (c == NULL || c->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl connection"; + return NGX_ERROR; + } + + rc = SSL_export_keying_material_early(ssl_conn, out, out_size, + label, llen, context, ctxlen); + + if (rc == 1) { + return NGX_OK; + } + + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "SSL_export_keying_material_early rc: %d, error: %s", + rc, ERR_error_string(ERR_get_error(), NULL)); + + *err = "SSL_export_keying_material_early() failed"; + + return NGX_ERROR; +#endif +} + +#endif /* NGX_HTTP_SSL */ diff --git a/src/ngx_http_lua_ssl_export_keying_material.h b/src/ngx_http_lua_ssl_export_keying_material.h new file mode 100644 index 0000000000..7a54bcda35 --- /dev/null +++ b/src/ngx_http_lua_ssl_export_keying_material.h @@ -0,0 +1,24 @@ + +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + + +#ifndef _NGX_HTTP_LUA_SSL_EXPORT_KEYING_MATERIAL_H_INCLUDED_ +#define _NGX_HTTP_LUA_SSL_EXPORT_KEYING_MATERIAL_H_INCLUDED_ + +#include "ngx_http_lua_common.h" + +#if (NGX_HTTP_SSL) +ngx_int_t ngx_http_lua_ffi_ssl_export_keying_material(ngx_http_request_t *r, + u_char *out, size_t out_size, const char *label, size_t llen, + const u_char *ctx, size_t ctxlen, int use_ctx, char **err); + +ngx_int_t ngx_http_lua_ffi_ssl_export_keying_material_early( + ngx_http_request_t *r, u_char *out, size_t out_size, const char *label, + size_t llen, const u_char *ctx, size_t ctxlen, char **err); +#endif + +#endif /* _NGX_HTTP_LUA_SSL_EXPORT_KEYING_MATERIAL_H_INCLUDED_ */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_ssl_ocsp.c b/src/ngx_http_lua_ssl_ocsp.c index 9ec8b5098c..73e1b9c7f5 100644 --- a/src/ngx_http_lua_ssl_ocsp.c +++ b/src/ngx_http_lua_ssl_ocsp.c @@ -16,13 +16,12 @@ #include "ngx_http_lua_common.h" -#ifndef NGX_LUA_NO_FFI_API - #ifdef NGX_HTTP_LUA_USE_OCSP static int ngx_http_lua_ssl_empty_status_callback(ngx_ssl_conn_t *ssl_conn, void *data); -#endif +static long ngx_http_lua_ssl_stapling_time(ASN1_GENERALIZEDTIME *asn1time); +#endif int ngx_http_lua_ffi_ssl_get_ocsp_responder_from_der_chain( @@ -264,7 +263,7 @@ ngx_http_lua_ffi_ssl_create_ocsp_request(const char *chain_data, int ngx_http_lua_ffi_ssl_validate_ocsp_response(const u_char *resp, size_t resp_len, const char *chain_data, size_t chain_len, - u_char *errbuf, size_t *errbuf_size) + u_char *errbuf, size_t *errbuf_size, long *valid) { #ifndef NGX_HTTP_LUA_USE_OCSP @@ -281,7 +280,7 @@ ngx_http_lua_ffi_ssl_validate_ocsp_response(const u_char *resp, OCSP_RESPONSE *ocsp = NULL; OCSP_BASICRESP *basic = NULL; STACK_OF(X509) *chain = NULL; - ASN1_GENERALIZEDTIME *thisupdate, *nextupdate; + ASN1_GENERALIZEDTIME *thisupdate = NULL, *nextupdate = NULL; ocsp = d2i_OCSP_RESPONSE(NULL, &resp, resp_len); if (ocsp == NULL) { @@ -385,6 +384,16 @@ ngx_http_lua_ffi_ssl_validate_ocsp_response(const u_char *resp, goto error; } + if (nextupdate) { + *valid = ngx_http_lua_ssl_stapling_time(nextupdate); + if (*valid == NGX_ERROR) { + *errbuf_size = ngx_snprintf(errbuf, *errbuf_size, + "invalid nextUpdate time " + "in certificate status") - errbuf; + goto error; + } + } + sk_X509_free(chain); X509_free(cert); X509_free(issuer); @@ -439,6 +448,40 @@ ngx_http_lua_ssl_empty_status_callback(ngx_ssl_conn_t *ssl_conn, void *data) { return SSL_TLSEXT_ERR_OK; } + + +static long +ngx_http_lua_ssl_stapling_time(ASN1_GENERALIZEDTIME *asn1time) +{ + BIO *bio; + char *value; + size_t len; + time_t time; + + /* + * OpenSSL doesn't provide a way to convert ASN1_GENERALIZEDTIME + * into long. To do this, we use ASN1_GENERALIZEDTIME_print(), + * which uses the "MMM DD HH:MM:SS YYYY [GMT]" format (e.g., + * "Feb 3 00:55:52 2015 GMT"), and parse the result. + */ + + bio = BIO_new(BIO_s_mem()); + if (bio == NULL) { + return NGX_ERROR; + } + + /* fake weekday prepended to match C asctime() format */ + + BIO_write(bio, "Tue ", sizeof("Tue ") - 1); + ASN1_GENERALIZEDTIME_print(bio, asn1time); + len = BIO_get_mem_data(bio, &value); + + time = ngx_parse_http_time((u_char *) value, len); + + BIO_free(bio); + + return time; +} #endif @@ -500,7 +543,5 @@ ngx_http_lua_ffi_ssl_set_ocsp_status_resp(ngx_http_request_t *r, #endif /* NGX_HTTP_LUA_USE_OCSP */ } -#endif /* NGX_LUA_NO_FFI_API */ - #endif /* NGX_HTTP_SSL */ diff --git a/src/ngx_http_lua_ssl_session_fetchby.c b/src/ngx_http_lua_ssl_session_fetchby.c index 2c75f6a912..fee039fca8 100644 --- a/src/ngx_http_lua_ssl_session_fetchby.c +++ b/src/ngx_http_lua_ssl_session_fetchby.c @@ -41,6 +41,7 @@ ngx_http_lua_ssl_sess_fetch_handler_file(ngx_http_request_t *r, rc = ngx_http_lua_cache_loadfile(r->connection->log, L, lscf->srv.ssl_sess_fetch_src.data, + &lscf->srv.ssl_sess_fetch_src_ref, lscf->srv.ssl_sess_fetch_src_key); if (rc != NGX_OK) { return rc; @@ -63,8 +64,9 @@ ngx_http_lua_ssl_sess_fetch_handler_inline(ngx_http_request_t *r, rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, lscf->srv.ssl_sess_fetch_src.data, lscf->srv.ssl_sess_fetch_src.len, + &lscf->srv.ssl_sess_fetch_src_ref, lscf->srv.ssl_sess_fetch_src_key, - "=ssl_session_fetch_by_lua_block"); + (const char *) lscf->srv.ssl_sess_fetch_chunkname); if (rc != NGX_OK) { return rc; } @@ -100,7 +102,9 @@ char * ngx_http_lua_ssl_sess_fetch_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - u_char *p; + size_t chunkname_len; + u_char *chunkname; + u_char *cache_key = NULL; u_char *name; ngx_str_t *value; ngx_http_lua_srv_conf_t *lscf = conf; @@ -127,44 +131,43 @@ ngx_http_lua_ssl_sess_fetch_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, if (cmd->post == ngx_http_lua_ssl_sess_fetch_handler_file) { /* Lua code in an external file */ - name = ngx_http_lua_rebase_path(cf->pool, value[1].data, value[1].len); if (name == NULL) { return NGX_CONF_ERROR; } - lscf->srv.ssl_sess_fetch_src.data = name; - lscf->srv.ssl_sess_fetch_src.len = ngx_strlen(name); - - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_FILE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - lscf->srv.ssl_sess_fetch_src_key = p; - - p = ngx_copy(p, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; + lscf->srv.ssl_sess_fetch_src.data = name; + lscf->srv.ssl_sess_fetch_src.len = ngx_strlen(name); } else { - /* inlined Lua code */ - - lscf->srv.ssl_sess_fetch_src = value[1]; - - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_INLINE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, + "ssl_session_fetch_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - lscf->srv.ssl_sess_fetch_src_key = p; + chunkname = ngx_http_lua_gen_chunk_name(cf, "ssl_session_fetch_by_lua", + sizeof("ssl_session_fetch_by_lua") - 1, &chunkname_len); + if (chunkname == NULL) { + return NGX_CONF_ERROR; + } - p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; + /* Don't eval nginx variables for inline lua code */ + lscf->srv.ssl_sess_fetch_src = value[1]; + lscf->srv.ssl_sess_fetch_chunkname = chunkname; } + lscf->srv.ssl_sess_fetch_src_key = cache_key; + return NGX_CONF_OK; } @@ -177,6 +180,9 @@ ngx_http_lua_ssl_sess_fetch_handler(ngx_ssl_conn_t *ssl_conn, #endif u_char *id, int len, int *copy) { +#if defined(NGX_SSL_TLSv1_3) && defined(TLS1_3_VERSION) + int tls_version; +#endif lua_State *L; ngx_int_t rc; ngx_connection_t *c, *fc = NULL; @@ -194,6 +200,18 @@ ngx_http_lua_ssl_sess_fetch_handler(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection(ssl_conn); +#if defined(NGX_SSL_TLSv1_3) && defined(TLS1_3_VERSION) + tls_version = SSL_version(ssl_conn); + + if (tls_version >= TLS1_3_VERSION) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl_session_fetch_by_lua*: skipped since " + "TLS version >= 1.3 (%xd)", tls_version); + + return 0; + } +#endif + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "ssl session fetch: connection reusable: %ud", c->reusable); @@ -228,7 +246,9 @@ ngx_http_lua_ssl_sess_fetch_handler(ngx_ssl_conn_t *ssl_conn, dd("first time"); +#if (nginx_version < 1017009) ngx_reusable_connection(c, 0); +#endif hc = c->data; @@ -258,26 +278,11 @@ ngx_http_lua_ssl_sess_fetch_handler(ngx_ssl_conn_t *ssl_conn, clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); -#if defined(nginx_version) && nginx_version >= 1003014 - -# if nginx_version >= 1009000 - +#if (nginx_version >= 1009000) ngx_set_connection_log(fc, clcf->error_log); -# else - - ngx_http_set_connection_log(fc, clcf->error_log); - -# endif - #else - - fc->log->file = clcf->error_log->file; - - if (!(fc->log->log_level & NGX_LOG_DEBUG_CONNECTION)) { - fc->log->log_level = clcf->error_log->log_level; - } - + ngx_http_set_connection_log(fc, clcf->error_log); #endif if (cctx == NULL) { @@ -285,6 +290,8 @@ ngx_http_lua_ssl_sess_fetch_handler(ngx_ssl_conn_t *ssl_conn, if (cctx == NULL) { goto failed; /* error */ } + + cctx->ctx_ref = LUA_NOREF; } cctx->exit_code = 1; /* successful by default */ @@ -409,7 +416,7 @@ ngx_http_lua_ssl_sess_fetch_aborted(void *data) { ngx_http_lua_ssl_ctx_t *cctx = data; - dd("lua ssl sess_fetch done"); + dd("lua ssl sess_fetch aborted"); if (cctx->done) { /* completed successfully already */ @@ -444,15 +451,18 @@ ngx_http_lua_log_ssl_sess_fetch_error(ngx_log_t *log, u_char *buf, size_t len) c = log->data; - if (c->addr_text.len) { - p = ngx_snprintf(buf, len, ", client: %V", &c->addr_text); - len -= p - buf; - buf = p; - } + if (c != NULL) { + if (c->addr_text.len) { + p = ngx_snprintf(buf, len, ", client: %V", &c->addr_text); + len -= p - buf; + buf = p; + } - if (c && c->listening && c->listening->addr_text.len) { - p = ngx_snprintf(buf, len, ", server: %V", &c->listening->addr_text); - buf = p; + if (c->listening && c->listening->addr_text.len) { + p = ngx_snprintf(buf, len, ", server: %V", + &c->listening->addr_text); + buf = p; + } } return buf; @@ -467,7 +477,7 @@ ngx_http_lua_ssl_sess_fetch_by_chunk(lua_State *L, ngx_http_request_t *r) ngx_int_t rc; lua_State *co; ngx_http_lua_ctx_t *ctx; - ngx_http_cleanup_t *cln; + ngx_pool_cleanup_t *cln; ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); @@ -501,9 +511,11 @@ ngx_http_lua_ssl_sess_fetch_by_chunk(lua_State *L, ngx_http_request_t *r) /* move code closure to new coroutine */ lua_xmove(L, co, 1); +#ifndef OPENRESTY_LUAJIT /* set closure's env table to new coroutine's globals table */ ngx_http_lua_get_globals_table(co); lua_setfenv(co, -2); +#endif /* save nginx request in coroutine globals table */ ngx_http_lua_set_req(co, r); @@ -515,9 +527,11 @@ ngx_http_lua_ssl_sess_fetch_by_chunk(lua_State *L, ngx_http_request_t *r) ctx->cur_co_ctx->co_top = 1; #endif + ngx_http_lua_attach_co_ctx_to_L(co, ctx->cur_co_ctx); + /* register request cleanup hooks */ if (ctx->cleanup == NULL) { - cln = ngx_http_cleanup_add(r, 0); + cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { rc = NGX_ERROR; ngx_http_lua_finalize_request(r, rc); @@ -551,8 +565,6 @@ ngx_http_lua_ssl_sess_fetch_by_chunk(lua_State *L, ngx_http_request_t *r) } -#ifndef NGX_LUA_NO_FFI_API - /* de-serialized a SSL session and set it back to the request at lua context */ int ngx_http_lua_ffi_ssl_set_serialized_session(ngx_http_request_t *r, @@ -563,6 +575,7 @@ ngx_http_lua_ffi_ssl_set_serialized_session(ngx_http_request_t *r, ngx_ssl_conn_t *ssl_conn; ngx_connection_t *c; ngx_ssl_session_t *session = NULL; + ngx_ssl_session_t *old_session; ngx_http_lua_ssl_ctx_t *cctx; c = r->connection; @@ -593,12 +606,15 @@ ngx_http_lua_ffi_ssl_set_serialized_session(ngx_http_request_t *r, return NGX_ERROR; } + old_session = cctx->session; cctx->session = session; + if (old_session != NULL) { + ngx_ssl_free_session(old_session); + } + return NGX_OK; } -#endif /* NGX_LUA_NO_FFI_API */ - #endif /* NGX_HTTP_SSL */ diff --git a/src/ngx_http_lua_ssl_session_storeby.c b/src/ngx_http_lua_ssl_session_storeby.c index 1512eb1ba2..b25cbcc2c4 100644 --- a/src/ngx_http_lua_ssl_session_storeby.c +++ b/src/ngx_http_lua_ssl_session_storeby.c @@ -39,6 +39,7 @@ ngx_http_lua_ssl_sess_store_handler_file(ngx_http_request_t *r, rc = ngx_http_lua_cache_loadfile(r->connection->log, L, lscf->srv.ssl_sess_store_src.data, + &lscf->srv.ssl_sess_store_src_ref, lscf->srv.ssl_sess_store_src_key); if (rc != NGX_OK) { return rc; @@ -61,8 +62,9 @@ ngx_http_lua_ssl_sess_store_handler_inline(ngx_http_request_t *r, rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, lscf->srv.ssl_sess_store_src.data, lscf->srv.ssl_sess_store_src.len, + &lscf->srv.ssl_sess_store_src_ref, lscf->srv.ssl_sess_store_src_key, - "=ssl_session_store_by_lua_block"); + (const char *) lscf->srv.ssl_sess_store_chunkname); if (rc != NGX_OK) { return rc; } @@ -98,7 +100,9 @@ char * ngx_http_lua_ssl_sess_store_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - u_char *p; + size_t chunkname_len; + u_char *chunkname; + u_char *cache_key = NULL; u_char *name; ngx_str_t *value; ngx_http_lua_srv_conf_t *lscf = conf; @@ -125,44 +129,43 @@ ngx_http_lua_ssl_sess_store_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, if (cmd->post == ngx_http_lua_ssl_sess_store_handler_file) { /* Lua code in an external file */ - name = ngx_http_lua_rebase_path(cf->pool, value[1].data, value[1].len); if (name == NULL) { return NGX_CONF_ERROR; } - lscf->srv.ssl_sess_store_src.data = name; - lscf->srv.ssl_sess_store_src.len = ngx_strlen(name); - - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_FILE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - lscf->srv.ssl_sess_store_src_key = p; - - p = ngx_copy(p, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; + lscf->srv.ssl_sess_store_src.data = name; + lscf->srv.ssl_sess_store_src.len = ngx_strlen(name); } else { - /* inlined Lua code */ - - lscf->srv.ssl_sess_store_src = value[1]; - - p = ngx_palloc(cf->pool, NGX_HTTP_LUA_INLINE_KEY_LEN + 1); - if (p == NULL) { + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, + "ssl_session_store_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { return NGX_CONF_ERROR; } - lscf->srv.ssl_sess_store_src_key = p; + chunkname = ngx_http_lua_gen_chunk_name(cf, "ssl_session_store_by_lua", + sizeof("ssl_session_store_by_lua") - 1, &chunkname_len); + if (chunkname == NULL) { + return NGX_CONF_ERROR; + } - p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN); - p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); - *p = '\0'; + /* Don't eval nginx variables for inline lua code */ + lscf->srv.ssl_sess_store_src = value[1]; + lscf->srv.ssl_sess_store_chunkname = chunkname; } + lscf->srv.ssl_sess_store_src_key = cache_key; + return NGX_CONF_OK; } @@ -172,6 +175,9 @@ int ngx_http_lua_ssl_sess_store_handler(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess) { +#if defined(NGX_SSL_TLSv1_3) && defined(TLS1_3_VERSION) + int tls_version; +#endif const u_char *sess_id; unsigned int sess_id_len; lua_State *L; @@ -185,6 +191,18 @@ ngx_http_lua_ssl_sess_store_handler(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection(ssl_conn); +#if defined(NGX_SSL_TLSv1_3) && defined(TLS1_3_VERSION) + tls_version = SSL_version(ssl_conn); + + if (tls_version >= TLS1_3_VERSION) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl_session_store_by_lua*: skipped since " + "TLS version >= 1.3 (%xd)", tls_version); + + return 0; + } +#endif + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "ssl session store: connection reusable: %ud", c->reusable); @@ -220,26 +238,11 @@ ngx_http_lua_ssl_sess_store_handler(ngx_ssl_conn_t *ssl_conn, clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); -#if defined(nginx_version) && nginx_version >= 1003014 - -# if nginx_version >= 1009000 - +#if (nginx_version >= 1009000) ngx_set_connection_log(fc, clcf->error_log); -# else - - ngx_http_set_connection_log(fc, clcf->error_log); - -# endif - #else - - fc->log->file = clcf->error_log->file; - - if (!(fc->log->log_level & NGX_LOG_DEBUG_CONNECTION)) { - fc->log->log_level = clcf->error_log->log_level; - } - + ngx_http_set_connection_log(fc, clcf->error_log); #endif if (cctx == NULL) { @@ -247,6 +250,8 @@ ngx_http_lua_ssl_sess_store_handler(ngx_ssl_conn_t *ssl_conn, if (cctx == NULL) { goto failed; /* error */ } + + cctx->ctx_ref = LUA_NOREF; } #if OPENSSL_VERSION_NUMBER >= 0x1000200fL @@ -332,15 +337,18 @@ ngx_http_lua_log_ssl_sess_store_error(ngx_log_t *log, u_char *buf, size_t len) c = log->data; - if (c->addr_text.len) { - p = ngx_snprintf(buf, len, ", client: %V", &c->addr_text); - len -= p - buf; - buf = p; - } + if (c != NULL) { + if (c->addr_text.len) { + p = ngx_snprintf(buf, len, ", client: %V", &c->addr_text); + len -= p - buf; + buf = p; + } - if (c && c->listening && c->listening->addr_text.len) { - p = ngx_snprintf(buf, len, ", server: %V", &c->listening->addr_text); - buf = p; + if (c->listening && c->listening->addr_text.len) { + p = ngx_snprintf(buf, len, ", server: %V", + &c->listening->addr_text); + buf = p; + } } return buf; @@ -376,6 +384,8 @@ ngx_http_lua_ssl_sess_store_by_chunk(lua_State *L, ngx_http_request_t *r) /* init nginx context in Lua VM */ ngx_http_lua_set_req(L, r); + +#ifndef OPENRESTY_LUAJIT ngx_http_lua_create_new_globals_table(L, 0 /* narr */, 1 /* nrec */); /* {{{ make new env inheriting main thread's globals table */ @@ -386,6 +396,7 @@ ngx_http_lua_ssl_sess_store_by_chunk(lua_State *L, ngx_http_request_t *r) /* }}} */ lua_setfenv(L, -2); /* set new running env for the code closure */ +#endif lua_pushcfunction(L, ngx_http_lua_traceback); lua_insert(L, 1); /* put it under chunk and args */ @@ -421,8 +432,6 @@ ngx_http_lua_ssl_sess_store_by_chunk(lua_State *L, ngx_http_request_t *r) } -#ifndef NGX_LUA_NO_FFI_API - /* serialize a session from lua context into buf. * the memory allocation of buf should be handled externally. */ int @@ -608,7 +617,5 @@ ngx_http_lua_ffi_ssl_get_session_id_size(ngx_http_request_t *r, return 2 * cctx->session_id.len; } -#endif /* NGX_LUA_NO_FFI_API */ - #endif /* NGX_HTTP_SSL */ diff --git a/src/ngx_http_lua_string.c b/src/ngx_http_lua_string.c index 239b2322ab..3028483609 100644 --- a/src/ngx_http_lua_string.c +++ b/src/ngx_http_lua_string.c @@ -27,27 +27,9 @@ #endif -#ifndef SHA_DIGEST_LENGTH -#define SHA_DIGEST_LENGTH 20 -#endif - - static uintptr_t ngx_http_lua_ngx_escape_sql_str(u_char *dst, u_char *src, - size_t size); -static int ngx_http_lua_ngx_escape_uri(lua_State *L); -static int ngx_http_lua_ngx_unescape_uri(lua_State *L); + size_t size); static int ngx_http_lua_ngx_quote_sql_str(lua_State *L); -static int ngx_http_lua_ngx_md5(lua_State *L); -static int ngx_http_lua_ngx_md5_bin(lua_State *L); - -#if (NGX_HAVE_SHA1) -static int ngx_http_lua_ngx_sha1_bin(lua_State *L); -#endif - -static int ngx_http_lua_ngx_decode_base64(lua_State *L); -static int ngx_http_lua_ngx_encode_base64(lua_State *L); -static int ngx_http_lua_ngx_crc32_short(lua_State *L); -static int ngx_http_lua_ngx_crc32_long(lua_State *L); static int ngx_http_lua_ngx_encode_args(lua_State *L); static int ngx_http_lua_ngx_decode_args(lua_State *L); #if (NGX_OPENSSL) @@ -58,12 +40,6 @@ static int ngx_http_lua_ngx_hmac_sha1(lua_State *L); void ngx_http_lua_inject_string_api(lua_State *L) { - lua_pushcfunction(L, ngx_http_lua_ngx_escape_uri); - lua_setfield(L, -2, "escape_uri"); - - lua_pushcfunction(L, ngx_http_lua_ngx_unescape_uri); - lua_setfield(L, -2, "unescape_uri"); - lua_pushcfunction(L, ngx_http_lua_ngx_encode_args); lua_setfield(L, -2, "encode_args"); @@ -73,29 +49,6 @@ ngx_http_lua_inject_string_api(lua_State *L) lua_pushcfunction(L, ngx_http_lua_ngx_quote_sql_str); lua_setfield(L, -2, "quote_sql_str"); - lua_pushcfunction(L, ngx_http_lua_ngx_decode_base64); - lua_setfield(L, -2, "decode_base64"); - - lua_pushcfunction(L, ngx_http_lua_ngx_encode_base64); - lua_setfield(L, -2, "encode_base64"); - - lua_pushcfunction(L, ngx_http_lua_ngx_md5_bin); - lua_setfield(L, -2, "md5_bin"); - - lua_pushcfunction(L, ngx_http_lua_ngx_md5); - lua_setfield(L, -2, "md5"); - -#if (NGX_HAVE_SHA1) - lua_pushcfunction(L, ngx_http_lua_ngx_sha1_bin); - lua_setfield(L, -2, "sha1_bin"); -#endif - - lua_pushcfunction(L, ngx_http_lua_ngx_crc32_short); - lua_setfield(L, -2, "crc32_short"); - - lua_pushcfunction(L, ngx_http_lua_ngx_crc32_long); - lua_setfield(L, -2, "crc32_long"); - #if (NGX_OPENSSL) lua_pushcfunction(L, ngx_http_lua_ngx_hmac_sha1); lua_setfield(L, -2, "hmac_sha1"); @@ -103,75 +56,6 @@ ngx_http_lua_inject_string_api(lua_State *L) } -static int -ngx_http_lua_ngx_escape_uri(lua_State *L) -{ - size_t len, dlen; - uintptr_t escape; - u_char *src, *dst; - - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one argument"); - } - - if (lua_isnil(L, 1)) { - lua_pushliteral(L, ""); - return 1; - } - - src = (u_char *) luaL_checklstring(L, 1, &len); - - if (len == 0) { - return 1; - } - - escape = 2 * ngx_http_lua_escape_uri(NULL, src, len, - NGX_ESCAPE_URI_COMPONENT); - - if (escape) { - dlen = escape + len; - dst = lua_newuserdata(L, dlen); - ngx_http_lua_escape_uri(dst, src, len, NGX_ESCAPE_URI_COMPONENT); - lua_pushlstring(L, (char *) dst, dlen); - } - - return 1; -} - - -static int -ngx_http_lua_ngx_unescape_uri(lua_State *L) -{ - size_t len, dlen; - u_char *p; - u_char *src, *dst; - - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one argument"); - } - - if (lua_isnil(L, 1)) { - lua_pushliteral(L, ""); - return 1; - } - - src = (u_char *) luaL_checklstring(L, 1, &len); - - /* the unescaped string can only be smaller */ - dlen = len; - - p = lua_newuserdata(L, dlen); - - dst = p; - - ngx_http_lua_unescape_uri(&dst, &src, len, NGX_UNESCAPE_URI_COMPONENT); - - lua_pushlstring(L, (char *) p, dst - p); - - return 1; -} - - static int ngx_http_lua_ngx_quote_sql_str(lua_State *L) { @@ -249,6 +133,7 @@ ngx_http_lua_ngx_escape_sql_str(u_char *dst, u_char *src, size_t size) break; } } + src++; size--; } @@ -308,9 +193,11 @@ ngx_http_lua_ngx_escape_sql_str(u_char *dst, u_char *src, size_t size) *dst++ = *src; break; } + } else { *dst++ = *src; } + src++; size--; } /* while (size) */ @@ -319,138 +206,6 @@ ngx_http_lua_ngx_escape_sql_str(u_char *dst, u_char *src, size_t size) } -static int -ngx_http_lua_ngx_md5(lua_State *L) -{ - u_char *src; - size_t slen; - - ngx_md5_t md5; - u_char md5_buf[MD5_DIGEST_LENGTH]; - u_char hex_buf[2 * sizeof(md5_buf)]; - - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one argument"); - } - - if (lua_isnil(L, 1)) { - src = (u_char *) ""; - slen = 0; - - } else { - src = (u_char *) luaL_checklstring(L, 1, &slen); - } - - ngx_md5_init(&md5); - ngx_md5_update(&md5, src, slen); - ngx_md5_final(md5_buf, &md5); - - ngx_hex_dump(hex_buf, md5_buf, sizeof(md5_buf)); - - lua_pushlstring(L, (char *) hex_buf, sizeof(hex_buf)); - - return 1; -} - - -static int -ngx_http_lua_ngx_md5_bin(lua_State *L) -{ - u_char *src; - size_t slen; - - ngx_md5_t md5; - u_char md5_buf[MD5_DIGEST_LENGTH]; - - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one argument"); - } - - if (lua_isnil(L, 1)) { - src = (u_char *) ""; - slen = 0; - - } else { - src = (u_char *) luaL_checklstring(L, 1, &slen); - } - - dd("slen: %d", (int) slen); - - ngx_md5_init(&md5); - ngx_md5_update(&md5, src, slen); - ngx_md5_final(md5_buf, &md5); - - lua_pushlstring(L, (char *) md5_buf, sizeof(md5_buf)); - - return 1; -} - - -#if (NGX_HAVE_SHA1) -static int -ngx_http_lua_ngx_sha1_bin(lua_State *L) -{ - u_char *src; - size_t slen; - - ngx_sha1_t sha; - u_char sha_buf[SHA_DIGEST_LENGTH]; - - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one argument"); - } - - if (lua_isnil(L, 1)) { - src = (u_char *) ""; - slen = 0; - - } else { - src = (u_char *) luaL_checklstring(L, 1, &slen); - } - - dd("slen: %d", (int) slen); - - ngx_sha1_init(&sha); - ngx_sha1_update(&sha, src, slen); - ngx_sha1_final(sha_buf, &sha); - - lua_pushlstring(L, (char *) sha_buf, sizeof(sha_buf)); - - return 1; -} -#endif - - -static int -ngx_http_lua_ngx_decode_base64(lua_State *L) -{ - ngx_str_t p, src; - - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one argument"); - } - - if (lua_type(L, 1) != LUA_TSTRING) { - return luaL_error(L, "string argument only"); - } - - src.data = (u_char *) luaL_checklstring(L, 1, &src.len); - - p.len = ngx_base64_decoded_length(src.len); - - p.data = lua_newuserdata(L, p.len); - - if (ngx_decode_base64(&p, &src) == NGX_OK) { - lua_pushlstring(L, (char *) p.data, p.len); - - } else { - lua_pushnil(L); - } - - return 1; -} - - static void ngx_http_lua_encode_base64(ngx_str_t *dst, ngx_str_t *src, int no_padding) { @@ -496,87 +251,6 @@ ngx_http_lua_encode_base64(ngx_str_t *dst, ngx_str_t *src, int no_padding) } -static size_t -ngx_http_lua_base64_encoded_length(size_t n, int no_padding) -{ - return no_padding ? (n * 8 + 5) / 6 : ngx_base64_encoded_length(n); -} - - -static int -ngx_http_lua_ngx_encode_base64(lua_State *L) -{ - int n; - int no_padding = 0; - ngx_str_t p, src; - - n = lua_gettop(L); - if (n != 1 && n != 2) { - return luaL_error(L, "expecting one or two arguments"); - } - - if (lua_isnil(L, 1)) { - src.data = (u_char *) ""; - src.len = 0; - - } else { - src.data = (u_char *) luaL_checklstring(L, 1, &src.len); - } - - if (n == 2) { - /* get the 2nd optional argument */ - luaL_checktype(L, 2, LUA_TBOOLEAN); - no_padding = lua_toboolean(L, 2); - } - - p.len = ngx_http_lua_base64_encoded_length(src.len, no_padding); - - p.data = lua_newuserdata(L, p.len); - - ngx_http_lua_encode_base64(&p, &src, no_padding); - - lua_pushlstring(L, (char *) p.data, p.len); - - return 1; -} - - -static int -ngx_http_lua_ngx_crc32_short(lua_State *L) -{ - u_char *p; - size_t len; - - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one argument, but got %d", - lua_gettop(L)); - } - - p = (u_char *) luaL_checklstring(L, 1, &len); - - lua_pushnumber(L, (lua_Number) ngx_crc32_short(p, len)); - return 1; -} - - -static int -ngx_http_lua_ngx_crc32_long(lua_State *L) -{ - u_char *p; - size_t len; - - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one argument, but got %d", - lua_gettop(L)); - } - - p = (u_char *) luaL_checklstring(L, 1, &len); - - lua_pushnumber(L, (lua_Number) ngx_crc32_long(p, len)); - return 1; -} - - static int ngx_http_lua_ngx_encode_args(lua_State *L) { @@ -657,7 +331,6 @@ ngx_http_lua_ngx_hmac_sha1(lua_State *L) #endif -#ifndef NGX_LUA_NO_FFI_API void ngx_http_lua_ffi_md5_bin(const u_char *src, size_t len, u_char *dst) { @@ -700,6 +373,20 @@ ngx_http_lua_ffi_sha1_bin(const u_char *src, size_t len, u_char *dst) } +unsigned int +ngx_http_lua_ffi_crc32_short(const u_char *src, size_t len) +{ + return ngx_crc32_short((u_char *) src, len); +} + + +unsigned int +ngx_http_lua_ffi_crc32_long(const u_char *src, size_t len) +{ + return ngx_crc32_long((u_char *) src, len); +} + + size_t ngx_http_lua_ffi_encode_base64(const u_char *src, size_t slen, u_char *dst, int no_padding) @@ -737,6 +424,26 @@ ngx_http_lua_ffi_decode_base64(const u_char *src, size_t slen, u_char *dst, } +int +ngx_http_lua_ffi_decode_base64mime(const u_char *src, size_t slen, u_char *dst, + size_t *dlen) +{ + ngx_int_t rc; + ngx_str_t in, out; + + in.data = (u_char *) src; + in.len = slen; + + out.data = dst; + + rc = ngx_http_lua_decode_base64mime(&out, &in); + + *dlen = out.len; + + return rc == NGX_OK; +} + + size_t ngx_http_lua_ffi_unescape_uri(const u_char *src, size_t len, u_char *dst) { @@ -749,19 +456,34 @@ ngx_http_lua_ffi_unescape_uri(const u_char *src, size_t len, u_char *dst) size_t -ngx_http_lua_ffi_uri_escaped_length(const u_char *src, size_t len) +ngx_http_lua_ffi_uri_escaped_length(const u_char *src, size_t len, + int type) { - return len + 2 * ngx_http_lua_escape_uri(NULL, (u_char *) src, len, - NGX_ESCAPE_URI_COMPONENT); + return len + 2 * ngx_http_lua_escape_uri(NULL, (u_char *) src, len, type); } void -ngx_http_lua_ffi_escape_uri(const u_char *src, size_t len, u_char *dst) +ngx_http_lua_ffi_escape_uri(const u_char *src, size_t len, u_char *dst, + int type) { - ngx_http_lua_escape_uri(dst, (u_char *) src, len, NGX_ESCAPE_URI_COMPONENT); + ngx_http_lua_escape_uri(dst, (u_char *) src, len, type); +} + + +void +ngx_http_lua_ffi_str_replace_char(u_char *buf, size_t len, const u_char find, + const u_char replace) +{ + while (len) { + if (*buf == find) { + *buf = replace; + } + + buf++; + len--; + } } -#endif /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_subrequest.c b/src/ngx_http_lua_subrequest.c index 47096e9511..c9a5c08622 100644 --- a/src/ngx_http_lua_subrequest.c +++ b/src/ngx_http_lua_subrequest.c @@ -27,6 +27,7 @@ #define ngx_http_lua_method_name(m) { sizeof(m) - 1, (u_char *) m " " } + ngx_str_t ngx_http_lua_get_method = ngx_http_lua_method_name("GET"); ngx_str_t ngx_http_lua_put_method = ngx_http_lua_method_name("PUT"); ngx_str_t ngx_http_lua_post_method = ngx_http_lua_method_name("POST"); @@ -51,20 +52,20 @@ ngx_str_t ngx_http_lua_patch_method = ngx_str_t ngx_http_lua_trace_method = ngx_http_lua_method_name("TRACE"); +ngx_str_t host_header = ngx_string("host"); + static ngx_str_t ngx_http_lua_content_length_header_key = ngx_string("Content-Length"); -static ngx_int_t ngx_http_lua_set_content_length_header(ngx_http_request_t *r, - off_t len); static ngx_int_t ngx_http_lua_adjust_subrequest(ngx_http_request_t *sr, ngx_uint_t method, int forward_body, ngx_http_request_body_t *body, unsigned vars_action, - ngx_array_t *extra_vars); + ngx_array_t *extra_vars, ngx_array_t *extra_headers); static int ngx_http_lua_ngx_location_capture(lua_State *L); static int ngx_http_lua_ngx_location_capture_multi(lua_State *L); -static void ngx_http_lua_process_vars_option(ngx_http_request_t *r, +static void ngx_http_lua_process_keyval_option(ngx_http_request_t *r, lua_State *L, int table, ngx_array_t **varsp); static ngx_int_t ngx_http_lua_subrequest_add_extra_vars(ngx_http_request_t *r, ngx_array_t *extra_vars); @@ -78,7 +79,12 @@ static void ngx_http_lua_cancel_subreq(ngx_http_request_t *r); static ngx_int_t ngx_http_post_request_to_head(ngx_http_request_t *r); static ngx_int_t ngx_http_lua_copy_in_file_request_body(ngx_http_request_t *r); static ngx_int_t ngx_http_lua_copy_request_headers(ngx_http_request_t *sr, - ngx_http_request_t *r); + ngx_http_request_t *pr, int pr_not_chunked, ngx_array_t *extra_headers); + + +enum { + NGX_HTTP_LUA_SUBREQ_TRUNCATED = 1, +}; /* ngx.location.capture is just a thin wrapper around @@ -121,6 +127,7 @@ ngx_http_lua_ngx_location_capture_multi(lua_State *L) ngx_http_lua_ctx_t *sr_ctx; ngx_http_lua_ctx_t *ctx; ngx_array_t *extra_vars; + ngx_array_t *extra_headers; ngx_str_t uri; ngx_str_t args; ngx_str_t extra_args; @@ -212,6 +219,7 @@ ngx_http_lua_ngx_location_capture_multi(lua_State *L) coctx->pending_subreqs = 0; extra_vars = NULL; + extra_headers = NULL; for (index = 0; index < nsubreqs; index++) { coctx->pending_subreqs++; @@ -251,6 +259,11 @@ ngx_http_lua_ngx_location_capture_multi(lua_State *L) extra_vars->nelts = 0; } + if (extra_headers != NULL) { + /* flush out existing elements in the array */ + extra_headers->nelts = 0; + } + vars_action = 0; custom_ctx = 0; @@ -306,7 +319,7 @@ ngx_http_lua_ngx_location_capture_multi(lua_State *L) switch (lua_type(L, -1)) { case LUA_TTABLE: - ngx_http_lua_process_vars_option(r, L, -1, &extra_vars); + ngx_http_lua_process_keyval_option(r, L, -1, &extra_vars); dd("post process vars top: %d", lua_gettop(L)); break; @@ -323,6 +336,29 @@ ngx_http_lua_ngx_location_capture_multi(lua_State *L) dd("queries query uri opts: %d", lua_gettop(L)); + /* check the headers option */ + + lua_getfield(L, 4, "headers"); + + switch (lua_type(L, -1)) { + case LUA_TTABLE: + ngx_http_lua_process_keyval_option(r, L, -1, &extra_headers); + + dd("post process vars top: %d", lua_gettop(L)); + break; + + case LUA_TNIL: + /* do nothing */ + break; + + default: + return luaL_error(L, "Bad headers option value"); + } + + lua_pop(L, 1); /* pop the headers */ + + dd("queries query uri opts: %d", lua_gettop(L)); + /* check the share_all_vars option */ lua_getfield(L, 4, "share_all_vars"); @@ -583,7 +619,8 @@ ngx_http_lua_ngx_location_capture_multi(lua_State *L) ngx_http_set_ctx(sr, sr_ctx, ngx_http_lua_module); rc = ngx_http_lua_adjust_subrequest(sr, method, always_forward_body, - body, vars_action, extra_vars); + body, vars_action, extra_vars, + extra_headers); if (rc != NGX_OK) { ngx_http_lua_cancel_subreq(sr); @@ -619,11 +656,11 @@ ngx_http_lua_ngx_location_capture_multi(lua_State *L) static ngx_int_t ngx_http_lua_adjust_subrequest(ngx_http_request_t *sr, ngx_uint_t method, int always_forward_body, ngx_http_request_body_t *body, - unsigned vars_action, ngx_array_t *extra_vars) + unsigned vars_action, ngx_array_t *extra_vars, ngx_array_t *extra_headers) { ngx_http_request_t *r; - ngx_int_t rc; ngx_http_core_main_conf_t *cmcf; + int pr_not_chunked = 0; size_t size; r = sr->parent; @@ -633,46 +670,34 @@ ngx_http_lua_adjust_subrequest(ngx_http_request_t *sr, ngx_uint_t method, if (body) { sr->request_body = body; - rc = ngx_http_lua_set_content_length_header(sr, - body->buf - ? ngx_buf_size(body->buf) - : 0); - - if (rc != NGX_OK) { - return NGX_ERROR; - } - } else if (!always_forward_body && method != NGX_HTTP_PUT && method != NGX_HTTP_POST && r->headers_in.content_length_n > 0) { - rc = ngx_http_lua_set_content_length_header(sr, 0); - if (rc != NGX_OK) { - return NGX_ERROR; - } - -#if 1 sr->request_body = NULL; -#endif } else { - if (ngx_http_lua_copy_request_headers(sr, r) != NGX_OK) { - return NGX_ERROR; + if (!r->headers_in.chunked) { + pr_not_chunked = 1; } - if (sr->request_body) { + if (sr->request_body && sr->request_body->temp_file) { /* deep-copy the request body */ - if (sr->request_body->temp_file) { - if (ngx_http_lua_copy_in_file_request_body(sr) != NGX_OK) { - return NGX_ERROR; - } + if (ngx_http_lua_copy_in_file_request_body(sr) != NGX_OK) { + return NGX_ERROR; } } } + if (ngx_http_lua_copy_request_headers(sr, r, pr_not_chunked, extra_headers) + != NGX_OK) + { + return NGX_ERROR; + } + sr->method = method; switch (method) { @@ -738,7 +763,7 @@ ngx_http_lua_adjust_subrequest(ngx_http_request_t *sr, ngx_uint_t method, default: ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "unsupported HTTP method: %u", (unsigned) method); + "unsupported HTTP method: %ui", method); return NGX_ERROR; } @@ -884,7 +909,7 @@ ngx_http_lua_subrequest_add_extra_vars(ngx_http_request_t *sr, static void -ngx_http_lua_process_vars_option(ngx_http_request_t *r, lua_State *L, +ngx_http_lua_process_keyval_option(ngx_http_request_t *r, lua_State *L, int table, ngx_array_t **varsp) { ngx_array_t *vars; @@ -1025,6 +1050,14 @@ ngx_http_lua_post_subrequest(ngx_http_request_t *r, void *data, ngx_int_t rc) dd("pr_coctx status: %d", (int) pr_coctx->sr_statuses[ctx->index]); /* copy subrequest response headers */ + if (ctx->headers_set) { + rc = ngx_http_lua_set_content_type(r, ctx); + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "failed to set default content type: %i", rc); + return NGX_ERROR; + } + } pr_coctx->sr_headers[ctx->index] = &r->headers_out; @@ -1067,11 +1100,7 @@ ngx_http_lua_post_subrequest(ngx_http_request_t *r, void *data, ngx_int_t rc) if (ctx->body) { -#if defined(nginx_version) && nginx_version >= 1001004 ngx_chain_update_chains(r->pool, -#else - ngx_chain_update_chains( -#endif &pr_ctx->free_bufs, &pr_ctx->busy_bufs, &ctx->body, (ngx_buf_tag_t) &ngx_http_lua_module); @@ -1118,100 +1147,6 @@ ngx_http_lua_post_subrequest(ngx_http_request_t *r, void *data, ngx_int_t rc) } -static ngx_int_t -ngx_http_lua_set_content_length_header(ngx_http_request_t *r, off_t len) -{ - ngx_table_elt_t *h, *header; - u_char *p; - ngx_list_part_t *part; - ngx_http_request_t *pr; - ngx_uint_t i; - - r->headers_in.content_length_n = len; - - if (ngx_list_init(&r->headers_in.headers, r->pool, 20, - sizeof(ngx_table_elt_t)) != NGX_OK) - { - return NGX_ERROR; - } - - h = ngx_list_push(&r->headers_in.headers); - if (h == NULL) { - return NGX_ERROR; - } - - h->key = ngx_http_lua_content_length_header_key; - h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); - if (h->lowcase_key == NULL) { - return NGX_ERROR; - } - - ngx_strlow(h->lowcase_key, h->key.data, h->key.len); - - r->headers_in.content_length = h; - - p = ngx_palloc(r->pool, NGX_OFF_T_LEN); - if (p == NULL) { - return NGX_ERROR; - } - - h->value.data = p; - - h->value.len = ngx_sprintf(h->value.data, "%O", len) - h->value.data; - - h->hash = ngx_http_lua_content_length_hash; - -#if 0 - dd("content length hash: %lu == %lu", (unsigned long) h->hash, - ngx_hash_key_lc((u_char *) "Content-Length", - sizeof("Content-Length") - 1)); -#endif - - dd("r content length: %.*s", - (int) r->headers_in.content_length->value.len, - r->headers_in.content_length->value.data); - - pr = r->parent; - - if (pr == NULL) { - return NGX_OK; - } - - /* forward the parent request's all other request headers */ - - part = &pr->headers_in.headers.part; - header = part->elts; - - for (i = 0; /* void */; i++) { - - if (i >= part->nelts) { - if (part->next == NULL) { - break; - } - - part = part->next; - header = part->elts; - i = 0; - } - - if (header[i].key.len == sizeof("Content-Length") - 1 - && ngx_strncasecmp(header[i].key.data, (u_char *) "Content-Length", - sizeof("Content-Length") - 1) == 0) - { - continue; - } - - if (ngx_http_lua_set_input_header(r, header[i].key, - header[i].value, 0) == NGX_ERROR) - { - return NGX_ERROR; - } - } - - return NGX_OK; -} - - static void ngx_http_lua_handle_subreq_responses(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx) @@ -1438,12 +1373,14 @@ ngx_http_lua_subrequest(ngx_http_request_t *r, ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr, ngx_http_post_subrequest_t *ps, ngx_uint_t flags) { +#if !defined freenginx ngx_time_t *tp; +#endif ngx_connection_t *c; ngx_http_request_t *sr; ngx_http_core_srv_conf_t *cscf; -#if nginx_version >= 1009005 +#if (nginx_version >= 1009005) if (r->subrequests == 0) { #if defined(NGX_DTRACE) && NGX_DTRACE @@ -1562,13 +1499,17 @@ ngx_http_lua_subrequest(ngx_http_request_t *r, sr->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1; -#if nginx_version >= 1009005 +#if (nginx_version >= 1009005) sr->subrequests = r->subrequests - 1; #endif +#if (defined freenginx && nginx_version >= 1029000) + sr->start_time = ngx_current_msec; +#else tp = ngx_timeofday(); sr->start_sec = tp->sec; sr->start_msec = tp->msec; +#endif r->main->count++; @@ -1726,11 +1667,19 @@ ngx_http_lua_copy_in_file_request_body(ngx_http_request_t *r) static ngx_int_t -ngx_http_lua_copy_request_headers(ngx_http_request_t *sr, ngx_http_request_t *r) +ngx_http_lua_copy_request_headers(ngx_http_request_t *sr, + ngx_http_request_t *pr, int pr_not_chunked, ngx_array_t *extra_headers) { - ngx_table_elt_t *header; + ngx_table_elt_t *clh, *header; ngx_list_part_t *part; + ngx_keyval_t *header_keyval; + ngx_chain_t *in; ngx_uint_t i; + u_char *p; + off_t len; + + dd("before: parent req headers count: %d", + (int) pr->headers_in.headers.part.nelts); if (ngx_list_init(&sr->headers_in.headers, sr->pool, 20, sizeof(ngx_table_elt_t)) != NGX_OK) @@ -1738,12 +1687,65 @@ ngx_http_lua_copy_request_headers(ngx_http_request_t *sr, ngx_http_request_t *r) return NGX_ERROR; } - dd("before: parent req headers count: %d", - (int) r->headers_in.headers.part.nelts); + if (sr->request_body && !pr_not_chunked) { - part = &r->headers_in.headers.part; + /* craft our own Content-Length */ + len = 0; + + for (in = sr->request_body->bufs; in; in = in->next) { + len += ngx_buf_size(in->buf); + } + + clh = ngx_list_push(&sr->headers_in.headers); + if (clh == NULL) { + return NGX_ERROR; + } + + clh->hash = ngx_http_lua_content_length_hash; + clh->key = ngx_http_lua_content_length_header_key; +#if defined(nginx_version) && nginx_version >= 1023000 + clh->next = NULL; +#endif + clh->lowcase_key = ngx_pnalloc(sr->pool, clh->key.len); + if (clh->lowcase_key == NULL) { + return NGX_ERROR; + } + + ngx_strlow(clh->lowcase_key, clh->key.data, clh->key.len); + + p = ngx_palloc(sr->pool, NGX_OFF_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + clh->value.data = p; + clh->value.len = ngx_sprintf(clh->value.data, "%O", len) + - clh->value.data; + + sr->headers_in.content_length = clh; + sr->headers_in.content_length_n = len; + + dd("sr crafted content-length: %.*s", + (int) sr->headers_in.content_length->value.len, + sr->headers_in.content_length->value.data); + } + + /* copy the parent request's headers */ + + part = &pr->headers_in.headers.part; header = part->elts; +#if (NGX_HTTP_V3) + if (pr->headers_in.server.data != NULL) { + if (ngx_http_lua_set_input_header(sr, host_header, + pr->headers_in.server, 0) + == NGX_ERROR) + { + return NGX_ERROR; + } + } +#endif + for (i = 0; /* void */; i++) { if (i >= part->nelts) { @@ -1756,7 +1758,14 @@ ngx_http_lua_copy_request_headers(ngx_http_request_t *sr, ngx_http_request_t *r) i = 0; } - dd("setting request header %.*s: %.*s", (int) header[i].key.len, + if (!pr_not_chunked && header[i].key.len == sizeof("Content-Length") - 1 + && ngx_strncasecmp(header[i].key.data, (u_char *) "Content-Length", + sizeof("Content-Length") - 1) == 0) + { + continue; + } + + dd("sr copied req header %.*s: %.*s", (int) header[i].key.len, header[i].key.data, (int) header[i].value.len, header[i].value.data); @@ -1767,10 +1776,25 @@ ngx_http_lua_copy_request_headers(ngx_http_request_t *sr, ngx_http_request_t *r) } } + if (extra_headers && extra_headers->nelts > 0) { + + header_keyval = extra_headers->elts; + + for (i = 0; i < extra_headers->nelts; i++, header_keyval++) { + + if (ngx_http_lua_set_input_header(sr, header_keyval->key, + header_keyval->value, 1) == NGX_ERROR) + { + return NGX_ERROR; + } + } + } + dd("after: parent req headers count: %d", - (int) r->headers_in.headers.part.nelts); + (int) pr->headers_in.headers.part.nelts); return NGX_OK; } + /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_time.c b/src/ngx_http_lua_time.c index 3272a75331..fa63115c4c 100644 --- a/src/ngx_http_lua_time.c +++ b/src/ngx_http_lua_time.c @@ -11,268 +11,124 @@ #include "ddebug.h" -#include "ngx_http_lua_time.h" -#include "ngx_http_lua_util.h" +#include "ngx_http_lua_common.h" -static int ngx_http_lua_ngx_today(lua_State *L); -static int ngx_http_lua_ngx_time(lua_State *L); -static int ngx_http_lua_ngx_now(lua_State *L); -static int ngx_http_lua_ngx_localtime(lua_State *L); -static int ngx_http_lua_ngx_utctime(lua_State *L); -static int ngx_http_lua_ngx_cookie_time(lua_State *L); -static int ngx_http_lua_ngx_http_time(lua_State *L); -static int ngx_http_lua_ngx_parse_http_time(lua_State *L); -static int ngx_http_lua_ngx_update_time(lua_State *L); -static int ngx_http_lua_ngx_req_start_time(lua_State *L); - - -static int -ngx_http_lua_ngx_today(lua_State *L) +double +ngx_http_lua_ffi_now(void) { - time_t now; - ngx_tm_t tm; - u_char buf[sizeof("2010-11-19") - 1]; - - now = ngx_time(); - ngx_gmtime(now + ngx_cached_time->gmtoff * 60, &tm); - - ngx_sprintf(buf, "%04d-%02d-%02d", tm.ngx_tm_year, tm.ngx_tm_mon, - tm.ngx_tm_mday); + ngx_time_t *tp; - lua_pushlstring(L, (char *) buf, sizeof(buf)); + tp = ngx_timeofday(); - return 1; + return tp->sec + tp->msec / 1000.0; } -static int -ngx_http_lua_ngx_localtime(lua_State *L) +double +ngx_http_lua_ffi_req_start_time(ngx_http_request_t *r) { - ngx_tm_t tm; - - u_char buf[sizeof("2010-11-19 20:56:31") - 1]; - - ngx_gmtime(ngx_time() + ngx_cached_time->gmtoff * 60, &tm); - - ngx_sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d", tm.ngx_tm_year, - tm.ngx_tm_mon, tm.ngx_tm_mday, tm.ngx_tm_hour, tm.ngx_tm_min, - tm.ngx_tm_sec); +#if (defined freenginx && nginx_version >= 1029000) + ngx_time_t *tp; - lua_pushlstring(L, (char *) buf, sizeof(buf)); + tp = ngx_timeofday(); + tp->sec -= (ngx_current_msec - r->start_time) / 1000; + tp->msec -= (ngx_current_msec - r->start_time) % 1000; + if (tp->msec > NGX_MAX_INT_T_VALUE) { + tp->msec += 1000; + tp->sec -= 1; + } - return 1; + return tp->sec + tp->msec / 1000.0; +#else + return r->start_sec + r->start_msec / 1000.0; +#endif } -static int -ngx_http_lua_ngx_time(lua_State *L) +long +ngx_http_lua_ffi_time(void) { - lua_pushnumber(L, (lua_Number) ngx_time()); - - return 1; + return (long) ngx_time(); } -static int -ngx_http_lua_ngx_now(lua_State *L) +long +ngx_http_lua_ffi_monotonic_msec(void) { - ngx_time_t *tp; - - tp = ngx_timeofday(); - - lua_pushnumber(L, (lua_Number) (tp->sec + tp->msec / 1000.0L)); - - return 1; + return (long) ngx_current_msec; } -static int -ngx_http_lua_ngx_update_time(lua_State *L) +void +ngx_http_lua_ffi_update_time(void) { ngx_time_update(); - return 0; } -static int -ngx_http_lua_ngx_utctime(lua_State *L) +void +ngx_http_lua_ffi_today(u_char *buf) { - ngx_tm_t tm; - u_char buf[sizeof("2010-11-19 20:56:31") - 1]; - - ngx_gmtime(ngx_time(), &tm); - - ngx_sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d", tm.ngx_tm_year, - tm.ngx_tm_mon, tm.ngx_tm_mday, tm.ngx_tm_hour, tm.ngx_tm_min, - tm.ngx_tm_sec); + ngx_tm_t tm; - lua_pushlstring(L, (char *) buf, sizeof(buf)); + ngx_gmtime(ngx_time() + ngx_cached_time->gmtoff * 60, &tm); - return 1; + ngx_sprintf(buf, "%04d-%02d-%02d", tm.ngx_tm_year, tm.ngx_tm_mon, + tm.ngx_tm_mday); } -static int -ngx_http_lua_ngx_cookie_time(lua_State *L) +void +ngx_http_lua_ffi_localtime(u_char *buf) { - time_t t; - u_char *p; - - u_char buf[sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1]; - - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one argument"); - } - - t = (time_t) luaL_checknumber(L, 1); - - p = buf; - p = ngx_http_cookie_time(p, t); + ngx_tm_t tm; - lua_pushlstring(L, (char *) buf, p - buf); + ngx_gmtime(ngx_time() + ngx_cached_time->gmtoff * 60, &tm); - return 1; + ngx_sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d", tm.ngx_tm_year, + tm.ngx_tm_mon, tm.ngx_tm_mday, tm.ngx_tm_hour, tm.ngx_tm_min, + tm.ngx_tm_sec); } -static int -ngx_http_lua_ngx_http_time(lua_State *L) +void +ngx_http_lua_ffi_utctime(u_char *buf) { - time_t t; - u_char *p; - - u_char buf[sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1]; - - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one argument"); - } - - t = (time_t) luaL_checknumber(L, 1); - - p = buf; - p = ngx_http_time(p, t); + ngx_tm_t tm; - lua_pushlstring(L, (char *) buf, p - buf); + ngx_gmtime(ngx_time(), &tm); - return 1; + ngx_sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d", tm.ngx_tm_year, + tm.ngx_tm_mon, tm.ngx_tm_mday, tm.ngx_tm_hour, tm.ngx_tm_min, + tm.ngx_tm_sec); } -static int -ngx_http_lua_ngx_parse_http_time(lua_State *L) +int +ngx_http_lua_ffi_cookie_time(u_char *buf, long t) { u_char *p; - size_t len; - time_t time; - - if (lua_gettop(L) != 1) { - return luaL_error(L, "expecting one argument"); - } - - p = (u_char *) luaL_checklstring(L, 1, &len); - time = ngx_http_parse_time(p, len); - if (time == NGX_ERROR) { - lua_pushnil(L); - return 1; - } - - lua_pushnumber(L, (lua_Number) time); - - return 1; -} - - -static int -ngx_http_lua_ngx_req_start_time(lua_State *L) -{ - ngx_http_request_t *r; - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request found"); - } - - lua_pushnumber(L, (lua_Number) (r->start_sec + r->start_msec / 1000.0L)); - return 1; + p = ngx_http_cookie_time(buf, t); + return p - buf; } void -ngx_http_lua_inject_time_api(lua_State *L) +ngx_http_lua_ffi_http_time(u_char *buf, long t) { - lua_pushcfunction(L, ngx_http_lua_ngx_utctime); - lua_setfield(L, -2, "utctime"); - - lua_pushcfunction(L, ngx_http_lua_ngx_time); - lua_setfield(L, -2, "get_now_ts"); /* deprecated */ - - lua_pushcfunction(L, ngx_http_lua_ngx_localtime); - lua_setfield(L, -2, "get_now"); /* deprecated */ - - lua_pushcfunction(L, ngx_http_lua_ngx_localtime); - lua_setfield(L, -2, "localtime"); - - lua_pushcfunction(L, ngx_http_lua_ngx_time); - lua_setfield(L, -2, "time"); - - lua_pushcfunction(L, ngx_http_lua_ngx_now); - lua_setfield(L, -2, "now"); - - lua_pushcfunction(L, ngx_http_lua_ngx_update_time); - lua_setfield(L, -2, "update_time"); - - lua_pushcfunction(L, ngx_http_lua_ngx_today); - lua_setfield(L, -2, "get_today"); /* deprecated */ - - lua_pushcfunction(L, ngx_http_lua_ngx_today); - lua_setfield(L, -2, "today"); - - lua_pushcfunction(L, ngx_http_lua_ngx_cookie_time); - lua_setfield(L, -2, "cookie_time"); - - lua_pushcfunction(L, ngx_http_lua_ngx_http_time); - lua_setfield(L, -2, "http_time"); - - lua_pushcfunction(L, ngx_http_lua_ngx_parse_http_time); - lua_setfield(L, -2, "parse_http_time"); + ngx_http_time(buf, t); } void -ngx_http_lua_inject_req_time_api(lua_State *L) -{ - lua_pushcfunction(L, ngx_http_lua_ngx_req_start_time); - lua_setfield(L, -2, "start_time"); -} - - -#ifndef NGX_LUA_NO_FFI_API -double -ngx_http_lua_ffi_now(void) +ngx_http_lua_ffi_parse_http_time(const u_char *str, size_t len, + long *time) { - ngx_time_t *tp; - - tp = ngx_timeofday(); - - return tp->sec + tp->msec / 1000.0; -} - - -double -ngx_http_lua_ffi_req_start_time(ngx_http_request_t *r) -{ - return r->start_sec + r->start_msec / 1000.0; -} - - -long -ngx_http_lua_ffi_time(void) -{ - return (long) ngx_time(); + /* ngx_http_parse_time doesn't modify 'str' actually */ + *time = ngx_http_parse_time((u_char *) str, len); } -#endif /* NGX_LUA_NO_FFI_API */ /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_time.h b/src/ngx_http_lua_time.h deleted file mode 100644 index 291f784ce8..0000000000 --- a/src/ngx_http_lua_time.h +++ /dev/null @@ -1,21 +0,0 @@ - -/* - * Copyright (C) Xiaozhe Wang (chaoslawful) - * Copyright (C) Yichun Zhang (agentzh) - */ - - -#ifndef _NGX_HTTP_LUA_TIME_H_INCLUDED_ -#define _NGX_HTTP_LUA_TIME_H_INCLUDED_ - - -#include "ngx_http_lua_common.h" - - -void ngx_http_lua_inject_time_api(lua_State *L); -void ngx_http_lua_inject_req_time_api(lua_State *L); - - -#endif /* _NGX_HTTP_LUA_TIME_H_INCLUDED_ */ - -/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_timer.c b/src/ngx_http_lua_timer.c index 596b2f77e9..11d29349a5 100644 --- a/src/ngx_http_lua_timer.c +++ b/src/ngx_http_lua_timer.c @@ -16,6 +16,9 @@ #include "ngx_http_lua_probe.h" +#define NGX_HTTP_LUA_TIMER_ERRBUF_SIZE 128 + + typedef struct { void **main_conf; void **srv_conf; @@ -129,7 +132,8 @@ ngx_http_lua_ngx_timer_every(lua_State *L) static int ngx_http_lua_ngx_timer_helper(lua_State *L, int every) { - int nargs, co_ref; + int nargs; + int co_ref; u_char *p; lua_State *vm; /* the main thread */ lua_State *co; @@ -169,6 +173,16 @@ ngx_http_lua_ngx_timer_helper(lua_State *L, int every) } ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + ngx_http_lua_assert(ctx != NULL); + + /* + * Since nginx has been confirmed that all timers have been cleaned up when + * exit worker is executed, all timers will no longer be executed in exit + * worker phase. + * Reference https://github.com/nginx/nginx/blob/f02e2a734ef472f0dcf83ab2 + * e8ce96d1acead8a5/src/os/unix/ngx_process_cycle.c#L715 + */ + ngx_http_lua_check_context(L, ctx, ~NGX_HTTP_LUA_CONTEXT_EXIT_WORKER); if (ngx_exiting && delay > 0) { lua_pushnil(L); @@ -214,31 +228,22 @@ ngx_http_lua_ngx_timer_helper(lua_State *L, int every) vm = ngx_http_lua_get_lua_vm(r, ctx); - co = lua_newthread(vm); + co_ref = ngx_http_lua_new_cached_thread(vm, &co, lmcf, 1); + + /* vm stack: coroutines thread */ /* L stack: time func [args] */ ngx_http_lua_probe_user_coroutine_create(r, L, co); - lua_createtable(co, 0, 0); /* the new globals table */ - - /* co stack: global_tb */ - - lua_createtable(co, 0, 1); /* the metatable */ - ngx_http_lua_get_globals_table(co); - lua_setfield(co, -2, "__index"); - lua_setmetatable(co, -2); - - /* co stack: global_tb */ - - ngx_http_lua_set_globals_table(co); - /* co stack: */ dd("stack top: %d", lua_gettop(L)); lua_xmove(vm, L, 1); /* move coroutine from main thread to L */ + lua_pop(vm, 1); /* pop coroutines */ + /* L stack: time func [args] thread */ /* vm stack: empty */ @@ -251,23 +256,13 @@ ngx_http_lua_ngx_timer_helper(lua_State *L, int every) /* L stack: time func [args] thread */ /* co stack: func */ +#ifndef OPENRESTY_LUAJIT ngx_http_lua_get_globals_table(co); lua_setfenv(co, -2); +#endif /* co stack: func */ - lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); - lua_rawget(L, LUA_REGISTRYINDEX); - - /* L stack: time func [args] thread coroutines */ - - lua_pushvalue(L, -2); - - /* L stack: time func [args] thread coroutines thread */ - - co_ref = luaL_ref(L, -2); - lua_pop(L, 1); - /* L stack: time func [args] thread */ if (nargs > 2) { @@ -329,7 +324,7 @@ ngx_http_lua_ngx_timer_helper(lua_State *L, int every) tctx->client_addr_text.data = NULL; } - if (ctx && ctx->vm_state) { + if (ctx->vm_state) { tctx->vm_state = ctx->vm_state; tctx->vm_state->count++; @@ -343,8 +338,22 @@ ngx_http_lua_ngx_timer_helper(lua_State *L, int every) lmcf->pending_timers++; +#ifdef HAVE_POSTED_DELAYED_EVENTS_PATCH + if (delay == 0 && !ngx_exiting) { + dd("posting 0 sec sleep event to head of delayed queue"); + ngx_post_event(ev, &ngx_posted_delayed_events); + + lua_pushinteger(L, 1); + return 1; + } +#endif + ngx_add_timer(ev, delay); + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "created timer (co: %p delay: %M ms): sz=%d", tctx->co, + delay, lua_gettop(L)); + lua_pushinteger(L, 1); return 1; @@ -358,9 +367,7 @@ ngx_http_lua_ngx_timer_helper(lua_State *L, int every) ngx_free(ev); } - lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); - lua_rawget(L, LUA_REGISTRYINDEX); - luaL_unref(L, -1, co_ref); + ngx_http_lua_free_thread(r, L, co_ref, co, lmcf); return luaL_error(L, "no memory"); } @@ -385,20 +392,7 @@ ngx_http_lua_timer_copy(ngx_http_lua_timer_ctx_t *old_tctx) vm = old_tctx->vm_state ? old_tctx->vm_state->vm : lmcf->lua; - co = lua_newthread(vm); - - lua_createtable(co, 0, 0); /* the new globals table */ - - /* co stack: global_tb */ - - lua_createtable(co, 0, 1); /* the metatable */ - ngx_http_lua_get_globals_table(co); - lua_setfield(co, -2, "__index"); - lua_setmetatable(co, -2); - - /* co stack: global_tb */ - - ngx_http_lua_set_globals_table(co); + co_ref = ngx_http_lua_new_cached_thread(vm, &co, lmcf, 1); /* co stack: */ @@ -406,6 +400,8 @@ ngx_http_lua_timer_copy(ngx_http_lua_timer_ctx_t *old_tctx) lua_xmove(vm, L, 1); /* move coroutine from main thread to L */ + lua_pop(vm, 1); /* pop coroutines */ + /* L stack: func [args] thread */ /* vm stack: empty */ @@ -418,22 +414,14 @@ ngx_http_lua_timer_copy(ngx_http_lua_timer_ctx_t *old_tctx) /* L stack: func [args] thread */ /* co stack: func */ +#ifndef OPENRESTY_LUAJIT ngx_http_lua_get_globals_table(co); lua_setfenv(co, -2); +#endif /* co stack: func */ - lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); - lua_rawget(L, LUA_REGISTRYINDEX); - - /* L stack: func [args] thread coroutines */ - - lua_pushvalue(L, -2); - - /* L stack: func [args] thread coroutines thread */ - - co_ref = luaL_ref(L, -2); - lua_pop(L, 2); + lua_pop(L, 1); /* pop thread */ /* L stack: func [args] */ @@ -498,6 +486,10 @@ ngx_http_lua_timer_copy(ngx_http_lua_timer_ctx_t *old_tctx) ngx_add_timer(ev, tctx->delay); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "created next timer (co: %p delay: %M ms)", tctx->co, + tctx->delay); + return NGX_OK; nomem: @@ -512,13 +504,7 @@ ngx_http_lua_timer_copy(ngx_http_lua_timer_ctx_t *old_tctx) /* L stack: func [args] */ - lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); - lua_rawget(L, LUA_REGISTRYINDEX); - luaL_unref(L, -1, co_ref); - - /* L stack: func [args] coroutines */ - - lua_pop(L, 1); + ngx_http_lua_free_thread(NULL, L, co_ref, co, lmcf); return NGX_ERROR; } @@ -528,24 +514,32 @@ static void ngx_http_lua_timer_handler(ngx_event_t *ev) { int n; - lua_State *L; + lua_State *L = NULL; ngx_int_t rc; ngx_connection_t *c = NULL; ngx_http_request_t *r = NULL; ngx_http_lua_ctx_t *ctx; - ngx_http_cleanup_t *cln; + ngx_pool_cleanup_t *cln; ngx_pool_cleanup_t *pcln; ngx_http_lua_timer_ctx_t tctx; ngx_http_lua_main_conf_t *lmcf; ngx_http_core_loc_conf_t *clcf; + lua_Debug ar; + u_char *p; + u_char errbuf[NGX_HTTP_LUA_TIMER_ERRBUF_SIZE]; + const char *source; + const char *errmsg; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, "lua ngx.timer expired"); ngx_memcpy(&tctx, ev->data, sizeof(ngx_http_lua_timer_ctx_t)); ngx_free(ev); + ngx_http_lua_assert(tctx.co_ref && tctx.co); + lmcf = tctx.lmcf; lmcf->pending_timers--; @@ -560,14 +554,19 @@ ngx_http_lua_timer_handler(ngx_event_t *ev) } if (lmcf->running_timers >= lmcf->max_running_timers) { - ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, - "%i lua_max_running_timers are not enough", - lmcf->max_running_timers); + p = ngx_snprintf(errbuf, NGX_HTTP_LUA_TIMER_ERRBUF_SIZE - 1, + "%i lua_max_running_timers are not enough", + lmcf->max_running_timers); + *p = '\0'; + errmsg = (const char *) errbuf; goto failed; } c = ngx_http_lua_create_fake_connection(tctx.pool); if (c == NULL) { + errmsg = "could not create fake connection"; + /* tctx.pool is freed in ngx_http_lua_create_fake_connection */ + tctx.pool = NULL; goto failed; } @@ -579,6 +578,7 @@ ngx_http_lua_timer_handler(ngx_event_t *ev) r = ngx_http_lua_create_fake_request(c); if (r == NULL) { + errmsg = "could not create fake request"; goto failed; } @@ -588,32 +588,18 @@ ngx_http_lua_timer_handler(ngx_event_t *ev) clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); -#if defined(nginx_version) && nginx_version >= 1003014 - -# if nginx_version >= 1009000 - +#if (nginx_version >= 1009000) ngx_set_connection_log(r->connection, clcf->error_log); -# else - - ngx_http_set_connection_log(r->connection, clcf->error_log); - -# endif - #else - - c->log->file = clcf->error_log->file; - - if (!(c->log->log_level & NGX_LOG_DEBUG_CONNECTION)) { - c->log->log_level = clcf->error_log->log_level; - } - + ngx_http_set_connection_log(r->connection, clcf->error_log); #endif dd("lmcf: %p", lmcf); ctx = ngx_http_lua_create_ctx(r); if (ctx == NULL) { + errmsg = "could not create ctx"; goto failed; } @@ -622,6 +608,7 @@ ngx_http_lua_timer_handler(ngx_event_t *ev) pcln = ngx_pool_cleanup_add(r->pool, 0); if (pcln == NULL) { + errmsg = "could not add vm cleanup"; goto failed; } @@ -633,8 +620,9 @@ ngx_http_lua_timer_handler(ngx_event_t *ev) L = ngx_http_lua_get_lua_vm(r, ctx); - cln = ngx_http_cleanup_add(r, 0); + cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { + errmsg = "could not add request cleanup"; goto failed; } @@ -656,6 +644,8 @@ ngx_http_lua_timer_handler(ngx_event_t *ev) /* save the request in coroutine globals table */ ngx_http_lua_set_req(tctx.co, r); + ngx_http_lua_attach_co_ctx_to_L(tctx.co, ctx->cur_co_ctx); + lmcf->running_timers++; lua_pushboolean(tctx.co, tctx.premature); @@ -691,18 +681,42 @@ ngx_http_lua_timer_handler(ngx_event_t *ev) failed: - if (tctx.co_ref && tctx.co) { - lua_pushlightuserdata(tctx.co, &ngx_http_lua_coroutines_key); - lua_rawget(tctx.co, LUA_REGISTRYINDEX); - luaL_unref(tctx.co, -1, tctx.co_ref); - lua_settop(tctx.co, 0); + /* co stack: func [args] */ + lua_pushvalue(tctx.co, 1); + /* co stack: func [args] func */ + lua_getinfo(tctx.co, ">Sf", &ar); + + source = ar.source; + + if (source == NULL) { + source = "(unknown)"; } - if (tctx.vm_state) { + ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, + "lua failed to run timer with function defined at %s:%d: %s", + source, ar.linedefined, errmsg); + +#if 1 + if (L == NULL) { + if (tctx.vm_state != NULL) { + L = tctx.vm_state->vm; + } + + if (L == NULL) { + L = lmcf->lua; + } + } +#endif + + if (L != NULL) { + ngx_http_lua_free_thread(r, L, tctx.co_ref, tctx.co, lmcf); + } + + if (tctx.vm_state != NULL) { ngx_http_lua_cleanup_vm(tctx.vm_state); } - if (c) { + if (c != NULL) { ngx_http_lua_close_fake_connection(c); } else if (tctx.pool) { @@ -731,16 +745,19 @@ ngx_http_lua_log_timer_error(ngx_log_t *log, u_char *buf, size_t len) len -= p - buf; buf = p; - if (c->addr_text.len) { - p = ngx_snprintf(buf, len, ", client: %V", &c->addr_text); - len -= p - buf; - buf = p; - } + if (c != NULL) { + if (c->addr_text.len) { + p = ngx_snprintf(buf, len, ", client: %V", &c->addr_text); + len -= p - buf; + buf = p; + } - if (c && c->listening && c->listening->addr_text.len) { - p = ngx_snprintf(buf, len, ", server: %V", &c->listening->addr_text); - /* len -= p - buf; */ - buf = p; + if (c->listening && c->listening->addr_text.len) { + p = ngx_snprintf(buf, len, ", server: %V", + &c->listening->addr_text); + /* len -= p - buf; */ + buf = p; + } } return buf; @@ -806,7 +823,7 @@ ngx_http_lua_abort_pending_timers(ngx_event_t *ev) prev = NULL; events = ngx_pcalloc(ngx_cycle->pool, - lmcf->pending_timers * sizeof(ngx_event_t)); + lmcf->pending_timers * sizeof(ngx_event_t *)); if (events == NULL) { return; } @@ -860,7 +877,7 @@ ngx_http_lua_abort_pending_timers(ngx_event_t *ev) next = cur->parent; } else { - /* not reacheable */ + /* not reachable */ next = NULL; } diff --git a/src/ngx_http_lua_uri.c b/src/ngx_http_lua_uri.c index 3559b5c0cc..c4d65f2905 100644 --- a/src/ngx_http_lua_uri.c +++ b/src/ngx_http_lua_uri.c @@ -32,14 +32,18 @@ ngx_http_lua_ngx_req_set_uri(lua_State *L) ngx_http_request_t *r; size_t len; u_char *p; + u_char byte; int n; int jump = 0; + int binary = 0; ngx_http_lua_ctx_t *ctx; + size_t buf_len; + u_char *buf; n = lua_gettop(L); - if (n != 1 && n != 2) { - return luaL_error(L, "expecting 1 or 2 arguments but seen %d", n); + if (n < 1 || n > 3) { + return luaL_error(L, "expecting 1, 2 or 3 arguments but seen %d", n); } r = ngx_http_lua_get_req(L); @@ -55,8 +59,29 @@ ngx_http_lua_ngx_req_set_uri(lua_State *L) return luaL_error(L, "attempt to use zero-length uri"); } - if (n == 2) { + if (n >= 3) { + luaL_checktype(L, 3, LUA_TBOOLEAN); + binary = lua_toboolean(L, 3); + } + + if (!binary + && ngx_http_lua_check_unsafe_uri_bytes(r, p, len, &byte) != NGX_OK) + { + buf_len = ngx_http_lua_escape_log(NULL, p, len) + 1; + buf = ngx_palloc(r->pool, buf_len); + if (buf == NULL) { + return NGX_ERROR; + } + + ngx_http_lua_escape_log(buf, p, len); + buf[buf_len - 1] = '\0'; + return luaL_error(L, "unsafe byte \"0x%02x\" in uri \"%s\" " + "(maybe you want to set the 'binary' argument?)", + byte, buf); + } + + if (n >= 2) { luaL_checktype(L, 2, LUA_TBOOLEAN); jump = lua_toboolean(L, 2); @@ -67,12 +92,14 @@ ngx_http_lua_ngx_req_set_uri(lua_State *L) return luaL_error(L, "no ctx found"); } - dd("rewrite: %d, access: %d, content: %d", + dd("server_rewrite: %d, rewrite: %d, access: %d, content: %d", + (int) ctx->entered_server_rewrite_phase, (int) ctx->entered_rewrite_phase, (int) ctx->entered_access_phase, (int) ctx->entered_content_phase); - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE); + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE + | NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "lua set uri jump to \"%*s\"", len, p); @@ -107,4 +134,5 @@ ngx_http_lua_ngx_req_set_uri(lua_State *L) return 0; } + /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_uthread.c b/src/ngx_http_lua_uthread.c index 8195ec0f82..2208b2a819 100644 --- a/src/ngx_http_lua_uthread.c +++ b/src/ngx_http_lua_uthread.c @@ -49,7 +49,7 @@ ngx_http_lua_inject_uthread_api(ngx_log_t *log, lua_State *L) static int ngx_http_lua_uthread_spawn(lua_State *L) { - int n; + int n, co_ref; ngx_http_request_t *r; ngx_http_lua_ctx_t *ctx; ngx_http_lua_co_ctx_t *coctx = NULL; @@ -66,21 +66,16 @@ ngx_http_lua_uthread_spawn(lua_State *L) return luaL_error(L, "no request ctx found"); } - ngx_http_lua_coroutine_create_helper(L, r, ctx, &coctx); + ngx_http_lua_coroutine_create_helper(L, r, ctx, &coctx, &co_ref); /* anchor the newly created coroutine into the Lua registry */ - lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); - lua_rawget(L, LUA_REGISTRYINDEX); - lua_pushvalue(L, -2); - coctx->co_ref = luaL_ref(L, -2); - lua_pop(L, 1); - if (n > 1) { lua_replace(L, 1); lua_xmove(L, coctx->co, n - 1); } + coctx->co_ref = co_ref; coctx->is_uthread = 1; ctx->uthreads++; @@ -96,6 +91,8 @@ ngx_http_lua_uthread_spawn(lua_State *L) coctx->parent_co_ctx = ctx->cur_co_ctx; ctx->cur_co_ctx = coctx; + ngx_http_lua_attach_co_ctx_to_L(coctx->co, coctx); + ngx_http_lua_probe_user_thread_spawn(r, L, coctx->co); dd("yielding with arg %s, top=%d, index-1:%s", luaL_typename(L, -1), @@ -123,15 +120,14 @@ ngx_http_lua_uthread_wait(lua_State *L) return luaL_error(L, "no request ctx found"); } - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE - | NGX_HTTP_LUA_CONTEXT_ACCESS - | NGX_HTTP_LUA_CONTEXT_CONTENT - | NGX_HTTP_LUA_CONTEXT_TIMER - | NGX_HTTP_LUA_CONTEXT_SSL_CERT); + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); coctx = ctx->cur_co_ctx; nargs = lua_gettop(L); + if (nargs == 0) { + return luaL_error(L, "at least one coroutine should be specified"); + } for (i = 1; i <= nargs; i++) { sub_co = lua_tothread(L, i); @@ -221,11 +217,7 @@ ngx_http_lua_uthread_kill(lua_State *L) return luaL_error(L, "no request ctx found"); } - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE - | NGX_HTTP_LUA_CONTEXT_ACCESS - | NGX_HTTP_LUA_CONTEXT_CONTENT - | NGX_HTTP_LUA_CONTEXT_TIMER - | NGX_HTTP_LUA_CONTEXT_SSL_CERT); + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); coctx = ctx->cur_co_ctx; @@ -279,7 +271,7 @@ ngx_http_lua_uthread_kill(lua_State *L) return 1; } - /* not reacheable */ + /* not reachable */ } /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_util.c b/src/ngx_http_lua_util.c index c2d5c41147..96e9ae5b20 100644 --- a/src/ngx_http_lua_util.c +++ b/src/ngx_http_lua_util.c @@ -16,22 +16,18 @@ #include "ngx_http_lua_util.h" #include "ngx_http_lua_exception.h" #include "ngx_http_lua_pcrefix.h" -#include "ngx_http_lua_regex.h" #include "ngx_http_lua_args.h" #include "ngx_http_lua_uri.h" #include "ngx_http_lua_req_body.h" #include "ngx_http_lua_headers.h" #include "ngx_http_lua_output.h" -#include "ngx_http_lua_time.h" #include "ngx_http_lua_control.h" #include "ngx_http_lua_ndk.h" #include "ngx_http_lua_subrequest.h" #include "ngx_http_lua_log.h" -#include "ngx_http_lua_variable.h" #include "ngx_http_lua_string.h" #include "ngx_http_lua_misc.h" #include "ngx_http_lua_consts.h" -#include "ngx_http_lua_req_method.h" #include "ngx_http_lua_shdict.h" #include "ngx_http_lua_coroutine.h" #include "ngx_http_lua_socket_tcp.h" @@ -41,17 +37,22 @@ #include "ngx_http_lua_headerfilterby.h" #include "ngx_http_lua_bodyfilterby.h" #include "ngx_http_lua_logby.h" -#include "ngx_http_lua_phase.h" #include "ngx_http_lua_probe.h" #include "ngx_http_lua_uthread.h" #include "ngx_http_lua_contentby.h" #include "ngx_http_lua_timer.h" #include "ngx_http_lua_config.h" -#include "ngx_http_lua_worker.h" #include "ngx_http_lua_socket_tcp.h" #include "ngx_http_lua_ssl_certby.h" #include "ngx_http_lua_ssl.h" #include "ngx_http_lua_log_ringbuf.h" +#if (NGX_THREADS) +#include "ngx_http_lua_worker_thread.h" +#endif +#if (NGX_HTTP_V3) +#include +#include +#endif #if 1 @@ -70,8 +71,26 @@ #endif +#if (NGX_HTTP_LUA_HAVE_SA_RESTART) +#define NGX_HTTP_LUA_SA_RESTART_SIGS { \ + ngx_signal_value(NGX_RECONFIGURE_SIGNAL), \ + ngx_signal_value(NGX_REOPEN_SIGNAL), \ + ngx_signal_value(NGX_NOACCEPT_SIGNAL), \ + ngx_signal_value(NGX_TERMINATE_SIGNAL), \ + ngx_signal_value(NGX_SHUTDOWN_SIGNAL), \ + ngx_signal_value(NGX_CHANGEBIN_SIGNAL), \ + SIGALRM, \ + SIGINT, \ + SIGIO, \ + SIGCHLD, \ + SIGSYS, \ + SIGPIPE, \ + 0 \ +}; +#endif + + char ngx_http_lua_code_cache_key; -char ngx_http_lua_regex_cache_key; char ngx_http_lua_socket_pool_key; char ngx_http_lua_coroutines_key; char ngx_http_lua_headers_metatable_key; @@ -86,6 +105,10 @@ static ngx_int_t ngx_http_lua_send_http10_headers(ngx_http_request_t *r, static void ngx_http_lua_init_registry(lua_State *L, ngx_log_t *log); static void ngx_http_lua_init_globals(lua_State *L, ngx_cycle_t *cycle, ngx_http_lua_main_conf_t *lmcf, ngx_log_t *log); +#ifdef OPENRESTY_LUAJIT +static void ngx_http_lua_inject_global_write_guard(lua_State *L, + ngx_log_t *log); +#endif static void ngx_http_lua_set_path(ngx_cycle_t *cycle, lua_State *L, int tab_idx, const char *fieldname, const char *path, const char *default_path, ngx_log_t *log); @@ -100,7 +123,6 @@ static int ngx_http_lua_thread_traceback(lua_State *L, lua_State *co, static void ngx_http_lua_inject_ngx_api(lua_State *L, ngx_http_lua_main_conf_t *lmcf, ngx_log_t *log); static void ngx_http_lua_inject_arg_api(lua_State *L); -static int ngx_http_lua_param_get(lua_State *L); static int ngx_http_lua_param_set(lua_State *L); static ngx_int_t ngx_http_lua_output_filter(ngx_http_request_t *r, ngx_chain_t *in); @@ -128,6 +150,13 @@ static int ngx_http_lua_get_raw_phase_context(lua_State *L); #define LUA_PATH_SEP ";" #endif + +#if !defined(LUA_DEFAULT_PATH) && (NGX_DEBUG) +#define LUA_DEFAULT_PATH "../lua-resty-core/lib/?.lua;" \ + "../lua-resty-lrucache/lib/?.lua" +#endif + + #define AUX_MARK "\1" @@ -171,6 +200,7 @@ ngx_http_lua_set_path(ngx_cycle_t *cycle, lua_State *L, int tab_idx, } +#ifndef OPENRESTY_LUAJIT /** * Create new table and set _G field to itself. * @@ -185,6 +215,7 @@ ngx_http_lua_create_new_globals_table(lua_State *L, int narr, int nrec) lua_pushvalue(L, -1); lua_setfield(L, -2, "_G"); } +#endif /* OPENRESTY_LUAJIT */ static lua_State * @@ -241,7 +272,6 @@ ngx_http_lua_new_state(lua_State *parent_vm, ngx_cycle_t *cycle, lua_pushliteral(L, LUA_DEFAULT_PATH ";"); /* package default */ lua_getfield(L, -2, "path"); /* package default old */ - old_path = lua_tolstring(L, -1, &old_path_len); lua_concat(L, 2); /* package new */ lua_setfield(L, -2, "path"); /* package */ #endif @@ -254,7 +284,6 @@ ngx_http_lua_new_state(lua_State *parent_vm, ngx_cycle_t *cycle, lua_pushliteral(L, LUA_DEFAULT_CPATH ";"); /* package default */ lua_getfield(L, -2, "cpath"); /* package default old */ - old_cpath = lua_tolstring(L, -1, &old_cpath_len); lua_concat(L, 2); /* package new */ lua_setfield(L, -2, "cpath"); /* package */ #endif @@ -308,39 +337,101 @@ ngx_http_lua_new_thread(ngx_http_request_t *r, lua_State *L, int *ref) int base; lua_State *co; +#ifdef HAVE_LUA_RESETTHREAD + ngx_queue_t *q; + + ngx_http_lua_main_conf_t *lmcf; + ngx_http_lua_thread_ref_t *tref; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "lua creating new thread"); - base = lua_gettop(L); + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); - lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); - lua_rawget(L, LUA_REGISTRYINDEX); + if (L == lmcf->lua && !ngx_queue_empty(&lmcf->cached_lua_threads)) { + q = ngx_queue_head(&lmcf->cached_lua_threads); + tref = ngx_queue_data(q, ngx_http_lua_thread_ref_t, queue); - co = lua_newthread(L); + ngx_http_lua_assert(tref->ref != LUA_NOREF); + ngx_http_lua_assert(tref->co != NULL); - /* {{{ inherit coroutine's globals to main thread's globals table - * for print() function will try to find tostring() in current - * globals table. - */ - /* new globals table for coroutine */ - ngx_http_lua_create_new_globals_table(co, 0, 0); + co = tref->co; + *ref = tref->ref; - lua_createtable(co, 0, 1); - ngx_http_lua_get_globals_table(co); - lua_setfield(co, -2, "__index"); - lua_setmetatable(co, -2); + tref->co = NULL; + tref->ref = LUA_NOREF; - ngx_http_lua_set_globals_table(co); - /* }}} */ + ngx_queue_remove(q); + ngx_queue_insert_head(&lmcf->free_lua_threads, q); - *ref = luaL_ref(L, -2); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua reusing cached lua thread %p (ref %d)", co, *ref); - if (*ref == LUA_NOREF) { - lua_settop(L, base); /* restore main thread stack */ - return NULL; +#if 0 + { + int n = 0; + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + coroutines_key)); + lua_rawget(L, LUA_REGISTRYINDEX); + lua_pushnil(L); /* first key */ + while (lua_next(L, -2) != 0) { + if (!lua_isnil(L, -1) && !lua_isnil(L, -2)) { + n++; + } + + lua_pop(L, 1); + } + + lua_pop(L, 1); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "! lua reusing cached lua thread %p (ref %d, n %d)", + co, *ref, n); + } +#endif + + } else +#endif + { + base = lua_gettop(L); + + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + coroutines_key)); + lua_rawget(L, LUA_REGISTRYINDEX); + + co = lua_newthread(L); + +#ifndef OPENRESTY_LUAJIT + /* {{{ inherit coroutine's globals to main thread's globals table + * for print() function will try to find tostring() in current + * globals table. + */ + /* new globals table for coroutine */ + ngx_http_lua_create_new_globals_table(co, 0, 0); + + lua_createtable(co, 0, 1); + ngx_http_lua_get_globals_table(co); + lua_setfield(co, -2, "__index"); + lua_setmetatable(co, -2); + + ngx_http_lua_set_globals_table(co); + /* }}} */ +#endif /* OPENRESTY_LUAJIT */ + + *ref = luaL_ref(L, -2); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, + ngx_cycle->log, 0, "lua ref lua thread %p (ref %d)", co, + *ref); + + if (*ref == LUA_NOREF) { + lua_settop(L, base); /* restore main thread stack */ + return NULL; + } + + lua_settop(L, base); } - lua_settop(L, base); return co; } @@ -349,23 +440,63 @@ void ngx_http_lua_del_thread(ngx_http_request_t *r, lua_State *L, ngx_http_lua_ctx_t *ctx, ngx_http_lua_co_ctx_t *coctx) { +#ifdef HAVE_LUA_RESETTHREAD + ngx_queue_t *q; + ngx_http_lua_main_conf_t *lmcf; + ngx_http_lua_thread_ref_t *tref; +#endif + if (coctx->co_ref == LUA_NOREF) { return; } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "lua deleting light thread"); - - lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); - lua_rawget(L, LUA_REGISTRYINDEX); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua deleting light thread %p (ref %d)", coctx->co, + coctx->co_ref); ngx_http_lua_probe_thread_delete(r, coctx->co, ctx); - luaL_unref(L, -1, coctx->co_ref); +#ifdef HAVE_LUA_RESETTHREAD + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); + + if (ctx != NULL + && coctx->co == ctx->entry_co_ctx.co + && L == lmcf->lua && !ngx_queue_empty(&lmcf->free_lua_threads)) + { + lua_resetthread(L, coctx->co); + q = ngx_queue_head(&lmcf->free_lua_threads); + tref = ngx_queue_data(q, ngx_http_lua_thread_ref_t, queue); + + ngx_http_lua_assert(tref->ref == LUA_NOREF); + ngx_http_lua_assert(tref->co == NULL); + + tref->ref = coctx->co_ref; + tref->co = coctx->co; + + ngx_queue_remove(q); + ngx_queue_insert_head(&lmcf->cached_lua_threads, q); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua caching unused lua thread %p (ref %d)", coctx->co, + coctx->co_ref); + + } else { +#endif + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http lua unref thread %p: %d", coctx->co, + coctx->co_ref); + + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + coroutines_key)); + lua_rawget(L, LUA_REGISTRYINDEX); + luaL_unref(L, -1, coctx->co_ref); + lua_pop(L, 1); +#ifdef HAVE_LUA_RESETTHREAD + } +#endif + coctx->co_ref = LUA_NOREF; coctx->co_status = NGX_HTTP_LUA_CO_DEAD; - - lua_pop(L, 1); } @@ -408,7 +539,9 @@ ngx_http_lua_send_header_if_needed(ngx_http_request_t *r, r->headers_out.status = NGX_HTTP_OK; } - if (!ctx->headers_set && ngx_http_lua_set_content_type(r) != NGX_OK) { + if (!ctx->mime_set + && ngx_http_lua_set_content_type(r, ctx) != NGX_OK) + { return NGX_ERROR; } @@ -420,6 +553,10 @@ ngx_http_lua_send_header_if_needed(ngx_http_request_t *r, if (!ctx->buffering) { dd("sending headers"); rc = ngx_http_send_header(r); + if (r->filter_finalize) { + ngx_http_set_ctx(r, ctx, ngx_http_lua_module); + } + ctx->header_sent = 1; return rc; } @@ -470,6 +607,12 @@ ngx_http_lua_send_chain_link(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx, if (r->header_only) { ctx->eof = 1; + if (!r->request_body && r == r->main) { + if (ngx_http_discard_request_body(r) != NGX_OK) { + return NGX_ERROR; + } + } + if (ctx->buffering) { return ngx_http_lua_send_http10_headers(r, ctx); } @@ -506,16 +649,6 @@ ngx_http_lua_send_chain_link(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx, } } -#if defined(nginx_version) && nginx_version <= 8004 - - /* earlier versions of nginx does not allow subrequests - to send last_buf themselves */ - if (r != r->main) { - return NGX_OK; - } - -#endif - ctx->eof = 1; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -598,11 +731,11 @@ ngx_http_lua_output_filter(ngx_http_request_t *r, ngx_chain_t *in) ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); -#if nginx_version >= 1001004 + if (ctx == NULL) { + return rc; + } + ngx_chain_update_chains(r->pool, -#else - ngx_chain_update_chains( -#endif &ctx->free_bufs, &ctx->busy_bufs, &in, (ngx_buf_tag_t) &ngx_http_lua_module); @@ -635,10 +768,6 @@ ngx_http_lua_send_http10_headers(ngx_http_request_t *r, } r->headers_out.content_length_n = size; - - if (r->headers_out.content_length) { - r->headers_out.content_length->hash = 0; - } } send: @@ -657,31 +786,30 @@ ngx_http_lua_init_registry(lua_State *L, ngx_log_t *log) /* {{{ register a table to anchor lua coroutines reliably: * {([int]ref) = [cort]} */ - lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + coroutines_key)); lua_createtable(L, 0, 32 /* nrec */); lua_rawset(L, LUA_REGISTRYINDEX); /* }}} */ - /* create the registry entry for the Lua request ctx data table */ - lua_pushliteral(L, ngx_http_lua_ctx_tables_key); - lua_createtable(L, 0, 32 /* nrec */); - lua_rawset(L, LUA_REGISTRYINDEX); + /* + * the the Lua request ctx data table will create in resty.core.ctx, + * just equivalent to the following code: + * lua_pushliteral(L, ngx_http_lua_ctx_tables_key); + * lua_createtable(L, 0, 0); + * lua_rawset(L, LUA_REGISTRYINDEX); + */ /* create the registry entry for the Lua socket connection pool table */ - lua_pushlightuserdata(L, &ngx_http_lua_socket_pool_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + socket_pool_key)); lua_createtable(L, 0, 8 /* nrec */); lua_rawset(L, LUA_REGISTRYINDEX); -#if (NGX_PCRE) - /* create the registry entry for the Lua precompiled regex object cache */ - lua_pushlightuserdata(L, &ngx_http_lua_regex_cache_key); - lua_createtable(L, 0, 16 /* nrec */); - lua_rawset(L, LUA_REGISTRYINDEX); -#endif - /* {{{ register table to cache user code: * { [(string)cache_key] = } */ - lua_pushlightuserdata(L, &ngx_http_lua_code_cache_key); + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + code_cache_key)); lua_createtable(L, 0, 8 /* nrec */); lua_rawset(L, LUA_REGISTRYINDEX); /* }}} */ @@ -695,9 +823,6 @@ ngx_http_lua_init_globals(lua_State *L, ngx_cycle_t *cycle, ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "lua initializing lua globals"); - lua_pushlightuserdata(L, cycle); - lua_setglobal(L, "__ngx_cycle"); - #if defined(NDK) && NDK ngx_http_lua_inject_ndk_api(L); #endif /* defined(NDK) && NDK */ @@ -710,7 +835,7 @@ static void ngx_http_lua_inject_ngx_api(lua_State *L, ngx_http_lua_main_conf_t *lmcf, ngx_log_t *log) { - lua_createtable(L, 0 /* narr */, 116 /* nrec */); /* ngx.* */ + lua_createtable(L, 0 /* narr */, 115 /* nrec */); /* ngx.* */ lua_pushcfunction(L, ngx_http_lua_get_raw_phase_context); lua_setfield(L, -2, "_phase_ctx"); @@ -722,30 +847,23 @@ ngx_http_lua_inject_ngx_api(lua_State *L, ngx_http_lua_main_conf_t *lmcf, ngx_http_lua_inject_log_api(L); ngx_http_lua_inject_output_api(L); - ngx_http_lua_inject_time_api(L); ngx_http_lua_inject_string_api(L); ngx_http_lua_inject_control_api(log, L); ngx_http_lua_inject_subrequest_api(L); ngx_http_lua_inject_sleep_api(L); - ngx_http_lua_inject_phase_api(L); - -#if (NGX_PCRE) - ngx_http_lua_inject_regex_api(L); -#endif ngx_http_lua_inject_req_api(log, L); ngx_http_lua_inject_resp_header_api(L); ngx_http_lua_create_headers_metatable(log, L); - ngx_http_lua_inject_variable_api(L); ngx_http_lua_inject_shdict_api(lmcf, L); ngx_http_lua_inject_socket_tcp_api(log, L); ngx_http_lua_inject_socket_udp_api(log, L); ngx_http_lua_inject_uthread_api(log, L); ngx_http_lua_inject_timer_api(L); ngx_http_lua_inject_config_api(L); - ngx_http_lua_inject_worker_api(L); - - ngx_http_lua_inject_misc_api(L); +#if (NGX_THREADS) + ngx_http_lua_inject_worker_thread_api(log, L); +#endif lua_getglobal(L, "package"); /* ngx package */ lua_getfield(L, -1, "loaded"); /* ngx package loaded */ @@ -759,6 +877,55 @@ ngx_http_lua_inject_ngx_api(lua_State *L, ngx_http_lua_main_conf_t *lmcf, } +#ifdef OPENRESTY_LUAJIT +static void +ngx_http_lua_inject_global_write_guard(lua_State *L, ngx_log_t *log) +{ + int rc; + + const char buf[] = + "local ngx_log = ngx.log\n" + "local ngx_WARN = ngx.WARN\n" + "local tostring = tostring\n" + "local ngx_get_phase = ngx.get_phase\n" + "local traceback = require 'debug'.traceback\n" + "local function newindex(table, key, value)\n" + "rawset(table, key, value)\n" + "local phase = ngx_get_phase()\n" + "if phase == 'init_worker' or phase == 'init' then\n" + "return\n" + "end\n" + "ngx_log(ngx_WARN, 'writing a global Lua variable " + "(\\'', tostring(key), '\\') which may lead to " + "race conditions between concurrent requests, so " + "prefer the use of \\'local\\' variables', " + "traceback('', 2))\n" + "end\n" + "setmetatable(_G, { __newindex = newindex })\n" + ; + + rc = luaL_loadbuffer(L, buf, sizeof(buf) - 1, "=_G write guard"); + + if (rc != 0) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "failed to load Lua code (%i): %s", + rc, lua_tostring(L, -1)); + + lua_pop(L, 1); + return; + } + + rc = lua_pcall(L, 0, 0, 0); + if (rc != 0) { + ngx_log_error(NGX_LOG_ERR, log, 0, + "failed to run Lua code (%i): %s", + rc, lua_tostring(L, -1)); + lua_pop(L, 1); + } +} +#endif + + void ngx_http_lua_discard_bufs(ngx_pool_t *pool, ngx_chain_t *in) { @@ -841,8 +1008,12 @@ ngx_http_lua_reset_ctx(ngx_http_request_t *r, lua_State *L, ngx_memzero(&ctx->entry_co_ctx, sizeof(ngx_http_lua_co_ctx_t)); + ctx->entry_co_ctx.next_zombie_child_thread = + &ctx->entry_co_ctx.zombie_child_threads; + ctx->entry_co_ctx.co_ref = LUA_NOREF; + ctx->entered_server_rewrite_phase = 0; ctx->entered_rewrite_phase = 0; ctx->entered_access_phase = 0; ctx->entered_content_phase = 0; @@ -869,11 +1040,13 @@ ngx_http_lua_generic_phase_post_read(ngx_http_request_t *r) ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); - ctx->read_body_done = 1; - -#if defined(nginx_version) && nginx_version >= 8011 r->main->count--; -#endif + + if (ctx == NULL) { + return; + } + + ctx->read_body_done = 1; if (ctx->waiting_more_body) { ctx->waiting_more_body = 0; @@ -963,10 +1136,16 @@ ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r, /* set Lua VM panic handler */ lua_atpanic(L, ngx_http_lua_atpanic); - dd("ctx = %p", ctx); - NGX_LUA_EXCEPTION_TRY { + /* + * silence a -Werror=clobbered warning with gcc 5.4 + * due to above setjmp + */ + err = NULL; + msg = NULL; + trace = NULL; + if (ctx->cur_co_ctx->thread_spawn_yielded) { ngx_http_lua_probe_info("thread spawn yielded"); @@ -976,19 +1155,15 @@ ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r, for ( ;; ) { - dd("calling lua_resume: vm %p, nret %d", ctx->cur_co_ctx->co, - (int) nrets); + dd("ctx: %p, co: %p, co status: %d, co is_wrap: %d", + ctx, ctx->cur_co_ctx->co, ctx->cur_co_ctx->co_status, + ctx->cur_co_ctx->is_wrap); #if (NGX_PCRE) /* XXX: work-around to nginx regex subsystem */ old_pool = ngx_http_lua_pcre_malloc_init(r->pool); #endif - /* run code */ - dd("ctx: %p", ctx); - dd("cur co: %p", ctx->cur_co_ctx->co); - dd("cur co status: %d", ctx->cur_co_ctx->co_status); - orig_coctx = ctx->cur_co_ctx; #ifdef NGX_LUA_USE_ASSERT @@ -1000,10 +1175,19 @@ ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r, #if DDEBUG if (lua_gettop(orig_coctx->co) > 0) { - dd("top elem: %s", luaL_typename(orig_coctx->co, -1)); + dd("co top elem: %s", luaL_typename(orig_coctx->co, -1)); + } + + if (orig_coctx->propagate_error) { + dd("co propagate_error: %d", orig_coctx->propagate_error); } #endif + if (orig_coctx->propagate_error) { + orig_coctx->propagate_error = 0; + goto propagate_error; + } + ngx_http_lua_assert(orig_coctx->co_top + nrets == lua_gettop(orig_coctx->co)); @@ -1148,12 +1332,6 @@ ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r, next_coctx = ctx->cur_co_ctx->parent_co_ctx; next_co = next_coctx->co; - /* - * prepare return values for coroutine.resume - * (true plus any retvals) - */ - lua_pushboolean(next_co, 1); - if (nrets) { dd("moving %d return values to next co", nrets); lua_xmove(ctx->cur_co_ctx->co, next_co, nrets); @@ -1162,7 +1340,15 @@ ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r, #endif } - nrets++; /* add the true boolean value */ + if (!ctx->cur_co_ctx->is_wrap) { + /* + * prepare return values for coroutine.resume + * (true plus any retvals) + */ + lua_pushboolean(next_co, 1); + lua_insert(next_co, 1); + nrets++; /* add the true boolean value */ + } ctx->cur_co_ctx = next_coctx; @@ -1273,12 +1459,6 @@ ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r, next_co = next_coctx->co; - /* - * ended successful, coroutine.resume returns true plus - * any return values - */ - lua_pushboolean(next_co, success); - if (nrets) { lua_xmove(ctx->cur_co_ctx->co, next_co, nrets); } @@ -1288,7 +1468,16 @@ ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r, ctx->uthreads--; } - nrets++; + if (!ctx->cur_co_ctx->is_wrap) { + /* + * ended successfully, coroutine.resume returns true plus + * any return values + */ + lua_pushboolean(next_co, success); + lua_insert(next_co, 1); + nrets++; + } + ctx->cur_co_ctx = next_coctx; ngx_http_lua_probe_info("set parent running"); @@ -1309,8 +1498,9 @@ ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r, break; case LUA_ERRMEM: - err = "memory allocation error"; - ngx_quit = 1; + err = "[lua] memory allocation error"; + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, err); + abort(); break; case LUA_ERRERR: @@ -1326,25 +1516,33 @@ ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r, ctx->cur_co_ctx = orig_coctx; } - if (lua_isstring(ctx->cur_co_ctx->co, -1)) { - dd("user custom error msg"); - msg = lua_tostring(ctx->cur_co_ctx->co, -1); - - } else { - msg = "unknown reason"; - } - ngx_http_lua_cleanup_pending_operation(ctx->cur_co_ctx); ngx_http_lua_probe_coroutine_done(r, ctx->cur_co_ctx->co, 0); ctx->cur_co_ctx->co_status = NGX_HTTP_LUA_CO_DEAD; - ngx_http_lua_thread_traceback(L, ctx->cur_co_ctx->co, - ctx->cur_co_ctx); - trace = lua_tostring(L, -1); + if (orig_coctx->is_uthread + || orig_coctx->is_wrap + || ngx_http_lua_is_entry_thread(ctx)) + { + ngx_http_lua_thread_traceback(L, orig_coctx->co, orig_coctx); + trace = lua_tostring(L, -1); + + if (lua_isstring(orig_coctx->co, -1)) { + msg = lua_tostring(orig_coctx->co, -1); + dd("user custom error msg: %s", msg); + + } else { + msg = "unknown reason"; + } + } + +propagate_error: if (ctx->cur_co_ctx->is_uthread) { + ngx_http_lua_assert(err != NULL && msg != NULL + && trace != NULL); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "lua user thread aborted: %s: %s\n%s", @@ -1395,6 +1593,9 @@ ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r, } if (ngx_http_lua_is_entry_thread(ctx)) { + ngx_http_lua_assert(err != NULL && msg != NULL + && trace != NULL); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "lua entry thread aborted: %s: %s\n%s", err, msg, trace); @@ -1433,19 +1634,25 @@ ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r, next_coctx->co_status = NGX_HTTP_LUA_CO_RUNNING; + ctx->cur_co_ctx = next_coctx; + + if (orig_coctx->is_wrap) { + /* + * coroutine.wrap propagates errors + * to its parent coroutine + */ + next_coctx->propagate_error = 1; + continue; + } + /* * ended with error, coroutine.resume returns false plus * err msg */ lua_pushboolean(next_co, 0); - lua_xmove(ctx->cur_co_ctx->co, next_co, 1); + lua_xmove(orig_coctx->co, next_co, 1); nrets = 2; - ctx->cur_co_ctx = next_coctx; - - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "lua coroutine: %s: %s\n%s", err, msg, trace); - /* try resuming on the new coroutine again */ continue; } @@ -1476,6 +1683,12 @@ ngx_http_lua_run_thread(lua_State *L, ngx_http_request_t *r, done: +#ifdef HAVE_PROXY_SSL_PATCH + if (ctx->context == NGX_HTTP_LUA_CONTEXT_PROXY_SSL_VERIFY) { + return NGX_OK; + } +#endif + if (ctx->entered_content_phase && r->connection->fd != (ngx_socket_t) -1) { @@ -1557,7 +1770,7 @@ ngx_http_lua_wev_handler(ngx_http_request_t *r) return NGX_DONE; } - if (c->buffered & NGX_HTTP_LOWLEVEL_BUFFERED) { + if (c->buffered & (NGX_HTTP_LOWLEVEL_BUFFERED | NGX_LOWLEVEL_BUFFERED)) { rc = ngx_http_lua_flush_pending_output(r, ctx); dd("flush pending output returned %d, c->error: %d", (int) rc, @@ -1709,7 +1922,7 @@ ngx_http_lua_flush_pending_output(ngx_http_request_t *r, return rc; } - if (c->buffered & NGX_HTTP_LOWLEVEL_BUFFERED) { + if (c->buffered & (NGX_HTTP_LOWLEVEL_BUFFERED | NGX_LOWLEVEL_BUFFERED)) { clcf = ngx_http_get_module_loc_conf(r->main, ngx_http_core_module); @@ -1813,13 +2026,13 @@ ngx_http_lua_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type) 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ - 0xfc00886d, /* 1111 1100 0000 0000 1000 1000 0110 1101 */ + 0x80000029, /* 1000 0000 0000 0000 0000 0000 0010 1001 */ /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ - 0x78000000, /* 0111 1000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ /* ~}| {zyx wvut srqp onml kjih gfed cba` */ - 0xa8000000, /* 1010 1000 0000 0000 0000 0000 0000 0000 */ + 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ @@ -1929,8 +2142,52 @@ ngx_http_lua_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type) /* mail_auth is the same as memcached */ + /* " ", """, "(", ")", ",", "/", ":", ";", "?", + * "<", "=", ">", "?", "@", "[", "]", "\", "{", + * "}", %00-%1F, %7F-%FF + */ + + static uint32_t header_name[] = { + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + + /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ + 0xfc009305, /* 1111 1100 0000 0000 1001 0011 0000 0101 */ + + /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ + 0x38000001, /* 0011 1000 0000 0000 0000 0000 0000 0001 */ + + /* ~}| {zyx wvut srqp onml kjih gfed cba` */ + 0xa8000000, /* 1010 1000 0000 0000 0000 0000 0000 0000 */ + + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + }; + + /* "%00-%08, %0A-%0F, %7F */ + + static uint32_t header_value[] = { + 0xfffffdff, /* 1111 1111 1111 1111 1111 1101 1111 1111 */ + + /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + + /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + + /* ~}| {zyx wvut srqp onml kjih gfed cba` */ + 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ + + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + }; + static uint32_t *map[] = - { uri, args, uri_component, html, refresh, memcached, memcached }; + { uri, args, uri_component, html, refresh, memcached, memcached, + header_name, header_value }; escape = map[type]; @@ -1944,6 +2201,7 @@ ngx_http_lua_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type) if (escape[*src >> 5] & (1 << (*src & 0x1f))) { n++; } + src++; size--; } @@ -1961,6 +2219,7 @@ ngx_http_lua_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type) } else { *dst++ = *src++; } + size--; } @@ -1968,157 +2227,95 @@ ngx_http_lua_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type) } +static int +ngx_http_lua_util_hex2int(char xdigit) +{ + if (isdigit(xdigit)) { + return xdigit - '0'; + } + + xdigit = tolower(xdigit); + if (xdigit <= 'f' && xdigit >= 'a') { + return xdigit - 'a' + 10; + } + + return -1; +} + + /* XXX we also decode '+' to ' ' */ void ngx_http_lua_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type) { - u_char *d, *s, ch, c, decoded; - enum { - sw_usual = 0, - sw_quoted, - sw_quoted_second - } state; - - d = *dst; - s = *src; - - state = 0; - decoded = 0; + u_char *d = *dst, *s = *src, *de = (*dst + size); + int isuri = type & NGX_UNESCAPE_URI; + int isredirect = type & NGX_UNESCAPE_REDIRECT; while (size--) { + u_char curr = *s++; - ch = *s++; + if (curr == '?' && + (type & (NGX_UNESCAPE_URI | NGX_UNESCAPE_REDIRECT))) + { + *d++ = '?'; + break; - switch (state) { - case sw_usual: - if (ch == '?' - && (type & (NGX_UNESCAPE_URI|NGX_UNESCAPE_REDIRECT))) - { - *d++ = ch; - goto done; + } else if (curr == '%') { + u_char ch; + if (size < 2 || !(isxdigit(s[0]) && isxdigit(s[1]))) { + *d++ = '%'; + continue; } + /* we can be sure here they must be hex digits */ + ch = ngx_http_lua_util_hex2int(s[0]) * 16 + + ngx_http_lua_util_hex2int(s[1]); - if (ch == '%') { - state = sw_quoted; + if ((isuri || isredirect) && ch == '?') { + *d++ = ch; break; - } - if (ch == '+') { - *d++ = ' '; - break; + } else if (isredirect && (ch <= '%' || ch >= 0x7f)) { + *d++ = '%'; + continue; } *d++ = ch; - break; + s += 2; + size -= 2; - case sw_quoted: + } else if (curr == '+') { + *d++ = ' '; + continue; - if (ch >= '0' && ch <= '9') { - decoded = (u_char) (ch - '0'); - state = sw_quoted_second; - break; - } + } else { + *d++ = curr; + } + } - c = (u_char) (ch | 0x20); - if (c >= 'a' && c <= 'f') { - decoded = (u_char) (c - 'a' + 10); - state = sw_quoted_second; - break; - } + /* a safe guard if dst need to be null-terminated */ + if (d != de) { + *d = '\0'; + } - /* the invalid quoted character */ + *dst = d; + *src = s; +} - state = sw_usual; - *d++ = ch; +void +ngx_http_lua_inject_req_api(ngx_log_t *log, lua_State *L) +{ + /* ngx.req table */ - break; + lua_createtable(L, 0 /* narr */, 23 /* nrec */); /* .req */ - case sw_quoted_second: - - state = sw_usual; - - if (ch >= '0' && ch <= '9') { - ch = (u_char) ((decoded << 4) + ch - '0'); - - if (type & NGX_UNESCAPE_REDIRECT) { - if (ch > '%' && ch < 0x7f) { - *d++ = ch; - break; - } - - *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1); - break; - } - - *d++ = ch; - - break; - } - - c = (u_char) (ch | 0x20); - if (c >= 'a' && c <= 'f') { - ch = (u_char) ((decoded << 4) + c - 'a' + 10); - - if (type & NGX_UNESCAPE_URI) { - if (ch == '?') { - *d++ = ch; - goto done; - } - - *d++ = ch; - break; - } - - if (type & NGX_UNESCAPE_REDIRECT) { - if (ch == '?') { - *d++ = ch; - goto done; - } - - if (ch > '%' && ch < 0x7f) { - *d++ = ch; - break; - } - - *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1); - break; - } - - *d++ = ch; - - break; - } - - /* the invalid quoted character */ - - break; - } - } - -done: - - *dst = d; - *src = s; -} - - -void -ngx_http_lua_inject_req_api(ngx_log_t *log, lua_State *L) -{ - /* ngx.req table */ - - lua_createtable(L, 0 /* narr */, 24 /* nrec */); /* .req */ - - ngx_http_lua_inject_req_header_api(L); - ngx_http_lua_inject_req_uri_api(log, L); - ngx_http_lua_inject_req_args_api(L); - ngx_http_lua_inject_req_body_api(L); - ngx_http_lua_inject_req_socket_api(L); - ngx_http_lua_inject_req_method_api(L); - ngx_http_lua_inject_req_time_api(L); - ngx_http_lua_inject_req_misc_api(L); + ngx_http_lua_inject_req_header_api(L); + ngx_http_lua_inject_req_uri_api(log, L); + ngx_http_lua_inject_req_args_api(L); + ngx_http_lua_inject_req_body_api(L); + ngx_http_lua_inject_req_socket_api(L); + ngx_http_lua_inject_req_misc_api(L); lua_setfield(L, -2, "req"); } @@ -2247,6 +2444,12 @@ ngx_http_lua_handle_exit(lua_State *L, ngx_http_request_t *r, return ctx->exit_code; } +#ifdef HAVE_PROXY_SSL_PATCH + if (ctx->context == NGX_HTTP_LUA_CONTEXT_PROXY_SSL_VERIFY) { + return ctx->exit_code; + } +#endif + #if 1 if (!r->header_sent && !ctx->header_sent @@ -2508,9 +2711,9 @@ ngx_http_lua_process_args_option(ngx_http_request_t *r, lua_State *L, if (lua_isboolean(L, -1)) { if (lua_toboolean(L, -1)) { if (total_escape) { - p = (u_char *) ngx_http_lua_escape_uri(p, key, - key_len, - NGX_ESCAPE_URI_COMPONENT); + p = (u_char *) + ngx_http_lua_escape_uri(p, key, key_len, + NGX_ESCAPE_URI_COMPONENT); } else { dd("shortcut: no escape required"); @@ -2698,9 +2901,7 @@ ngx_http_lua_open_and_stat_file(u_char *name, ngx_open_file_info_t *of, of->uniq = ngx_file_uniq(&fi); of->mtime = ngx_file_mtime(&fi); of->size = ngx_file_size(&fi); -#if defined(nginx_version) && nginx_version >= 1000001 of->fs_size = ngx_file_fs_size(&fi); -#endif of->is_dir = ngx_is_dir(&fi); of->is_file = ngx_is_file(&fi); of->is_link = ngx_is_link(&fi); @@ -2920,9 +3121,6 @@ ngx_http_lua_inject_arg_api(lua_State *L) lua_createtable(L, 0 /* narr */, 2 /* nrec */); /* the metatable */ - lua_pushcfunction(L, ngx_http_lua_param_get); - lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, ngx_http_lua_param_set); lua_setfield(L, -2, "__newindex"); @@ -2934,35 +3132,6 @@ ngx_http_lua_inject_arg_api(lua_State *L) } -static int -ngx_http_lua_param_get(lua_State *L) -{ - ngx_http_lua_ctx_t *ctx; - ngx_http_request_t *r; - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return 0; - } - - ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); - if (ctx == NULL) { - return luaL_error(L, "ctx not found"); - } - - ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_SET - | NGX_HTTP_LUA_CONTEXT_BODY_FILTER); - - if (ctx->context & (NGX_HTTP_LUA_CONTEXT_SET)) { - return ngx_http_lua_setby_param_get(L); - } - - /* ctx->context & (NGX_HTTP_LUA_CONTEXT_BODY_FILTER) */ - - return ngx_http_lua_body_filter_param_get(L); -} - - static int ngx_http_lua_param_set(lua_State *L) { @@ -2988,6 +3157,9 @@ ngx_http_lua_param_set(lua_State *L) ngx_http_lua_co_ctx_t * ngx_http_lua_get_co_ctx(lua_State *L, ngx_http_lua_ctx_t *ctx) { +#ifdef HAVE_LUA_EXDATA2 + return (ngx_http_lua_co_ctx_t *) lua_getexdata2(L); +#else ngx_uint_t i; ngx_list_part_t *part; ngx_http_lua_co_ctx_t *coctx; @@ -3023,6 +3195,7 @@ ngx_http_lua_get_co_ctx(lua_State *L, ngx_http_lua_ctx_t *ctx) } return NULL; +#endif } @@ -3046,6 +3219,7 @@ ngx_http_lua_create_co_ctx(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx) ngx_memzero(coctx, sizeof(ngx_http_lua_co_ctx_t)); + coctx->next_zombie_child_thread = &coctx->zombie_child_threads; coctx->co_ref = LUA_NOREF; return coctx; @@ -3132,17 +3306,13 @@ static void ngx_http_lua_finalize_threads(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx, lua_State *L) { -#ifdef NGX_LUA_USE_ASSERT - int top; -#endif - int inited = 0, ref; + int ref; ngx_uint_t i; ngx_list_part_t *part; ngx_http_lua_co_ctx_t *cc, *coctx; -#ifdef NGX_LUA_USE_ASSERT - top = lua_gettop(L); -#endif + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http lua finalize threads"); #if 1 coctx = ctx->on_abort_co_ctx; @@ -3154,16 +3324,7 @@ ngx_http_lua_finalize_threads(ngx_http_request_t *r, ctx->uthreads--; } - ngx_http_lua_probe_thread_delete(r, coctx->co, ctx); - - lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); - lua_rawget(L, LUA_REGISTRYINDEX); - inited = 1; - - luaL_unref(L, -1, coctx->co_ref); - coctx->co_ref = LUA_NOREF; - - coctx->co_status = NGX_HTTP_LUA_CO_DEAD; + ngx_http_lua_del_thread(r, L, ctx, coctx); ctx->on_abort_co_ctx = NULL; } #endif @@ -3191,20 +3352,8 @@ ngx_http_lua_finalize_threads(ngx_http_request_t *r, if (ref != LUA_NOREF) { ngx_http_lua_cleanup_pending_operation(coctx); - ngx_http_lua_probe_thread_delete(r, coctx->co, ctx); - - if (!inited) { - lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); - lua_rawget(L, LUA_REGISTRYINDEX); - inited = 1; - } - - ngx_http_lua_assert(lua_gettop(L) - top == 1); - - luaL_unref(L, -1, ref); - coctx->co_ref = LUA_NOREF; + ngx_http_lua_del_thread(r, L, ctx, coctx); - coctx->co_status = NGX_HTTP_LUA_CO_DEAD; ctx->uthreads--; } } @@ -3219,24 +3368,7 @@ ngx_http_lua_finalize_threads(ngx_http_request_t *r, ref = coctx->co_ref; if (ref != LUA_NOREF) { ngx_http_lua_cleanup_pending_operation(coctx); - - ngx_http_lua_probe_thread_delete(r, coctx->co, ctx); - - if (!inited) { - lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key); - lua_rawget(L, LUA_REGISTRYINDEX); - inited = 1; - } - - ngx_http_lua_assert(lua_gettop(L) - top == 1); - - luaL_unref(L, -1, coctx->co_ref); - coctx->co_ref = LUA_NOREF; - coctx->co_status = NGX_HTTP_LUA_CO_DEAD; - } - - if (inited) { - lua_pop(L, 1); + ngx_http_lua_del_thread(r, L, ctx, coctx); } } @@ -3245,7 +3377,6 @@ static ngx_int_t ngx_http_lua_post_zombie_thread(ngx_http_request_t *r, ngx_http_lua_co_ctx_t *parent, ngx_http_lua_co_ctx_t *thread) { - ngx_http_lua_posted_thread_t **p; ngx_http_lua_posted_thread_t *pt; pt = ngx_palloc(r->pool, sizeof(ngx_http_lua_posted_thread_t)); @@ -3256,9 +3387,10 @@ ngx_http_lua_post_zombie_thread(ngx_http_request_t *r, pt->co_ctx = thread; pt->next = NULL; - for (p = &parent->zombie_child_threads; *p; p = &(*p)->next) { /* void */ } + ngx_http_lua_assert(parent->next_zombie_child_thread != NULL); - *p = pt; + *parent->next_zombie_child_thread = pt; + parent->next_zombie_child_thread = &pt->next; return NGX_OK; } @@ -3278,6 +3410,7 @@ ngx_http_lua_cleanup_zombie_child_uthreads(ngx_http_request_t *r, } coctx->zombie_child_threads = NULL; + coctx->next_zombie_child_thread = &coctx->zombie_child_threads; } @@ -3288,7 +3421,7 @@ ngx_http_lua_check_broken_connection(ngx_http_request_t *r, ngx_event_t *ev) char buf[1]; ngx_err_t err; ngx_int_t event; - ngx_connection_t *c; + ngx_connection_t *c; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ev->log, 0, "http lua check client, write event:%d, \"%V\"", @@ -3552,12 +3685,46 @@ void ngx_http_lua_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_http_lua_ctx_t *ctx; +#if (NGX_HTTP_SSL) +#ifdef HAVE_PROXY_SSL_PATCH + ngx_http_upstream_t *u; + ngx_connection_t *c; + ngx_http_lua_ssl_ctx_t *cctx; +#endif +#endif ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); if (ctx && ctx->cur_co_ctx) { ngx_http_lua_cleanup_pending_operation(ctx->cur_co_ctx); } +#if (NGX_HTTP_SSL) +#ifdef HAVE_PROXY_SSL_PATCH + u = r->upstream; + if (u) { + c = u->peer.connection; + if (c && c->ssl) { + cctx = ngx_http_lua_ssl_get_ctx(c->ssl->connection); + if (cctx && cctx->pool) { + if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { + cctx->exit_code = 0; + } + + if (r->main->count > cctx->original_request_count) { + r->main->count--; + return; + } + + ngx_destroy_pool(cctx->pool); + cctx->pool = NULL; + + return; + } + } + } +#endif +#endif + if (r->connection->fd != (ngx_socket_t) -1) { ngx_http_finalize_request(r, rc); return; @@ -3709,6 +3876,18 @@ ngx_http_lua_close_fake_connection(ngx_connection_t *c) c->read->closed = 1; c->write->closed = 1; + /* When destroying the pool, the registered clean callbacks will be + * executed. If the ngx_connection_t is freed before these callbacks are + * run, and a new ngx_connection_t is created within a clean callback, + * it is possible for the freed ngx_connection_t to be reused again. + * If this reused ngx_connection_t is destroyed again within the clean + * callback logic, it may result in other clean callbacks holding a + * ngx_connection_t that has already been destroyed. + */ + if (pool) { + ngx_destroy_pool(pool); + } + /* we temporarily use a valid fd (0) to make ngx_free_connection happy */ c->fd = 0; @@ -3724,18 +3903,15 @@ ngx_http_lua_close_fake_connection(ngx_connection_t *c) if (ngx_cycle->files) { ngx_cycle->files[0] = saved_c; } - - if (pool) { - ngx_destroy_pool(pool); - } } -lua_State * -ngx_http_lua_init_vm(lua_State *parent_vm, ngx_cycle_t *cycle, - ngx_pool_t *pool, ngx_http_lua_main_conf_t *lmcf, ngx_log_t *log, - ngx_pool_cleanup_t **pcln) +ngx_int_t +ngx_http_lua_init_vm(lua_State **new_vm, lua_State *parent_vm, + ngx_cycle_t *cycle, ngx_pool_t *pool, ngx_http_lua_main_conf_t *lmcf, + ngx_log_t *log, ngx_pool_cleanup_t **pcln) { + int rc; lua_State *L; ngx_uint_t i; ngx_pool_cleanup_t *cln; @@ -3744,13 +3920,13 @@ ngx_http_lua_init_vm(lua_State *parent_vm, ngx_cycle_t *cycle, cln = ngx_pool_cleanup_add(pool, 0); if (cln == NULL) { - return NULL; + return NGX_ERROR; } /* create new Lua VM instance */ L = ngx_http_lua_new_state(parent_vm, cycle, lmcf, log); if (L == NULL) { - return NULL; + return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "lua initialize the " @@ -3761,8 +3937,9 @@ ngx_http_lua_init_vm(lua_State *parent_vm, ngx_cycle_t *cycle, state = ngx_alloc(sizeof(ngx_http_lua_vm_state_t), log); if (state == NULL) { - return NULL; + return NGX_ERROR; } + state->vm = L; state->count = 1; @@ -3778,6 +3955,11 @@ ngx_http_lua_init_vm(lua_State *parent_vm, ngx_cycle_t *cycle, *pcln = cln; } +#ifdef OPENRESTY_LUAJIT + /* load FFI library first since cdata needs it */ + luaopen_ffi(L); +#endif + if (lmcf->preload_hooks) { /* register the 3rd-party module's preload hooks */ @@ -3789,7 +3971,8 @@ ngx_http_lua_init_vm(lua_State *parent_vm, ngx_cycle_t *cycle, for (i = 0; i < lmcf->preload_hooks->nelts; i++) { - ngx_http_lua_probe_register_preload_package(L, hook[i].package); + ngx_http_lua_probe_register_preload_package(L, + hook[i].package); lua_pushcfunction(L, hook[i].loader); lua_setfield(L, -2, (char *) hook[i].package); @@ -3798,7 +3981,21 @@ ngx_http_lua_init_vm(lua_State *parent_vm, ngx_cycle_t *cycle, lua_pop(L, 2); } - return L; + *new_vm = L; + + lua_getglobal(L, "require"); + lua_pushstring(L, "resty.core"); + + rc = lua_pcall(L, 1, 1, 0); + if (rc != 0) { + return NGX_DECLINED; + } + +#ifdef OPENRESTY_LUAJIT + ngx_http_lua_inject_global_write_guard(L, log); +#endif + + return NGX_OK; } @@ -4041,7 +4238,12 @@ ngx_http_lua_get_raw_phase_context(lua_State *L) ngx_http_request_t *r; ngx_http_lua_ctx_t *ctx; +#ifdef OPENRESTY_LUAJIT + r = lua_getexdata(L); +#else r = lua_touserdata(L, 1); +#endif + if (r == NULL) { return 0; } @@ -4129,4 +4331,351 @@ ngx_http_lua_cleanup_free(ngx_http_request_t *r, ngx_http_cleanup_pt *cleanup) } +#if (NGX_HTTP_LUA_HAVE_SA_RESTART) +void +ngx_http_lua_set_sa_restart(ngx_log_t *log) +{ + int *signo; + int sigs[] = NGX_HTTP_LUA_SA_RESTART_SIGS; + struct sigaction act; + + for (signo = sigs; *signo != 0; signo++) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, + "setting SA_RESTART for signal %d", *signo); + + if (sigaction(*signo, NULL, &act) != 0) { + ngx_log_error(NGX_LOG_WARN, log, ngx_errno, "failed to get " + "sigaction for signal %d", *signo); + } + + act.sa_flags |= SA_RESTART; + + if (sigaction(*signo, &act, NULL) != 0) { + ngx_log_error(NGX_LOG_WARN, log, ngx_errno, "failed to set " + "sigaction for signal %d", *signo); + } + } +} +#endif + + +size_t +ngx_http_lua_escape_log(u_char *dst, u_char *src, size_t size) +{ + size_t n; + u_char c; + static u_char hex[] = "0123456789ABCDEF"; + + static uint32_t escape[] = { + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + + /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ + 0x00000004, /* 0000 0000 0000 0000 0000 0000 0000 0100 */ + + /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ + 0x10000000, /* 0001 0000 0000 0000 0000 0000 0000 0000 */ + + /* ~}| {zyx wvut srqp onml kjih gfed cba` */ + 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ + + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + }; + + if (dst == NULL) { + + /* find the number of characters to be escaped */ + + n = 0; + + while (size) { + c = *src; + if (escape[c >> 5] & (1 << (c & 0x1f))) { + n += 4; + + } else { + n++; + } + + src++; + size--; + } + + return n; + } + + while (size) { + c = *src; + if (escape[c >> 5] & (1 << (c & 0x1f))) { + *dst++ = '\\'; + *dst++ = 'x'; + *dst++ = hex[*src >> 4]; + *dst++ = hex[*src & 0xf]; + src++; + + } else { + *dst++ = *src++; + } + + size--; + } + + return 0; +} + + +ngx_int_t +ngx_http_lua_copy_escaped_header(ngx_http_request_t *r, + ngx_str_t *dst, int is_name) +{ + size_t escape; + size_t len; + u_char *data; + int type; + + type = is_name + ? NGX_HTTP_LUA_ESCAPE_HEADER_NAME : NGX_HTTP_LUA_ESCAPE_HEADER_VALUE; + + data = dst->data; + len = dst->len; + + escape = ngx_http_lua_escape_uri(NULL, data, len, type); + if (escape > 0) { + /* + * we allocate space for the trailing '\0' char here because nginx + * header values must be null-terminated + */ + dst->data = ngx_palloc(r->pool, len + 2 * escape + 1); + if (dst->data == NULL) { + return NGX_ERROR; + } + + ngx_http_lua_escape_uri(dst->data, data, len, type); + dst->len = len + 2 * escape; + dst->data[dst->len] = '\0'; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_lua_decode_base64mime(ngx_str_t *dst, ngx_str_t *src) +{ + size_t i; + u_char *d, *s, ch; + size_t data_len = 0; + u_char buf[4]; + size_t buf_len = 0; + static u_char basis[] = { + 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, + 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, + 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 62, 77, 77, 77, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 77, 77, 77, 77, 77, 77, + 77, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 77, 77, 77, 77, 77, + 77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 77, 77, 77, 77, 77, + + 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, + 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, + 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, + 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, + 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, + 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, + 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, + 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77 + }; + + for (i = 0; i < src->len; i++) { + ch = src->data[i]; + if (ch == '=') { + break; + } + + if (basis[ch] == 77) { + continue; + } + + data_len++; + } + + if (data_len % 4 == 1) { + return NGX_ERROR; + } + + s = src->data; + d = dst->data; + + for (i = 0; i < src->len; i++) { + if (s[i] == '=') { + break; + } + + if (basis[s[i]] == 77) { + continue; + } + + buf[buf_len++] = s[i]; + if (buf_len == 4) { + *d++ = (u_char) (basis[buf[0]] << 2 | basis[buf[1]] >> 4); + *d++ = (u_char) (basis[buf[1]] << 4 | basis[buf[2]] >> 2); + *d++ = (u_char) (basis[buf[2]] << 6 | basis[buf[3]]); + buf_len = 0; + } + } + + if (buf_len > 1) { + *d++ = (u_char) (basis[buf[0]] << 2 | basis[buf[1]] >> 4); + } + + if (buf_len > 2) { + *d++ = (u_char) (basis[buf[1]] << 4 | basis[buf[2]] >> 2); + } + + dst->len = d - dst->data; + + return NGX_OK; +} + + +ngx_addr_t * +ngx_http_lua_parse_addr(lua_State *L, u_char *text, size_t len) +{ + ngx_addr_t *addr; + size_t socklen; + in_addr_t inaddr; + ngx_uint_t family; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct in6_addr inaddr6; + struct sockaddr_in6 *sin6; + + /* + * prevent MSVC8 warning: + * potentially uninitialized local variable 'inaddr6' used + */ + ngx_memzero(&inaddr6, sizeof(struct in6_addr)); +#endif + + inaddr = ngx_inet_addr(text, len); + + if (inaddr != INADDR_NONE) { + family = AF_INET; + socklen = sizeof(struct sockaddr_in); + +#if (NGX_HAVE_INET6) + + } else if (ngx_inet6_addr(text, len, inaddr6.s6_addr) == NGX_OK) { + family = AF_INET6; + socklen = sizeof(struct sockaddr_in6); +#endif + + } else { + return NULL; + } + + addr = lua_newuserdata(L, sizeof(ngx_addr_t) + socklen + len); + if (addr == NULL) { + luaL_error(L, "no memory"); + return NULL; + } + + addr->sockaddr = (struct sockaddr *) ((u_char *) addr + sizeof(ngx_addr_t)); + + ngx_memzero(addr->sockaddr, socklen); + + addr->sockaddr->sa_family = (u_char) family; + addr->socklen = socklen; + + switch (family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) addr->sockaddr; + ngx_memcpy(sin6->sin6_addr.s6_addr, inaddr6.s6_addr, 16); + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) addr->sockaddr; + sin->sin_addr.s_addr = inaddr; + break; + } + + addr->name.data = (u_char *) addr->sockaddr + socklen; + addr->name.len = len; + ngx_memcpy(addr->name.data, text, len); + + return addr; +} + + +void +ngx_http_lua_ffi_bypass_if_checks(ngx_http_request_t *r) +{ + r->disable_not_modified = 1; +} + + +#if (HAVE_QUIC_SSL_LUA_YIELD_PATCH && NGX_HTTP_V3) +void +ngx_http_lua_resume_quic_ssl_handshake(ngx_connection_t *c) +{ + ngx_int_t rc, sslerr; + ngx_ssl_conn_t *ssl_conn; + + if (c == NULL || c->ssl == NULL || c->ssl->connection == NULL) { + return; + } + + if (!c->udp || c->read == NULL + || c->read->handler != ngx_quic_input_handler) + { + return; + } + + ssl_conn = c->ssl->connection; + + /* Openresty ssl lua scripts are triggered by registering callbacks into + * openssl. If a lua script calls a yield api during execution, openssl's + * SSL_do_handshake will return a specific error code. The application + * should not treat these codes as fatal. The lua script will resume on + * yield-related events until it finishes. After completion, + * SSL_do_handshake should be called again to advance openssl's state + * machine. + * + * Note that nginx quic and openresty ssl lua scripts are independent. When + * openresty ssl lua runs, the client is waiting for a server response, so + * nginx quic does not affect the execution of the lua script. After the lua + * script finishes, SSL_do_handshake is called again, and nginx quic's + * registered callback continues the handshake. + */ + rc = SSL_do_handshake(ssl_conn); + sslerr = SSL_get_error(ssl_conn, rc); + + if (rc <= 0 && sslerr != SSL_ERROR_WANT_READ +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + && sslerr != SSL_ERROR_WANT_X509_LOOKUP +#endif +#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB + && sslerr != SSL_ERROR_WANT_CLIENT_HELLO_CB +#endif + ) { + /* If a fatal error occurs or lua script exits with error during quic + * handshake, the quic connection will be closed immediately. + */ + ngx_quic_close_connection(c, NGX_ERROR); + + } else { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "lua resuming quic ssl handshake: rc %d, err %d, will " + "continue driving handshake or next lua script phase", + rc, sslerr); + } +} +#endif + /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_util.h b/src/ngx_http_lua_util.h index 69203254cc..9d7a0bd1a3 100644 --- a/src/ngx_http_lua_util.h +++ b/src/ngx_http_lua_util.h @@ -9,75 +9,91 @@ #define _NGX_HTTP_LUA_UTIL_H_INCLUDED_ -#include "ngx_http_lua_common.h" - - -#ifndef NGX_UNESCAPE_URI_COMPONENT -#define NGX_UNESCAPE_URI_COMPONENT 0 +#ifdef DDEBUG +#include "ddebug.h" #endif -#ifndef NGX_LUA_NO_FFI_API -typedef struct { - int len; - /* this padding hole on 64-bit systems is expected */ - u_char *data; -} ngx_http_lua_ffi_str_t; - - -typedef struct { - ngx_http_lua_ffi_str_t key; - ngx_http_lua_ffi_str_t value; -} ngx_http_lua_ffi_table_elt_t; -#endif /* NGX_LUA_NO_FFI_API */ +#include "ngx_http_lua_common.h" +#include "ngx_http_lua_ssl.h" +#include "ngx_http_lua_api.h" -/* char whose address we use as the key in Lua vm registry for - * user code cache table */ -extern char ngx_http_lua_code_cache_key; +#ifndef NGX_UNESCAPE_URI_COMPONENT +# define NGX_UNESCAPE_URI_COMPONENT 0 +#endif -/* key in Lua vm registry for all the "ngx.ctx" tables */ -#define ngx_http_lua_ctx_tables_key "ngx_lua_ctx_tables" +#ifndef NGX_HTTP_SWITCHING_PROTOCOLS +# define NGX_HTTP_SWITCHING_PROTOCOLS 101 +#endif +#define NGX_HTTP_LUA_ESCAPE_HEADER_NAME 7 -/* char whose address we use as the key in Lua vm registry for - * regex cache table */ -extern char ngx_http_lua_regex_cache_key; +#define NGX_HTTP_LUA_ESCAPE_HEADER_VALUE 8 -/* char whose address we use as the key in Lua vm registry for - * socket connection pool table */ -extern char ngx_http_lua_socket_pool_key; +#ifdef HAVE_PROXY_SSL_PATCH -/* char whose address we use as the key for the coroutine parent relationship */ -extern char ngx_http_lua_coroutine_parents_key; +#define NGX_HTTP_LUA_CONTEXT_YIELDABLE (NGX_HTTP_LUA_CONTEXT_REWRITE \ + | NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE \ + | NGX_HTTP_LUA_CONTEXT_ACCESS \ + | NGX_HTTP_LUA_CONTEXT_CONTENT \ + | NGX_HTTP_LUA_CONTEXT_TIMER \ + | NGX_HTTP_LUA_CONTEXT_PROXY_SSL_VERIFY \ + | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO \ + | NGX_HTTP_LUA_CONTEXT_SSL_CERT \ + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH) -/* coroutine anchoring table key in Lua VM registry */ -extern char ngx_http_lua_coroutines_key; +#else -/* key to the metatable for ngx.req.get_headers() and ngx.resp.get_headers() */ -extern char ngx_http_lua_headers_metatable_key; +#define NGX_HTTP_LUA_CONTEXT_YIELDABLE (NGX_HTTP_LUA_CONTEXT_REWRITE \ + | NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE \ + | NGX_HTTP_LUA_CONTEXT_ACCESS \ + | NGX_HTTP_LUA_CONTEXT_CONTENT \ + | NGX_HTTP_LUA_CONTEXT_TIMER \ + | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO \ + | NGX_HTTP_LUA_CONTEXT_SSL_CERT \ + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH) +#endif /* HAVE_PROXY_SSL_PATCH */ -#ifndef ngx_str_set -#define ngx_str_set(str, text) \ - (str)->len = sizeof(text) - 1; (str)->data = (u_char *) text -#endif +/* key in Lua vm registry for all the "ngx.ctx" tables */ +#define ngx_http_lua_ctx_tables_key "ngx_lua_ctx_tables" -#ifndef NGX_HTTP_SWITCHING_PROTOCOLS -#define NGX_HTTP_SWITCHING_PROTOCOLS 101 -#endif +#ifdef HAVE_PROXY_SSL_PATCH -#if defined(nginx_version) && nginx_version < 1000000 -#define ngx_memmove(dst, src, n) (void) memmove(dst, src, n) -#endif +#define ngx_http_lua_context_name(c) \ + ((c) == NGX_HTTP_LUA_CONTEXT_SET ? "set_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_REWRITE ? "rewrite_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE ? "server_rewrite_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_ACCESS ? "access_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_CONTENT ? "content_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_LOG ? "log_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_HEADER_FILTER ? "header_filter_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_BODY_FILTER ? "body_filter_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_TIMER ? "ngx.timer" \ + : (c) == NGX_HTTP_LUA_CONTEXT_INIT_WORKER ? "init_worker_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_EXIT_WORKER ? "exit_worker_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_BALANCER ? "balancer_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_PROXY_SSL_VERIFY ? \ + "proxy_ssl_verify_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO ? \ + "ssl_client_hello_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_SSL_CERT ? "ssl_certificate_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE ? \ + "ssl_session_store_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH ? \ + "ssl_session_fetch_by_lua*" \ + : "(unknown)") +#else #define ngx_http_lua_context_name(c) \ ((c) == NGX_HTTP_LUA_CONTEXT_SET ? "set_by_lua*" \ : (c) == NGX_HTTP_LUA_CONTEXT_REWRITE ? "rewrite_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_SERVER_REWRITE ? "server_rewrite_by_lua*" \ : (c) == NGX_HTTP_LUA_CONTEXT_ACCESS ? "access_by_lua*" \ : (c) == NGX_HTTP_LUA_CONTEXT_CONTENT ? "content_by_lua*" \ : (c) == NGX_HTTP_LUA_CONTEXT_LOG ? "log_by_lua*" \ @@ -85,7 +101,10 @@ extern char ngx_http_lua_headers_metatable_key; : (c) == NGX_HTTP_LUA_CONTEXT_BODY_FILTER ? "body_filter_by_lua*" \ : (c) == NGX_HTTP_LUA_CONTEXT_TIMER ? "ngx.timer" \ : (c) == NGX_HTTP_LUA_CONTEXT_INIT_WORKER ? "init_worker_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_EXIT_WORKER ? "exit_worker_by_lua*" \ : (c) == NGX_HTTP_LUA_CONTEXT_BALANCER ? "balancer_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO ? \ + "ssl_client_hello_by_lua*" \ : (c) == NGX_HTTP_LUA_CONTEXT_SSL_CERT ? "ssl_certificate_by_lua*" \ : (c) == NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE ? \ "ssl_session_store_by_lua*" \ @@ -93,6 +112,8 @@ extern char ngx_http_lua_headers_metatable_key; "ssl_session_fetch_by_lua*" \ : "(unknown)") +#endif /* HAVE_PROXY_SSL_PATCH */ + #define ngx_http_lua_check_context(L, ctx, flags) \ if (!((ctx)->context & (flags))) { \ @@ -101,25 +122,6 @@ extern char ngx_http_lua_headers_metatable_key; } -#ifndef NGX_LUA_NO_FFI_API -static ngx_inline ngx_int_t -ngx_http_lua_ffi_check_context(ngx_http_lua_ctx_t *ctx, unsigned flags, - u_char *err, size_t *errlen) -{ - if (!(ctx->context & flags)) { - *errlen = ngx_snprintf(err, *errlen, - "API disabled in the context of %s", - ngx_http_lua_context_name((ctx)->context)) - - err; - - return NGX_DECLINED; - } - - return NGX_OK; -} -#endif - - #define ngx_http_lua_check_fake_request(L, r) \ if ((r)->connection->fd == (ngx_socket_t) -1) { \ return luaL_error(L, "API disabled in the current context"); \ @@ -133,13 +135,61 @@ ngx_http_lua_ffi_check_context(ngx_http_lua_ctx_t *ctx, unsigned flags, } +#define ngx_http_lua_check_if_abortable(L, ctx) \ + if ((ctx)->no_abort) { \ + return luaL_error(L, "attempt to abort with pending subrequests"); \ + } + + #define ngx_http_lua_ssl_get_ctx(ssl_conn) \ SSL_get_ex_data(ssl_conn, ngx_http_lua_ssl_ctx_index) -lua_State *ngx_http_lua_init_vm(lua_State *parent_vm, ngx_cycle_t *cycle, - ngx_pool_t *pool, ngx_http_lua_main_conf_t *lmcf, ngx_log_t *log, - ngx_pool_cleanup_t **pcln); +#define ngx_http_lua_hash_literal(s) \ + ngx_http_lua_hash_str((u_char *) s, sizeof(s) - 1) + + +typedef struct { + ngx_http_lua_ffi_str_t key; + ngx_http_lua_ffi_str_t value; +} ngx_http_lua_ffi_table_elt_t; + + +/* char whose address we use as the key in Lua vm registry for + * user code cache table */ +extern char ngx_http_lua_code_cache_key; + +/* char whose address we use as the key in Lua vm registry for + * socket connection pool table */ +extern char ngx_http_lua_socket_pool_key; + +/* coroutine anchoring table key in Lua VM registry */ +extern char ngx_http_lua_coroutines_key; + +/* key to the metatable for ngx.req.get_headers() and ngx.resp.get_headers() */ +extern char ngx_http_lua_headers_metatable_key; + + +static ngx_inline ngx_int_t +ngx_http_lua_ffi_check_context(ngx_http_lua_ctx_t *ctx, unsigned flags, + u_char *err, size_t *errlen) +{ + if (!(ctx->context & flags)) { + *errlen = ngx_snprintf(err, *errlen, + "API disabled in the context of %s", + ngx_http_lua_context_name((ctx)->context)) + - err; + + return NGX_DECLINED; + } + + return NGX_OK; +} + + +ngx_int_t ngx_http_lua_init_vm(lua_State **new_vm, lua_State *parent_vm, + ngx_cycle_t *cycle, ngx_pool_t *pool, ngx_http_lua_main_conf_t *lmcf, + ngx_log_t *log, ngx_pool_cleanup_t **pcln); lua_State *ngx_http_lua_new_thread(ngx_http_request_t *r, lua_State *l, int *ref); @@ -163,7 +213,7 @@ void ngx_http_lua_reset_ctx(ngx_http_request_t *r, lua_State *L, void ngx_http_lua_generic_phase_post_read(ngx_http_request_t *r); -void ngx_http_lua_request_cleanup(ngx_http_lua_ctx_t *ctx, int foricible); +void ngx_http_lua_request_cleanup(ngx_http_lua_ctx_t *ctx, int forcible); void ngx_http_lua_request_cleanup_handler(void *data); @@ -183,6 +233,9 @@ void ngx_http_lua_unescape_uri(u_char **dst, u_char **src, size_t size, uintptr_t ngx_http_lua_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type); +ngx_int_t ngx_http_lua_copy_escaped_header(ngx_http_request_t *r, + ngx_str_t *dst, int is_name); + void ngx_http_lua_inject_req_api(ngx_log_t *log, lua_State *L); void ngx_http_lua_process_args_option(ngx_http_request_t *r, @@ -194,7 +247,9 @@ ngx_int_t ngx_http_lua_open_and_stat_file(u_char *name, ngx_chain_t *ngx_http_lua_chain_get_free_buf(ngx_log_t *log, ngx_pool_t *p, ngx_chain_t **free, size_t len); +#ifndef OPENRESTY_LUAJIT void ngx_http_lua_create_new_globals_table(lua_State *L, int narr, int nrec); +#endif int ngx_http_lua_traceback(lua_State *L); @@ -249,11 +304,18 @@ ngx_http_cleanup_t *ngx_http_lua_cleanup_add(ngx_http_request_t *r, void ngx_http_lua_cleanup_free(ngx_http_request_t *r, ngx_http_cleanup_pt *cleanup); +#if (NGX_HTTP_LUA_HAVE_SA_RESTART) +void ngx_http_lua_set_sa_restart(ngx_log_t *log); +#endif +ngx_int_t ngx_http_lua_decode_base64mime(ngx_str_t *dst, ngx_str_t *src); -#define ngx_http_lua_check_if_abortable(L, ctx) \ - if ((ctx)->no_abort) { \ - return luaL_error(L, "attempt to abort with pending subrequests"); \ - } +ngx_addr_t *ngx_http_lua_parse_addr(lua_State *L, u_char *text, size_t len); + +size_t ngx_http_lua_escape_log(u_char *dst, u_char *src, size_t size); + +#if (HAVE_QUIC_SSL_LUA_YIELD_PATCH && NGX_HTTP_V3) +void ngx_http_lua_resume_quic_ssl_handshake(ngx_connection_t *c); +#endif static ngx_inline void @@ -262,6 +324,8 @@ ngx_http_lua_init_ctx(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx) ngx_memzero(ctx, sizeof(ngx_http_lua_ctx_t)); ctx->ctx_ref = LUA_NOREF; ctx->entry_co_ctx.co_ref = LUA_NOREF; + ctx->entry_co_ctx.next_zombie_child_thread = + &ctx->entry_co_ctx.zombie_child_threads; ctx->resume_handler = ngx_http_lua_wev_handler; ctx->request = r; } @@ -270,7 +334,8 @@ ngx_http_lua_init_ctx(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx) static ngx_inline ngx_http_lua_ctx_t * ngx_http_lua_create_ctx(ngx_http_request_t *r) { - lua_State *L; + ngx_int_t rc; + lua_State *L = NULL; ngx_http_lua_ctx_t *ctx; ngx_pool_cleanup_t *cln; ngx_http_lua_loc_conf_t *llcf; @@ -288,16 +353,37 @@ ngx_http_lua_create_ctx(ngx_http_request_t *r) if (!llcf->enable_code_cache && r->connection->fd != (ngx_socket_t) -1) { lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); +#ifdef DDEBUG dd("lmcf: %p", lmcf); +#endif + + rc = ngx_http_lua_init_vm(&L, lmcf->lua, lmcf->cycle, r->pool, lmcf, + r->connection->log, &cln); + if (rc != NGX_OK) { + if (rc == NGX_DECLINED) { + ngx_http_lua_assert(L != NULL); + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "failed to load the 'resty.core' module " + "(https://github.com/openresty/lua-resty" + "-core); ensure you are using an OpenResty " + "release from https://openresty.org/en/" + "download.html (reason: %s)", + lua_tostring(L, -1)); + + } else { + /* rc == NGX_ERROR */ + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "failed to initialize Lua VM"); + } - L = ngx_http_lua_init_vm(lmcf->lua, lmcf->cycle, r->pool, lmcf, - r->connection->log, &cln); - if (L == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "failed to initialize Lua VM"); return NULL; } + /* rc == NGX_OK */ + + ngx_http_lua_assert(L != NULL); + if (lmcf->init_handler) { if (lmcf->init_handler(r->connection->log, lmcf, L) != NGX_OK) { /* an error happened */ @@ -329,17 +415,26 @@ ngx_http_lua_get_lua_vm(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx) } lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); + +#ifdef DDEBUG dd("lmcf->lua: %p", lmcf->lua); +#endif + return lmcf->lua; } +#ifndef OPENRESTY_LUAJIT #define ngx_http_lua_req_key "__ngx_req" +#endif static ngx_inline ngx_http_request_t * ngx_http_lua_get_req(lua_State *L) { +#ifdef OPENRESTY_LUAJIT + return lua_getexdata(L); +#else ngx_http_request_t *r; lua_getglobal(L, ngx_http_lua_req_key); @@ -347,17 +442,32 @@ ngx_http_lua_get_req(lua_State *L) lua_pop(L, 1); return r; +#endif } static ngx_inline void ngx_http_lua_set_req(lua_State *L, ngx_http_request_t *r) { +#ifdef OPENRESTY_LUAJIT + lua_setexdata(L, (void *) r); +#else lua_pushlightuserdata(L, r); lua_setglobal(L, ngx_http_lua_req_key); +#endif } +static ngx_inline void +ngx_http_lua_attach_co_ctx_to_L(lua_State *L, ngx_http_lua_co_ctx_t *coctx) +{ +#ifdef HAVE_LUA_EXDATA2 + lua_setexdata2(L, (void *) coctx); +#endif +} + + +#ifndef OPENRESTY_LUAJIT static ngx_inline void ngx_http_lua_get_globals_table(lua_State *L) { @@ -370,10 +480,7 @@ ngx_http_lua_set_globals_table(lua_State *L) { lua_replace(L, LUA_GLOBALSINDEX); } - - -#define ngx_http_lua_hash_literal(s) \ - ngx_http_lua_hash_str((u_char *) s, sizeof(s) - 1) +#endif /* OPENRESTY_LUAJIT */ static ngx_inline ngx_uint_t @@ -393,10 +500,12 @@ ngx_http_lua_hash_str(u_char *src, size_t n) static ngx_inline ngx_int_t -ngx_http_lua_set_content_type(ngx_http_request_t *r) +ngx_http_lua_set_content_type(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx) { ngx_http_lua_loc_conf_t *llcf; + ctx->mime_set = 1; + llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); if (llcf->use_default_type && r->headers_out.status != NGX_HTTP_NOT_MODIFIED) @@ -465,6 +574,209 @@ ngx_inet_get_port(struct sockaddr *sa) #endif +static ngx_inline ngx_int_t +ngx_http_lua_check_unsafe_uri_bytes(ngx_http_request_t *r, u_char *str, + size_t len, u_char *byte) +{ + size_t i; + u_char c; + + /* %00-%08, %0A-%1F, %7F */ + + static uint32_t unsafe[] = { + 0xfffffdff, /* 1111 1111 1111 1111 1111 1101 1111 1111 */ + + /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + + /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + + /* ~}| {zyx wvut srqp onml kjih gfed cba` */ + 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ + + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x00000000 /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + }; + + for (i = 0; i < len; i++, str++) { + c = *str; + if (unsafe[c >> 5] & (1 << (c & 0x1f))) { + *byte = c; + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_inline void +ngx_http_lua_free_thread(ngx_http_request_t *r, lua_State *L, int co_ref, + lua_State *co, ngx_http_lua_main_conf_t *lmcf) +{ +#ifdef HAVE_LUA_RESETTHREAD + ngx_queue_t *q; + ngx_http_lua_thread_ref_t *tref; + ngx_http_lua_ctx_t *ctx; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, + r == NULL ? ngx_cycle->log : r->connection->log, 0, + "lua freeing light thread %p (ref %d)", co, co_ref); + + ctx = r != NULL ? ngx_http_get_module_ctx(r, ngx_http_lua_module) : NULL; + if (ctx != NULL + && L == ctx->entry_co_ctx.co + && L == lmcf->lua + && !ngx_queue_empty(&lmcf->free_lua_threads)) + { + lua_resetthread(L, co); + + q = ngx_queue_head(&lmcf->free_lua_threads); + tref = ngx_queue_data(q, ngx_http_lua_thread_ref_t, queue); + + ngx_http_lua_assert(tref->ref == LUA_NOREF); + ngx_http_lua_assert(tref->co == NULL); + + tref->ref = co_ref; + tref->co = co; + + ngx_queue_remove(q); + ngx_queue_insert_head(&lmcf->cached_lua_threads, q); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, + r != NULL ? r->connection->log : ngx_cycle->log, 0, + "lua caching unused lua thread %p (ref %d)", co, + co_ref); + + return; + } +#endif + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, + r != NULL ? r->connection->log : ngx_cycle->log, 0, + "lua unref lua thread %p (ref %d)", co, co_ref); + + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + coroutines_key)); + lua_rawget(L, LUA_REGISTRYINDEX); + + luaL_unref(L, -1, co_ref); + lua_pop(L, 1); +} + + +static ngx_inline int +ngx_http_lua_new_cached_thread(lua_State *L, lua_State **out_co, + ngx_http_lua_main_conf_t *lmcf, int set_globals) +{ + int co_ref; + lua_State *co; + +#ifdef HAVE_LUA_RESETTHREAD + ngx_queue_t *q; + ngx_http_lua_thread_ref_t *tref; + + if (L == lmcf->lua && !ngx_queue_empty(&lmcf->cached_lua_threads)) { + q = ngx_queue_head(&lmcf->cached_lua_threads); + tref = ngx_queue_data(q, ngx_http_lua_thread_ref_t, queue); + + ngx_http_lua_assert(tref->ref != LUA_NOREF); + ngx_http_lua_assert(tref->co != NULL); + + co = tref->co; + co_ref = tref->ref; + + tref->co = NULL; + tref->ref = LUA_NOREF; + + ngx_queue_remove(q); + ngx_queue_insert_head(&lmcf->free_lua_threads, q); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua reusing cached lua thread %p (ref %d)", co, co_ref); + + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + coroutines_key)); + lua_rawget(L, LUA_REGISTRYINDEX); + lua_rawgeti(L, -1, co_ref); + + } else +#endif + { + lua_pushlightuserdata(L, ngx_http_lua_lightudata_mask( + coroutines_key)); + lua_rawget(L, LUA_REGISTRYINDEX); + co = lua_newthread(L); + lua_pushvalue(L, -1); + co_ref = luaL_ref(L, -3); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua ref lua thread %p (ref %d)", co, co_ref); + +#ifndef OPENRESTY_LUAJIT + if (set_globals) { + lua_createtable(co, 0, 0); /* the new globals table */ + + /* co stack: global_tb */ + + lua_createtable(co, 0, 1); /* the metatable */ + ngx_http_lua_get_globals_table(co); + lua_setfield(co, -2, "__index"); + lua_setmetatable(co, -2); + + /* co stack: global_tb */ + + ngx_http_lua_set_globals_table(co); + } +#endif + } + + *out_co = co; + + return co_ref; +} + + +static ngx_inline void * +ngx_http_lua_hash_find_lc(ngx_hash_t *hash, ngx_uint_t key, u_char *name, + size_t len) +{ + ngx_uint_t i; + ngx_hash_elt_t *elt; + + elt = hash->buckets[key % hash->size]; + + if (elt == NULL) { + return NULL; + } + + while (elt->value) { + if (len != (size_t) elt->len) { + goto next; + } + + for (i = 0; i < len; i++) { + if (ngx_tolower(name[i]) != elt->name[i]) { + goto next; + } + } + + return elt->value; + + next: + + elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len, + sizeof(void *)); + continue; + } + + return NULL; +} + + extern ngx_uint_t ngx_http_lua_location_hash; extern ngx_uint_t ngx_http_lua_content_length_hash; diff --git a/src/ngx_http_lua_variable.c b/src/ngx_http_lua_variable.c index e489d9e57e..2c6c6233bd 100644 --- a/src/ngx_http_lua_variable.c +++ b/src/ngx_http_lua_variable.c @@ -11,282 +11,9 @@ #include "ddebug.h" -#include "ngx_http_lua_variable.h" #include "ngx_http_lua_util.h" -static int ngx_http_lua_var_get(lua_State *L); -static int ngx_http_lua_var_set(lua_State *L); - - -void -ngx_http_lua_inject_variable_api(lua_State *L) -{ - /* {{{ register reference maps */ - lua_newtable(L); /* ngx.var */ - - lua_createtable(L, 0, 2 /* nrec */); /* metatable for .var */ - lua_pushcfunction(L, ngx_http_lua_var_get); - lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, ngx_http_lua_var_set); - lua_setfield(L, -2, "__newindex"); - lua_setmetatable(L, -2); - - lua_setfield(L, -2, "var"); -} - - -/** - * Get nginx internal variables content - * - * @retval Always return a string or nil on Lua stack. Return nil when failed - * to get content, and actual content string when found the specified variable. - * @seealso ngx_http_lua_var_set - * */ -static int -ngx_http_lua_var_get(lua_State *L) -{ - ngx_http_request_t *r; - u_char *p, *lowcase; - size_t len; - ngx_uint_t hash; - ngx_str_t name; - ngx_http_variable_value_t *vv; - -#if (NGX_PCRE) - u_char *val; - ngx_uint_t n; - LUA_NUMBER index; - int *cap; -#endif - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } - - ngx_http_lua_check_fake_request(L, r); - -#if (NGX_PCRE) - if (lua_type(L, -1) == LUA_TNUMBER) { - /* it is a regex capturing variable */ - - index = lua_tonumber(L, -1); - - if (index <= 0) { - lua_pushnil(L); - return 1; - } - - n = (ngx_uint_t) index * 2; - - dd("n = %d, ncaptures = %d", (int) n, (int) r->ncaptures); - - if (r->captures == NULL - || r->captures_data == NULL - || n >= r->ncaptures) - { - lua_pushnil(L); - return 1; - } - - /* n >= 0 && n < r->ncaptures */ - - cap = r->captures; - - p = r->captures_data; - - val = &p[cap[n]]; - - lua_pushlstring(L, (const char *) val, (size_t) (cap[n + 1] - cap[n])); - - return 1; - } -#endif - - if (lua_type(L, -1) != LUA_TSTRING) { - return luaL_error(L, "bad variable name"); - } - - p = (u_char *) lua_tolstring(L, -1, &len); - - lowcase = lua_newuserdata(L, len); - - hash = ngx_hash_strlow(lowcase, p, len); - - name.len = len; - name.data = lowcase; - - vv = ngx_http_get_variable(r, &name, hash); - - if (vv == NULL || vv->not_found) { - lua_pushnil(L); - return 1; - } - - lua_pushlstring(L, (const char *) vv->data, (size_t) vv->len); - return 1; -} - - -/** - * Set nginx internal variable content - * - * @retval Always return a boolean on Lua stack. Return true when variable - * content was modified successfully, false otherwise. - * @seealso ngx_http_lua_var_get - * */ -static int -ngx_http_lua_var_set(lua_State *L) -{ - ngx_http_variable_t *v; - ngx_http_variable_value_t *vv; - ngx_http_core_main_conf_t *cmcf; - u_char *p, *lowcase, *val; - size_t len; - ngx_str_t name; - ngx_uint_t hash; - ngx_http_request_t *r; - int value_type; - const char *msg; - - r = ngx_http_lua_get_req(L); - if (r == NULL) { - return luaL_error(L, "no request object found"); - } - - ngx_http_lua_check_fake_request(L, r); - - /* we skip the first argument that is the table */ - - /* we read the variable name */ - - if (lua_type(L, 2) != LUA_TSTRING) { - return luaL_error(L, "bad variable name"); - } - - p = (u_char *) lua_tolstring(L, 2, &len); - - lowcase = lua_newuserdata(L, len + 1); - - hash = ngx_hash_strlow(lowcase, p, len); - lowcase[len] = '\0'; - - name.len = len; - name.data = lowcase; - - /* we read the variable new value */ - - value_type = lua_type(L, 3); - switch (value_type) { - case LUA_TNUMBER: - case LUA_TSTRING: - p = (u_char *) luaL_checklstring(L, 3, &len); - - val = ngx_palloc(r->pool, len); - if (val == NULL) { - return luaL_error(L, "memory allocation error"); - } - - ngx_memcpy(val, p, len); - - break; - - case LUA_TNIL: - /* undef the variable */ - - val = NULL; - len = 0; - - break; - - default: - msg = lua_pushfstring(L, "string, number, or nil expected, " - "but got %s", lua_typename(L, value_type)); - return luaL_argerror(L, 1, msg); - } - - /* we fetch the variable itself */ - - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - - v = ngx_hash_find(&cmcf->variables_hash, hash, name.data, name.len); - - if (v) { - if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) { - return luaL_error(L, "variable \"%s\" not changeable", lowcase); - } - - if (v->set_handler) { - - dd("set variables with set_handler"); - - vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t)); - if (vv == NULL) { - return luaL_error(L, "no memory"); - } - - if (value_type == LUA_TNIL) { - vv->valid = 0; - vv->not_found = 1; - vv->no_cacheable = 0; - vv->data = NULL; - vv->len = 0; - - } else { - vv->valid = 1; - vv->not_found = 0; - vv->no_cacheable = 0; - - vv->data = val; - vv->len = len; - } - - v->set_handler(r, vv, v->data); - - return 0; - } - - if (v->flags & NGX_HTTP_VAR_INDEXED) { - vv = &r->variables[v->index]; - - dd("set indexed variable"); - - if (value_type == LUA_TNIL) { - vv->valid = 0; - vv->not_found = 1; - vv->no_cacheable = 0; - - vv->data = NULL; - vv->len = 0; - - } else { - vv->valid = 1; - vv->not_found = 0; - vv->no_cacheable = 0; - - vv->data = val; - vv->len = len; - } - - return 0; - } - - return luaL_error(L, "variable \"%s\" cannot be assigned a value", - lowcase); - } - - /* variable not found */ - - return luaL_error(L, "variable \"%s\" not found for writing; " - "maybe it is a built-in variable that is not changeable " - "or you forgot to use \"set $%s '';\" " - "in the config file to define it first", - lowcase, lowcase); -} - - -#ifndef NGX_LUA_NO_FFI_API int ngx_http_lua_ffi_var_get(ngx_http_request_t *r, u_char *name_data, size_t name_len, u_char *lowcase_buf, int capture_id, u_char **value, @@ -343,6 +70,18 @@ ngx_http_lua_ffi_var_get(ngx_http_request_t *r, u_char *name_data, } #endif +#if (NGX_HTTP_V3) + if (name_len == 9 + && r->http_version == NGX_HTTP_VERSION_30 + && ngx_strncasecmp(name_data, (u_char *) "http_host", 9) == 0 + && r->headers_in.server.data != NULL) + { + *value = r->headers_in.server.data; + *value_len = r->headers_in.server.len; + return NGX_OK; + } +#endif + hash = ngx_hash_strlow(lowcase_buf, name_data, name_len); name.data = lowcase_buf; @@ -503,7 +242,6 @@ ngx_http_lua_ffi_var_set(ngx_http_request_t *r, u_char *name_data, *errlen = ngx_snprintf(errbuf, *errlen, "no memory") - errbuf; return NGX_ERROR; } -#endif /* NGX_LUA_NO_FFI_API */ /* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_variable.h b/src/ngx_http_lua_variable.h deleted file mode 100644 index 550e4d3888..0000000000 --- a/src/ngx_http_lua_variable.h +++ /dev/null @@ -1,20 +0,0 @@ - -/* - * Copyright (C) Xiaozhe Wang (chaoslawful) - * Copyright (C) Yichun Zhang (agentzh) - */ - - -#ifndef _NGX_HTTP_LUA_VARIABLE_H_INCLUDED_ -#define _NGX_HTTP_LUA_VARIABLE_H_INCLUDED_ - - -#include "ngx_http_lua_common.h" - - -void ngx_http_lua_inject_variable_api(lua_State *L); - - -#endif /* _NGX_HTTP_LUA_VARIABLE_H_INCLUDED_ */ - -/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_worker.c b/src/ngx_http_lua_worker.c index 461509b436..5fdf0b4760 100644 --- a/src/ngx_http_lua_worker.c +++ b/src/ngx_http_lua_worker.c @@ -9,94 +9,53 @@ #endif #include "ddebug.h" - -#include "ngx_http_lua_worker.h" +#if !(NGX_WIN32) +#include +#endif #define NGX_PROCESS_PRIVILEGED_AGENT 99 -static int ngx_http_lua_ngx_worker_exiting(lua_State *L); -static int ngx_http_lua_ngx_worker_pid(lua_State *L); -static int ngx_http_lua_ngx_worker_id(lua_State *L); -static int ngx_http_lua_ngx_worker_count(lua_State *L); - - -void -ngx_http_lua_inject_worker_api(lua_State *L) +int +ngx_http_lua_ffi_worker_pid(void) { - lua_createtable(L, 0 /* narr */, 4 /* nrec */); /* ngx.worker. */ - - lua_pushcfunction(L, ngx_http_lua_ngx_worker_exiting); - lua_setfield(L, -2, "exiting"); - - lua_pushcfunction(L, ngx_http_lua_ngx_worker_pid); - lua_setfield(L, -2, "pid"); - - lua_pushcfunction(L, ngx_http_lua_ngx_worker_id); - lua_setfield(L, -2, "id"); - - lua_pushcfunction(L, ngx_http_lua_ngx_worker_count); - lua_setfield(L, -2, "count"); - - lua_setfield(L, -2, "worker"); + return (int) ngx_pid; } -static int -ngx_http_lua_ngx_worker_exiting(lua_State *L) +#if !(NGX_WIN32) +int +ngx_http_lua_ffi_worker_pids(int *pids, size_t *pids_len) { - lua_pushboolean(L, ngx_exiting); - return 1; -} + size_t n; + ngx_int_t i; + n = 0; + for (i = 0; n < *pids_len && i < NGX_MAX_PROCESSES; i++) { + if (i != ngx_process_slot && ngx_processes[i].pid == 0) { + break; + } -static int -ngx_http_lua_ngx_worker_pid(lua_State *L) -{ - lua_pushinteger(L, (lua_Integer) ngx_pid); - return 1; -} - + /* The current process */ + if (i == ngx_process_slot) { + pids[n++] = ngx_pid; + } -static int -ngx_http_lua_ngx_worker_id(lua_State *L) -{ -#if (nginx_version >= 1009001) - if (ngx_process != NGX_PROCESS_WORKER - && ngx_process != NGX_PROCESS_SINGLE) - { - lua_pushnil(L); - return 1; + if (ngx_processes[i].channel[0] > 0 && ngx_processes[i].pid > 0) { + pids[n++] = ngx_processes[i].pid; + } } - lua_pushinteger(L, (lua_Integer) ngx_worker); -#else - lua_pushnil(L); -#endif - return 1; -} - - -static int -ngx_http_lua_ngx_worker_count(lua_State *L) -{ - ngx_core_conf_t *ccf; - - ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, - ngx_core_module); - - lua_pushinteger(L, (lua_Integer) ccf->worker_processes); - return 1; -} + if (n == 0) { + return NGX_ERROR; + } + *pids_len = n; -#ifndef NGX_LUA_NO_FFI_API -int -ngx_http_lua_ffi_worker_pid(void) -{ - return (int) ngx_pid; + return NGX_OK; } +#endif int @@ -153,6 +112,8 @@ ngx_http_lua_ffi_master_pid(void) int ngx_http_lua_ffi_get_process_type(void) { + ngx_core_conf_t *ccf; + #if defined(HAVE_PRIVILEGED_PROCESS_PATCH) && !NGX_WIN32 if (ngx_process == NGX_PROCESS_HELPER) { if (ngx_is_privileged_agent) { @@ -161,12 +122,25 @@ ngx_http_lua_ffi_get_process_type(void) } #endif + if (ngx_process == NGX_PROCESS_SINGLE) { + ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_core_module); + + if (ccf->master) { + return NGX_PROCESS_MASTER; + } + } + return ngx_process; } - +#if defined(nginx_version) && nginx_version >= 1019003 +int +ngx_http_lua_ffi_enable_privileged_agent(char **err, unsigned int connections) +#else int ngx_http_lua_ffi_enable_privileged_agent(char **err) +#endif { #ifdef HAVE_PRIVILEGED_PROCESS_PATCH ngx_core_conf_t *ccf; @@ -175,6 +149,9 @@ ngx_http_lua_ffi_enable_privileged_agent(char **err) ngx_core_module); ccf->privileged_agent = 1; +#if defined(nginx_version) && nginx_version >= 1019003 + ccf->privileged_agent_connections = connections; +#endif return NGX_OK; @@ -190,4 +167,6 @@ ngx_http_lua_ffi_process_signal_graceful_exit(void) { ngx_quit = 1; } -#endif + + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_worker.h b/src/ngx_http_lua_worker.h deleted file mode 100644 index 30803949f3..0000000000 --- a/src/ngx_http_lua_worker.h +++ /dev/null @@ -1,17 +0,0 @@ - -/* - * Copyright (C) Yichun Zhang (agentzh) - */ - - -#ifndef _NGX_HTTP_LUA_WORKER_H_INCLUDED_ -#define _NGX_HTTP_LUA_WORKER_H_INCLUDED_ - - -#include "ngx_http_lua_common.h" - - -void ngx_http_lua_inject_worker_api(lua_State *L); - - -#endif /* _NGX_HTTP_LUA_WORKER_H_INCLUDED_ */ diff --git a/src/ngx_http_lua_worker_thread.c b/src/ngx_http_lua_worker_thread.c new file mode 100644 index 0000000000..ad747e8104 --- /dev/null +++ b/src/ngx_http_lua_worker_thread.c @@ -0,0 +1,643 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + * Copyright (C) Jinhua Luo (kingluo) + * I hereby assign copyright in this code to the lua-nginx-module project, + * to be licensed under the same terms as the rest of the code. + */ + + +#ifndef DDEBUG +#define DDEBUG 0 +#endif +#include "ddebug.h" + + +#include "ngx_http_lua_worker_thread.h" +#include "ngx_http_lua_util.h" +#include "ngx_http_lua_string.h" +#include "ngx_http_lua_config.h" +#include "ngx_http_lua_shdict.h" + +#ifndef STRINGIFY +#define TOSTRING(x) #x +#define STRINGIFY(x) TOSTRING(x) +#endif + +#if (NGX_THREADS) + + +#include +#include + +#define LUA_COPY_MAX_DEPTH 100 + +typedef struct ngx_http_lua_task_ctx_s { + lua_State *vm; + struct ngx_http_lua_task_ctx_s *next; +} ngx_http_lua_task_ctx_t; + + +typedef struct { + ngx_http_lua_task_ctx_t *ctx; + ngx_http_lua_co_ctx_t *wait_co_ctx; + int n_args; + int rc; + ngx_uint_t is_abort:1; +} ngx_http_lua_worker_thread_ctx_t; + + +static ngx_http_lua_task_ctx_t dummy_ctx; +static ngx_http_lua_task_ctx_t *ctxpool = &dummy_ctx; +static ngx_uint_t worker_thread_vm_count; + + +void +ngx_http_lua_thread_exit_process(void) +{ + ngx_http_lua_task_ctx_t *ctx; + + while (ctxpool->next != NULL) { + ctx = ctxpool->next; + ctxpool->next = ctx->next; + lua_close(ctx->vm); + ngx_free(ctx); + } +} + + +/* + * Re-implement ngx_thread_task_alloc to avoid alloc from request pool + * since the request may exit before worker thread finish. + * And we may implement a memory pool for this allocation in the future + * to avoid memory fragmentation. + */ +static ngx_thread_task_t * +ngx_http_lua_thread_task_alloc(size_t size) +{ + ngx_thread_task_t *task; + + task = ngx_calloc(sizeof(ngx_thread_task_t) + size, ngx_cycle->log); + if (task == NULL) { + return NULL; + } + + task->ctx = task + 1; + + return task; +} + + +static void +ngx_http_lua_thread_task_free(void *ctx) +{ + ngx_thread_task_t *task = ctx; + ngx_free(task - 1); +} + + +static ngx_http_lua_task_ctx_t * +ngx_http_lua_get_task_ctx(lua_State *L, ngx_http_request_t *r) +{ + ngx_http_lua_task_ctx_t *ctx = NULL; + + size_t path_len; + const char *path; + size_t cpath_len; + const char *cpath; + lua_State *vm; + + ngx_http_lua_main_conf_t *lmcf; + + lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module); + + if (ctxpool->next == NULL) { + if (worker_thread_vm_count >= lmcf->worker_thread_vm_pool_size) { + return NULL; + } + + ctx = ngx_calloc(sizeof(ngx_http_lua_task_ctx_t), ngx_cycle->log); + if (ctx == NULL) { + return NULL; + } + + vm = luaL_newstate(); + + if (vm == NULL) { + ngx_free(ctx); + return NULL; + } + + worker_thread_vm_count++; + + ctx->vm = vm; + + luaL_openlibs(vm); + + /* copy package.path and package.cpath */ + lua_getglobal(L, "package"); + lua_getfield(L, -1, "path"); + path = lua_tolstring(L, -1, &path_len); + lua_getfield(L, -2, "cpath"); + cpath = lua_tolstring(L, -1, &cpath_len); + + lua_getglobal(vm, "package"); + lua_pushlstring(vm, path, path_len); + lua_setfield(vm, -2, "path"); + lua_pushlstring(vm, cpath, cpath_len); + lua_setfield(vm, -2, "cpath"); + lua_pop(vm, 1); + + /* pop path, cpath and "package" table from L */ + lua_pop(L, 3); + + /* inject API from C */ + lua_newtable(vm); /* ngx.* */ + ngx_http_lua_inject_string_api(vm); + ngx_http_lua_inject_config_api(vm); + ngx_http_lua_inject_shdict_api(lmcf, vm); + lua_setglobal(vm, "ngx"); + + lua_getglobal(vm, "require"); + lua_pushstring(vm, "resty.core.hash"); + if (lua_pcall(vm, 1, 0, 0) != 0) { + lua_close(vm); + ngx_free(ctx); + return NULL; + } + + lua_getglobal(vm, "require"); + lua_pushstring(vm, "resty.core.base64"); + if (lua_pcall(vm, 1, 0, 0) != 0) { + lua_close(vm); + ngx_free(ctx); + return NULL; + } + + lua_getglobal(vm, "require"); + lua_pushstring(vm, "resty.core.shdict"); + if (lua_pcall(vm, 1, 0, 0) != 0) { + lua_close(vm); + ngx_free(ctx); + return NULL; + } + + } else { + ctx = ctxpool->next; + ctxpool->next = ctx->next; + ctx->next = NULL; + } + + return ctx; +} + + +static void +ngx_http_lua_free_task_ctx(ngx_http_lua_task_ctx_t *ctx) +{ + lua_State *vm; + + ctx->next = ctxpool->next; + ctxpool->next = ctx; + + /* clean Lua stack */ + vm = ctx->vm; + + /* call collectgarbage("collect") */ + lua_settop(vm, 0); + lua_getglobal(vm, "collectgarbage"); + lua_pushstring(vm, "collect"); + lua_pcall(vm, 1, 1, 0); + + lua_settop(vm, 0); +} + + +static int +ngx_http_lua_xcopy(lua_State *from, lua_State *to, int idx, + const int allow_nil, const int depth, const char **err) +{ + size_t len = 0; + const char *str; + int typ; + int top_from, top_to; + + typ = lua_type(from, idx); + switch (typ) { + case LUA_TBOOLEAN: + lua_pushboolean(to, lua_toboolean(from, idx)); + return LUA_TBOOLEAN; + + case LUA_TLIGHTUSERDATA: + lua_pushlightuserdata(to, lua_touserdata(from, idx)); + return LUA_TLIGHTUSERDATA; + + case LUA_TNUMBER: + lua_pushnumber(to, lua_tonumber(from, idx)); + return LUA_TNUMBER; + + case LUA_TSTRING: + str = lua_tolstring(from, idx, &len); + lua_pushlstring(to, str, len); + return LUA_TSTRING; + + case LUA_TTABLE: + if (depth >= LUA_COPY_MAX_DEPTH) { + *err = "suspicious circular references, " + "table depth exceed max depth: " + STRINGIFY(LUA_COPY_MAX_DEPTH); + return LUA_TNONE; + } + + top_from = lua_gettop(from); + top_to = lua_gettop(to); + + lua_newtable(to); + + /* to positive number */ + if (idx < 0) { + idx = lua_gettop(from) + idx + 1; + } + + lua_pushnil(from); + + while (lua_next(from, idx) != 0) { + if (ngx_http_lua_xcopy(from, to, -2, 0, depth + 1, err) != LUA_TNONE + && ngx_http_lua_xcopy(from, to, -1, 0, + depth + 1, err) != LUA_TNONE) + { + lua_rawset(to, -3); + + } else { + lua_settop(from, top_from); + lua_settop(to, top_to); + return LUA_TNONE; + } + + lua_pop(from, 1); + } + + return LUA_TTABLE; + + case LUA_TNIL: + if (allow_nil) { + lua_pushnil(to); + return LUA_TNIL; + } + + *err = "unsupported Lua type: LUA_TNIL"; + return LUA_TNONE; + + case LUA_TFUNCTION: + *err = "unsupported Lua type: LUA_TFUNCTION"; + return LUA_TNONE; + + case LUA_TUSERDATA: + *err = "unsupported Lua type: LUA_TUSERDATA"; + return LUA_TNONE; + + case LUA_TTHREAD: + *err = "unsupported Lua type: LUA_TTHREAD"; + return LUA_TNONE; + + default: + *err = "unsupported Lua type"; + return LUA_TNONE; + } +} + + +/* executed in a separate thread */ +static void +ngx_http_lua_worker_thread_handler(void *data, ngx_log_t *log) +{ + ngx_http_lua_worker_thread_ctx_t *ctx = data; + lua_State *vm = ctx->ctx->vm; + + /* function + args in the lua stack */ + ngx_http_lua_assert(lua_gettop(vm) == ctx->n_args + 1); + + ctx->rc = lua_pcall(vm, ctx->n_args, LUA_MULTRET, 0); +} + + +static ngx_int_t +ngx_http_lua_worker_thread_resume(ngx_http_request_t *r) +{ + lua_State *vm; + ngx_connection_t *c; + ngx_int_t rc; + ngx_uint_t nreqs; + ngx_http_lua_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + return NGX_ERROR; + } + + ctx->resume_handler = ngx_http_lua_wev_handler; + + c = r->connection; + vm = ngx_http_lua_get_lua_vm(r, ctx); + nreqs = c->requests; + + rc = ngx_http_lua_run_thread(vm, r, ctx, + ctx->cur_co_ctx->nresults_from_worker_thread); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua run thread returned %d", rc); + + if (rc == NGX_AGAIN) { + return ngx_http_lua_run_posted_threads(c, vm, r, ctx, nreqs); + } + + if (rc == NGX_DONE) { + ngx_http_lua_finalize_request(r, NGX_DONE); + return ngx_http_lua_run_posted_threads(c, vm, r, ctx, nreqs); + } + + if (ctx->entered_content_phase) { + ngx_http_lua_finalize_request(r, rc); + return NGX_DONE; + } + + return rc; +} + + +/* executed in nginx event loop */ +static void +ngx_http_lua_worker_thread_event_handler(ngx_event_t *ev) +{ + ngx_http_lua_worker_thread_ctx_t *worker_thread_ctx; + lua_State *L; + ngx_http_request_t *r; + ngx_connection_t *c; + int nresults; + size_t len; + const char *str; + int i; + ngx_http_lua_ctx_t *ctx; + lua_State *vm; + int saved_top; + const char *err; + + worker_thread_ctx = ev->data; + + if (worker_thread_ctx->is_abort) { + goto failed; + } + + L = worker_thread_ctx->wait_co_ctx->co; + + r = ngx_http_lua_get_req(L); + if (r == NULL) { + goto failed; + } + + c = r->connection; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + goto failed; + } + + vm = worker_thread_ctx->ctx->vm; + + if (worker_thread_ctx->rc != 0) { + str = lua_tolstring(vm, 1, &len); + lua_pushboolean(L, 0); + lua_pushlstring(L, str, len); + nresults = 2; + + } else { + /* copying return values */ + saved_top = lua_gettop(L); + lua_pushboolean(L, 1); + nresults = lua_gettop(vm) + 1; + for (i = 1; i < nresults; i++) { + err = NULL; + if (ngx_http_lua_xcopy(vm, L, i, 1, 1, &err) == LUA_TNONE) { + lua_settop(L, saved_top); + lua_pushboolean(L, 0); + lua_pushfstring(L, "%s in the return value", + err != NULL ? err : "unsupoorted Lua type"); + nresults = 2; + break; + } + } + } + + ctx->cur_co_ctx = worker_thread_ctx->wait_co_ctx; + ctx->cur_co_ctx->nresults_from_worker_thread = nresults; + ctx->cur_co_ctx->cleanup = NULL; + + ngx_http_lua_free_task_ctx(worker_thread_ctx->ctx); + ngx_http_lua_thread_task_free(worker_thread_ctx); + + /* resume the caller coroutine */ + + if (ctx->entered_content_phase) { + (void) ngx_http_lua_worker_thread_resume(r); + + } else { + ctx->resume_handler = ngx_http_lua_worker_thread_resume; + ngx_http_core_run_phases(r); + } + + ngx_http_run_posted_requests(c); + + return; + +failed: + + ngx_http_lua_free_task_ctx(worker_thread_ctx->ctx); + ngx_http_lua_thread_task_free(worker_thread_ctx); + return; +} + + +static void +ngx_http_lua_worker_thread_cleanup(void *data) +{ + ngx_http_lua_co_ctx_t *ctx = data; + ngx_http_lua_worker_thread_ctx_t *worker_thread_ctx = ctx->data; + worker_thread_ctx->is_abort = 1; +} + + +/* It's not easy to use pure ffi here, using the Lua C API for now. */ +static int +ngx_http_lua_run_worker_thread(lua_State *L) +{ + ngx_http_request_t *r; + ngx_http_lua_ctx_t *ctx; + int n_args; + ngx_str_t thread_pool_name; + ngx_thread_pool_t *thread_pool; + ngx_http_lua_task_ctx_t *tctx; + lua_State *vm; + size_t len; + const char *mod_name; + const char *func_name; + int rc; + const char *err; + int i; + ngx_thread_task_t *task; + ngx_http_lua_worker_thread_ctx_t *worker_thread_ctx; + + r = ngx_http_lua_get_req(L); + if (r == NULL) { + return luaL_error(L, "no request found"); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + return luaL_error(L, "no ctx found"); + } + + ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_YIELDABLE); + + n_args = lua_gettop(L); + + if (n_args < 3) { + lua_pushboolean(L, 0); + lua_pushstring(L, "expecting at least 3 arguments"); + return 2; + } + + thread_pool_name.data = (u_char *) + lua_tolstring(L, 1, &thread_pool_name.len); + + if (thread_pool_name.data == NULL) { + lua_pushboolean(L, 0); + lua_pushstring(L, "threadpool should be a string"); + return 2; + } + + thread_pool = ngx_thread_pool_get((ngx_cycle_t *) ngx_cycle, + &thread_pool_name); + + if (thread_pool == NULL) { + lua_pushboolean(L, 0); + lua_pushfstring(L, "thread pool %s not found", thread_pool_name.data); + return 2; + } + + mod_name = lua_tolstring(L, 2, &len); + if (mod_name == NULL) { + lua_pushboolean(L, 0); + lua_pushstring(L, "module name should be a string"); + return 2; + } + + func_name = lua_tolstring(L, 3, NULL); + if (func_name == NULL) { + lua_pushboolean(L, 0); + lua_pushstring(L, "function name should be a string"); + return 2; + } + + /* get vm */ + tctx = ngx_http_lua_get_task_ctx(L, r); + if (tctx == NULL) { + lua_pushboolean(L, 0); + lua_pushstring(L, "no available Lua vm"); + return 2; + } + + vm = tctx->vm; + + ngx_http_lua_assert(lua_gettop(vm) == 0); + + /* push function from module require */ + lua_getfield(vm, LUA_GLOBALSINDEX, "require"); + lua_pushlstring(vm, mod_name, len); + rc = lua_pcall(vm, 1, 1, 0); + + if (rc != 0) { + err = lua_tolstring(vm, 1, &len); + lua_pushboolean(L, 0); + lua_pushlstring(L, err, len); + ngx_http_lua_free_task_ctx(tctx); + return 2; + } + + if (!lua_istable(vm, -1)) { + lua_pushboolean(L, 0); + lua_pushstring(L, "invalid lua module"); + ngx_http_lua_free_task_ctx(tctx); + return 2; + } + + lua_getfield(vm, -1, func_name); + if (!lua_isfunction(vm, -1)) { + lua_pushboolean(L, 0); + lua_pushstring(L, "invalid function"); + ngx_http_lua_free_task_ctx(tctx); + return 2; + } + + /* remove the table returned by require */ + lua_remove(vm, 1); + + /* copying passed arguments */ + for (i = 4; i <= n_args; i++) { + err = NULL; + if (ngx_http_lua_xcopy(L, vm, i, 1, 1, &err) == LUA_TNONE) { + lua_pushboolean(L, 0); + lua_pushfstring(L, "%s in the argument", + err != NULL ? err : "unsupoorted Lua type"); + ngx_http_lua_free_task_ctx(tctx); + return 2; + } + } + + /* post task */ + task = ngx_http_lua_thread_task_alloc( + sizeof(ngx_http_lua_worker_thread_ctx_t)); + + if (task == NULL) { + ngx_http_lua_free_task_ctx(tctx); + lua_pushboolean(L, 0); + lua_pushstring(L, "no memory"); + return 2; + } + + worker_thread_ctx = task->ctx; + + worker_thread_ctx->ctx = tctx; + worker_thread_ctx->wait_co_ctx = ctx->cur_co_ctx; + + ctx->cur_co_ctx->cleanup = ngx_http_lua_worker_thread_cleanup; + ctx->cur_co_ctx->data = worker_thread_ctx; + + worker_thread_ctx->n_args = n_args - 3; + worker_thread_ctx->rc = 0; + worker_thread_ctx->is_abort = 0; + + task->handler = ngx_http_lua_worker_thread_handler; + task->event.handler = ngx_http_lua_worker_thread_event_handler; + task->event.data = worker_thread_ctx; + + if (ngx_thread_task_post(thread_pool, task) != NGX_OK) { + ngx_http_lua_free_task_ctx(tctx); + ngx_http_lua_thread_task_free(task); + lua_pushboolean(L, 0); + lua_pushstring(L, "ngx_thread_task_post failed"); + return 2; + } + + return lua_yield(L, 0); +} + + +void +ngx_http_lua_inject_worker_thread_api(ngx_log_t *log, lua_State *L) +{ + lua_pushcfunction(L, ngx_http_lua_run_worker_thread); + lua_setfield(L, -2, "run_worker_thread"); +} + +#endif + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_worker_thread.h b/src/ngx_http_lua_worker_thread.h new file mode 100644 index 0000000000..2daa520a59 --- /dev/null +++ b/src/ngx_http_lua_worker_thread.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + * Copyright (C) Jinhua Luo (kingluo) + * I hereby assign copyright in this code to the lua-nginx-module project, + * to be licensed under the same terms as the rest of the code. + */ + +#ifndef _NGX_HTTP_LUA_WORKER_THREAD_H_INCLUDED_ +#define _NGX_HTTP_LUA_WORKER_THREAD_H_INCLUDED_ + + +#include "ngx_http_lua_common.h" + + +void ngx_http_lua_inject_worker_thread_api(ngx_log_t *log, lua_State *L); +void ngx_http_lua_thread_exit_process(void); + + +#endif /* _NGX_HTTP_LUA_WORKER_THREAD_H_INCLUDED_ */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/t/000--init.t b/t/000--init.t index dbe2c33099..0016e14486 100644 --- a/t/000--init.t +++ b/t/000--init.t @@ -10,12 +10,8 @@ $ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; $ENV{TEST_NGINX_MYSQL_PORT} ||= 3306; our $http_config = <<'_EOC_'; - upstream database { - drizzle_server 127.0.0.1:$TEST_NGINX_MYSQL_PORT protocol=mysql - dbname=ngx_test user=ngx_test password=ngx_test; - } - - lua_package_path "../lua-resty-mysql/lib/?.lua;;"; + # lua-resty-string is required for lua-resty-mysql + lua_package_path "../lua-resty-rsa/lib/?.lua;../lua-resty-mysql/lib/?.lua;../lua-resty-string/lib/?.lua;;"; _EOC_ no_shuffle(); diff --git a/t/001-set.t b/t/001-set.t index ba8f22cb68..009159e484 100644 --- a/t/001-set.t +++ b/t/001-set.t @@ -4,7 +4,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); -plan tests => repeat_each() * (blocks() * 3 + 4); +plan tests => repeat_each() * (blocks() * 3 + 5); #log_level("warn"); no_long_string(); @@ -46,7 +46,7 @@ helloworld === TEST 3: internal only --- config location /lua { - set_by_lua $res "function fib(n) if n > 2 then return fib(n-1)+fib(n-2) else return 1 end end return fib(10)"; + set_by_lua $res "local function fib(n) if n > 2 then return fib(n-1)+fib(n-2) else return 1 end end return fib(10)"; echo $res; } --- request @@ -76,7 +76,7 @@ GET /lua?a=1&b=2 === TEST 5: fib by arg --- config location /fib { - set_by_lua $res "function fib(n) if n > 2 then return fib(n-1)+fib(n-2) else return 1 end end return fib(tonumber(ngx.arg[1]))" $arg_n; + set_by_lua $res "local function fib(n) if n > 2 then return fib(n-1)+fib(n-2) else return 1 end end return fib(tonumber(ngx.arg[1]))" $arg_n; echo $res; } --- request @@ -389,8 +389,8 @@ GET /lua GET /lua --- response_body_like: 500 Internal Server Error --- error_code: 500 ---- error_log -API disabled in the context of set_by_lua* +--- error_log eval +qr/(?:API disabled in the context of set_by_lua\*|http3 requests are not supported without content-length header)/ms @@ -404,8 +404,16 @@ API disabled in the context of set_by_lua* GET /lua --- response_body_like: 500 Internal Server Error --- error_code: 500 ---- error_log -API disabled in the context of set_by_lua* +--- error_log eval +my $err_log; + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $err_log = "http v3 not supported yet"; +} else { + $err_log = "API disabled in the context of set_by_lua*"; +} + +$err_log; @@ -573,7 +581,7 @@ GET /lua --- response_body_like: 500 Internal Server Error --- error_code: 500 --- error_log -failed to run set_by_lua*: set_by_lua:1: Bad +failed to run set_by_lua*: set_by_lua(nginx.conf:40):1: Bad @@ -592,25 +600,30 @@ failed to run set_by_lua*: unknown reason -=== TEST 37: globals get cleared for every single request +=== TEST 37: globals are shared in all requests. --- config location /lua { - set_by_lua $res ' + set_by_lua_block $res { if not foo then foo = 1 else + ngx.log(ngx.INFO, "old foo: ", foo) foo = foo + 1 end return foo - '; + } echo $res; } --- request GET /lua ---- response_body -1 +--- response_body_like chomp +\A[12] +\z --- no_error_log [error] +--- grep_error_log eval: qr/(old foo: \d+|writing a global Lua variable \('\w+'\))/ +--- grep_error_log_out eval +["writing a global Lua variable \('foo'\)\n", "old foo: 1\n"] @@ -755,6 +768,8 @@ GET /lua?name=jim --- config location /t { set_by_lua $a ' + local bar + local foo function foo() bar() end diff --git a/t/002-content.t b/t/002-content.t index cc92d6f756..26070f5287 100644 --- a/t/002-content.t +++ b/t/002-content.t @@ -10,7 +10,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); #repeat_each(1); -plan tests => repeat_each() * (blocks() * 2 + 23); +plan tests => repeat_each() * (blocks() * 2 + 32); #no_diff(); #no_long_string(); @@ -36,6 +36,14 @@ GET /lua Hello, Lua! --- no_error_log [error] +--- grep_error_log eval: qr/lua caching unused lua thread|lua reusing cached lua thread/ +--- grep_error_log_out eval +[ + "lua caching unused lua thread\n", + "lua reusing cached lua thread +lua caching unused lua thread +", +] @@ -86,7 +94,7 @@ qr/content_by_lua\(nginx\.conf:\d+\):1: attempt to call field 'echo' \(a nil val location /lua { # NOTE: the newline escape sequence must be double-escaped, as nginx config # parser will unescape first! - content_by_lua 'v = ngx.var["request_uri"] ngx.print("request_uri: ", v, "\\n")'; + content_by_lua 'local v = ngx.var["request_uri"] ngx.print("request_uri: ", v, "\\n")'; } --- request GET /lua?a=1&b=2 @@ -102,7 +110,7 @@ request_uri: /lua?a=1&b=2 } --- user_files >>> test.lua -v = ngx.var["request_uri"] +local v = ngx.var["request_uri"] ngx.print("request_uri: ", v, "\n") --- request GET /lua?a=1&b=2 @@ -154,7 +162,7 @@ result: -0.4090441561579 === TEST 7: read $arg_xxx --- config location = /lua { - content_by_lua 'who = ngx.var.arg_who + content_by_lua 'local who = ngx.var.arg_who ngx.print("Hello, ", who, "!")'; } --- request @@ -171,7 +179,7 @@ Hello, agentzh! } location /lua { - content_by_lua 'res = ngx.location.capture("/other"); ngx.print("status=", res.status, " "); ngx.print("body=", res.body)'; + content_by_lua 'local res = ngx.location.capture("/other"); ngx.print("status=", res.status, " "); ngx.print("body=", res.body)'; } --- request GET /lua @@ -183,7 +191,7 @@ status=200 body=hello, world ei= TEST 9: capture non-existed location --- config location /lua { - content_by_lua 'res = ngx.location.capture("/other"); ngx.print("status=", res.status)'; + content_by_lua 'local res = ngx.location.capture("/other"); ngx.print("status=", res.status)'; } --- request GET /lua @@ -194,7 +202,7 @@ GET /lua === TEST 9: invalid capture location (not as expected...) --- config location /lua { - content_by_lua 'res = ngx.location.capture("*(#*"); ngx.say("res=", res.status)'; + content_by_lua 'local res = ngx.location.capture("*(#*"); ngx.say("res=", res.status)'; } --- request GET /lua @@ -247,7 +255,7 @@ GET /lua ngx.print("num is: ", num, "\\n"); if (num > 0) then - res = ngx.location.capture("/recur?num="..tostring(num - 1)); + local res = ngx.location.capture("/recur?num="..tostring(num - 1)); ngx.print("status=", res.status, " "); ngx.print("body=", res.body, "\\n"); else @@ -271,7 +279,7 @@ end ngx.print("num is: ", num, "\\n"); if (num > 0) then - res = ngx.location.capture("/recur?num="..tostring(num - 1)); + local res = ngx.location.capture("/recur?num="..tostring(num - 1)); ngx.print("status=", res.status, " "); ngx.print("body=", res.body); else @@ -353,7 +361,7 @@ location /sub { } location /parent { set $a 12; - content_by_lua 'res = ngx.location.capture("/sub"); ngx.print(res.body)'; + content_by_lua 'local res = ngx.location.capture("/sub"); ngx.print(res.body)'; } --- request GET /parent @@ -369,7 +377,7 @@ location /sub { location /parent { set $a 12; content_by_lua ' - res = ngx.location.capture( + local res = ngx.location.capture( "/sub", { share_all_vars = true } ); @@ -390,7 +398,7 @@ location /sub { } location /parent { content_by_lua ' - res = ngx.location.capture("/sub", { share_all_vars = true }); + local res = ngx.location.capture("/sub", { share_all_vars = true }); ngx.say(ngx.var.a) '; } @@ -408,7 +416,7 @@ location /sub { } location /parent { content_by_lua ' - res = ngx.location.capture("/sub", { share_all_vars = false }); + local res = ngx.location.capture("/sub", { share_all_vars = false }); ngx.say(ngx.var.a) '; } @@ -427,7 +435,7 @@ GET /parent location /lua { content_by_lua ' - res = ngx.location.capture("/other"); + local res = ngx.location.capture("/other"); ngx.say("type: ", res.header["Content-Type"]); '; } @@ -454,7 +462,7 @@ type: foo/bar location /lua { content_by_lua ' - res = ngx.location.capture("/other"); + local res = ngx.location.capture("/other"); ngx.say("type: ", type(res.header["Set-Cookie"])); ngx.say("len: ", #res.header["Set-Cookie"]); ngx.say("value: ", table.concat(res.header["Set-Cookie"], "|")) @@ -482,7 +490,7 @@ value: a|hello, world|foo location /lua { content_by_lua ' - res = ngx.location.capture("/other"); + local res = ngx.location.capture("/other"); ngx.say("type: ", res.header["Content-Type"]); ngx.say("Bar: ", res.header["Bar"]); '; @@ -507,7 +515,7 @@ Bar: Bah location /lua { content_by_lua ' - res = ngx.location.capture("/other"); + local res = ngx.location.capture("/other"); ngx.say("type: ", res.header["Content-Type"]); ngx.say("Bar: ", res.header["Bar"] or "nil"); '; @@ -524,7 +532,7 @@ Bar: nil --- config location /lua { content_by_lua ' - data = "hello, world" + local data = "hello, world" -- ngx.header["Content-Length"] = #data -- ngx.header.content_length = #data ngx.print(data) @@ -742,7 +750,7 @@ true --- config location /lua { content_by_lua ' - data = "hello,\\nworld\\n" + local data = "hello,\\nworld\\n" ngx.header["Content-Length"] = #data ngx.say("hello,") ngx.flush() @@ -801,7 +809,7 @@ world } --- user_files >>> test.lua -v = ngx.var["request_uri"] +local v = ngx.var["request_uri"] ngx.print("request_uri: ", v, "\n") --- request GET /lua?a=1&b=2 @@ -841,4 +849,275 @@ GET /lua --- response_body_like: 500 Internal Server Error --- error_code: 500 --- error_log eval -qr/failed to load inlined Lua code: / +qr/failed to load inlined Lua code: content_by_lua\(nginx.conf:40\)/ + + + +=== TEST 43: syntax error in content_by_lua_block +--- config + location /lua { + + content_by_lua_block { + 'for end'; + } + } +--- request +GET /lua +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log eval +qr/failed to load inlined Lua code: content_by_lua\(nginx.conf:41\)/ + + + +=== TEST 44: syntax error in second content_by_lua_block +--- config + location /foo { + content_by_lua_block { + 'for end'; + } + } + + location /lua { + content_by_lua_block { + 'for end'; + } + } +--- request +GET /lua +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log eval +qr/failed to load inlined Lua code: content_by_lua\(nginx.conf:46\)/ + + + +=== TEST 45: syntax error in thrid content_by_lua_block +--- config + location /foo { + content_by_lua_block { + 'for end'; + } + } + + location /bar { + content_by_lua_block { + 'for end'; + } + } + + location /lua { + content_by_lua_block { + 'for end'; + } + } +--- request +GET /lua +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log eval +qr/failed to load inlined Lua code: content_by_lua\(nginx.conf:52\)/ + + + +=== TEST 46: syntax error in included file +--- config + location /foo { + content_by_lua_block { + 'for end'; + } + } + + location /bar { + content_by_lua_block { + 'for end'; + } + } + + include ../html/lua.conf; +--- user_files +>>> lua.conf + location /lua { + content_by_lua_block { + 'for end'; + } + } +--- request +GET /lua +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +failed to load inlined Lua code: content_by_lua(../html/lua.conf:2):2: unexpected symbol near ''for end'' + + + +=== TEST 47: syntax error with very long filename +--- config + location /foo { + content_by_lua_block { + 'for end'; + } + } + + location /bar { + content_by_lua_block { + 'for end'; + } + } + + include ../html/1234567890123456789012345678901234.conf; +--- user_files +>>> 1234567890123456789012345678901234.conf + location /lua { + content_by_lua_block { + 'for end'; + } + } +--- request +GET /lua +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +failed to load inlined Lua code: content_by_lua(...234567890123456789012345678901234.conf:2) + + + +=== TEST 48: syntax error in /tmp/lua.conf +--- config + location /foo { + content_by_lua_block { + 'for end'; + } + } + + location /bar { + content_by_lua_block { + 'for end'; + } + } + + include /tmp/lua.conf; +--- user_files +>>> /tmp/lua.conf + location /lua { + content_by_lua_block { + 'for end'; + } + } +--- request +GET /lua +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +failed to load inlined Lua code: content_by_lua(/tmp/lua.conf:2) + + + +=== TEST 49: syntax error in /tmp/12345678901234567890123456789012345.conf +--- config + location /foo { + content_by_lua_block { + 'for end'; + } + } + + location /bar { + content_by_lua_block { + 'for end'; + } + } + + include /tmp/12345678901234567890123456789012345.conf; + +--- user_files +>>> /tmp/12345678901234567890123456789012345.conf + location /lua { + content_by_lua_block { + 'for end'; + } + } +--- request +GET /lua +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +failed to load inlined Lua code: content_by_lua(...345678901234567890123456789012345.conf:2) + + + +=== TEST 50: the error line number greater than 9 +--- config + location /foo { + content_by_lua_block { + 'for end'; + } + } + + location /bar { + content_by_lua_block { + 'for end'; + } + } + + include /tmp/12345678901234567890123456789012345.conf; + +--- user_files +>>> /tmp/12345678901234567890123456789012345.conf + location /lua { + + + + + + + + + + + + + content_by_lua_block { + 'for end'; + } + } +--- request +GET /lua +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +failed to load inlined Lua code: content_by_lua(...45678901234567890123456789012345.conf:14) + + + +=== TEST 51: Lua file permission denied +--- config + location /lua { + content_by_lua_file /etc/shadow; + } +--- request +GET /lua +--- response_body_like: 503 Service Temporarily Unavailable +--- error_code: 503 + + + +=== TEST 52: send_header trigger filter finalize does not clear the ctx +--- config + location /lua { + content_by_lua_block { + ngx.header["Last-Modified"] = ngx.http_time(ngx.time()) + ngx.send_headers() + local phase = ngx.get_phase() + } + header_filter_by_lua_block { + ngx.header["X-Hello-World"] = "Hello World" + } + } +--- request +GET /lua +--- more_headers +If-Unmodified-Since: Wed, 01 Jan 2020 07:28:00 GMT +--- error_code: 412 +--- no_error_log +unknown phase: 0 +--- skip_eval: 2:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) diff --git a/t/004-require.t b/t/004-require.t index 35e04db814..17e1d57c9f 100644 --- a/t/004-require.t +++ b/t/004-require.t @@ -24,7 +24,7 @@ __DATA__ === TEST 1: sanity --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /main { echo_location /load; @@ -85,7 +85,7 @@ hello, foo --- request GET /main --- user_files ---- response_body_like: ^[^;]+/servroot/html/\?.so$ +--- response_body_like: ^[^;]+/servroot(_\d+)?/html/\?\.so$ @@ -100,7 +100,7 @@ GET /main } --- request GET /main ---- response_body_like: ^[^;]+/servroot/html/\?.lua;.+\.lua;$ +--- response_body_like: ^[^;]+/servroot(_\d+)?/html/\?\.lua;(.+\.lua)?;*$ @@ -115,7 +115,7 @@ GET /main } --- request GET /main ---- response_body_like: ^[^;]+/servroot/html/\?.so;.+\.so;$ +--- response_body_like: ^[^;]+/servroot(_\d+)?/html/\?\.so;(.+\.so)?;*$ @@ -130,7 +130,7 @@ GET /main } --- request GET /main ---- response_body_like: ^.+\.lua;[^;]+/servroot/html/\?.lua$ +--- response_body_like: ^(.+\.lua)?;*?[^;]+/servroot(_\d+)?/html/\?\.lua$ @@ -145,7 +145,7 @@ GET /main } --- request GET /main ---- response_body_like: ^.+\.so;[^;]+/servroot/html/\?.so$ +--- response_body_like: ^(.+\.so)?;*?[^;]+/servroot(_\d+)?/html/\?\.so$ diff --git a/t/005-exit.t b/t/005-exit.t index a5a28b3e28..7b82a81e4c 100644 --- a/t/005-exit.t +++ b/t/005-exit.t @@ -3,6 +3,7 @@ use Test::Nginx::Socket::Lua; #repeat_each(20000); +#repeat_each(200); repeat_each(2); #master_on(); #workers(1); @@ -64,7 +65,7 @@ GET /lua --- error_log attempt to set status 404 via ngx.exit after sending out the response status 200 --- no_error_log -alert +[alert] --- response_body hi @@ -122,6 +123,8 @@ GET /api?user=agentz === TEST 6: working with ngx_auth_request (simplest form, w/o ngx_memc) +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} +--- no_http2 --- http_config eval " lua_package_cpath '$::LuaCpath'; @@ -194,6 +197,8 @@ Logged in 56 === TEST 7: working with ngx_auth_request (simplest form) +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} +--- no_http2 --- http_config eval " lua_package_cpath '$::LuaCpath'; @@ -266,6 +271,8 @@ Logged in 56 === TEST 8: working with ngx_auth_request +--- no_http2 +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} --- http_config eval " lua_package_cpath '$::LuaCpath'; @@ -452,7 +459,7 @@ Hi --- config location /lua { content_by_lua ' - function f () + local function f () ngx.say("hello") ngx.exit(200) end @@ -652,6 +659,7 @@ GET /t --- response_body_like: 403 Forbidden --- no_error_log [error] +--- skip_eval: 3:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) @@ -670,6 +678,7 @@ GET /t --- response_body --- no_error_log [error] +--- skip_eval: 3:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) @@ -706,6 +715,7 @@ F(ngx_http_lua_header_filter_by_chunk).return { --- no_error_log [error] [alert] +--- skip_eval: 4:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) @@ -723,3 +733,105 @@ GET /t --- response_body --- no_error_log [error] +--- skip_eval: 3:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) + + + +=== TEST 25: 501 Method Not Implemented +--- config + location /lua { + content_by_lua ' + ngx.exit(ngx.HTTP_NOT_IMPLEMENTED) + '; + } +--- request +GET /lua +--- error_code: 501 +--- response_body_like: 501 (?:Method )?Not Implemented +--- no_error_log +[error] + + + +=== TEST 26: accepts NGX_OK +--- config + location = /t { + content_by_lua_block { + ngx.exit(ngx.OK) + } + } +--- request +GET /t +--- response_body +--- no_error_log +[error] + + + +=== TEST 27: accepts NGX_ERROR +--- no_http2 +--- config + location = /t { + content_by_lua_block { + ngx.exit(ngx.ERROR) + } + } +--- request +GET /t +--- error_code: +--- response_body +--- no_error_log +[error] +--- curl_error +curl: (95) HTTP/3 stream 0 reset by server + + + +=== TEST 28: accepts NGX_DECLINED +--- no_http2 +--- config + location = /t { + content_by_lua_block { + ngx.exit(ngx.DECLINED) + } + } +--- request +GET /t +--- error_code: +--- response_body +--- no_error_log +[error] +--- curl_error +curl: (95) HTTP/3 stream 0 reset by server + + + +=== TEST 29: refuses NGX_AGAIN +--- config + location = /t { + content_by_lua_block { + ngx.exit(ngx.AGAIN) + } + } +--- request +GET /t +--- error_code: 500 +--- response_body_like: 500 Internal Server Error +--- error_log eval +qr/\[error\] .*? bad argument to 'ngx.exit': does not accept NGX_AGAIN or NGX_DONE/ + + + +=== TEST 30: refuses NGX_DONE +--- config + location = /t { + content_by_lua_block { + ngx.exit(ngx.DONE) + } + } +--- request +GET /t +--- error_code: 500 +--- response_body_like: 500 Internal Server Error +--- error_log eval +qr/\[error\] .*? bad argument to 'ngx.exit': does not accept NGX_AGAIN or NGX_DONE/ diff --git a/t/006-escape.t b/t/006-escape.t index a21d3cb509..28714ab893 100644 --- a/t/006-escape.t +++ b/t/006-escape.t @@ -4,7 +4,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); -plan tests => repeat_each() * (blocks() * 2 + 2); +plan tests => repeat_each() * (blocks() * 2 + 4); no_long_string(); @@ -197,3 +197,134 @@ GET /lua %2C%24%40%7C%60 --- no_error_log [error] + + + +=== TEST 15: escape type argument +--- config + location /lua { + content_by_lua_block { + ngx.say(ngx.escape_uri("https://www.google.com", 0)) + ngx.say(ngx.escape_uri("https://www.google.com/query?q=test", 0)) + ngx.say(ngx.escape_uri("https://www.google.com/query?\r\nq=test", 0)) + ngx.say(ngx.escape_uri("-_.~!*'();:@&=+$,/?#", 0)) + ngx.say(ngx.escape_uri("<>[]{}\\\" ", 0)) + } + } +--- request +GET /lua +--- response_body +https://www.google.com +https://www.google.com/query%3Fq=test +https://www.google.com/query%3F%0D%0Aq=test +-_.~!*'();:@&=+$,/%3F%23 +<>[]{}\"%20 +--- no_error_log +[error] + + + +=== TEST 16: escape type argument +--- config + location /lua { + content_by_lua_block { + ngx.say(ngx.escape_uri("https://www.google.com/?t=abc@ :", 0)) + ngx.say(ngx.escape_uri("https://www.google.com/?t=abc@ :", 1)) + ngx.say(ngx.escape_uri("https://www.google.com/?t=abc@ :", 2)) + ngx.say(ngx.escape_uri("https://www.google.com/?t=abc@ :", 3)) + ngx.say(ngx.escape_uri("https://www.google.com/?t=abc@ :", 4)) + ngx.say(ngx.escape_uri("https://www.google.com/?t=abc@ :", 5)) + ngx.say(ngx.escape_uri("https://www.google.com/?t=abc@ :", 6)) + } + } +--- request +GET /lua +--- response_body +https://www.google.com/%3Ft=abc@%20: +https://www.google.com/%3Ft=abc@%20: +https%3A%2F%2Fwww.google.com%2F%3Ft%3Dabc%40%20%3A +https://www.google.com/?t=abc@%20: +https://www.google.com/?t=abc@%20: +https://www.google.com/?t=abc@%20: +https://www.google.com/?t=abc@%20: +--- no_error_log +[error] + + + +=== TEST 17: escape type out of range +--- config + location /lua { + content_by_lua_block { + ngx.say(ngx.escape_uri("https://www.google.com", -1)) + } + } +--- request +GET /lua +--- error_code: 500 +--- error_log eval +qr/\[error\] \d+#\d+: \*\d+ lua entry thread aborted: runtime error: "type" \-1 out of range/ + + + +=== TEST 18: escape type out of range +--- config + location /lua { + content_by_lua_block { + ngx.say(ngx.escape_uri("https://www.google.com", 10)) + } + } +--- request +GET /lua +--- error_code: 500 +--- error_log eval +qr/\[error\] \d+#\d+: \*\d+ lua entry thread aborted: runtime error: "type" 10 out of range/ + + + +=== TEST 19: escape type not integer +--- config + location /lua { + content_by_lua_block { + ngx.say(ngx.escape_uri("https://www.google.com", true)) + } + } +--- request +GET /lua +--- error_code: 500 +--- error_log eval +qr/\[error\] \d+#\d+: \*\d+ lua entry thread aborted: runtime error: "type" is not a number/ + + + +=== TEST 20: invalid unescape sequences +--- config + location /lua { + content_by_lua_block { + ngx.say(ngx.unescape_uri("%ua%%20%au")) + } + } +--- request +GET /lua +--- response_body +%ua% %au + + + +=== TEST 21: invalid unescape sequences where remain length less than 2 +--- config + location /lua { + content_by_lua_block { + ngx.say(ngx.unescape_uri("%a")) -- first character is good + ngx.say(ngx.unescape_uri("%u")) -- first character is bad + ngx.say(ngx.unescape_uri("%")) + ngx.say(ngx.unescape_uri("good%20job%")) + } + } +--- request +GET /lua +--- response_body +%a +%u +% +good job% diff --git a/t/009-log.t b/t/009-log.t index 68c057f82d..4446c71277 100644 --- a/t/009-log.t +++ b/t/009-log.t @@ -243,7 +243,7 @@ GET /log --- response_body 32 --- error_log eval -qr/\[error\] \S+: \S+ \[lua\] set_by_lua:2: HELLO,/ +qr/\[error\] \S+: \S+ \[lua\] set_by_lua\(nginx.conf:43\):2: HELLO,/ @@ -261,7 +261,7 @@ GET /log --- response_body 32 --- error_log eval -qr/\[error\] \S+: \S+ \[lua\] set_by_lua:2: truefalsenil,/ +qr/\[error\] \S+: \S+ \[lua\] set_by_lua\(nginx.conf:43\):2: truefalsenil,/ @@ -368,7 +368,7 @@ GET /log --- response_headers foo: 32 --- error_log eval -qr/\[notice\] .*? \[lua\] header_filter_by_lua:2: hello world/ +qr/\[notice\] .*? \[lua\] header_filter_by_lua\(nginx.conf:43\):2: hello world/ --- response_body hi @@ -390,7 +390,7 @@ foo: 32 --- response_body hi --- error_log eval -qr/\[error\] .*? \[lua\] header_filter_by_lua:2: howdy, lua!/ +qr/\[error\] .*? \[lua\] header_filter_by_lua\(nginx.conf:43\):2: howdy, lua!/ @@ -414,6 +414,8 @@ GET /log --- config location /log { content_by_lua ' + local foo + local bar function foo() bar() end @@ -431,7 +433,7 @@ GET /log --- response_body done --- error_log eval -qr/\[error\] \S+: \S+ \[lua\] content_by_lua\(nginx\.conf:\d+\):7: bar\(\): hello, log12343.14159/ +qr/\[error\] \S+: \S+ \[lua\] content_by_lua\(nginx\.conf:\d+\):9: bar\(\): hello, log12343.14159/ @@ -439,6 +441,8 @@ qr/\[error\] \S+: \S+ \[lua\] content_by_lua\(nginx\.conf:\d+\):7: bar\(\): hell --- config location /log { content_by_lua ' + local foo + local bar function foo() return bar(5) end @@ -461,7 +465,7 @@ GET /log --- response_body done --- error_log eval -qr/\[error\] \S+: \S+ \[lua\] content_by_lua\(nginx\.conf:\d+\):8:(?: foo\(\):)? hello, log12343.14159/ +qr/\[error\] \S+: \S+ \[lua\] content_by_lua\(nginx\.conf:\d+\):10:(?: foo\(\):)? hello, log12343.14159/ @@ -472,6 +476,8 @@ qr/\[error\] \S+: \S+ \[lua\] content_by_lua\(nginx\.conf:\d+\):8:(?: foo\(\):)? } --- user_files >>> test.lua +local foo +local bar function foo() bar() end @@ -488,7 +494,7 @@ GET /log --- response_body done --- error_log eval -qr/\[error\] \S+: \S+ \[lua\] test.lua:6: bar\(\): hello, log12343.14159/ +qr/\[error\] \S+: \S+ \[lua\] test.lua:8: bar\(\): hello, log12343.14159/ @@ -542,3 +548,23 @@ ok [error] --- error_log eval "2: hello\0world, client: " + + + +=== TEST 27: test log-level STDERR +Note: maximum number of digits after the decimal-point character is 13 +--- config + location /log { + content_by_lua_block { + ngx.say("before log") + ngx.log(ngx.STDERR, 3.14159265357939723846) + ngx.say("after log") + } + } +--- request +GET /log +--- response_body +before log +after log +--- error_log eval +qr/\[\] \S+: \S+ \[lua\] content_by_lua\(nginx\.conf:\d+\):3: 3.1415926535794/ diff --git a/t/010-request_body.t b/t/010-request_body.t index e669d9480e..c47f9fc13d 100644 --- a/t/010-request_body.t +++ b/t/010-request_body.t @@ -270,3 +270,22 @@ Expect: 100-Continue http finalize request: 500, "/echo_body?" a:1, c:2 http finalize request: 500, "/echo_body?" a:1, c:0 --- log_level: debug +--- skip_eval: 4:$ENV{TEST_NGINX_USE_HTTP3} + + + +=== TEST 13: test reading the first n bytes of request body +--- config + location /echo_body { + lua_need_request_body on; + content_by_lua_block { + local data = ngx.req.get_body_data(1) + ngx.say(data) + } + } +--- request +POST /echo_body +hello +--- response_body +h +--- skip_eval: 2:$ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/011-md5_bin.t b/t/011-md5_bin.t index ae7c974f49..dc0fd9c9b6 100644 --- a/t/011-md5_bin.t +++ b/t/011-md5_bin.t @@ -156,7 +156,7 @@ d41d8cd98f00b204e9800998ecf8427e --- config location = /t { content_by_lua ' - s = ngx.md5_bin(45) + local s = ngx.md5_bin(45) s = string.gsub(s, ".", function (c) return string.format("%02x", string.byte(c)) end) diff --git a/t/013-base64.t b/t/013-base64.t index 1cc27de5e9..d1a8d8b094 100644 --- a/t/013-base64.t +++ b/t/013-base64.t @@ -242,4 +242,4 @@ GET /t --- response_body_like: 500 Internal Server Error --- error_code: 500 --- error_log eval -qr/bad argument \#2 to 'encode_base64' \(boolean expected, got number\)|\[error\] .*? boolean argument only/ +qr/bad argument \#2 to 'encode_base64' \(boolean expected, got number\)|\[error\] .*? bad no_padding: boolean expected, got number/ diff --git a/t/014-bugs.t b/t/014-bugs.t index 9aadff0e25..b1c7d442ae 100644 --- a/t/014-bugs.t +++ b/t/014-bugs.t @@ -8,7 +8,8 @@ log_level('debug'); repeat_each(3); -plan tests => repeat_each() * (blocks() * 2 + 30); +# NB: the shutdown_error_log block is independent from repeat times +plan tests => repeat_each() * (blocks() * 2 + 41); our $HtmlDir = html_dir; #warn $html_dir; @@ -43,7 +44,7 @@ __DATA__ === TEST 1: sanity --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /load { content_by_lua ' @@ -69,7 +70,7 @@ end === TEST 2: sanity --- http_config -lua_package_path '/home/agentz/rpm/BUILD/lua-yajl-1.1/build/?.so;/home/lz/luax/?.so;./?.so'; +lua_package_cpath '/home/agentz/rpm/BUILD/lua-yajl-1.1/build/?.so;/home/lz/luax/?.so;./?.so'; --- config location = '/report/listBidwordPrices4lzExtra.htm' { content_by_lua ' @@ -112,7 +113,7 @@ GET /report/listBidwordPrices4lzExtra.htm?words=123,156,2532 } location = /main { content_by_lua ' - res = ngx.location.capture("/memc?c=get&k=foo&v=") + local res = ngx.location.capture("/memc?c=get&k=foo&v=") ngx.say("1: ", res.body) res = ngx.location.capture("/memc?c=set&k=foo&v=bar"); @@ -193,6 +194,14 @@ Hi" === TEST 8: github issue 37: header bug https://github.com/chaoslawful/lua-nginx-module/issues/37 + +https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2 + Just as in HTTP/1.x, header field names are strings of ASCII + characters that are compared in a case-insensitive fashion. However, + header field names MUST be converted to lowercase prior to their + encoding in HTTP/2. A request or response containing uppercase + header field names MUST be treated as malformed +--- no_http2 --- config location /sub { content_by_lua ' @@ -204,7 +213,7 @@ https://github.com/chaoslawful/lua-nginx-module/issues/37 content_by_lua ' -- local yajl = require "yajl" ngx.header["Set-Cookie"] = {} - res = ngx.location.capture("/sub") + local res = ngx.location.capture("/sub") for i,j in pairs(res.header) do ngx.header[i] = j @@ -219,8 +228,17 @@ https://github.com/chaoslawful/lua-nginx-module/issues/37 --- request GET /lua --- raw_response_headers_like eval -".*Set-Cookie: TestCookie1=foo\r +my $headers; + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $headers = ".*set-cookie: TestCookie1=foo\r +set-cookie: TestCookie2=bar.*" +} else { + $headers = ".*Set-Cookie: TestCookie1=foo\r Set-Cookie: TestCookie2=bar.*" +} + +$headers; @@ -231,7 +249,7 @@ Set-Cookie: TestCookie2=bar.*" } --- user_files >>> foo.lua -res = {} +local res = {} res = {'good 1', 'good 2', 'good 3'} return ngx.redirect("/somedir/" .. ngx.escape_uri(res[math.random(1,#res)])) --- request @@ -579,7 +597,7 @@ $s -=== TEST 26: unexpected globals sharing by using _G +=== TEST 26: globals sharing by using _G --- config location /test { content_by_lua ' @@ -593,12 +611,12 @@ $s } --- pipelined_requests eval ["GET /test", "GET /test", "GET /test"] ---- response_body eval -["0", "0", "0"] +--- response_body_like eval +[qr/\A[036]\z/, qr/\A[147]\z/, qr/\A[258]\z/] -=== TEST 27: unexpected globals sharing by using _G (set_by_lua*) +=== TEST 27: globals sharing by using _G (set_by_lua*) --- config location /test { set_by_lua $a ' @@ -613,12 +631,12 @@ $s } --- pipelined_requests eval ["GET /test", "GET /test", "GET /test"] ---- response_body eval -["0", "0", "0"] +--- response_body_like eval +[qr/\A[036]\z/, qr/\A[147]\z/, qr/\A[258]\z/] -=== TEST 28: unexpected globals sharing by using _G (log_by_lua*) +=== TEST 28: globals sharing by using _G (log_by_lua*) --- http_config lua_shared_dict log_dict 100k; --- config @@ -633,19 +651,19 @@ $s if _G.t then _G.t = _G.t + 1 else - _G.t = 0 + _G.t = 1 end log_dict:set("cnt", t) '; } --- pipelined_requests eval ["GET /test", "GET /test", "GET /test"] ---- response_body eval -["0", "0", "0"] +--- response_body_like eval +[qr/\A[036]\z/, qr/\A[147]\z/, qr/\A[258]\z/] -=== TEST 29: unexpected globals sharing by using _G (header_filter_by_lua*) +=== TEST 29: globals sharing by using _G (header_filter_by_lua*) --- config location /test { header_filter_by_lua ' @@ -663,12 +681,12 @@ $s } --- pipelined_requests eval ["GET /test", "GET /test", "GET /test"] ---- response_body eval -["0", "0", "0"] +--- response_body_like eval +[qr/\A[036]\z/, qr/\A[147]\z/, qr/\A[258]\z/] -=== TEST 30: unexpected globals sharing by using _G (body_filter_by_lua*) +=== TEST 30: globals sharing by using _G (body_filter_by_lua*) --- config location /test { body_filter_by_lua ' @@ -686,8 +704,9 @@ $s } --- request GET /test ---- response_body -a0 +--- response_body_like eval +qr/\Aa[036] +\z/ --- no_error_log [error] @@ -712,6 +731,7 @@ Content-Type: application/json; charset=utf-8 === TEST 32: hang on upstream_next (from kindy) +--- no_http2 --- no_check_leak --- http_config upstream xx { @@ -768,6 +788,10 @@ eof found in body stream === TEST 34: testing a segfault when using ngx_poll_module + ngx_resolver See more details here: http://mailman.nginx.org/pipermail/nginx-devel/2013-January/003275.html + +http3 may cache the dns result. +so need to skip for http3 +--- skip_eval: 2:$ENV{TEST_NGINX_USE_HTTP3} --- config location /t { set $myserver nginx.org; @@ -784,6 +808,8 @@ See more details here: http://mailman.nginx.org/pipermail/nginx-devel/2013-Janua [alert] --- error_log eval qr/(?:send|recv)\(\) failed \(\d+: Connection refused\) while resolving/ +--- curl_error eval +qr/curl: \(28\) Operation timed out after \d+ milliseconds with 0 bytes received/ @@ -802,7 +828,7 @@ qr/(?:send|recv)\(\) failed \(\d+: Connection refused\) while resolving/ rewrite ^/myproxy/(.*) /$1 break; resolver_timeout 3s; #resolver 172.16.0.23; # AWS DNS resolver address is the same in all regions - 172.16.0.23 - resolver 8.8.8.8; + resolver $TEST_NGINX_RESOLVER; proxy_read_timeout 1s; proxy_send_timeout 1s; proxy_connect_timeout 1s; @@ -846,7 +872,7 @@ ok === TEST 37: resolving names with a trailing dot --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -859,14 +885,15 @@ GET /t --- no_error_log [error] --- timeout: 10 +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} === TEST 38: resolving names with a trailing dot --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua'; + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; server { - listen 12354; + listen \$TEST_NGINX_RAND_PORT_1; location = /t { echo 'args: \$args'; @@ -876,7 +903,7 @@ GET /t --- config location = /t { set $args "foo=1&bar=2"; - proxy_pass http://127.0.0.1:12354; + proxy_pass http://127.0.0.1:$TEST_NGINX_RAND_PORT_1; } --- request @@ -891,7 +918,7 @@ args: foo=1&bar=2 === TEST 39: lua_code_cache off + setkeepalive --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config lua_code_cache off; location = /t { @@ -1018,3 +1045,413 @@ write timer set: 1 --- no_error_log [error] [alert] + + + +=== TEST 42: tcp: nginx crash when resolve an not exist domain in ngx.thread.spawn +https://github.com/openresty/lua-nginx-module/issues/1915 +--- config + resolver $TEST_NGINX_RESOLVER ipv6=off; + location = /t { + content_by_lua_block { + local function tcp(host, port) + local sock = ngx.socket.tcp() + local ok,err = sock:connect(host, port) + if not ok then + ngx.log(ngx.WARN, "failed: ", err) + sock:close() + return false + end + + sock:close() + return true + end + + local host = "nonexistent.openresty.org" + local port = 80 + + local threads = {} + for i = 1, 3 do + threads[i] = ngx.thread.spawn(tcp, host, port) + end + + local ok, res = ngx.thread.wait(threads[1],threads[2],threads[3]) + if not ok then + ngx.say("failed to wait thread") + return + end + + ngx.say("res: ", res) + + for i = 1, 3 do + ngx.thread.kill(threads[i]) + end + } + } + +--- request +GET /t +--- response_body +res: false +--- error_log +nonexistent.openresty.org could not be resolved + + + +=== TEST 43: domain exists with tcp socket +https://github.com/openresty/lua-nginx-module/issues/1915 +--- config + resolver $TEST_NGINX_RESOLVER ipv6=off; + location = /t { + content_by_lua_block { + local function tcp(host, port) + local sock = ngx.socket.tcp() + local ok,err = sock:connect(host, port) + if not ok then + ngx.log(ngx.WARN, "failed: ", err) + sock:close() + return false + end + + sock:close() + return true + end + + local host = "www.openresty.org" + local port = 80 + + local threads = {} + for i = 1, 3 do + threads[i] = ngx.thread.spawn(tcp, host, port) + end + + local ok, res = ngx.thread.wait(threads[1],threads[2],threads[3]) + if not ok then + ngx.say("failed to wait thread") + return + end + + ngx.say("res: ", res) + + for i = 1, 3 do + ngx.thread.kill(threads[i]) + end + } + } + +--- request +GET /t +--- response_body +res: true + + + +=== TEST 44: domain exists with udp socket +https://github.com/openresty/lua-nginx-module/issues/1915 +--- config + resolver $TEST_NGINX_RESOLVER ipv6=off; + location = /t { + content_by_lua_block { + local function udp(host, port) + local sock = ngx.socket.udp() + local ok,err = sock:setpeername(host, port) + if not ok then + ngx.log(ngx.WARN, "failed: ", err) + sock:close() + return false + end + + sock:close() + return true + end + + local host = "nonexistent.openresty.org" + local port = 80 + + local threads = {} + for i = 1, 3 do + threads[i] = ngx.thread.spawn(udp, host, port) + end + + local ok, res = ngx.thread.wait(threads[1],threads[2],threads[3]) + if not ok then + ngx.say("failed to wait thread") + return + end + + ngx.say("res: ", res) + + for i = 1, 3 do + ngx.thread.kill(threads[i]) + end + } + } + +--- request +GET /t +--- response_body +res: false +--- error_log +nonexistent.openresty.org could not be resolved + + + +=== TEST 45: udp: nginx crash when resolve an not exist domain in ngx.thread.spawn +https://github.com/openresty/lua-nginx-module/issues/1915 +--- config + resolver $TEST_NGINX_RESOLVER ipv6=off; + location = /t { + content_by_lua_block { + local function udp(host, port) + local sock = ngx.socket.udp() + local ok,err = sock:setpeername(host, port) + if not ok then + ngx.log(ngx.WARN, "failed: ", err) + sock:close() + return false + end + + sock:close() + return true + end + + local host = "www.openresty.org" + local port = 80 + + local threads = {} + for i = 1, 3 do + threads[i] = ngx.thread.spawn(udp, host, port) + end + + local ok, res = ngx.thread.wait(threads[1],threads[2],threads[3]) + if not ok then + ngx.say("failed to wait thread") + return + end + + ngx.say("res: ", res) + + for i = 1, 3 do + ngx.thread.kill(threads[i]) + end + } + } + +--- request +GET /t +--- response_body +res: true + + + +=== TEST 46: nginx crash when parsing a word or a single configuration item that is too long +https://github.com/openresty/lua-nginx-module/issues/1938 +--- http_config + init_worker_by_lua ' + err_big_str = 'A NA' + '; +--- config + location /t { + content_by_lua ' + ngx.say("hello world") + '; + } +--- request +GET /t +--- response_body +res: true +--- no_error_log +[error] +--- must_die +--- error_log eval +qr/\[emerg\] \d+#\d+: unexpected "A" in/ + + + +=== TEST 47: cosocket does not exit on worker_shutdown_timeout +This test must enable master process +--- SKIP +--- main_config +worker_shutdown_timeout 1; +--- config +location /t { + content_by_lua_block { + local function thread_func(port) + local sock = ngx.socket.tcp() + local ok, err = sock:connect("127.0.0.1", port) + local bytes, err = sock:send("hello") + if bytes ~= 5 then + sock:close() + return ngx.exit(500) + end + + local data, err = sock:receive(20) + local line, err, partial = sock:receive() + if not line then + ngx.log(ngx.ERR, "failed to read a line: ", err) + return + end + + ngx.log(ngx.ERR, "successfully read a line: ", line) + end + + local function timer_func(port) + ngx.thread.spawn(thread_func, port) + end + + ngx.timer.at(1, timer_func, ngx.var.server_port) + ngx.say("Hello world") + } +} +--- request + GET /t +--- response_body +Hello world +--- shutdown_error_log eval +my $expr; + +if ($ENV{TEST_NGINX_USE_HTTP3}) { + $expr = qr|lua close the global Lua VM| +} else { + $expr = qr/failed to read a line: closed|attempt to send data on a closed socket/ +} + +$expr; +--- timeout: 1.2 + + + +=== TEST 48: nginx crashes when encountering an illegal http if header +crash with ngx.send_headers() +--- main_config +--- config +error_page 412 /my_error_handler_412; + +location /t { + rewrite_by_lua_block { + ngx.send_headers() + -- ngx.print() -- this also triggers the bug + } +} +location = /my_error_handler_412 { + return 412 "hello"; +} +--- request + GET /t +--- more_headers +If-Match: 1 +--- error_code: 412 +--- response_body eval +qr/\Ahello\z/ + + + +=== TEST 49: nginx crashes when encountering an illegal http if header +crash with ngx.print() +--- main_config +--- config +error_page 412 /my_error_handler_412; + +location /t { + rewrite_by_lua_block { + ngx.print() + } +} +location = /my_error_handler_412 { + return 412 "hello"; +} +--- request + GET /t +--- more_headers +If-Match: 1 +--- error_code: 412 +--- response_body eval +qr/\Ahello\z/ + + + +=== TEST 50: nginx crashes when encountering an illegal http if header +crash with ngx.print() +--- main_config +--- config +error_page 412 /my_error_handler_412; + +location /t { + access_by_lua_block { + local ngx_resp = require "ngx.resp" + ngx_resp.bypass_if_checks() + ngx.print("hello") + ngx.exit(200) + } +} +location = /my_error_handler_412 { + content_by_lua_block { + ngx.sleep(0.002) + ngx.header["Content-Type"] = "text/plain" + } +} +--- request + GET /t +--- more_headers +If-Match: 1 +--- error_code: 200 +--- response_body eval +qr/\Ahello\z/ + + + +=== TEST 51: subrequest cycle problem in rewrite_by_lua_file +--- http_config + lua_code_cache off; +--- config + set $main "foo"; + set $sub "bar"; + location = /main { + rewrite_by_lua_file html/main.lua; + echo $main; + } + + location = /sub { + rewrite_by_lua_file html/sub.lua; + echo $sub; + } +--- user_files +>>> main.lua +local res = ngx.location.capture("/sub") +ngx.var.main = "main " .. res.body +>>> sub.lua +ngx.var.sub = "sub" + +--- pipelined_requests eval +["GET /sub", "GET /main"] +--- response_body eval +["sub\n", "main sub\n\n"] +--- no_error_log +[error] + + + +=== TEST 52: subrequest cycle problem in content_by_lua_file +--- http_config + lua_code_cache off; +--- config + location = /main { + content_by_lua_file html/main.lua; + } + + location = /sub { + content_by_lua_file html/sub.lua; + } +--- user_files +>>> main.lua +local res = ngx.location.capture("/sub") +ngx.print("main " .. res.body) +>>> sub.lua +ngx.print("sub") + +--- pipelined_requests eval +["GET /sub", "GET /main"] +--- response_body eval +["sub", "main sub"] +--- no_error_log +[error] diff --git a/t/015-status.t b/t/015-status.t index 666dae727c..d3f6a3b4cf 100644 --- a/t/015-status.t +++ b/t/015-status.t @@ -9,7 +9,7 @@ log_level('warn'); #repeat_each(120); repeat_each(2); -plan tests => repeat_each() * (blocks() * 2 + 9); +plan tests => repeat_each() * (blocks() * 3 + 2); #no_diff(); #no_long_string(); @@ -29,6 +29,8 @@ __DATA__ GET /nil --- response_body nil +--- no_error_log +[error] @@ -43,6 +45,8 @@ nil GET /nil --- response_body not nil +--- no_error_log +[error] @@ -57,6 +61,8 @@ not nil GET /nil --- response_body 0 +--- no_error_log +[error] @@ -73,6 +79,8 @@ GET /nil --- response_body blah 200 +--- no_error_log +[error] @@ -89,6 +97,8 @@ GET /201 --- response_body created --- error_code: 201 +--- no_error_log +[error] @@ -105,6 +115,8 @@ GET /201 --- response_body created --- error_code: 201 +--- no_error_log +[error] @@ -121,6 +133,8 @@ GET /201 --- response_body created --- error_code: 201 +--- no_error_log +[error] @@ -136,6 +150,8 @@ created GET /201 --- response_body_like: 500 Internal Server Error --- error_code: 500 +--- no_error_log +[crit] @@ -233,6 +249,8 @@ GET /t --- error_code: 101 --- no_error_log [error] +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} +--- no_http2 @@ -246,6 +264,8 @@ GET /t --- http09 --- response_body status = 9 +--- no_error_log +[error] @@ -272,7 +292,7 @@ ngx.status = 502 -=== TEST 16: ngx.status assignmnt should clear r->err_status +=== TEST 16: ngx.status assignment should clear r->err_status --- config location = /t { return 502; @@ -291,3 +311,221 @@ ngx.status: 654 --- no_error_log [error] --- error_code: 654 + + + +=== TEST 17: set status and reason +--- config +location = /upstream { + content_by_lua_block { + local resp = require "ngx.resp" + resp.set_status(500, "user defined reason") + ngx.say("set_status_and_reason") + } +} + +location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local port = ngx.var.server_port + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local req = "GET /upstream HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + local found = false + while true do + local line, err, part = sock:receive() + if line then + if ngx.re.find(line, "HTTP/1.1 500 user defined reason") then + ngx.say("match") + end + else + break + end + end + + sock:close() + } +} +--- request +GET /t +--- response_body +match +--- no_error_log +[error] + + + +=== TEST 18: set ngx.status in server_rewrite_by_lua_block +don't proxy_pass to upstream +--- config + server_rewrite_by_lua_block { + if ngx.var.uri == "/t" then + ngx.status = 403 + ngx.say("Hello World") + end + } + + location /t { + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/u; + } + + location /u { + content_by_lua_block { + ngx.say("From upstream") + } + } +--- request +GET /t HTTP/1.0 +--- response_body +Hello World +--- error_code: 403 +--- no_error_log +[error] + + + +=== TEST 19: set ngx.status in rewrite_by_lua_block +don't proxy_pass to upstream +--- config + location /t { + rewrite_by_lua_block { + ngx.status = 403 + ngx.say("Hello World") + } + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/u; + } + + location /u { + content_by_lua_block { + ngx.say("From upstream") + } + } +--- request +GET /t HTTP/1.0 +--- response_body +Hello World +--- error_code: 403 +--- no_error_log +[error] + + + +=== TEST 20: set ngx.status in access_by_lua_block +don't proxy_pass to upstream +--- config + location /t { + access_by_lua_block { + ngx.status = 403 + ngx.say("Hello World") + } + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/u; + } + + location /u { + content_by_lua_block { + ngx.say("From upstream") + } + } +--- request +GET /t HTTP/1.0 +--- response_body +Hello World +--- error_code: 403 +--- no_error_log +[error] + + + +=== TEST 21: set ngx.status in server_rewrite_by_lua_block with sleep +don't proxy_pass to upstream +--- config + server_rewrite_by_lua_block { + if ngx.var.uri == "/t" then + ngx.sleep(0.001) + ngx.status = 403 + ngx.say("Hello World") + end + } + + location /t { + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/u; + } + + location /u { + content_by_lua_block { + ngx.say("From upstream") + } + } +--- request +GET /t HTTP/1.0 +--- response_body +Hello World +--- error_code: 403 +--- no_error_log +[error] + + + +=== TEST 22: set ngx.status in rewrite_by_lua_block with sleep +don't proxy_pass to upstream +--- config + location /t { + rewrite_by_lua_block { + ngx.sleep(0.001) + ngx.status = 403 + ngx.say("Hello World") + } + + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/u; + } + + location /u { + content_by_lua_block { + ngx.say("From upstream") + } + } +--- request +GET /t HTTP/1.0 +--- response_body +Hello World +--- error_code: 403 +--- no_error_log +[error] + + + +=== TEST 23: set ngx.status in access_by_lua_block +don't proxy_pass to upstream +--- config + location /t { + access_by_lua_block { + ngx.sleep(0.001) + ngx.status = 403 + ngx.say("Hello World") + } + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/u; + } + + location /u { + content_by_lua_block { + ngx.say("From upstream") + } + } +--- request +GET /t HTTP/1.0 +--- response_body +Hello World +--- error_code: 403 +--- no_error_log +[error] diff --git a/t/016-resp-header.t b/t/016-resp-header.t index 1fae2922a5..f5c58fa855 100644 --- a/t/016-resp-header.t +++ b/t/016-resp-header.t @@ -8,7 +8,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); -plan tests => repeat_each() * (blocks() * 3 + 41); +plan tests => repeat_each() * (blocks() * 3 + 79); #no_diff(); no_long_string(); @@ -51,7 +51,7 @@ Content-Type: text/html -=== TEST 3: set response content-type header +=== TEST 3: set response content-length header --- config location /read { content_by_lua ' @@ -65,6 +65,7 @@ GET /read Content-Length: 3 --- response_body chop Hel +--- skip_eval: 3:defined($ENV{TEST_NGINX_USE_HTTP3}) || defined($ENV{TEST_NGINX_USE_HTTP2}) @@ -113,8 +114,16 @@ Hello } --- request GET /read ---- raw_response_headers_like chomp -X-Foo: a\r\n.*?X-Foo: bc\r\n +--- raw_response_headers_like eval +my $headers; + +if (defined($ENV{TEST_NGINX_USE_HTTP3}) || defined($ENV{TEST_NGINX_USE_HTTP2})) { + $headers = qr/x-foo: a\r\n.*?x-foo: bc\r\n/ +} else { + $headers = qr/X-Foo: a\r\n.*?X-Foo: bc\r\n/ +} + +$headers; --- response_body Hello @@ -184,8 +193,16 @@ Hello } --- request GET /read ---- raw_response_headers_like chomp -X-Foo: a\r\n.*?X-Foo: abc\r\n +--- raw_response_headers_like eval +my $headers; + +if (defined($ENV{TEST_NGINX_USE_HTTP3}) || defined($ENV{TEST_NGINX_USE_HTTP2})) { + $headers = "x-foo: a\r\n.*?x-foo: abc\r\n" +} else { + $headers = "X-Foo: a\r\n.*?X-Foo: abc\r\n" +} + +$headers; --- response_body Hello @@ -203,8 +220,17 @@ Hello --- request GET /lua --- raw_response_headers_like eval -".*Foo: a\r -Foo: b.*" +my $headers; + +if (defined($ENV{TEST_NGINX_USE_HTTP3}) || defined($ENV{TEST_NGINX_USE_HTTP2})) { + $headers = ".*foo: a\r +foo: b.*"; +} else { + $headers = ".*Foo: a\r +Foo: b.*"; +} + +$headers; --- response_body @@ -222,8 +248,17 @@ Foo: b.*" --- request GET /lua --- raw_response_headers_like eval -".*Foo: a\r -Foo: b.*" +my $headers; + +if (defined($ENV{TEST_NGINX_USE_HTTP3}) || defined($ENV{TEST_NGINX_USE_HTTP2})) { + $headers = ".*foo: a\r +foo: b.*"; +} else { + $headers = ".*Foo: a\r +Foo: b.*"; +} + +$headers; --- response_body @@ -263,6 +298,7 @@ Fooy: cony1, cony2 === TEST 15: set header after ngx.print +--- no_http2 --- config location /lua { default_type "text/plain"; @@ -278,7 +314,7 @@ hello --- error_log attempt to set ngx.header.HEADER after sending out response headers --- no_error_log eval -["alert", "warn"] +["[alert]", "[warn]"] @@ -630,7 +666,46 @@ Cache-Control: private, no-store -=== TEST 32: set multi values to cache-control and override it with a single value +=== TEST 32: set single value to Link header +--- config + location = /t { + content_by_lua_block { + ngx.header.link = "; rel=preload" + ngx.say("Link: ", ngx.var.sent_http_link) + } + } +--- request +GET /t +--- response_headers +Link: ; rel=preload +--- response_body +Link: ; rel=preload + + + +=== TEST 33: set multi values to Link header +--- config + location = /t { + content_by_lua_block { + ngx.header.link = { + "; rel=preload", + "; rel=preload; as=style" + } + + ngx.say("Link: ", ngx.var.sent_http_link) + } + } +--- request +GET /t +--- response_headers +Link: ; rel=preload, ; rel=preload; as=style +--- response_body_like chop +^Link: ; rel=preload[;,] ; rel=preload; as=style$ +--- skip_nginx: 3: < 1.13.9 + + + +=== TEST 34: set multi values to cache-control and override it with a single value --- config location /lua { content_by_lua ' @@ -650,7 +725,30 @@ Cache-Control: no-cache -=== TEST 33: set multi values to cache-control and override it with multiple values +=== TEST 35: set multi values to Link header and override it with a single value +--- config + location /lua { + content_by_lua_block { + ngx.header.link = { + "; rel=preload", + "; rel=preload; as=style" + } + ngx.header.link = "; rel=preload" + ngx.say("Link: ", ngx.var.sent_http_link) + ngx.say("Link: ", ngx.header.link) + } + } +--- request + GET /lua +--- response_headers +Link: ; rel=preload +--- response_body +Link: ; rel=preload +Link: ; rel=preload + + + +=== TEST 36: set multi values to cache-control and override it with multiple values --- config location /lua { content_by_lua ' @@ -672,7 +770,37 @@ Cache-Control: no-cache[;,] blah[;,] foo$ -=== TEST 34: set the www-authenticate response header +=== TEST 37: set multi values to Link header and override it with multiple values +--- config + location /lua { + content_by_lua_block { + ngx.header.link = { + "; rel=preload", + "; rel=preload; as=style" + } + ngx.header.link = { + "; rel=preload", + "; rel=preload", + "; rel=preload; as=style" + } + ngx.say("Link: ", ngx.var.sent_http_link) + ngx.say("Link: ", table.concat(ngx.header.link, ", ")) + } + } +--- request + GET /lua +--- response_headers +Link: ; rel=preload, ; rel=preload, ; rel=preload; as=style +--- response_body_like chop +^Link: ; rel=preload[;,] ; rel=preload[;,] ; rel=preload; as=style +Link: ; rel=preload[;,] ; rel=preload[;,] ; rel=preload; as=style$ +--- no_error_log +[error] +--- skip_nginx: 4: < 1.13.9 + + + +=== TEST 38: set the www-authenticate response header --- config location /lua { content_by_lua ' @@ -689,7 +817,7 @@ WWW-Authenticate: blah -=== TEST 35: set and clear the www-authenticate response header +=== TEST 39: set and clear the www-authenticate response header --- config location /lua { content_by_lua ' @@ -707,7 +835,7 @@ Foo: nil -=== TEST 36: set multi values to cache-control and override it with multiple values (to reproduce a bug) +=== TEST 40: set multi values to cache-control and override it with multiple values (to reproduce a bug) --- config location /lua { content_by_lua ' @@ -727,7 +855,7 @@ Cache-Control: blah -=== TEST 37: set last-modified and return 304 +=== TEST 41: set last-modified and return 304 --- config location /lua { content_by_lua ' @@ -745,7 +873,7 @@ Last-Modified: Thu, 18 Nov 2010 11:27:35 GMT -=== TEST 38: set last-modified and return 200 +=== TEST 42: set last-modified and return 200 --- config location /lua { content_by_lua ' @@ -764,7 +892,7 @@ Thu, 18 Nov 2010 11:27:35 GMT -=== TEST 39: set response content-encoding header should bypass ngx_http_gzip_filter_module +=== TEST 43: set response content-encoding header should bypass ngx_http_gzip_filter_module --- config default_type text/plain; gzip on; @@ -781,13 +909,16 @@ GET /read --- more_headers Accept-Encoding: gzip --- response_headers -Content-Type: text/plain +Content-Encoding: gzip +--- no_error_log +[error] +http gzip filter --- response_body Hello, world, my dear friend! -=== TEST 40: no transform underscores (write) +=== TEST 44: no transform underscores (write) --- config lua_transform_underscores_in_response_headers off; location = /t { @@ -807,7 +938,7 @@ nil -=== TEST 41: with transform underscores (write) +=== TEST 45: with transform underscores (write) --- config lua_transform_underscores_in_response_headers on; location = /t { @@ -827,7 +958,7 @@ Hello -=== TEST 42: github issue #199: underscores in lua variables +=== TEST 46: github issue #199: underscores in lua variables --- config location /read { content_by_lua ' @@ -836,7 +967,7 @@ Hello local results = {} results.something = "hello" results.content_type = "anything" - results.somehing_else = "hi" + results.something_else = "hi" local arr = {} for k in pairs(results) do table.insert(arr, k) end @@ -853,14 +984,14 @@ Content-Type: text/my-plain --- response_body content_type: anything -somehing_else: hi something: hello +something_else: hi --- no_error_log [error] -=== TEST 43: set multiple response header +=== TEST 47: set multiple response header --- config location /read { content_by_lua ' @@ -880,7 +1011,7 @@ text/my-plain-50 -=== TEST 44: set multiple response header and then reset and then clear +=== TEST 48: set multiple response header and then reset and then clear --- config location /read { content_by_lua ' @@ -909,7 +1040,7 @@ ok -=== TEST 45: set response content-type header for multiple times +=== TEST 49: set response content-type header for multiple times --- config location /read { content_by_lua ' @@ -927,7 +1058,7 @@ Hi -=== TEST 46: set Last-Modified response header for multiple times +=== TEST 50: set Last-Modified response header for multiple times --- config location /read { content_by_lua ' @@ -945,7 +1076,7 @@ ok -=== TEST 47: set Last-Modified response header and then clear +=== TEST 51: set Last-Modified response header and then clear --- config location /read { content_by_lua ' @@ -963,7 +1094,7 @@ ok -=== TEST 48: github #20: segfault caused by the nasty optimization in the nginx core (write) +=== TEST 52: github #20: segfault caused by the nasty optimization in the nginx core (write) --- config location = /t/ { header_filter_by_lua ' @@ -976,8 +1107,8 @@ GET /t --- more_headers Foo: bar Bah: baz ---- response_headers -Location: http://localhost:$ServerPort/t/ +--- response_headers_like +Location: https?://localhost:\d+/t/ --- response_body_like: 301 Moved Permanently --- error_code: 301 --- no_error_log @@ -985,7 +1116,7 @@ Location: http://localhost:$ServerPort/t/ -=== TEST 49: github #20: segfault caused by the nasty optimization in the nginx core (read) +=== TEST 53: github #20: segfault caused by the nasty optimization in the nginx core (read) --- config location = /t/ { header_filter_by_lua ' @@ -999,15 +1130,15 @@ GET /t Foo: bar Bah: baz --- response_body_like: 301 Moved Permanently ---- response_headers -Location: http://localhost:$ServerPort/t/ +--- response_headers_like +Location: https?://localhost:\d+/t/ --- error_code: 301 --- no_error_log [error] -=== TEST 50: github #20: segfault caused by the nasty optimization in the nginx core (read Location) +=== TEST 54: github #20: segfault caused by the nasty optimization in the nginx core (read Location) --- config location = /t/ { header_filter_by_lua ' @@ -1020,8 +1151,8 @@ GET /t --- more_headers Foo: bar Bah: baz ---- response_headers -Location: http://localhost:$ServerPort/t/ +--- response_headers_like +Location: https?://localhost:\d+/t/ Foo: /t/ --- response_body_like: 301 Moved Permanently --- error_code: 301 @@ -1030,7 +1161,7 @@ Foo: /t/ -=== TEST 51: github #20: segfault caused by the nasty optimization in the nginx core (set Foo and read Location) +=== TEST 55: github #20: segfault caused by the nasty optimization in the nginx core (set Foo and read Location) --- config location = /t/ { header_filter_by_lua ' @@ -1044,8 +1175,8 @@ GET /t --- more_headers Foo: bar Bah: baz ---- response_headers -Location: http://localhost:$ServerPort/t/ +--- response_headers_like +Location: https?://localhost:\d+/t/ Foo: /t/ --- response_body_like: 301 Moved Permanently --- error_code: 301 @@ -1054,7 +1185,7 @@ Foo: /t/ -=== TEST 52: case sensitive cache-control header +=== TEST 56: case sensitive cache-control header --- config location /lua { content_by_lua ' @@ -1064,14 +1195,39 @@ Foo: /t/ } --- request GET /lua ---- raw_response_headers_like chop -cache-Control: private +--- raw_response_headers_like eval +my $headers; + +if (defined($ENV{TEST_NGINX_USE_HTTP3}) || defined($ENV{TEST_NGINX_USE_HTTP2})) { + $headers = "cache-control: private" +} else { + $headers = "cache-Control: private" +} + +$headers; --- response_body Cache-Control: private -=== TEST 53: clear Cache-Control when there was no Cache-Control +=== TEST 57: case sensitive Link header +--- config + location /lua { + content_by_lua_block { + ngx.header["link"] = "; rel=preload" + ngx.say("Link: ", ngx.var.sent_http_link) + } + } +--- request + GET /lua +--- raw_response_headers_like chop +link: ; rel=preload +--- response_body +Link: ; rel=preload + + + +=== TEST 58: clear Cache-Control when there was no Cache-Control --- config location /lua { content_by_lua ' @@ -1088,7 +1244,24 @@ Cache-Control: nil -=== TEST 54: set response content-type header +=== TEST 59: clear Link header when there was no Link +--- config + location /lua { + content_by_lua_block { + ngx.header["Link"] = nil + ngx.say("Link: ", ngx.var.sent_http_link) + } + } +--- request + GET /lua +--- raw_response_headers_unlike eval +qr/Link/i +--- response_body +Link: nil + + + +=== TEST 60: set response content-type header --- config location /read { content_by_lua ' @@ -1107,7 +1280,7 @@ s = content_type -=== TEST 55: set a number header name +=== TEST 61: set a number header name --- config location /lua { content_by_lua ' @@ -1126,7 +1299,7 @@ s = content_type -=== TEST 56: set a number header name (in a table value) +=== TEST 62: set a number header name (in a table value) --- config location /lua { content_by_lua ' @@ -1145,16 +1318,29 @@ foo: 32 -=== TEST 57: random access resp headers +=== TEST 63: random access resp headers --- config location /resp-header { content_by_lua ' ngx.header["Foo"] = "bar" ngx.header["Bar"] = "baz" - ngx.say("Foo: ", ngx.resp.get_headers()["Foo"] or "nil") - ngx.say("foo: ", ngx.resp.get_headers()["foo"] or "nil") - ngx.say("Bar: ", ngx.resp.get_headers()["Bar"] or "nil") - ngx.say("bar: ", ngx.resp.get_headers()["bar"] or "nil") + local headers, err = ngx.resp.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + ngx.say("Foo: ", headers["Foo"] or "nil") + ngx.say("foo: ", headers["foo"] or "nil") + ngx.say("Bar: ", headers["Bar"] or "nil") + + headers, err = ngx.resp.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + ngx.say("bar: ", headers["bar"] or "nil") '; } --- request @@ -1167,17 +1353,26 @@ Foo: bar foo: bar Bar: baz bar: baz +--- no_error_log +[error] -=== TEST 58: iterating through raw resp headers +=== TEST 64: iterating through raw resp headers --- config location /resp-header { content_by_lua ' ngx.header["Foo"] = "bar" ngx.header["Bar"] = "baz" + + local headers, err = ngx.resp.get_headers(nil, true) + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + local h = {} - for k, v in pairs(ngx.resp.get_headers(nil, true)) do + for k, v in pairs(headers) do h[k] = v end ngx.say("Foo: ", h["Foo"] or "nil") @@ -1199,17 +1394,24 @@ bar: nil -=== TEST 59: removed response headers +=== TEST 65: removed response headers --- config location /resp-header { content_by_lua ' ngx.header["Foo"] = "bar" ngx.header["Foo"] = nil ngx.header["Bar"] = "baz" - ngx.say("Foo: ", ngx.resp.get_headers()["Foo"] or "nil") - ngx.say("foo: ", ngx.resp.get_headers()["foo"] or "nil") - ngx.say("Bar: ", ngx.resp.get_headers()["Bar"] or "nil") - ngx.say("bar: ", ngx.resp.get_headers()["bar"] or "nil") + + local headers, err = ngx.resp.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + ngx.say("Foo: ", headers["Foo"] or "nil") + ngx.say("foo: ", headers["foo"] or "nil") + ngx.say("Bar: ", headers["Bar"] or "nil") + ngx.say("bar: ", headers["bar"] or "nil") '; } --- request @@ -1225,7 +1427,7 @@ bar: baz -=== TEST 60: built-in Content-Type header +=== TEST 66: built-in Content-Type header --- config location = /t { content_by_lua ' @@ -1233,7 +1435,12 @@ bar: baz '; header_filter_by_lua ' - local hs = ngx.resp.get_headers() + local hs, err = ngx.resp.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + print("my Content-Type: ", hs["Content-Type"]) print("my content-type: ", hs["content-type"]) print("my content_type: ", hs["content_type"]) @@ -1253,7 +1460,7 @@ my content_type: text/plain -=== TEST 61: built-in Content-Length header +=== TEST 67: built-in Content-Length header --- config location = /t { content_by_lua ' @@ -1261,7 +1468,12 @@ my content_type: text/plain '; header_filter_by_lua ' - local hs = ngx.resp.get_headers() + local hs, err = ngx.resp.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + print("my Content-Length: ", hs["Content-Length"]) print("my content-length: ", hs["content-length"]) print("my content_length: ", hs.content_length) @@ -1281,7 +1493,7 @@ my content_length: 3 -=== TEST 62: built-in Connection header +=== TEST 68: built-in Connection header --- config location = /t { content_by_lua ' @@ -1289,7 +1501,12 @@ my content_length: 3 '; header_filter_by_lua ' - local hs = ngx.resp.get_headers() + local hs, err = ngx.resp.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + print("my Connection: ", hs["Connection"]) print("my connection: ", hs["connection"]) '; @@ -1307,7 +1524,7 @@ my connection: close -=== TEST 63: built-in Transfer-Encoding header (chunked) +=== TEST 69: built-in Transfer-Encoding header (chunked) --- config location = /t { content_by_lua ' @@ -1315,7 +1532,12 @@ my connection: close '; body_filter_by_lua ' - local hs = ngx.resp.get_headers() + local hs, err = ngx.resp.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + print("my Transfer-Encoding: ", hs["Transfer-Encoding"]) print("my transfer-encoding: ", hs["transfer-encoding"]) print("my transfer_encoding: ", hs.transfer_encoding) @@ -1331,10 +1553,11 @@ hi --- error_log my Transfer-Encoding: chunked my transfer-encoding: chunked +--- skip_eval: 6:defined($ENV{TEST_NGINX_USE_HTTP3}) || defined($ENV{TEST_NGINX_USE_HTTP2}) -=== TEST 64: built-in Transfer-Encoding header (none) +=== TEST 70: built-in Transfer-Encoding header (none) --- config location = /t { content_by_lua ' @@ -1342,7 +1565,12 @@ my transfer-encoding: chunked '; body_filter_by_lua ' - local hs = ngx.resp.get_headers() + local hs, err = ngx.resp.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + print("my Transfer-Encoding: ", hs["Transfer-Encoding"]) print("my transfer-encoding: ", hs["transfer-encoding"]) print("my transfer_encoding: ", hs.transfer_encoding) @@ -1362,7 +1590,7 @@ my transfer_encoding: nil -=== TEST 65: set Location (no host) +=== TEST 71: set Location (no host) --- config location = /t { content_by_lua ' @@ -1381,7 +1609,7 @@ Location: /foo/bar -=== TEST 66: set Location (with host) +=== TEST 72: set Location (with host) --- config location = /t { content_by_lua ' @@ -1400,7 +1628,7 @@ Location: http://test.com/foo/bar -=== TEST 67: ngx.header["Content-Type"] with ngx_gzip +=== TEST 73: ngx.header["Content-Type"] with ngx_gzip --- config gzip on; gzip_min_length 1; @@ -1424,7 +1652,7 @@ Content-Type: text/html; charset=utf-8 -=== TEST 68: ngx.header["Content-Type"] with "; blah" +=== TEST 74: ngx.header["Content-Type"] with "; blah" --- config location = /test2 { content_by_lua ' @@ -1444,68 +1672,602 @@ test -=== TEST 69: return the matched content-type instead of default_type ---- http_config -types { - image/png png; -} +=== TEST 75: exceeding max header limit (default 100) --- config -location /set/ { - default_type text/html; - content_by_lua_block { - ngx.say(ngx.header["content-type"]) - } -} + location /resp-header { + content_by_lua_block { + for i = 1, 100 do + ngx.header["Foo" .. i] = "Foo" + end + + local headers, err = ngx.resp.get_headers() + if err then + ngx.say("err: ", err) + end + + local cnt = 0 + for k, v in pairs(headers) do + cnt = cnt + 1 + end + + ngx.say("found ", cnt, " resp headers"); + } + } +--- request +GET /resp-header +--- response_body +err: truncated +found 100 resp headers +--- no_error_log +[error] +--- log_level: debug +--- error_log +lua exceeding response header limit 101 > 100 + + + +=== TEST 76: NOT exceeding max header limit (default 100) +--- config + location /resp-header { + content_by_lua_block { + for i = 1, 99 do + ngx.header["Foo" .. i] = "Foo" + end + + local headers, err = ngx.resp.get_headers() + if err then + ngx.say("err: ", err) + end + + local cnt = 0 + for k, v in pairs(headers) do + cnt = cnt + 1 + end + + ngx.say("found ", cnt, " resp headers"); + } + } +--- request +GET /resp-header +--- response_body +found 100 resp headers +--- no_error_log +[error] +lua exceeding response header limit +--- log_level: debug + + + +=== TEST 77: exceeding max header limit (custom limit, 3) +--- config + location /resp-header { + content_by_lua_block { + for i = 1, 3 do + ngx.header["Foo" .. i] = "Foo" + end + + local headers, err = ngx.resp.get_headers(3) + if err then + ngx.say("err: ", err) + end + + local cnt = 0 + for k, v in pairs(headers) do + cnt = cnt + 1 + end + + ngx.say("found ", cnt, " resp headers"); + } + } +--- request +GET /resp-header +--- response_body +err: truncated +found 3 resp headers +--- no_error_log +[error] +--- error_log +lua exceeding response header limit 4 > 3 +--- log_level: debug + + + +=== TEST 78: NOT exceeding max header limit (custom limit, 3) +--- config + location /resp-header { + content_by_lua_block { + for i = 1, 2 do + ngx.header["Foo" .. i] = "Foo" + end + + local headers, err = ngx.resp.get_headers(3) + if err then + ngx.say("err: ", err) + end + + local cnt = 0 + for k, v in pairs(headers) do + cnt = cnt + 1 + end + + ngx.say("found ", cnt, " resp headers"); + } + } +--- request +GET /resp-header +--- response_body +found 3 resp headers +--- no_error_log +[error] +lua exceeding response header limit + + + +=== TEST 79: return nil if Content-Type is not set yet +--- config + location /t { + default_type text/html; + content_by_lua_block { + ngx.log(ngx.WARN, "Content-Type: ", ngx.header["content-type"]) + ngx.say("Content-Type: ", ngx.header["content-type"]) + } + } --- request -GET /set/hello.png +GET /t --- response_headers -Content-Type: image/png +Content-Type: text/html --- response_body -image/png +Content-Type: nil --- no_error_log [error] +--- error_log +Content-Type: nil -=== TEST 70: always return the matched content-type +=== TEST 80: don't generate Content-Type when setting other response header --- config - location /set/ { - default_type "image/png"; + location = /backend { content_by_lua_block { - ngx.say(ngx.header["content-type"]) - ngx.say(ngx.header["content-type"]) + ngx.say("foo") + } + header_filter_by_lua_block { + ngx.header.content_type = nil } } + + location = /t { + default_type text/html; + rewrite_by_lua_block { + ngx.header.blah = "foo" + } + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/backend; + } --- request -GET /set/hello.png +GET /t +--- response_body +foo --- response_headers -Content-Type: image/png +blah: foo +!Content-Type +--- no_error_log +[error] + + + +=== TEST 81: don't generate Content-Type when getting other response header +--- config + location = /backend { + content_by_lua_block { + ngx.say("foo") + } + header_filter_by_lua_block { + ngx.header.content_type = nil + } + } + + location = /t { + default_type text/html; + rewrite_by_lua_block { + local h = ngx.header.content_length + } + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/backend; + } +--- request +GET /t --- response_body -image/png -image/png +foo +--- response_headers +!Content-Type --- no_error_log [error] -=== TEST 71: return the matched content-type after ngx.resp.get_headers() ---- http_config -types { - image/png png; -} +=== TEST 82: don't generate Content-Type when getting it +--- config + location = /backend { + content_by_lua_block { + ngx.say("foo") + } + header_filter_by_lua_block { + ngx.header.content_type = nil + } + } + + location /t { + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/backend; + header_filter_by_lua_block { + ngx.log(ngx.WARN, "Content-Type: ", ngx.header["content-type"]) + } + } +--- request +GET /t +--- response_body +foo +--- response_headers +!Content-Type +--- no_error_log +[error] +--- error_log +Content-Type: nil + + + +=== TEST 83: generate default Content-Type when setting other response header +--- config + location = /t { + default_type text/html; + content_by_lua_block { + ngx.header.blah = "foo" + ngx.say("foo") + } + } +--- request +GET /t +--- response_body +foo +--- response_headers +blah: foo +Content-Type: text/html +--- no_error_log +[error] + + + +=== TEST 84: don't generate Content-Type when calling ngx.resp.get_headers() +--- config + location = /backend { + content_by_lua_block { + ngx.say("foo") + } + header_filter_by_lua_block { + ngx.header.content_type = nil + } + } + + location /t { + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/backend; + header_filter_by_lua_block { + local h, err = ngx.resp.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return + end + + ngx.log(ngx.WARN, "Content-Type: ", h["content-type"]) + } + } +--- request +GET /t +--- response_body +foo +--- response_headers +!Content-Type +--- no_error_log +[error] +--- error_log +Content-Type: nil + + + +=== TEST 85: don't generate default Content-Type when Content-Type is cleared --- config - location /set/ { + location = /t { default_type text/html; content_by_lua_block { - local h = ngx.resp.get_headers() - ngx.say(h["content-type"]) + ngx.header["Content-Type"] = nil + ngx.say("foo") + } + } +--- request +GET /t +--- response_body +foo +--- response_headers +!Content-Type +--- no_error_log +[error] + + + +=== TEST 86: don't generate default Content-Type when Content-Type is set +--- config + location = /t { + default_type text/html; + content_by_lua_block { + ngx.header["Content-Type"] = "application/json" + ngx.say("foo") + } + } +--- request +GET /t +--- response_body +foo +--- response_headers +Content-Type: application/json +--- no_error_log +[error] + + + +=== TEST 87: unsafe header value (with '\r') +--- config + location = /t { + content_by_lua_block { + ngx.header.header = "value\rfoo:bar\nbar:foo" + ngx.say("foo") } } --- request -GET /set/hello.png +GET /t --- response_headers -Content-Type: image/png +header: value%0Dfoo:bar%0Abar:foo +foo: +bar: +--- no_error_log +[error] + + + +=== TEST 88: unsafe header value (with '\n') +--- config + location = /t { + content_by_lua_block { + ngx.header.header = "value\nfoo:bar\rbar:foo" + ngx.say("foo") + } + } +--- request +GET /t +--- response_headers +header: value%0Afoo:bar%0Dbar:foo +foo: +bar: +--- no_error_log +[error] + + + +=== TEST 89: unsafe header name (with '\r') +--- config + location = /t { + content_by_lua_block { + ngx.header["header: value\rfoo:bar\nbar:foo"] = "xx" + ngx.say("foo") + } + } +--- request +GET /t +--- response_headers +header%3A%20value%0Dfoo%3Abar%0Abar%3Afoo: xx +header: +foo: +bar: +--- no_error_log +[error] + + + +=== TEST 90: unsafe header name (with '\n') +--- config + location = /t { + content_by_lua_block { + ngx.header["header: value\nfoo:bar\rbar:foo"] = "xx" + ngx.say("foo") + } + } +--- request +GET /t +--- response_headers +header%3A%20value%0Afoo%3Abar%0Dbar%3Afoo: xx +header: +foo: +bar: +--- no_error_log +[error] + + + +=== TEST 91: unsafe header name (with prefix '\r') +--- config + location = /t { + content_by_lua_block { + ngx.header["\rheader: value\rfoo:bar\nbar:foo"] = "xx" + ngx.say("foo") + } + } +--- request +GET /t +--- response_headers +%0Dheader%3A%20value%0Dfoo%3Abar%0Abar%3Afoo: xx +header: +foo: +bar: +--- no_error_log +[error] + + + +=== TEST 92: unsafe header name (with prefix '\n') +--- config + location = /t { + content_by_lua_block { + ngx.header["\nheader: value\nfoo:bar\rbar:foo"] = "xx" + ngx.say("foo") + } + } +--- request +GET /t +--- response_headers +%0Aheader%3A%20value%0Afoo%3Abar%0Dbar%3Afoo: xx +header: +foo: +bar: +--- no_error_log +[error] + + + +=== TEST 93: multiple unsafe header values (with '\n' and '\r') +--- config + location = /t { + content_by_lua_block { + ngx.header["foo"] = { + "foo\nxx:bar", + "bar\rxxx:foo", + } + ngx.say("foo") + } + } +--- request +GET /t +--- response_headers +xx: +xxx: +--- raw_response_headers_like chomp +foo: foo%0Axx:bar\r\nfoo: bar%0Dxxx:foo\r\n +--- no_error_log +[error] + + + +=== TEST 94: fix negative content-length number(#1791) +--- config + location = /big-upstream { + content_by_lua_block { + ngx.header['Content-Length'] = math.pow(2, 33) - 1 + ngx.say('hi') + } + } + + location = /t { + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/big-upstream; + proxy_buffering off; + + header_filter_by_lua_block { + local hs, err = ngx.resp.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + print("my Content-Length: ", hs["Content-Length"]) + + ngx.header['Content-Length'] = 3 + } + } +--- request + GET /t +--- response_body +hi +--- no_error_log +[alert] +--- error_log eval +[ "my Content-Length: 8589934591", +qr/upstream prematurely closed connection while sending to client|upstream prematurely closed connection while reading upstream/] + + + +=== TEST 95: Expose the 'Last-Modified' response header as ngx.header["Last-Modified"] +--- config + location /a.txt { + header_filter_by_lua_block { + local last_modified = ngx.header["Last-Modified"] + if last_modified == nil then + ngx.log(ngx.ERR, "can not get lasted modified") + ngx.exit(500) + return + end + + local last_mod = ngx.parse_http_time(last_modified) + local age = ngx.time() - last_mod + ngx.header["Age"] = age + } + } +--- user_files +>>> a.txt +Foo +--- request +GET /a.txt +--- raw_response_headers_like eval +qr/^(a|A)ge: \d\r\n/ms +--- no_error_log +[error] + + + +=== TEST 96: 'Last-Modified' from upstream +--- config + location /test/ { + proxy_pass http://127.0.0.1:$server_port/; + + header_filter_by_lua_block { + local last_modified = ngx.header["Last-Modified"] + if last_modified == nil then + ngx.log(ngx.ERR, "can not get lasted modified") + ngx.exit(500) + return + end + + local last_mod = ngx.parse_http_time(last_modified) + local age = ngx.time() - last_mod + ngx.header["Age"] = age + } + } + +--- user_files +>>> a.txt +Foo +--- request +GET /test/a.txt +--- raw_response_headers_like eval +qr/^(a|A)ge: \d\r\n/ms +--- no_error_log +[error] + + + +=== TEST 97: 'Last-Modified' does not exist +--- config + location /test { + header_filter_by_lua_block { + local last_modified = ngx.header["Last-Modified"] + if last_modified == nil then + ngx.log(ngx.INFO, "Last-Modified is nil as expected") + return + end + + ngx.log(ngx.ERR, "Last-Modified expected to be nil, but got ", last_modified) + } + + content_by_lua_block { + ngx.say("Hello World") + } + } +--- request +GET /test --- response_body -image/png +Hello World --- no_error_log [error] diff --git a/t/017-exec.t b/t/017-exec.t index 544b8bb898..a73e93ee7d 100644 --- a/t/017-exec.t +++ b/t/017-exec.t @@ -382,7 +382,7 @@ hello --- config location /lua { content_by_lua ' - function f () + local function f () ngx.exec("/hi") end @@ -524,7 +524,7 @@ hello --- config location /main { rewrite_by_lua ' - res = ngx.location.capture("/test_loc"); + local res = ngx.location.capture("/test_loc"); ngx.print("hello, ", res.body) '; content_by_lua return; diff --git a/t/020-subrequest.t b/t/020-subrequest.t index 3390d6480b..e91f3f6253 100644 --- a/t/020-subrequest.t +++ b/t/020-subrequest.t @@ -1,6 +1,7 @@ # vim:set ft= ts=4 sw=4 et fdm=marker: use Test::Nginx::Socket::Lua; +use Test::Nginx::Util 'is_tcp_port_used'; #master_on(); #workers(1); @@ -14,6 +15,17 @@ repeat_each(2); plan tests => repeat_each() * (blocks() * 3 + 23); $ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +# NB: tcp_listen_port needs to be greater than 10000, +# because the test cases expect it to be a 5-digit number +my $tcp_listen_port = 19113; +while (++$tcp_listen_port < 65535) { + if (!is_tcp_port_used $tcp_listen_port) { + last; + } +} +$ENV{TEST_NGINX_TCP_LISTEN_PORT} = $tcp_listen_port; #no_diff(); no_long_string(); @@ -32,7 +44,7 @@ __DATA__ location /lua { content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_DELETE }); ngx.print(res.body) @@ -62,7 +74,7 @@ lua http subrequest "/other?" location /lua { content_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_DELETE }); ngx.print(res.body) @@ -90,7 +102,7 @@ DELETE location /t { content_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_POST }); ngx.print(res.body) @@ -114,7 +126,7 @@ POST location /lua { content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_HEAD }); ngx.print(res.body) @@ -141,7 +153,7 @@ GET /lua location /lua { content_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_GET }); ngx.print(res.body) @@ -169,7 +181,7 @@ GET location /lua { content_by_lua ' - res = ngx.location.capture("/foo") + local res = ngx.location.capture("/foo") ngx.print(res.body) '; @@ -196,7 +208,7 @@ GET location /lua { content_by_lua ' - res = ngx.location.capture("/foo", {}) + local res = ngx.location.capture("/foo", {}) ngx.print(res.body) '; @@ -210,7 +222,7 @@ GET -=== TEST 8: PUT (nobody, proxy method) +=== TEST 8: PUT (with body, proxy method) --- config location /other { default_type 'foo/bar'; @@ -226,7 +238,7 @@ GET location /lua { content_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_PUT, body = "hello" }); ngx.print(res.body) @@ -242,7 +254,7 @@ hello -=== TEST 9: PUT (nobody, no proxy method) +=== TEST 9: PUT (with body, no proxy method) --- config location /other { default_type 'foo/bar'; @@ -255,7 +267,7 @@ hello location /lua { content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_PUT, body = "hello" }); ngx.print(res.body) @@ -271,7 +283,7 @@ hello -=== TEST 10: PUT (nobody, no proxy method) +=== TEST 10: PUT (no body, no proxy method) --- config location /other { default_type 'foo/bar'; @@ -291,7 +303,7 @@ hello location /lua { content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_PUT, body = "hello" }); ngx.print(res.body) @@ -329,7 +341,7 @@ GET location /lua { content_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_POST, body = "hello" }); ngx.print(res.body) @@ -362,7 +374,7 @@ hello content_by_lua ' ngx.location.capture("/flush"); - res = ngx.location.capture("/memc"); + local res = ngx.location.capture("/memc"); ngx.say("GET: " .. res.status); res = ngx.location.capture("/memc", @@ -404,7 +416,7 @@ cached: hello ngx.location.capture("/flush", { share_all_vars = true }); - res = ngx.location.capture("/memc", + local res = ngx.location.capture("/memc", { share_all_vars = true }); ngx.say("GET: " .. res.status); @@ -435,7 +447,7 @@ cached: hello location /lua { content_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = {} }) ngx.print(res.body) '; @@ -456,7 +468,7 @@ GET /lua location /lua { content_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { ["fo="] = "=>" } }) ngx.print(res.body) '; @@ -478,7 +490,7 @@ fo%3D=%3D%3E location /lua { content_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { ["fo="] = "=>", ["="] = ":" } }) ngx.print(res.body) @@ -503,7 +515,7 @@ GET /lua location /lua { content_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { foo = 3, bar = "hello" } }) ngx.print(res.body) @@ -526,7 +538,7 @@ GET /lua location /lua { content_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { [57] = "hi" } }) ngx.print(res.body) '; @@ -548,7 +560,7 @@ attempt to use a non-string key in the "args" option table location /lua { content_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { "hi" } }) ngx.print(res.body) '; @@ -570,7 +582,7 @@ attempt to use a non-string key in the "args" option table location /lua { content_by_lua ' - res = ngx.location.capture("/foo?a=3", + local res = ngx.location.capture("/foo?a=3", { args = { b = 4 } }) ngx.print(res.body) '; @@ -592,7 +604,7 @@ a=3&b=4 location /lua { content_by_lua ' - res = ngx.location.capture("/foo?a=3", + local res = ngx.location.capture("/foo?a=3", { args = "b=4" }) ngx.print(res.body) '; @@ -685,7 +697,7 @@ https://github.com/chaoslawful/lua-nginx-module/issues/38 location /lua { content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_GET }); ngx.say("header foo: [", res.body, "]") '; @@ -711,7 +723,7 @@ https://github.com/chaoslawful/lua-nginx-module/issues/38 location /lua { content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { body = "abc" }); ngx.say("header foo: [", res.body, "]") '; @@ -746,8 +758,8 @@ header foo: [bar] } location /main { content_by_lua ' - res1, res2 = ngx.location.capture_multi({{"/a"}, {"/b"}}) - res3 = ngx.location.capture("/c") + local res1, res2 = ngx.location.capture_multi({{"/a"}, {"/b"}}) + local res3 = ngx.location.capture("/c") ngx.print(res1.body, res2.body, res3.body) '; } @@ -780,7 +792,7 @@ lua reuse free buf memory location /lua { content_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_POST, body = "hello" }); ngx.print(res.body) @@ -801,7 +813,7 @@ hello --- config location /lua { content_by_lua ' - res = ngx.location.capture("/foo.html") + local res = ngx.location.capture("/foo.html") ngx.say(res.status) ngx.say(res.header["Last-Modified"]) @@ -832,7 +844,7 @@ hello, static file$ location /lua { content_by_lua ' local ctx = {} - res = ngx.location.capture("/sub", { ctx = ctx }) + local res = ngx.location.capture("/sub", { ctx = ctx }) ngx.say(ctx.foo); ngx.say(ngx.ctx.foo); @@ -857,7 +869,7 @@ nil } location /lua { content_by_lua ' - res = ngx.location.capture("/sub", { ctx = ngx.ctx }) + local res = ngx.location.capture("/sub", { ctx = ngx.ctx }) ngx.say(ngx.ctx.foo); '; } @@ -885,7 +897,7 @@ bar location /t { content_by_lua ' - res = ngx.location.capture("/memc", + local res = ngx.location.capture("/memc", { method = ngx.HTTP_PUT, body = "hello 1234" }); -- ngx.say("PUT: " .. res.status); @@ -922,7 +934,7 @@ lua reuse free buf chain, but reallocate memory because location /lua { content_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_GET }); ngx.print(res.body) @@ -959,7 +971,7 @@ nil content_by_lua ' ngx.req.read_body() - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_GET }); ngx.print(res.body) @@ -996,7 +1008,7 @@ nil content_by_lua ' ngx.req.read_body() - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_POST }); ngx.print(res.body) @@ -1033,7 +1045,7 @@ hello, world content_by_lua ' ngx.req.read_body() - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_PUT }); ngx.print(res.body) @@ -1074,7 +1086,7 @@ lua subrequests cycle while processing "/t" location /lua { content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_OPTIONS }); ngx.print(res.body) @@ -1099,7 +1111,7 @@ OPTIONS location /lua { content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_OPTIONS, body = "hello world" }); ngx.print(res.body) @@ -1119,7 +1131,11 @@ hello world --- config location /t { content_by_lua ' - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + if err then + ngx.say("err: ", err) + end + local res = ngx.location.capture("/sub", { args = args }) ngx.print(res.body) '; @@ -1147,7 +1163,7 @@ r%5B%5D=http%3A%2F%2Fajax.googleapis.com%3A80%2Fajax%2Flibs%2Fjquery%2F1.7.2%2Fj location /main { content_by_lua ' - res = ngx.location.capture("/sub") + local res = ngx.location.capture("/sub") ngx.say("status: ", res.status) ngx.say("body: ", res.body) '; @@ -1168,7 +1184,7 @@ body: location /main { content_by_lua ' - res = ngx.location.capture("/sub") + local res = ngx.location.capture("/sub") ngx.say("status: ", res.status) ngx.say("body: ", res.body) '; @@ -1182,6 +1198,7 @@ body: === TEST 43: subrequests with an output body filter returning NGX_ERROR +--- no_http2 --- config location /sub { echo hello world; @@ -1192,7 +1209,7 @@ body: location /main { content_by_lua ' - res = ngx.location.capture("/sub") + local res = ngx.location.capture("/sub") ngx.say("status: ", res.status) ngx.say("body: ", res.body) '; @@ -1222,6 +1239,8 @@ F(ngx_http_finalize_request) { --- error_code --- no_error_log [error] +--- curl_error eval +qr{(\Qcurl: (52) Empty reply from server\E|\Qcurl: (95) HTTP/3 stream 0 reset by server\E)}ms @@ -1233,12 +1252,12 @@ F(ngx_http_finalize_request) { set $memc_key 'foo'; #set $memc_exptime 300; - memc_pass 127.0.0.1:19112; #$TEST_NGINX_MEMCACHED_PORT; + memc_pass 127.0.0.1:$TEST_NGINX_RAND_PORT_1; #$TEST_NGINX_MEMCACHED_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/memc") + local res = ngx.location.capture("/memc") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -1246,7 +1265,7 @@ F(ngx_http_finalize_request) { } --- request GET /main ---- tcp_listen: 19112 +--- tcp_listen: $TEST_NGINX_RAND_PORT_1 --- tcp_query_len: 9 --- tcp_reply eval "VALUE foo 0 1024\r\nhello world" @@ -1303,12 +1322,12 @@ upstream prematurely closed connection set $memc_key 'foo'; #set $memc_exptime 300; - memc_pass 127.0.0.1:19112; #$TEST_NGINX_MEMCACHED_PORT; + memc_pass 127.0.0.1:$TEST_NGINX_RAND_PORT_1; #$TEST_NGINX_MEMCACHED_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/memc") + local res = ngx.location.capture("/memc") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -1316,7 +1335,7 @@ upstream prematurely closed connection } --- request GET /main ---- tcp_listen: 19112 +--- tcp_listen: $TEST_NGINX_RAND_PORT_1 --- tcp_no_close --- tcp_reply eval "VALUE foo 0 1024\r\nhello world" @@ -1376,12 +1395,12 @@ upstream timed out #proxy_read_timeout 100ms; proxy_buffering on; - proxy_pass http://127.0.0.1:19113; + proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -1389,7 +1408,7 @@ upstream timed out } --- request GET /main ---- tcp_listen: 19113 +--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT --- tcp_query_len: 65 --- tcp_reply eval "HTTP/1.0 200 OK\r\nContent-Length: 1024\r\n\r\nhello world" @@ -1436,12 +1455,12 @@ upstream prematurely closed connection proxy_read_timeout 100ms; proxy_buffering on; - proxy_pass http://127.0.0.1:19113; + proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -1449,7 +1468,7 @@ upstream prematurely closed connection } --- request GET /main ---- tcp_listen: 19113 +--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT --- tcp_no_close --- tcp_reply eval "HTTP/1.0 200 OK\r\nContent-Length: 1024\r\n\r\nhello world" @@ -1498,12 +1517,12 @@ upstream timed out #proxy_read_timeout 100ms; proxy_buffering on; - proxy_pass http://127.0.0.1:19113; + proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -1511,7 +1530,7 @@ upstream timed out } --- request GET /main ---- tcp_listen: 19113 +--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT --- tcp_query_len: 65 --- tcp_reply eval "HTTP/1.0 200 OK\r\n\r\nhello world" @@ -1558,12 +1577,12 @@ truncated: false proxy_read_timeout 100ms; proxy_buffering on; - proxy_pass http://127.0.0.1:19113; + proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -1571,7 +1590,7 @@ truncated: false } --- request GET /main ---- tcp_listen: 19113 +--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT --- tcp_no_close --- tcp_reply eval "HTTP/1.0 200 OK\r\n\r\nhello world" @@ -1621,12 +1640,12 @@ upstream timed out #proxy_read_timeout 100ms; proxy_buffering off; - proxy_pass http://127.0.0.1:19113; + proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -1634,7 +1653,7 @@ upstream timed out } --- request GET /main ---- tcp_listen: 19113 +--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT --- tcp_query_len: 65 --- tcp_reply eval "HTTP/1.0 200 OK\r\n\r\nhello world" @@ -1681,12 +1700,12 @@ truncated: false proxy_read_timeout 500ms; proxy_buffering off; - proxy_pass http://127.0.0.1:19113; + proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -1694,7 +1713,7 @@ truncated: false } --- request GET /main ---- tcp_listen: 19113 +--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT --- tcp_no_close --- tcp_reply eval "HTTP/1.0 200 OK\r\n\r\nhello world" @@ -1752,7 +1771,7 @@ upstream timed out ngx.req.read_body() for i = 1, 2 do - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_POST }); ngx.say(res.body) @@ -1792,7 +1811,7 @@ hello world ngx.req.read_body() for i = 1, 2 do - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_POST }); ngx.say(res.body) @@ -1832,7 +1851,7 @@ hello world ngx.req.read_body() for i = 1, 2 do - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_POST }); ngx.say(res.body) @@ -1907,12 +1926,12 @@ a client request body is buffered to a temporary file #proxy_read_timeout 100ms; proxy_http_version 1.1; proxy_buffering on; - proxy_pass http://127.0.0.1:19113; + proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -1920,7 +1939,7 @@ a client request body is buffered to a temporary file } --- request GET /main ---- tcp_listen: 19113 +--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT --- tcp_query_len: 65 --- tcp_reply eval "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nhello world\r" @@ -1970,12 +1989,12 @@ upstream prematurely closed connection #proxy_read_timeout 100ms; proxy_http_version 1.1; proxy_buffering off; - proxy_pass http://127.0.0.1:19113; + proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -1983,7 +2002,7 @@ upstream prematurely closed connection } --- request GET /main ---- tcp_listen: 19113 +--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT --- tcp_query_len: 65 --- tcp_reply eval "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nhello world\r" @@ -2031,12 +2050,12 @@ upstream prematurely closed connection proxy_read_timeout 100ms; proxy_buffering on; proxy_http_version 1.1; - proxy_pass http://127.0.0.1:19113; + proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -2044,7 +2063,7 @@ upstream prematurely closed connection } --- request GET /main ---- tcp_listen: 19113 +--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT --- tcp_no_close --- tcp_reply eval "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nhello world\r" @@ -2093,12 +2112,12 @@ upstream timed out #proxy_read_timeout 100ms; proxy_buffering on; proxy_http_version 1.1; - proxy_pass http://127.0.0.1:19113; + proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -2106,7 +2125,7 @@ upstream timed out } --- request GET /main ---- tcp_listen: 19113 +--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT --- tcp_no_close --- tcp_reply eval "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n" @@ -2151,12 +2170,12 @@ truncated: false #proxy_read_timeout 100ms; proxy_buffering off; proxy_http_version 1.1; - proxy_pass http://127.0.0.1:19113; + proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -2164,7 +2183,7 @@ truncated: false } --- request GET /main ---- tcp_listen: 19113 +--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT --- tcp_no_close --- tcp_reply eval "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n" @@ -2210,12 +2229,12 @@ truncated: false #proxy_read_timeout 100ms; proxy_buffering off; - proxy_pass http://127.0.0.1:19113; + proxy_pass http://127.0.0.1:$TEST_NGINX_TCP_LISTEN_PORT; } location /main { content_by_lua ' - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("status: ", res.status) ngx.say("body: ", res.body) ngx.say("truncated: ", res.truncated) @@ -2223,7 +2242,7 @@ truncated: false } --- request GET /main ---- tcp_listen: 19113 +--- tcp_listen: $TEST_NGINX_TCP_LISTEN_PORT --- tcp_query_len: 65 --- tcp_reply eval "HTTP/1.0 200 OK\r\nContent-Length: 1024\r\n\r\nhello world" @@ -2284,7 +2303,7 @@ upstream prematurely closed connection } for i, method in ipairs(methods) do - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = method }) ngx.print(res.body) end @@ -2320,7 +2339,7 @@ method: TRACE location /lua { content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_DELETE }); ngx.print(res.body) @@ -2333,6 +2352,7 @@ hello world nil --- no_error_log [error] +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} @@ -2349,7 +2369,7 @@ nil location /lua { content_by_lua ' ngx.req.read_body() - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_DELETE, always_forward_body = true }); ngx.print(res.body) @@ -2378,7 +2398,7 @@ hello world location /lua { content_by_lua ' ngx.req.read_body() - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_DELETE, always_forward_body = true }); ngx.print(res.body) @@ -2406,7 +2426,7 @@ hello world --- config location = /t { content_by_lua ' - res = ngx.location.capture("/sub") + local res = ngx.location.capture("/sub") ngx.print(res.body) '; } @@ -2599,7 +2619,7 @@ qr/Assertion .*? failed/ location = /t { content_by_lua ' - res = ngx.location.capture("/sub") + local res = ngx.location.capture("/sub") ngx.print(res.body) ngx.say("pr: User-Agent: ", ngx.var.http_user_agent) ngx.say("pr: Host: ", ngx.var.http_host) @@ -2617,6 +2637,7 @@ pr: Host: localhost --- no_error_log [error] +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} @@ -2632,7 +2653,7 @@ pr: Host: localhost location = /t { content_by_lua ' - res = ngx.location.capture("/sub") + local res = ngx.location.capture("/sub") ngx.print(res.body) ngx.say("pr: User-Agent: ", ngx.var.http_user_agent) ngx.say("pr: Host: ", ngx.var.http_host) @@ -2650,6 +2671,7 @@ pr: Host: localhost --- no_error_log [error] +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} @@ -2665,7 +2687,7 @@ pr: Host: localhost location = /t { content_by_lua ' - res = ngx.location.capture("/sub") + local res = ngx.location.capture("/sub") ngx.print(res.body) ngx.say("pr: User-Agent: ", ngx.var.http_user_agent) ngx.say("pr: Host: ", ngx.var.http_host) @@ -2695,7 +2717,7 @@ pr: Host: localhost location = /t { content_by_lua ' - res = ngx.location.capture("/sub") + local res = ngx.location.capture("/sub") ngx.print(res.body) ngx.say("pr: Cookie: ", ngx.var.http_cookie) '; @@ -2723,7 +2745,7 @@ pr: Cookie: foo; bar location = /t { content_by_lua ' - res = ngx.location.capture("/sub") + local res = ngx.location.capture("/sub") ngx.print(res.body) ngx.say("pr: Cookie: ", ngx.var.http_cookie) '; @@ -2746,7 +2768,7 @@ pr: Cookie: foo; bar --- config location /lua { content_by_lua ' - res = ngx.location.capture("/index.html", + local res = ngx.location.capture("/index.html", { method = ngx.HTTP_HEAD }); ngx.say("content-length: ", res.header["Content-Length"]) ngx.say("body: [", res.body, "]") @@ -2873,3 +2895,727 @@ DELETE /file.txt, response status: 204 --- no_error_log [error] --- error_code: 200 + + + +=== TEST 77: avoid request smuggling 1/4 (default capture + smuggle in header) +--- http_config + upstream backend { + server unix:$TEST_NGINX_HTML_DIR/nginx.sock; + keepalive 32; + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; + + location / { + content_by_lua_block { + ngx.say("method: ", ngx.var.request_method, + ", uri: ", ngx.var.uri, + ", X: ", ngx.var.http_x) + } + } + } +--- config + location /proxy { + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_pass http://backend/foo; + } + + location /capture { + server_tokens off; + more_clear_headers Date; + + content_by_lua_block { + local res = ngx.location.capture("/proxy") + ngx.print(res.body) + } + } + + location /t { + content_by_lua_block { + local req = [[ +GET /capture HTTP/1.1 +Host: test.com +Content-Length: 37 +Transfer-Encoding: chunked + +0 + +GET /capture HTTP/1.1 +Host: test.com +X: GET /bar HTTP/1.0 + +]] + + local sock = ngx.socket.tcp() + sock:settimeout(1000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send req: ", err) + return + end + + ngx.say("req bytes: ", bytes) + + local n_resp = 0 + + local reader = sock:receiveuntil("\r\n") + while true do + local line, err = reader() + if line then + ngx.say(line) + if line == "0" then + n_resp = n_resp + 1 + end + + if n_resp >= 2 then + break + end + + else + ngx.say("err: ", err) + break + end + end + + sock:close() + } + } +--- request +GET /t +--- response_body +req bytes: 146 +HTTP/1.1 200 OK +Server: nginx +Content-Type: text/plain +Transfer-Encoding: chunked +Connection: keep-alive + +1f +method: GET, uri: /foo, X: nil + +0 + +HTTP/1.1 200 OK +Server: nginx +Content-Type: text/plain +Transfer-Encoding: chunked +Connection: keep-alive + +2d +method: GET, uri: /foo, X: GET /bar HTTP/1.0 + +0 +--- no_error_log +[error] +--- skip_nginx +3: >= 1.21.1 + + + +=== TEST 78: avoid request smuggling 2/4 (POST capture + smuggle in body) +--- http_config + upstream backend { + server unix:$TEST_NGINX_HTML_DIR/nginx.sock; + keepalive 32; + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; + + location / { + content_by_lua_block { + ngx.say("method: ", ngx.var.request_method, + ", uri: ", ngx.var.uri) + } + } + } +--- config + location /proxy { + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_pass http://backend/foo; + } + + location /capture { + server_tokens off; + more_clear_headers Date; + + content_by_lua_block { + ngx.req.read_body() + local res = ngx.location.capture("/proxy", { method = ngx.HTTP_POST }) + ngx.print(res.body) + } + } + + location /t { + content_by_lua_block { + local req = [[ +GET /capture HTTP/1.1 +Host: test.com +Content-Length: 57 +Transfer-Encoding: chunked + +0 + +POST /capture HTTP/1.1 +Host: test.com +Content-Length: 60 + +POST /bar HTTP/1.1 +Host: test.com +Content-Length: 5 + +hello + +]] + + local sock = ngx.socket.tcp() + sock:settimeout(1000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send req: ", err) + return + end + + ngx.say("req bytes: ", bytes) + + local n_resp = 0 + + local reader = sock:receiveuntil("\r\n") + while true do + local line, err = reader() + if line then + ngx.say(line) + if line == "0" then + n_resp = n_resp + 1 + end + + if n_resp >= 2 then + break + end + + else + ngx.say("err: ", err) + break + end + end + + sock:close() + } + } +--- request +GET /t +--- response_body +req bytes: 205 +HTTP/1.1 200 OK +Server: nginx +Content-Type: text/plain +Transfer-Encoding: chunked +Connection: keep-alive + +18 +method: POST, uri: /foo + +0 + +HTTP/1.1 200 OK +Server: nginx +Content-Type: text/plain +Transfer-Encoding: chunked +Connection: keep-alive + +18 +method: POST, uri: /foo + +0 +--- no_error_log +[error] +--- skip_nginx +3: >= 1.21.1 + + + +=== TEST 79: avoid request smuggling 3/4 (POST capture w/ always_forward_body + smuggle in body) +--- http_config + upstream backend { + server unix:$TEST_NGINX_HTML_DIR/nginx.sock; + keepalive 32; + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; + + location / { + content_by_lua_block { + ngx.say("method: ", ngx.var.request_method, + ", uri: ", ngx.var.uri) + } + } + } +--- config + location /proxy { + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_pass http://backend/foo; + } + + location /capture { + server_tokens off; + more_clear_headers Date; + + content_by_lua_block { + ngx.req.read_body() + local res = ngx.location.capture("/proxy", { + method = ngx.HTTP_POST, + always_forward_body = true + }) + ngx.print(res.body) + } + } + + location /t { + content_by_lua_block { + local req = [[ +GET /capture HTTP/1.1 +Host: test.com +Content-Length: 57 +Transfer-Encoding: chunked + +0 + +POST /capture HTTP/1.1 +Host: test.com +Content-Length: 60 + +POST /bar HTTP/1.1 +Host: test.com +Content-Length: 5 + +hello + +]] + + local sock = ngx.socket.tcp() + sock:settimeout(1000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send req: ", err) + return + end + + ngx.say("req bytes: ", bytes) + + local n_resp = 0 + + local reader = sock:receiveuntil("\r\n") + while true do + local line, err = reader() + if line then + ngx.say(line) + if line == "0" then + n_resp = n_resp + 1 + end + + if n_resp >= 2 then + break + end + + else + ngx.say("err: ", err) + break + end + end + + sock:close() + } + } +--- request +GET /t +--- response_body +req bytes: 205 +HTTP/1.1 200 OK +Server: nginx +Content-Type: text/plain +Transfer-Encoding: chunked +Connection: keep-alive + +18 +method: POST, uri: /foo + +0 + +HTTP/1.1 200 OK +Server: nginx +Content-Type: text/plain +Transfer-Encoding: chunked +Connection: keep-alive + +18 +method: POST, uri: /foo + +0 +--- no_error_log +[error] +--- skip_nginx +3: >= 1.21.1 + + + +=== TEST 80: avoid request smuggling 4/4 (POST capture w/ body + smuggle in body) +--- http_config + upstream backend { + server unix:$TEST_NGINX_HTML_DIR/nginx.sock; + keepalive 32; + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; + + location / { + content_by_lua_block { + ngx.say("method: ", ngx.var.request_method, + ", uri: ", ngx.var.uri) + } + } + } +--- config + location /proxy { + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_pass http://backend/foo; + } + + location /capture { + server_tokens off; + more_clear_headers Date; + + content_by_lua_block { + ngx.req.read_body() + local res = ngx.location.capture("/proxy", { + method = ngx.HTTP_POST, + always_forward_body = true, + body = ngx.req.get_body_data() + }) + ngx.print(res.body) + } + } + + location /t { + content_by_lua_block { + local req = [[ +GET /capture HTTP/1.1 +Host: test.com +Content-Length: 57 +Transfer-Encoding: chunked + +0 + +POST /capture HTTP/1.1 +Host: test.com +Content-Length: 60 + +POST /bar HTTP/1.1 +Host: test.com +Content-Length: 5 + +hello + +]] + + local sock = ngx.socket.tcp() + sock:settimeout(1000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send req: ", err) + return + end + + ngx.say("req bytes: ", bytes) + + local n_resp = 0 + + local reader = sock:receiveuntil("\r\n") + while true do + local line, err = reader() + if line then + ngx.say(line) + if line == "0" then + n_resp = n_resp + 1 + end + + if n_resp >= 2 then + break + end + + else + ngx.say("err: ", err) + break + end + end + + sock:close() + } + } +--- request +GET /t +--- response_body +req bytes: 205 +HTTP/1.1 200 OK +Server: nginx +Content-Type: text/plain +Transfer-Encoding: chunked +Connection: keep-alive + +18 +method: POST, uri: /foo + +0 + +HTTP/1.1 200 OK +Server: nginx +Content-Type: text/plain +Transfer-Encoding: chunked +Connection: keep-alive + +18 +method: POST, uri: /foo + +0 +--- no_error_log +[error] +--- skip_nginx +3: >= 1.21.1 + + + +=== TEST 81: bad HTTP method +--- config + location /other { } + + location /lua { + content_by_lua_block { + local res = ngx.location.capture("/other", + { method = 10240 }); + } + } +--- request +GET /lua +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +unsupported HTTP method: 10240 + + + +=== TEST 82: bad requests with both Content-Length and Transfer-Encoding (nginx >= 1.21.1) +--- http_config + upstream backend { + server unix:$TEST_NGINX_HTML_DIR/nginx.sock; + keepalive 32; + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; + + location / { + content_by_lua_block { + ngx.say("method: ", ngx.var.request_method, + ", uri: ", ngx.var.uri, + ", X: ", ngx.var.http_x) + } + } + } +--- config + location /proxy { + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_pass http://backend/foo; + } + + location /capture { + server_tokens off; + more_clear_headers Date; + + content_by_lua_block { + local res = ngx.location.capture("/proxy") + ngx.print(res.body) + } + } + + location /t { + content_by_lua_block { + local req = [[ +GET /capture HTTP/1.1 +Host: test.com +Content-Length: 37 +Transfer-Encoding: chunked + +0 + +GET /capture HTTP/1.1 +Host: test.com +X: GET /bar HTTP/1.0 + +]] + + local sock = ngx.socket.tcp() + sock:settimeout(1000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send req: ", err) + return + end + + ngx.say("req bytes: ", bytes) + + local n_resp = 0 + + local reader = sock:receiveuntil("\r\n") + while true do + local line, err = reader() + if line then + ngx.say(line) + if line == "0" then + n_resp = n_resp + 1 + end + + if n_resp >= 2 then + break + end + + else + ngx.say("err: ", err) + break + end + end + + sock:close() + } + } +--- request +GET /t +--- response_body_like +req bytes: 146 +HTTP/1.1 400 Bad Request +--- no_error_log +[error] +--- skip_nginx +3: < 1.21.1 + + + +=== TEST 83: avoid request smuggling of HEAD req +--- config + location /capture { + server_tokens off; + more_clear_headers Date; + + content_by_lua_block { + ngx.say("Hello") + } + } + + location /t { + content_by_lua_block { + local req = [[ +HEAD /capture HTTP/1.1 +Host: test.com +Content-Length: 63 + +GET /capture HTTP/1.1 +Host: test.com +X: GET /bar HTTP/1.0 + +]] + + local sock = ngx.socket.tcp() + sock:settimeout(1000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send req: ", err) + return + end + + ngx.say("req bytes: ", bytes) + + local n_resp = 0 + + local reader = sock:receiveuntil("\r\n") + while true do + local line, err = reader() + if line then + ngx.say(line) + if line == "0" then + n_resp = n_resp + 1 + end + + if n_resp >= 2 then + break + end + + else + ngx.say("err: ", err) + break + end + end + + sock:close() + } + } +--- request +GET /t +--- response_body +req bytes: 117 +HTTP/1.1 200 OK +Server: nginx +Content-Type: text/plain +Connection: keep-alive + +err: timeout +--- error_log +lua tcp socket read timed out diff --git a/t/022-redirect.t b/t/022-redirect.t index fae39e3adf..197c76e1d7 100644 --- a/t/022-redirect.t +++ b/t/022-redirect.t @@ -9,7 +9,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); #repeat_each(1); -plan tests => repeat_each() * (blocks() * 3 + 2); +plan tests => repeat_each() * (blocks() * 3 + 9); #no_diff(); #no_long_string(); @@ -84,7 +84,7 @@ GET /read --- response_body_like: 500 Internal Server Error --- error_code: 500 --- error_log -only ngx.HTTP_MOVED_TEMPORARILY, ngx.HTTP_MOVED_PERMANENTLY, ngx.HTTP_SEE_OTHER, and ngx.HTTP_TEMPORARY_REDIRECT are allowed +only ngx.HTTP_MOVED_TEMPORARILY, ngx.HTTP_MOVED_PERMANENTLY, ngx.HTTP_PERMANENT_REDIRECT, ngx.HTTP_SEE_OTHER, and ngx.HTTP_TEMPORARY_REDIRECT are allowed @@ -122,7 +122,16 @@ GET /read } --- request GET /read ---- raw_response_headers_like: Location: /echo\r\n +--- raw_response_headers_like eval +my $headers; + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $headers = "location: /echo\r\n" +} else { + $headers = "Location: /echo\r\n" +} + +$headers; --- response_body_like: 302 Found --- error_code: 302 @@ -269,3 +278,151 @@ GET /read Location: http://agentzh.org/foo?a=b&c=d --- response_body_like: 303 See Other --- error_code: 303 + + + +=== TEST 15: explicit 308 with args +--- config + location /read { + content_by_lua ' + ngx.redirect("http://agentzh.org/foo?a=b&c=d", ngx.HTTP_PERMANENT_REDIRECT); + ngx.say("hi") + '; + } +--- request +GET /read +--- response_body_like: 308 Permanent Redirect +--- response_headers +Location: http://agentzh.org/foo?a=b&c=d +--- error_code: 308 + + + +=== TEST 16: explicit 308 +--- config + location /read { + content_by_lua ' + ngx.redirect("http://agentzh.org/foo?a=b&c=d", 308); + ngx.say("hi") + '; + } +--- request +GET /read +--- response_body_like: 308 Permanent Redirect +--- response_headers +Location: http://agentzh.org/foo?a=b&c=d +--- error_code: 308 + + + +=== TEST 17: explicit 308 with args +--- config + location /read { + content_by_lua ' + ngx.redirect("http://agentzh.org/foo?a=b&c=d", 308); + ngx.say("hi") + '; + } +--- request +GET /read +--- response_body_like: 308 Permanent Redirect +--- response_headers +Location: http://agentzh.org/foo?a=b&c=d +--- error_code: 308 + + + +=== TEST 18: unsafe uri (with '\r') +--- config + location = /t { + content_by_lua_block { + ngx.redirect("http://agentzh.org/foo\rfoo:bar\nbar:foo"); + ngx.say("hi") + } + } +--- request +GET /t +--- error_code: 500 +--- response_headers +Location: +foo: +bar: +--- error_log +unsafe byte "0x0d" in redirect uri "http://agentzh.org/foo\x0Dfoo:bar\x0Abar:foo" + + + +=== TEST 19: unsafe uri (with '\n') +--- config + location = /t { + content_by_lua_block { + ngx.redirect("http://agentzh.org/foo\nfoo:bar\rbar:foo"); + ngx.say("hi") + } + } +--- request +GET /t +--- error_code: 500 +--- response_headers +Location: +foo: +bar: +--- error_log +unsafe byte "0x0a" in redirect uri "http://agentzh.org/foo\x0Afoo:bar\x0Dbar:foo" + + + +=== TEST 20: unsafe uri (with prefix '\n') +--- config + location = /t { + content_by_lua_block { + ngx.redirect("\nfoo:http://agentzh.org/foo"); + ngx.say("hi") + } + } +--- request +GET /t +--- error_code: 500 +--- response_headers +Location: +foo: +--- error_log +unsafe byte "0x0a" in redirect uri "\x0Afoo:http://agentzh.org/foo" + + + +=== TEST 21: unsafe uri (with prefix '\r') +--- config + location = /t { + content_by_lua_block { + ngx.redirect("\rfoo:http://agentzh.org/foo"); + ngx.say("hi") + } + } +--- request +GET /t +--- error_code: 500 +--- response_headers +Location: +foo: +--- error_log +unsafe byte "0x0d" in redirect uri "\x0Dfoo:http://agentzh.org/foo" + + + +=== TEST 22: unsafe uri logging escapes '"' and '\' characters +--- config + location = /t { + content_by_lua_block { + ngx.redirect("\rhttp\\://\"agentzh.org\"/foo"); + ngx.say("hi") + } + } +--- request +GET /t +--- error_code: 500 +--- response_headers +Location: +foo: +--- error_log +unsafe byte "0x0d" in redirect uri "\x0Dhttp\x5C://\x22agentzh.org\x22/foo" diff --git a/t/023-rewrite/client-abort.t b/t/023-rewrite/client-abort.t index 117d17e5ef..61ada3a1b0 100644 --- a/t/023-rewrite/client-abort.t +++ b/t/023-rewrite/client-abort.t @@ -19,7 +19,13 @@ our $StapScript = $t::StapThread::StapScript; repeat_each(2); -plan tests => repeat_each() * (blocks() * 3 - 1); +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + plan(skip_all => "HTTP3 does not support client abort"); +} elsif (defined $ENV{TEST_NGINX_USE_HTTP2}) { + plan(skip_all => "HTTP2 does not support client abort"); +} else { + plan tests => repeat_each() * (blocks() * 3 - 1); +} $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; $ENV{TEST_NGINX_MEMCACHED_PORT} ||= '11211'; @@ -199,7 +205,7 @@ bad things happen location = /sub { proxy_ignore_client_abort on; - proxy_pass http://agentzh.org:12345/; + proxy_pass http://127.0.0.2:12345/; } location = /sleep { @@ -240,7 +246,7 @@ client prematurely closed connection location = /sub { proxy_ignore_client_abort off; - proxy_pass http://agentzh.org:12345/; + proxy_pass http://127.0.0.2:12345/; } --- request GET /t @@ -545,7 +551,7 @@ client prematurely closed connection return end - ok, err = sock:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) if not ok then ngx.log(ngx.ERR, "failed to connect: ", err) return diff --git a/t/023-rewrite/exec.t b/t/023-rewrite/exec.t index edd4607355..59691cb599 100644 --- a/t/023-rewrite/exec.t +++ b/t/023-rewrite/exec.t @@ -326,7 +326,7 @@ hello --- config location /main { rewrite_by_lua ' - res = ngx.location.capture("/test_loc"); + local res = ngx.location.capture("/test_loc"); ngx.print("hello, ", res.body) '; content_by_lua return; @@ -354,7 +354,7 @@ hello, bah --- config location /main { rewrite_by_lua ' - res = ngx.location.capture("/test_loc"); + local res = ngx.location.capture("/test_loc"); ngx.print("hello, ", res.body) '; content_by_lua return; diff --git a/t/023-rewrite/exit.t b/t/023-rewrite/exit.t index 39ea5cbd62..7f01717eb6 100644 --- a/t/023-rewrite/exit.t +++ b/t/023-rewrite/exit.t @@ -120,6 +120,7 @@ GET /api?user=agentz === TEST 6: working with ngx_auth_request (simplest form, w/o ngx_memc) +--- no_http2 --- http_config eval " lua_package_cpath '$::LuaCpath'; @@ -187,10 +188,12 @@ ngx.var.uid = res[1].uid; GET /api?uid=32 --- response_body Logged in 56 +--- skip_eval: 2:$ENV{TEST_NGINX_USE_HTTP3} === TEST 7: working with ngx_auth_request (simplest form) +--- no_http2 --- http_config eval " lua_package_cpath '$::LuaCpath'; @@ -258,10 +261,12 @@ ngx.var.uid = res[1].uid; GET /api?uid=32 --- response_body Logged in 56 +--- skip_eval: 2:$ENV{TEST_NGINX_USE_HTTP3} === TEST 8: working with ngx_auth_request +--- no_http2 --- http_config eval " lua_package_cpath '$::LuaCpath'; @@ -342,6 +347,7 @@ GET /api?uid=32 Logged in 56 --- no_error_log [error] +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/023-rewrite/mixed.t b/t/023-rewrite/mixed.t index 0f742b255a..5b38d3f45f 100644 --- a/t/023-rewrite/mixed.t +++ b/t/023-rewrite/mixed.t @@ -35,7 +35,7 @@ __DATA__ rewrite_by_lua ' ngx.location.capture("/flush"); - res = ngx.location.capture("/memc"); + local res = ngx.location.capture("/memc"); print("rewrite GET: " .. res.status); res = ngx.location.capture("/memc", @@ -49,7 +49,7 @@ __DATA__ content_by_lua ' ngx.location.capture("/flush"); - res = ngx.location.capture("/memc"); + local res = ngx.location.capture("/memc"); ngx.say("content GET: " .. res.status); res = ngx.location.capture("/memc", diff --git a/t/023-rewrite/multi-capture.t b/t/023-rewrite/multi-capture.t index 083ec78c90..a0a02b7a2d 100644 --- a/t/023-rewrite/multi-capture.t +++ b/t/023-rewrite/multi-capture.t @@ -154,7 +154,7 @@ res2.body = b location /main { rewrite_by_lua ' - res = ngx.location.capture("/foo?n=1") + local res = ngx.location.capture("/foo?n=1") ngx.say("top res.status = " .. res.status) ngx.say("top res.body = [" .. res.body .. "]") '; diff --git a/t/023-rewrite/on-abort.t b/t/023-rewrite/on-abort.t index 336b7ce4b5..0535732a0b 100644 --- a/t/023-rewrite/on-abort.t +++ b/t/023-rewrite/on-abort.t @@ -19,7 +19,13 @@ our $StapScript = $t::StapThread::StapScript; repeat_each(2); -plan tests => repeat_each() * (blocks() * 4 + 15); +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + plan(skip_all => "HTTP3 does not support on_abort"); +} elsif (defined $ENV{TEST_NGINX_USE_HTTP2}) { + plan(skip_all => "HTTP2 does not support on_abort"); +} else { + plan tests => repeat_each() * (blocks() * 4 + 15); +} $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; $ENV{TEST_NGINX_MEMCACHED_PORT} ||= '11211'; @@ -419,7 +425,7 @@ main handler done -=== TEST 9: regsiter on_abort callback but no client abortion +=== TEST 9: register on_abort callback but no client abortion --- config location /t { lua_check_client_abort on; @@ -560,7 +566,7 @@ on abort called -=== TEST 12: regsiter on_abort callback but no client abortion (uthread) +=== TEST 12: register on_abort callback but no client abortion (uthread) --- config location /t { lua_check_client_abort on; @@ -607,7 +613,7 @@ main handler done -=== TEST 13: regsiter on_abort callback multiple times +=== TEST 13: register on_abort callback multiple times --- config location /t { lua_check_client_abort on; diff --git a/t/023-rewrite/redirect.t b/t/023-rewrite/redirect.t index 99f3fd4f70..2b3dccb0c0 100644 --- a/t/023-rewrite/redirect.t +++ b/t/023-rewrite/redirect.t @@ -119,6 +119,55 @@ GET /read } --- request GET /read ---- raw_response_headers_like: Location: /foo\r\n +--- raw_response_headers_like eval +my $headers; + +if (defined($ENV{TEST_NGINX_USE_HTTP3}) || defined($ENV{TEST_NGINX_USE_HTTP2})) { + $headers = "location: /foo\r\n" +} else { + $headers = "Location: /foo\r\n" +} + +$headers; --- response_body_like: 302 Found --- error_code: 302 + + + +=== TEST 7: internal redirects that do not clear module ctx +--- http_config + rewrite_by_lua_no_postpone on; +--- config + rewrite_by_lua_block { + -- this is empty by intention + } + + location /url1 { + rewrite ^ /url2; + } +--- request +GET /url1 +--- response_body_like: 404 Not Found +--- error_log eval +qr{\[error\] .*?/html/url2".*?No such file or directory} +--- error_code: 404 + + + +=== TEST 8: internal redirects that do not clear module ctx (yield in rewrite handler) +--- http_config + rewrite_by_lua_no_postpone on; +--- config + rewrite_by_lua_block { + ngx.sleep(0.001) + } + + location /url1 { + rewrite ^ /url2; + } +--- request +GET /url1 +--- response_body_like: 404 Not Found +--- error_log eval +qr{\[error\] .*?/html/url2".*?No such file or directory} +--- error_code: 404 diff --git a/t/023-rewrite/req-body.t b/t/023-rewrite/req-body.t index 13bdcb2511..0c1857384e 100644 --- a/t/023-rewrite/req-body.t +++ b/t/023-rewrite/req-body.t @@ -125,6 +125,7 @@ Expect: 100-Continue [alert] [error] http finalize request: 500, "/test?" a:1, c:0 +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/023-rewrite/req-socket.t b/t/023-rewrite/req-socket.t index 87cbbbef26..280001578d 100644 --- a/t/023-rewrite/req-socket.t +++ b/t/023-rewrite/req-socket.t @@ -1,6 +1,14 @@ # vim:set ft= ts=4 sw=4 et fdm=marker: -use Test::Nginx::Socket::Lua; +our $SkipReason; + +BEGIN { + if ($ENV{TEST_NGINX_USE_HTTP3}) { + $SkipReason = "http3 does not support ngx.req.socket"; + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); repeat_each(2); @@ -325,7 +333,7 @@ found the end of the stream === TEST 4: attempt to use the req socket across request boundary --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { rewrite_by_lua ' @@ -376,7 +384,7 @@ hello world === TEST 5: receive until on request_body - receiveuntil(1) on the last byte of the body See https://groups.google.com/group/openresty/browse_thread/thread/43cf01da3c681aba for details --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { rewrite_by_lua ' @@ -440,7 +448,7 @@ done === TEST 6: pipelined POST requests --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { rewrite_by_lua ' @@ -532,3 +540,4 @@ Expect: 100-Continue \breceived: hello\b.*?\breceived: worl\b --- no_error_log [error] +--- skip_eval: 3:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) diff --git a/t/023-rewrite/sanity.t b/t/023-rewrite/sanity.t index b90aa0e167..73a85dc562 100644 --- a/t/023-rewrite/sanity.t +++ b/t/023-rewrite/sanity.t @@ -69,7 +69,7 @@ GET /lua location /lua { # NOTE: the newline escape sequence must be double-escaped, as nginx config # parser will unescape first! - rewrite_by_lua 'v = ngx.var["request_uri"] ngx.print("request_uri: ", v, "\\n")'; + rewrite_by_lua 'local v = ngx.var["request_uri"] ngx.print("request_uri: ", v, "\\n")'; content_by_lua 'ngx.exit(ngx.OK)'; } --- request @@ -87,7 +87,7 @@ request_uri: /lua?a=1&b=2 } --- user_files >>> test.lua -v = ngx.var["request_uri"] +local v = ngx.var["request_uri"] ngx.print("request_uri: ", v, "\n") --- request GET /lua?a=1&b=2 @@ -140,7 +140,7 @@ result: -0.4090441561579 === TEST 7: read $arg_xxx --- config location = /lua { - rewrite_by_lua 'who = ngx.var.arg_who + rewrite_by_lua 'local who = ngx.var.arg_who ngx.print("Hello, ", who, "!")'; content_by_lua 'ngx.exit(ngx.OK)'; } @@ -159,7 +159,7 @@ Hello, agentzh! location /lua { rewrite_by_lua ' -res = ngx.location.capture("/other") +local res = ngx.location.capture("/other") ngx.print("status=", res.status, " ") ngx.print("body=", res.body) '; @@ -175,7 +175,7 @@ status=200 body=hello, world === TEST 9: capture non-existed location --- config location /lua { - rewrite_by_lua 'res = ngx.location.capture("/other"); ngx.print("status=", res.status)'; + rewrite_by_lua 'local res = ngx.location.capture("/other"); ngx.print("status=", res.status)'; content_by_lua 'ngx.exit(ngx.OK)'; } --- request @@ -187,7 +187,7 @@ GET /lua === TEST 10: invalid capture location (not as expected...) --- config location /lua { - rewrite_by_lua 'res = ngx.location.capture("*(#*"); ngx.say("res=", res.status)'; + rewrite_by_lua 'local res = ngx.location.capture("*(#*"); ngx.say("res=", res.status)'; content_by_lua 'ngx.exit(ngx.OK)'; } --- request @@ -270,7 +270,7 @@ end ngx.print("num is: ", num, "\\n"); if (num > 0) then - res = ngx.location.capture("/recur?num="..tostring(num - 1)); + local res = ngx.location.capture("/recur?num="..tostring(num - 1)); ngx.print("status=", res.status, " "); ngx.print("body=", res.body); else @@ -359,7 +359,7 @@ location /sub { } location /parent { set $a 12; - rewrite_by_lua 'res = ngx.location.capture("/sub"); ngx.print(res.body)'; + rewrite_by_lua 'local res = ngx.location.capture("/sub"); ngx.print(res.body)'; content_by_lua 'ngx.exit(ngx.OK)'; } --- request @@ -377,7 +377,7 @@ location /parent { set $a ''; rewrite_by_lua ' ngx.var.a = 12; - res = ngx.location.capture( + local res = ngx.location.capture( "/sub", { share_all_vars = true } ); @@ -399,7 +399,7 @@ location /sub { } location /parent { rewrite_by_lua ' - res = ngx.location.capture("/sub", { share_all_vars = true }); + local res = ngx.location.capture("/sub", { share_all_vars = true }); ngx.say(ngx.var.a) '; @@ -421,7 +421,7 @@ location /sub { location /parent { rewrite_by_lua ' - res = ngx.location.capture("/sub", { share_all_vars = false }); + local res = ngx.location.capture("/sub", { share_all_vars = false }); ngx.say(ngx.var.a) '; content_by_lua return; @@ -441,7 +441,7 @@ GET /parent location /lua { rewrite_by_lua ' - res = ngx.location.capture("/other"); + local res = ngx.location.capture("/other"); ngx.say("type: ", res.header["Content-Type"]); '; @@ -466,7 +466,7 @@ type: foo/bar location /lua { rewrite_by_lua ' - res = ngx.location.capture("/other"); + local res = ngx.location.capture("/other"); ngx.say("type: ", res.header["Content-Type"]); ngx.say("Bar: ", res.header["Bar"]); '; @@ -494,7 +494,7 @@ Bar: Bah location /lua { rewrite_by_lua ' - res = ngx.location.capture("/other"); + local res = ngx.location.capture("/other"); ngx.say("type: ", res.header["Content-Type"]); ngx.say("Bar: ", res.header["Bar"] or "nil"); '; diff --git a/t/023-rewrite/sleep.t b/t/023-rewrite/sleep.t index 8d4c2da2ea..0135d5e440 100644 --- a/t/023-rewrite/sleep.t +++ b/t/023-rewrite/sleep.t @@ -110,6 +110,7 @@ bad argument #1 to 'sleep' === TEST 5: sleep 0.5 - multi-times +--- quic_max_idle_timeout: 1.0 --- config location /test { rewrite_by_lua ' @@ -135,6 +136,7 @@ lua sleep timer expired: "/test?" === TEST 6: sleep 0.5 - interleaved by ngx.say() - ended by ngx.sleep +--- quic_max_idle_timeout: 2.05 --- config location /test { rewrite_by_lua ' @@ -163,6 +165,7 @@ lua sleep timer expired: "/test?" === TEST 7: sleep 0.5 - interleaved by ngx.say() - not ended by ngx.sleep +--- quic_max_idle_timeout: 0.85 --- config location /test { rewrite_by_lua ' diff --git a/t/023-rewrite/socket-keepalive.t b/t/023-rewrite/socket-keepalive.t index 489a70fc93..9ce8d5e290 100644 --- a/t/023-rewrite/socket-keepalive.t +++ b/t/023-rewrite/socket-keepalive.t @@ -27,7 +27,7 @@ __DATA__ === TEST 1: sanity --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -104,7 +104,7 @@ lua tcp socket get keepalive peer: using connection === TEST 2: free up the whole connection pool if no active connections --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -176,8 +176,9 @@ received: OK === TEST 3: upstream sockets close prematurely +--- no_http3 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; keepalive_timeout 100ms; @@ -253,8 +254,9 @@ done === TEST 4: http keepalive +--- no_http3 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location /t { @@ -330,6 +332,7 @@ done === TEST 5: lua_socket_keepalive_timeout +--- quic_max_idle_timeout: 1.1 --- config server_tokens off; location /t { @@ -409,6 +412,7 @@ qr/lua tcp socket connection pool size: 30\b/] === TEST 6: lua_socket_pool_size +--- quic_max_idle_timeout: 1.1 --- config server_tokens off; location /t { @@ -489,6 +493,7 @@ qr/lua tcp socket connection pool size: 1\b/] === TEST 7: "lua_socket_keepalive_timeout 0" means unlimited +--- quic_max_idle_timeout: 1.2 --- config server_tokens off; location /t { @@ -571,6 +576,7 @@ lua tcp socket keepalive timeout: unlimited === TEST 8: setkeepalive(timeout) overrides lua_socket_keepalive_timeout +--- quic_max_idle_timeout: 1.1 --- config server_tokens off; location /t { @@ -650,6 +656,7 @@ qr/lua tcp socket connection pool size: 30\b/] === TEST 9: sock:setkeepalive(timeout, size) overrides lua_socket_pool_size +--- quic_max_idle_timeout: 1.1 --- config server_tokens off; location /t { @@ -730,6 +737,7 @@ qr/lua tcp socket connection pool size: 25\b/] === TEST 10: sock:keepalive_timeout(0) means unlimited +--- quic_max_idle_timeout: 1.1 --- config server_tokens off; location /t { @@ -815,7 +823,7 @@ lua tcp socket keepalive timeout: unlimited === TEST 11: sanity (uds) --- http_config eval " - lua_package_path '$::HtmlDir/?.lua;./?.lua'; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; server { listen unix:$::HtmlDir/nginx.sock; default_type 'text/plain'; @@ -903,7 +911,7 @@ lua tcp socket get keepalive peer: using connection -=== TEST 12: github issue #108: ngx.locaiton.capture + redis.set_keepalive +=== TEST 12: github issue #108: ngx.location.capture + redis.set_keepalive --- http_config eval qq{ lua_package_path "$::HtmlDir/?.lua;;"; @@ -953,7 +961,7 @@ lua tcp socket get keepalive peer: using connection === TEST 13: github issue #110: ngx.exit with HTTP_NOT_FOUND causes worker process to exit --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config error_page 404 /404.html; location /t { diff --git a/t/023-rewrite/subrequest.t b/t/023-rewrite/subrequest.t index 5d1e8f0839..cb8523c46b 100644 --- a/t/023-rewrite/subrequest.t +++ b/t/023-rewrite/subrequest.t @@ -27,7 +27,7 @@ __DATA__ location /lua { rewrite_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_DELETE }); ngx.print(res.body) @@ -54,7 +54,7 @@ DELETE location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_DELETE }); ngx.print(res.body) @@ -82,7 +82,7 @@ DELETE location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_POST }); ngx.print(res.body) @@ -105,7 +105,7 @@ POST location /lua { rewrite_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_HEAD }); ngx.print(res.body) @@ -131,7 +131,7 @@ GET /lua location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_GET }); ngx.print(res.body) @@ -158,7 +158,7 @@ GET location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo") + local res = ngx.location.capture("/foo") ngx.print(res.body) '; @@ -184,7 +184,7 @@ GET location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo", {}) + local res = ngx.location.capture("/foo", {}) ngx.print(res.body) '; @@ -213,7 +213,7 @@ GET location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_PUT, body = "hello" }); ngx.print(res.body) @@ -241,7 +241,7 @@ hello location /lua { rewrite_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_PUT, body = "hello" }); ngx.print(res.body) @@ -276,7 +276,7 @@ hello location /lua { rewrite_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_PUT, body = "hello" }); ngx.print(res.body) @@ -313,7 +313,7 @@ GET location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_POST, body = "hello" }); ngx.print(res.body) @@ -345,7 +345,7 @@ hello rewrite_by_lua ' ngx.location.capture("/flush"); - res = ngx.location.capture("/memc"); + local res = ngx.location.capture("/memc"); ngx.say("GET: " .. res.status); res = ngx.location.capture("/memc", @@ -386,7 +386,7 @@ cached: hello ngx.location.capture("/flush", { share_all_vars = true }); - res = ngx.location.capture("/memc", + local res = ngx.location.capture("/memc", { share_all_vars = true }); ngx.say("GET: " .. res.status); @@ -417,7 +417,7 @@ cached: hello location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = {} }) ngx.print(res.body) '; @@ -437,7 +437,7 @@ GET /lua location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { ["fo="] = "=>" } }) ngx.print(res.body) '; @@ -458,7 +458,7 @@ fo%3D=%3D%3E location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { ["fo="] = "=>", ["="] = ":" } }) ngx.print(res.body) @@ -480,7 +480,7 @@ GET /lua location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { foo = 3, bar = "hello" } }) ngx.print(res.body) @@ -502,7 +502,7 @@ GET /lua location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { [57] = "hi" } }) ngx.print(res.body) '; @@ -523,7 +523,7 @@ GET /lua location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { "hi" } }) ngx.print(res.body) '; @@ -544,7 +544,7 @@ GET /lua location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo?a=3", + local res = ngx.location.capture("/foo?a=3", { args = { b = 4 } }) ngx.print(res.body) '; @@ -565,7 +565,7 @@ a=3&b=4 location /lua { rewrite_by_lua ' - res = ngx.location.capture("/foo?a=3", + local res = ngx.location.capture("/foo?a=3", { args = "b=4" }) ngx.print(res.body) '; diff --git a/t/023-rewrite/tcp-socket-timeout.t b/t/023-rewrite/tcp-socket-timeout.t index 7ac09e243b..9be8081321 100644 --- a/t/023-rewrite/tcp-socket-timeout.t +++ b/t/023-rewrite/tcp-socket-timeout.t @@ -16,6 +16,7 @@ BEGIN { $ENV{MOCKEAGAIN} = 'w'; } + delete($ENV{TEST_NGINX_USE_HTTP2}); $ENV{TEST_NGINX_EVENT_TYPE} = 'poll'; $ENV{MOCKEAGAIN_WRITE_TIMEOUT_PATTERN} = 'get helloworld'; } @@ -46,7 +47,7 @@ __DATA__ location /t1 { rewrite_by_lua ' local sock = ngx.socket.tcp() - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -63,7 +64,7 @@ GET /t1 failed to connect: timeout --- error_log lua tcp socket connect timeout: 100 -lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 +lua tcp socket connect timed out, when connecting to 127.0.0.2:12345 --- timeout: 10 @@ -79,7 +80,7 @@ lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 rewrite_by_lua ' local sock = ngx.socket.tcp() sock:settimeout(150) - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -114,7 +115,7 @@ lua tcp socket connect timeout: 150 rewrite_by_lua ' local sock = ngx.socket.tcp() sock:settimeout(nil) - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -149,7 +150,7 @@ lua tcp socket connect timeout: 102 rewrite_by_lua ' local sock = ngx.socket.tcp() sock:settimeout(0) - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -185,7 +186,7 @@ lua tcp socket connect timeout: 102 rewrite_by_lua ' local sock = ngx.socket.tcp() sock:settimeout(-1) - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return diff --git a/t/023-rewrite/tcp-socket.t b/t/023-rewrite/tcp-socket.t index a951014b74..966365f906 100644 --- a/t/023-rewrite/tcp-socket.t +++ b/t/023-rewrite/tcp-socket.t @@ -86,6 +86,7 @@ failed to receive a line: closed [] close: 1 nil --- no_error_log [error] +--- no_http2 @@ -155,6 +156,7 @@ failed to receive a line: closed [foo] closed --- no_error_log [error] +--- no_http2 @@ -197,6 +199,7 @@ connected: nil failed to send request: closed --- error_log attempt to send data on a closed socket: +--- no_http2 @@ -250,11 +253,11 @@ attempt to send data on a closed socket: } --- request GET /t ---- response_body +--- response_body_like connected: 1 request sent: 56 -first line received: HTTP/1.1 200 OK -second line received: Server: openresty +first line received: HTTP\/1\.1 200 OK +second line received: (?:Date|Server): .*? --- no_error_log [error] @@ -304,7 +307,7 @@ qr/connect\(\) failed \(\d+: Connection refused\)/ location /test { rewrite_by_lua ' local sock = ngx.socket.tcp() - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) ngx.say("connect: ", ok, " ", err) local bytes @@ -329,7 +332,7 @@ send: nil closed receive: nil closed close: nil closed --- error_log -lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 +lua tcp socket connect timed out, when connecting to 127.0.0.2:12345 --- timeout: 10 @@ -521,6 +524,7 @@ failed to receive a line: closed close: 1 nil --- no_error_log [error] +--- no_http2 @@ -591,6 +595,7 @@ close: 1 nil " --- no_error_log [error] +--- no_http2 @@ -672,6 +677,7 @@ close: 1 nil " --- no_error_log [error] +--- no_http2 @@ -749,6 +755,7 @@ close: 1 nil " --- no_error_log [error] +--- no_http2 @@ -827,6 +834,7 @@ close: 1 nil " --- no_error_log [error] +--- no_http2 @@ -898,6 +906,7 @@ failed to receive a line: closed [] close: 1 nil --- no_error_log [error] +--- no_http2 @@ -940,7 +949,7 @@ close: 1 nil end end - ok, err = sock:close() + local ok, err = sock:close() ngx.say("close: ", ok, " ", err) '; @@ -967,6 +976,7 @@ failed to receive a line: closed [] close: 1 nil --- no_error_log [error] +--- no_http2 @@ -1077,12 +1087,13 @@ close: 1 nil " --- no_error_log [error] +--- no_http2 === TEST 19: cannot survive across request boundary (send) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location /t { @@ -1143,7 +1154,7 @@ received: OK|failed to send request: closed)\$" === TEST 20: cannot survive across request boundary (receive) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location /t { @@ -1214,7 +1225,7 @@ received: OK|failed to receive a line: closed \[nil\])$/ === TEST 21: cannot survive across request boundary (close) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location /t { @@ -1285,7 +1296,7 @@ received: OK|failed to close: closed)$/ === TEST 22: cannot survive across request boundary (connect) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location /t { @@ -1521,6 +1532,7 @@ GET /t 2: close: 1 nil --- no_error_log [error] +--- no_http2 @@ -1593,6 +1605,7 @@ failed to receive a line: closed [] close: 1 nil --- no_error_log [error] +--- no_http2 @@ -1654,6 +1667,9 @@ GET /t --- ignore_response --- error_log bad argument #1 to 'send' (bad data type nil found) +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server# +--- no_http2 @@ -1715,6 +1731,9 @@ GET /t --- ignore_response --- error_log bad argument #1 to 'send' (bad data type boolean found) +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server# +--- no_http2 @@ -1776,6 +1795,9 @@ GET /t --- ignore_response --- error_log bad argument #1 to 'send' (bad data type userdata found) +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server# +--- no_http2 @@ -1845,6 +1867,7 @@ subrequest: 200, OK\r " --- no_error_log [error] +--- no_http2 @@ -1916,6 +1939,7 @@ close: 1 nil --- no_error_log [error] --- SKIP +--- no_http2 @@ -1976,6 +2000,7 @@ receive(0): [] close: 1 nil --- no_error_log [error] +--- no_http2 @@ -2036,12 +2061,13 @@ send(""): 0 close: 1 nil --- no_error_log [error] +--- no_http2 === TEST 33: bad request tries to connect --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -2092,12 +2118,13 @@ qr/runtime error: rewrite_by_lua\(nginx\.conf:\d+\):7: bad request/ --- no_error_log [alert] +--- no_http2 === TEST 34: bad request tries to receive --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -2151,12 +2178,13 @@ qr/runtime error: rewrite_by_lua\(nginx\.conf:\d+\):14: bad request/ --- no_error_log [alert] +--- no_http2 === TEST 35: bad request tries to send --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -2215,7 +2243,7 @@ qr/runtime error: rewrite_by_lua\(nginx\.conf:\d+\):14: bad request/ === TEST 36: bad request tries to close --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -2274,7 +2302,7 @@ qr/runtime error: rewrite_by_lua\(nginx\.conf:\d+\):14: bad request/ === TEST 37: bad request tries to set keepalive --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -2333,7 +2361,7 @@ qr/runtime error: rewrite_by_lua\(nginx\.conf:\d+\):14: bad request/ === TEST 38: bad request tries to receiveuntil --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { diff --git a/t/023-rewrite/unix-socket.t b/t/023-rewrite/unix-socket.t index 8a5f00019b..91826fc3eb 100644 --- a/t/023-rewrite/unix-socket.t +++ b/t/023-rewrite/unix-socket.t @@ -79,7 +79,7 @@ qr{\[crit\] .*? connect\(\) to unix:/tmp/nosuchfile\.sock failed} --- request GET /test --- response_body -failed to connect: failed to parse host name "/tmp/test-nginx.sock": invalid host +failed to connect: missing the port number diff --git a/t/023-rewrite/uthread-exec.t b/t/023-rewrite/uthread-exec.t index 2bea7e76e7..9428bd67b2 100644 --- a/t/023-rewrite/uthread-exec.t +++ b/t/023-rewrite/uthread-exec.t @@ -23,7 +23,7 @@ __DATA__ --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.exec("/foo") end @@ -59,7 +59,7 @@ i am foo --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exec("/foo") end @@ -95,7 +95,7 @@ i am foo --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exec("/foo") end @@ -178,12 +178,12 @@ hello foo --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exec("/foo") end - function g() + local function g() ngx.sleep(1) end @@ -270,7 +270,7 @@ hello foo location /lua { client_body_timeout 12000ms; rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exec("/foo") end diff --git a/t/023-rewrite/uthread-exit.t b/t/023-rewrite/uthread-exit.t index 3df46835b6..87f850ce8b 100644 --- a/t/023-rewrite/uthread-exit.t +++ b/t/023-rewrite/uthread-exit.t @@ -24,7 +24,7 @@ __DATA__ --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.exit(0) end @@ -87,7 +87,7 @@ hello in thread --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -172,13 +172,13 @@ after --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.say("f") ngx.exit(0) end - function g() + local function g() ngx.sleep(1) ngx.say("g") end @@ -262,7 +262,7 @@ f --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.say("exiting the user thread") ngx.exit(0) @@ -298,10 +298,10 @@ exiting the user thread === TEST 5: exit in user thread (entry thread is still pending on the DNS resolver for ngx.socket.tcp) --- config location /lua { - resolver agentzh.org; + resolver 127.0.0.2:12345; resolver_timeout 12s; rewrite_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.001) ngx.exit(0) @@ -311,7 +311,7 @@ exiting the user thread ngx.thread.spawn(f) ngx.say("after") local sock = ngx.socket.tcp() - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -403,11 +403,11 @@ after === TEST 6: exit in user thread (entry thread is still pending on the DNS resolver for ngx.socket.udp) --- config location /lua { - resolver agentzh.org; + resolver 127.0.0.2:12345; #resolver 127.0.0.1; resolver_timeout 12s; rewrite_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.001) ngx.exit(0) @@ -510,7 +510,7 @@ after --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -521,7 +521,7 @@ after ngx.say("after") local sock = ngx.socket.tcp() sock:settimeout(12000) - local ok, err = sock:connect("106.184.1.99", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -600,7 +600,7 @@ after --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -700,7 +700,7 @@ after --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -806,7 +806,7 @@ after --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -901,7 +901,7 @@ after --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -987,6 +987,7 @@ hello in thread after --- no_error_log [error] +--- skip_eval: 4:$ENV{TEST_NGINX_USE_HTTP3} @@ -995,7 +996,7 @@ after location /lua { client_body_timeout 12000ms; rewrite_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -1072,6 +1073,7 @@ hello in thread after --- no_error_log [error] +--- skip_eval: 4:$ENV{TEST_NGINX_USE_HTTP3} @@ -1080,7 +1082,7 @@ after location /lua { client_body_timeout 12000ms; rewrite_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -1164,7 +1166,7 @@ attempt to abort with pending subrequests location /lua { client_body_timeout 12000ms; rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exit(0) end @@ -1247,7 +1249,7 @@ attempt to abort with pending subrequests location /lua { client_body_timeout 12000ms; rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exit(0) end diff --git a/t/023-rewrite/uthread-redirect.t b/t/023-rewrite/uthread-redirect.t index 83de1a36ed..0e636f7bba 100644 --- a/t/023-rewrite/uthread-redirect.t +++ b/t/023-rewrite/uthread-redirect.t @@ -25,7 +25,7 @@ __DATA__ location /lua { client_body_timeout 12000ms; rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.redirect(301) end @@ -113,7 +113,7 @@ attempt to abort with pending subrequests --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.redirect(301) end diff --git a/t/023-rewrite/uthread-spawn.t b/t/023-rewrite/uthread-spawn.t index 5552107e39..62d837cff7 100644 --- a/t/023-rewrite/uthread-spawn.t +++ b/t/023-rewrite/uthread-spawn.t @@ -23,7 +23,7 @@ __DATA__ --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("hello in thread") end @@ -68,11 +68,11 @@ after --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("in thread 1") end - function g() + local function g() ngx.say("in thread 2") end @@ -117,7 +117,7 @@ after 2 --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("before sleep") ngx.sleep(0.1) ngx.say("after sleep") @@ -154,13 +154,13 @@ after sleep --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("1: before sleep") ngx.sleep(0.2) ngx.say("1: after sleep") end - function g() + local function g() ngx.say("2: before sleep") ngx.sleep(0.1) ngx.say("2: after sleep") @@ -210,7 +210,7 @@ delete thread 2 --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.blah() end @@ -241,9 +241,9 @@ qr/lua user thread aborted: runtime error: rewrite_by_lua\(nginx\.conf:\d+\):3: --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("before capture") - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("after capture: ", res.body) end @@ -287,7 +287,7 @@ after capture: hello world --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("before capture") local res = ngx.location.capture("/proxy?foo") ngx.say("after capture: ", res.body) @@ -340,7 +340,7 @@ after capture: hello foo --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("before capture") local res = ngx.location.capture("/proxy?foo") ngx.say("after capture: ", res.body) @@ -394,13 +394,13 @@ capture: hello bar --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("f: before capture") local res = ngx.location.capture("/proxy?foo") ngx.say("f: after capture: ", res.body) end - function g() + local function g() ngx.say("g: before capture") local res = ngx.location.capture("/proxy?bah") ngx.say("g: after capture: ", res.body) @@ -472,7 +472,8 @@ g: after capture: hello bah --- config location /lua { rewrite_by_lua ' - function f() + local g + local function f() ngx.say("before g") ngx.thread.spawn(g) ngx.say("after g") @@ -518,7 +519,8 @@ after g --- config location /lua { rewrite_by_lua ' - function f() + local g + local function f() ngx.say("before g") ngx.thread.spawn(g) ngx.say("after g") @@ -566,7 +568,7 @@ hello in g() location /lua { rewrite_by_lua ' local co - function f() + local function f() co = coroutine.running() ngx.sleep(0.1) end @@ -599,7 +601,7 @@ status: running location /lua { rewrite_by_lua ' local co - function f() + local function f() co = coroutine.running() end @@ -631,7 +633,8 @@ status: zombie location /lua { rewrite_by_lua ' local co - function f() + local g + local function f() co = coroutine.running() local co2 = coroutine.create(g) coroutine.resume(co2) @@ -670,7 +673,8 @@ status: normal --- config location /lua { rewrite_by_lua ' - function f() + local g + local function f() ngx.say("before g") ngx.thread.spawn(g) ngx.say("after g") @@ -717,7 +721,7 @@ after f rewrite_by_lua ' local yield = coroutine.yield - function f() + local function f() local self = coroutine.running() ngx.say("f 1") yield(self) @@ -770,7 +774,7 @@ f 3 rewrite_by_lua ' local yield = coroutine.yield - function f() + local function f() local self = coroutine.running() ngx.say("f 1") yield(self) @@ -779,7 +783,7 @@ f 3 ngx.say("f 3") end - function g() + local function g() local self = coroutine.running() ngx.say("g 1") yield(self) @@ -826,7 +830,7 @@ g 3 --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("hello in thread") coroutine.yield(coroutine.running) ngx.flush(true) @@ -863,12 +867,12 @@ after --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.say("hello from f") ngx.flush(true) end - function g() + local function g() ngx.say("hello from g") ngx.flush(true) end @@ -914,7 +918,7 @@ hello from g --- config location /lua { rewrite_by_lua ' - function f() + local function f() local sock = ngx.socket.tcp() local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) if not ok then @@ -966,7 +970,7 @@ received: OK --- config location /lua { rewrite_by_lua ' - function f() + local function f() local sock = ngx.socket.udp() local ok, err = sock:setpeername("127.0.0.1", 12345) local bytes, err = sock:send("blah") @@ -1027,7 +1031,7 @@ after)$ --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.req.read_body() local body = ngx.req.get_body_data() ngx.say("body: ", body) @@ -1073,7 +1077,7 @@ body: hello world)$ --- config location /lua { rewrite_by_lua ' - function f() + local function f() local sock = ngx.req.socket() local body, err = sock:receive(11) if not body then @@ -1116,6 +1120,7 @@ body: hello world)$ --- no_error_log [error] +--- skip_eval: 4:$ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/024-access/client-abort.t b/t/024-access/client-abort.t index c16f4eaab7..e4abb4ebd9 100644 --- a/t/024-access/client-abort.t +++ b/t/024-access/client-abort.t @@ -1,6 +1,16 @@ # vim:set ft= ts=4 sw=4 et fdm=marker: -use Test::Nginx::Socket::Lua; +our $SkipReason; + +BEGIN { + if ($ENV{TEST_NGINX_USE_HTTP3}) { + $SkipReason = "http3 does not support ngx.req.socket and lua_check_client_abort"; + } elsif ($ENV{TEST_NGINX_USE_HTTP2}) { + $SkipReason = "http2 does not support ngx.req.socket and lua_check_client_abort"; + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); use t::StapThread; our $GCScript = <<_EOC_; @@ -200,7 +210,7 @@ bad things happen location = /sub { proxy_ignore_client_abort on; - proxy_pass http://agentzh.org:12345/; + proxy_pass http://127.0.0.2:12345/; } location = /sleep { @@ -241,7 +251,7 @@ client prematurely closed connection location = /sub { proxy_ignore_client_abort off; - proxy_pass http://agentzh.org:12345/; + proxy_pass http://127.0.0.2:12345/; } --- request GET /t @@ -546,7 +556,7 @@ client prematurely closed connection return end - ok, err = sock:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) if not ok then ngx.log(ngx.ERR, "failed to connect: ", err) return diff --git a/t/024-access/exec.t b/t/024-access/exec.t index 43c1a7752a..d168a47d87 100644 --- a/t/024-access/exec.t +++ b/t/024-access/exec.t @@ -326,7 +326,7 @@ hello --- config location /main { access_by_lua ' - res = ngx.location.capture("/test_loc"); + local res = ngx.location.capture("/test_loc"); ngx.print("hello, ", res.body) '; content_by_lua return; diff --git a/t/024-access/exit.t b/t/024-access/exit.t index d6d66b8cfe..661ee2d8bc 100644 --- a/t/024-access/exit.t +++ b/t/024-access/exit.t @@ -114,6 +114,7 @@ GET /api?user=agentz === TEST 6: working with ngx_auth_request (simplest form, w/o ngx_memc) +--- no_http2 --- http_config eval " lua_package_cpath '$::LuaCpath'; @@ -177,10 +178,12 @@ GET /api?uid=32 --- response_body Logged in 56 --- timeout: 3 +--- skip_eval: 2:$ENV{TEST_NGINX_USE_HTTP3} === TEST 7: working with ngx_auth_request (simplest form) +--- no_http2 --- http_config eval " lua_package_cpath '$::LuaCpath'; @@ -243,10 +246,12 @@ ngx.var.uid = res[1].uid; GET /api?uid=32 --- response_body Logged in 56 +--- skip_eval: 2:$ENV{TEST_NGINX_USE_HTTP3} === TEST 8: working with ngx_auth_request +--- no_http2 --- http_config eval " lua_package_cpath '$::LuaCpath'; @@ -320,6 +325,7 @@ ngx.var.uid = res[1].uid; GET /api?uid=32 --- response_body Logged in 56 +--- skip_eval: 2:$ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/024-access/mixed.t b/t/024-access/mixed.t index a9f8039b42..22f0037919 100644 --- a/t/024-access/mixed.t +++ b/t/024-access/mixed.t @@ -35,7 +35,7 @@ __DATA__ access_by_lua ' ngx.location.capture("/flush"); - res = ngx.location.capture("/memc"); + local res = ngx.location.capture("/memc"); print("access GET: ", res.status); res = ngx.location.capture("/memc", @@ -49,7 +49,7 @@ __DATA__ content_by_lua ' ngx.location.capture("/flush"); - res = ngx.location.capture("/memc"); + local res = ngx.location.capture("/memc"); ngx.say("content GET: " .. res.status); res = ngx.location.capture("/memc", @@ -187,7 +187,7 @@ world\x03\x04\xff rewrite_by_lua ' ngx.location.capture("/flush"); - res = ngx.location.capture("/memc"); + local res = ngx.location.capture("/memc"); print("rewrite GET: " .. res.status); res = ngx.location.capture("/memc", @@ -201,7 +201,7 @@ world\x03\x04\xff access_by_lua ' ngx.location.capture("/flush"); - res = ngx.location.capture("/memc"); + local res = ngx.location.capture("/memc"); print("access GET: " .. res.status); res = ngx.location.capture("/memc", @@ -215,7 +215,7 @@ world\x03\x04\xff content_by_lua ' ngx.location.capture("/flush"); - res = ngx.location.capture("/memc"); + local res = ngx.location.capture("/memc"); ngx.say("content GET: " .. res.status); res = ngx.location.capture("/memc", diff --git a/t/024-access/multi-capture.t b/t/024-access/multi-capture.t index 930b74dad9..b1757dd120 100644 --- a/t/024-access/multi-capture.t +++ b/t/024-access/multi-capture.t @@ -154,7 +154,7 @@ res2.body = b location /main { access_by_lua ' - res = ngx.location.capture("/foo?n=1") + local res = ngx.location.capture("/foo?n=1") ngx.say("top res.status = " .. res.status) ngx.say("top res.body = [" .. res.body .. "]") '; diff --git a/t/024-access/on-abort.t b/t/024-access/on-abort.t index 5bb948bb68..70637ba862 100644 --- a/t/024-access/on-abort.t +++ b/t/024-access/on-abort.t @@ -19,7 +19,13 @@ our $StapScript = $t::StapThread::StapScript; repeat_each(2); -plan tests => repeat_each() * (blocks() * 4 + 15); +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + plan(skip_all => "HTTP3 does not support on_abort"); +} elsif (defined $ENV{TEST_NGINX_USE_HTTP2}) { + plan(skip_all => "HTTP2 does not support on_abort"); +} else { + plan tests => repeat_each() * (blocks() * 4 + 15); +} $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; $ENV{TEST_NGINX_MEMCACHED_PORT} ||= '11211'; @@ -417,7 +423,7 @@ main handler done -=== TEST 9: regsiter on_abort callback but no client abortion +=== TEST 9: register on_abort callback but no client abortion --- config location /t { lua_check_client_abort on; @@ -558,7 +564,7 @@ on abort called -=== TEST 12: regsiter on_abort callback but no client abortion (uthread) +=== TEST 12: register on_abort callback but no client abortion (uthread) --- config location /t { lua_check_client_abort on; @@ -603,7 +609,7 @@ main handler done -=== TEST 13: regsiter on_abort callback multiple times +=== TEST 13: register on_abort callback multiple times --- config location /t { lua_check_client_abort on; diff --git a/t/024-access/redirect.t b/t/024-access/redirect.t index b45fac718b..e8609af060 100644 --- a/t/024-access/redirect.t +++ b/t/024-access/redirect.t @@ -119,6 +119,15 @@ GET /read } --- request GET /read ---- raw_response_headers_like: Location: /foo\r\n +--- raw_response_headers_like eval +my $headers; + +if (defined($ENV{TEST_NGINX_USE_HTTP3}) || defined($ENV{TEST_NGINX_USE_HTTP2})) { + $headers = "location: /foo\r\n" +} else { + $headers = "Location: /foo\r\n" +} + +$headers; --- response_body_like: 302 Found --- error_code: 302 diff --git a/t/024-access/req-body.t b/t/024-access/req-body.t index 70db85c1c2..48caeb9001 100644 --- a/t/024-access/req-body.t +++ b/t/024-access/req-body.t @@ -122,6 +122,7 @@ Expect: 100-Continue [alert] [error] http finalize request: 500, "/test?" a:1, c:0 +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/024-access/sanity.t b/t/024-access/sanity.t index 7ff177fcf7..e5612a8b8a 100644 --- a/t/024-access/sanity.t +++ b/t/024-access/sanity.t @@ -69,7 +69,7 @@ GET /lua location /lua { # NOTE: the newline escape sequence must be double-escaped, as nginx config # parser will unescape first! - access_by_lua 'v = ngx.var["request_uri"] ngx.print("request_uri: ", v, "\\n")'; + access_by_lua 'local v = ngx.var["request_uri"] ngx.print("request_uri: ", v, "\\n")'; content_by_lua 'ngx.exit(ngx.OK)'; } --- request @@ -87,7 +87,7 @@ request_uri: /lua?a=1&b=2 } --- user_files >>> test.lua -v = ngx.var["request_uri"] +local v = ngx.var["request_uri"] ngx.print("request_uri: ", v, "\n") --- request GET /lua?a=1&b=2 @@ -140,7 +140,7 @@ result: -0.4090441561579 === TEST 7: read $arg_xxx --- config location = /lua { - access_by_lua 'who = ngx.var.arg_who + access_by_lua 'local who = ngx.var.arg_who ngx.print("Hello, ", who, "!")'; content_by_lua 'ngx.exit(ngx.OK)'; } @@ -159,7 +159,7 @@ Hello, agentzh! location /lua { access_by_lua ' -res = ngx.location.capture("/other") +local res = ngx.location.capture("/other") ngx.print("status=", res.status, " ") ngx.print("body=", res.body) '; @@ -175,7 +175,7 @@ status=200 body=hello, world === TEST 9: capture non-existed location --- config location /lua { - access_by_lua 'res = ngx.location.capture("/other"); ngx.print("status=", res.status)'; + access_by_lua 'local res = ngx.location.capture("/other"); ngx.print("status=", res.status)'; content_by_lua 'ngx.exit(ngx.OK)'; } --- request @@ -187,7 +187,7 @@ GET /lua === TEST 10: invalid capture location (not as expected...) --- config location /lua { - access_by_lua 'res = ngx.location.capture("*(#*"); ngx.say("res=", res.status)'; + access_by_lua 'local res = ngx.location.capture("*(#*"); ngx.say("res=", res.status)'; content_by_lua 'ngx.exit(ngx.OK)'; } --- request @@ -244,7 +244,7 @@ GET /lua ngx.print("num is: ", num, "\\n"); if (num > 0) then - res = ngx.location.capture("/recur?num="..tostring(num - 1)); + local res = ngx.location.capture("/recur?num="..tostring(num - 1)); ngx.print("status=", res.status, " "); ngx.print("body=", res.body, "\\n"); else @@ -271,7 +271,7 @@ access phase not running in subrequests ngx.print("num is: ", num, "\\n"); if (num > 0) then - res = ngx.location.capture("/recur?num="..tostring(num - 1)); + local res = ngx.location.capture("/recur?num="..tostring(num - 1)); ngx.print("status=", res.status, " "); ngx.print("body=", res.body); else @@ -357,7 +357,7 @@ location /sub { } location /parent { set $a 12; - access_by_lua 'res = ngx.location.capture("/sub"); ngx.print(res.body)'; + access_by_lua 'local res = ngx.location.capture("/sub"); ngx.print(res.body)'; content_by_lua 'ngx.exit(ngx.OK)'; } --- request @@ -375,7 +375,7 @@ location /parent { set $a ''; access_by_lua ' ngx.var.a = 12; - res = ngx.location.capture( + local res = ngx.location.capture( "/sub", { share_all_vars = true } ); @@ -397,7 +397,7 @@ location /sub { } location /parent { access_by_lua ' - res = ngx.location.capture("/sub", { share_all_vars = true }); + local res = ngx.location.capture("/sub", { share_all_vars = true }); ngx.say(ngx.var.a) '; @@ -419,7 +419,7 @@ location /sub { location /parent { access_by_lua ' - res = ngx.location.capture("/sub", { share_all_vars = false }); + local res = ngx.location.capture("/sub", { share_all_vars = false }); ngx.say(ngx.var.a) '; content_by_lua return; @@ -439,7 +439,7 @@ GET /parent location /lua { access_by_lua ' - res = ngx.location.capture("/other"); + local res = ngx.location.capture("/other"); ngx.say("type: ", res.header["Content-Type"]); '; @@ -464,7 +464,7 @@ type: foo/bar location /lua { access_by_lua ' - res = ngx.location.capture("/other"); + local res = ngx.location.capture("/other"); ngx.say("type: ", res.header["Content-Type"]); ngx.say("Bar: ", res.header["Bar"]); '; @@ -492,7 +492,7 @@ Bar: Bah location /lua { access_by_lua ' - res = ngx.location.capture("/other"); + local res = ngx.location.capture("/other"); ngx.say("type: ", res.header["Content-Type"]); ngx.say("Bar: ", res.header["Bar"] or "nil"); '; diff --git a/t/024-access/sleep.t b/t/024-access/sleep.t index fc1fc024b5..c7aa0b65a4 100644 --- a/t/024-access/sleep.t +++ b/t/024-access/sleep.t @@ -110,6 +110,7 @@ bad argument #1 to 'sleep' === TEST 5: sleep 0.5 - multi-times +--- quic_max_idle_timeout: 1.0 --- config location /test { access_by_lua ' @@ -135,6 +136,7 @@ lua sleep timer expired: "/test?" === TEST 6: sleep 0.5 - interleaved by ngx.say() - ended by ngx.sleep +--- quic_max_idle_timeout: 2.2 --- config location /test { access_by_lua ' @@ -163,6 +165,7 @@ lua sleep timer expired: "/test?" === TEST 7: sleep 0.5 - interleaved by ngx.say() - not ended by ngx.sleep +--- quic_max_idle_timeout: 0.85 --- config location /test { access_by_lua ' diff --git a/t/024-access/subrequest.t b/t/024-access/subrequest.t index b6ccf11990..665780a6dd 100644 --- a/t/024-access/subrequest.t +++ b/t/024-access/subrequest.t @@ -27,7 +27,7 @@ __DATA__ location /lua { access_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_DELETE }); ngx.print(res.body) @@ -54,7 +54,7 @@ DELETE location /lua { access_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_DELETE }); ngx.print(res.body) @@ -82,7 +82,7 @@ DELETE location /lua { access_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_POST }); ngx.print(res.body) @@ -105,7 +105,7 @@ POST location /lua { access_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_HEAD }); ngx.print(res.body) @@ -131,7 +131,7 @@ GET /lua location /lua { access_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_GET }); ngx.print(res.body) @@ -158,7 +158,7 @@ GET location /lua { access_by_lua ' - res = ngx.location.capture("/foo") + local res = ngx.location.capture("/foo") ngx.print(res.body) '; @@ -184,7 +184,7 @@ GET location /lua { access_by_lua ' - res = ngx.location.capture("/foo", {}) + local res = ngx.location.capture("/foo", {}) ngx.print(res.body) '; @@ -213,7 +213,7 @@ GET location /lua { access_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_PUT, body = "hello" }); ngx.print(res.body) @@ -241,7 +241,7 @@ hello location /lua { access_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_PUT, body = "hello" }); ngx.print(res.body) @@ -276,7 +276,7 @@ hello location /lua { access_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { method = ngx.HTTP_PUT, body = "hello" }); ngx.print(res.body) @@ -313,7 +313,7 @@ GET location /lua { access_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { method = ngx.HTTP_POST, body = "hello" }); ngx.print(res.body) @@ -345,7 +345,7 @@ hello access_by_lua ' ngx.location.capture("/flush"); - res = ngx.location.capture("/memc"); + local res = ngx.location.capture("/memc"); ngx.say("GET: " .. res.status); res = ngx.location.capture("/memc", @@ -386,7 +386,7 @@ cached: hello ngx.location.capture("/flush", { share_all_vars = true }); - res = ngx.location.capture("/memc", + local res = ngx.location.capture("/memc", { share_all_vars = true }); ngx.say("GET: " .. res.status); @@ -417,7 +417,7 @@ cached: hello location /lua { access_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = {} }) ngx.print(res.body) '; @@ -437,7 +437,7 @@ GET /lua location /lua { access_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { ["fo="] = "=>" } }) ngx.print(res.body) '; @@ -458,7 +458,7 @@ fo%3D=%3D%3E location /lua { access_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { ["fo="] = "=>", ["="] = ":" } }) ngx.print(res.body) @@ -480,7 +480,7 @@ GET /lua location /lua { access_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { foo = 3, bar = "hello" } }) ngx.print(res.body) @@ -502,7 +502,7 @@ GET /lua location /lua { access_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { [57] = "hi" } }) ngx.print(res.body) '; @@ -523,7 +523,7 @@ GET /lua location /lua { access_by_lua ' - res = ngx.location.capture("/foo", + local res = ngx.location.capture("/foo", { args = { "hi" } }) ngx.print(res.body) '; @@ -544,7 +544,7 @@ GET /lua location /lua { access_by_lua ' - res = ngx.location.capture("/foo?a=3", + local res = ngx.location.capture("/foo?a=3", { args = { b = 4 } }) ngx.print(res.body) '; @@ -565,7 +565,7 @@ a=3&b=4 location /lua { access_by_lua ' - res = ngx.location.capture("/foo?a=3", + local res = ngx.location.capture("/foo?a=3", { args = "b=4" }) ngx.print(res.body) '; diff --git a/t/024-access/uthread-exec.t b/t/024-access/uthread-exec.t index 7add3d4218..9c88eb33e3 100644 --- a/t/024-access/uthread-exec.t +++ b/t/024-access/uthread-exec.t @@ -23,7 +23,7 @@ __DATA__ --- config location /lua { access_by_lua ' - function f() + local function f() ngx.exec("/foo") end @@ -59,7 +59,7 @@ i am foo --- config location /lua { access_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exec("/foo") end @@ -95,7 +95,7 @@ i am foo --- config location /lua { access_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exec("/foo") end @@ -179,12 +179,12 @@ hello foo --- config location /lua { access_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exec("/foo") end - function g() + local function g() ngx.sleep(1) end @@ -271,7 +271,7 @@ hello foo location /lua { client_body_timeout 12000ms; access_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exec("/foo") end diff --git a/t/024-access/uthread-exit.t b/t/024-access/uthread-exit.t index 7c146ae7ec..bd165ae708 100644 --- a/t/024-access/uthread-exit.t +++ b/t/024-access/uthread-exit.t @@ -24,7 +24,7 @@ __DATA__ --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.exit(0) end @@ -87,7 +87,7 @@ hello in thread --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -172,13 +172,13 @@ after --- config location /lua { access_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.say("f") ngx.exit(0) end - function g() + local function g() ngx.sleep(1) ngx.say("g") end @@ -262,7 +262,7 @@ f --- config location /lua { access_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.say("exiting the user thread") ngx.exit(0) @@ -298,10 +298,10 @@ exiting the user thread === TEST 5: exit in user thread (entry thread is still pending on the DNS resolver for ngx.socket.tcp) --- config location /lua { - resolver agentzh.org; + resolver 127.0.0.2:12345; resolver_timeout 12s; access_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.001) ngx.exit(0) @@ -394,10 +394,10 @@ after === TEST 6: exit in user thread (entry thread is still pending on the DNS resolver for ngx.socket.udp) --- config location /lua { - resolver agentzh.org; + resolver 127.0.0.2:12345; resolver_timeout 12s; access_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.001) ngx.exit(0) @@ -491,7 +491,7 @@ after --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -502,7 +502,7 @@ after ngx.say("after") local sock = ngx.socket.tcp() sock:settimeout(12000) - local ok, err = sock:connect("106.184.1.99", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -582,7 +582,7 @@ after --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -682,7 +682,7 @@ after --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -788,7 +788,7 @@ after --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -880,10 +880,11 @@ after === TEST 11: exit in user thread (entry thread is still pending on reqsock:receive) +--- skip_eval: 5:$ENV{TEST_NGINX_USE_HTTP3} --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -973,11 +974,12 @@ after === TEST 12: exit in user thread (entry thread is still pending on ngx.req.read_body) +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} --- config location /lua { client_body_timeout 12000ms; access_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -1062,7 +1064,7 @@ after location /lua { client_body_timeout 12000ms; access_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -1146,7 +1148,7 @@ attempt to abort with pending subrequests location /lua { client_body_timeout 12000ms; access_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exit(0) end @@ -1229,7 +1231,7 @@ attempt to abort with pending subrequests location /lua { client_body_timeout 12000ms; access_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exit(0) end diff --git a/t/024-access/uthread-redirect.t b/t/024-access/uthread-redirect.t index 4eb4759585..cb99a35125 100644 --- a/t/024-access/uthread-redirect.t +++ b/t/024-access/uthread-redirect.t @@ -25,7 +25,7 @@ __DATA__ location /lua { client_body_timeout 12000ms; access_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.redirect(301) end @@ -113,7 +113,7 @@ attempt to abort with pending subrequests --- config location /lua { access_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.redirect(301) end diff --git a/t/024-access/uthread-spawn.t b/t/024-access/uthread-spawn.t index 7c7ba3b931..b31edbcb1e 100644 --- a/t/024-access/uthread-spawn.t +++ b/t/024-access/uthread-spawn.t @@ -23,7 +23,7 @@ __DATA__ --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("hello in thread") end @@ -68,11 +68,11 @@ after --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("in thread 1") end - function g() + local function g() ngx.say("in thread 2") end @@ -117,7 +117,7 @@ after 2 --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("before sleep") ngx.sleep(0.1) ngx.say("after sleep") @@ -154,13 +154,13 @@ after sleep --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("1: before sleep") ngx.sleep(0.2) ngx.say("1: after sleep") end - function g() + local function g() ngx.say("2: before sleep") ngx.sleep(0.1) ngx.say("2: after sleep") @@ -210,7 +210,7 @@ delete thread 2 --- config location /lua { access_by_lua ' - function f() + local function f() ngx.blah() end @@ -241,9 +241,9 @@ qr/lua user thread aborted: runtime error: access_by_lua\(nginx\.conf:\d+\):3: a --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("before capture") - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("after capture: ", res.body) end @@ -287,7 +287,7 @@ after capture: hello world --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("before capture") local res = ngx.location.capture("/proxy?foo") ngx.say("after capture: ", res.body) @@ -340,7 +340,7 @@ after capture: hello foo --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("before capture") local res = ngx.location.capture("/proxy?foo") ngx.say("after capture: ", res.body) @@ -395,13 +395,13 @@ capture: hello bar --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("f: before capture") local res = ngx.location.capture("/proxy?foo") ngx.say("f: after capture: ", res.body) end - function g() + local function g() ngx.say("g: before capture") local res = ngx.location.capture("/proxy?bah") ngx.say("g: after capture: ", res.body) @@ -473,7 +473,8 @@ g: after capture: hello bah --- config location /lua { access_by_lua ' - function f() + local g + local function f() ngx.say("before g") ngx.thread.spawn(g) ngx.say("after g") @@ -519,7 +520,8 @@ after g --- config location /lua { access_by_lua ' - function f() + local g + local function f() ngx.say("before g") ngx.thread.spawn(g) ngx.say("after g") @@ -567,7 +569,7 @@ hello in g() location /lua { access_by_lua ' local co - function f() + local function f() co = coroutine.running() ngx.sleep(0.1) end @@ -600,7 +602,7 @@ status: running location /lua { access_by_lua ' local co - function f() + local function f() co = coroutine.running() end @@ -632,7 +634,8 @@ status: zombie location /lua { access_by_lua ' local co - function f() + local g + local function f() co = coroutine.running() local co2 = coroutine.create(g) coroutine.resume(co2) @@ -671,7 +674,8 @@ status: normal --- config location /lua { access_by_lua ' - function f() + local g + local function f() ngx.say("before g") ngx.thread.spawn(g) ngx.say("after g") @@ -718,7 +722,7 @@ after f access_by_lua ' local yield = coroutine.yield - function f() + local function f() local self = coroutine.running() ngx.say("f 1") yield(self) @@ -771,7 +775,7 @@ f 3 access_by_lua ' local yield = coroutine.yield - function f() + local function f() local self = coroutine.running() ngx.say("f 1") yield(self) @@ -780,7 +784,7 @@ f 3 ngx.say("f 3") end - function g() + local function g() local self = coroutine.running() ngx.say("g 1") yield(self) @@ -827,7 +831,7 @@ g 3 --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("hello in thread") coroutine.yield(coroutine.running) ngx.flush(true) @@ -864,12 +868,12 @@ after --- config location /lua { access_by_lua ' - function f() + local function f() ngx.say("hello from f") ngx.flush(true) end - function g() + local function g() ngx.say("hello from g") ngx.flush(true) end @@ -915,7 +919,7 @@ hello from g --- config location /lua { access_by_lua ' - function f() + local function f() local sock = ngx.socket.tcp() local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) if not ok then @@ -967,7 +971,7 @@ received: OK --- config location /lua { access_by_lua ' - function f() + local function f() local sock = ngx.socket.udp() local ok, err = sock:setpeername("127.0.0.1", 12345) local bytes, err = sock:send("blah") @@ -1028,7 +1032,7 @@ after)$ --- config location /lua { access_by_lua ' - function f() + local function f() ngx.req.read_body() local body = ngx.req.get_body_data() ngx.say("body: ", body) @@ -1073,7 +1077,7 @@ body: hello world)$ --- config location /lua { access_by_lua ' - function f() + local function f() local sock = ngx.req.socket() local body, err = sock:receive(11) if not body then @@ -1116,3 +1120,4 @@ body: hello world)$ --- no_error_log [error] +--- skip_eval: 4:$ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/025-codecache.t b/t/025-codecache.t index 20791d7c70..6e3e335097 100644 --- a/t/025-codecache.t +++ b/t/025-codecache.t @@ -1,12 +1,16 @@ # vim:set ft= ts=4 sw=4 et fdm=marker: use Test::Nginx::Socket::Lua; +use Cwd qw(abs_path realpath); +use File::Basename; repeat_each(2); -plan tests => repeat_each() * 155; +plan tests => repeat_each() * 198; #$ENV{LUA_PATH} = $ENV{HOME} . '/work/JSON4Lua-0.9.30/json/?.lua'; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); +$ENV{TEST_NGINX_CERT_DIR} ||= dirname(realpath(abs_path(__FILE__))); no_long_string(); @@ -27,7 +31,7 @@ __DATA__ location /update { content_by_lua ' -- os.execute("(echo HERE; pwd) > /dev/stderr") - local f = assert(io.open("t/servroot/html/test.lua", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/test.lua", "w")) f:write("ngx.say(101)") f:close() ngx.say("updated") @@ -61,7 +65,7 @@ updated location /update { content_by_lua ' -- os.execute("(echo HERE; pwd) > /dev/stderr") - local f = assert(io.open("t/servroot/html/test.lua", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/test.lua", "w")) f:write("ngx.say(101)") f:close() ngx.say("updated") @@ -95,7 +99,7 @@ updated location /update { content_by_lua ' -- os.execute("(echo HERE; pwd) > /dev/stderr") - local f = assert(io.open("t/servroot/html/test.lua", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/test.lua", "w")) f:write("ngx.say(101)") f:close() ngx.say("updated") @@ -130,7 +134,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ location /update { content_by_lua ' -- os.execute("(echo HERE; pwd) > /dev/stderr") - local f = assert(io.open("t/servroot/html/test.lua", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/test.lua", "w")) f:write("ngx.say(101)") f:close() ngx.say("updated") @@ -166,7 +170,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ location /update { content_by_lua ' -- os.execute("(echo HERE; pwd) > /dev/stderr") - local f = assert(io.open("t/servroot/html/test.lua", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/test.lua", "w")) f:write("ngx.say(101)") f:close() ngx.say("updated") @@ -193,7 +197,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ === TEST 6: code cache explicitly off (affects require) + content_by_lua --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /lua { lua_code_cache off; @@ -204,7 +208,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ location /update { content_by_lua ' -- os.execute("(echo HERE; pwd) > /dev/stderr") - local f = assert(io.open("t/servroot/html/foo.lua", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/foo.lua", "w")) f:write("module(..., package.seeall); ngx.say(102);") f:close() ngx.say("updated") @@ -231,7 +235,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ === TEST 7: code cache explicitly off (affects require) + content_by_lua_file --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /lua { lua_code_cache off; @@ -240,7 +244,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ location /update { content_by_lua ' -- os.execute("(echo HERE; pwd) > /dev/stderr") - local f = assert(io.open("t/servroot/html/foo.lua", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/foo.lua", "w")) f:write("module(..., package.seeall); ngx.say(102);") f:close() ngx.say("updated") @@ -269,7 +273,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ === TEST 8: code cache explicitly off (affects require) + set_by_lua_file --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /lua { lua_code_cache off; @@ -279,7 +283,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ location /update { content_by_lua ' -- os.execute("(echo HERE; pwd) > /dev/stderr") - local f = assert(io.open("t/servroot/html/foo.lua", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/foo.lua", "w")) f:write("module(..., package.seeall); return 102;") f:close() ngx.say("updated") @@ -308,7 +312,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ === TEST 9: code cache explicitly on (affects require) + set_by_lua_file --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /lua { lua_code_cache on; @@ -318,7 +322,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ location /update { content_by_lua ' -- os.execute("(echo HERE; pwd) > /dev/stderr") - local f = assert(io.open("t/servroot/html/foo.lua", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/foo.lua", "w")) f:write("module(..., package.seeall); return 102;") f:close() ngx.say("updated") @@ -355,7 +359,7 @@ updated location /update { content_by_lua ' -- os.execute("(echo HERE; pwd) > /dev/stderr") - local f = assert(io.open("t/servroot/html/test.lua", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/test.lua", "w")) f:write("return 101") f:close() ngx.say("updated") @@ -390,7 +394,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ location /update { content_by_lua ' -- os.execute("(echo HERE; pwd) > /dev/stderr") - local f = assert(io.open("t/servroot/html/test.lua", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/test.lua", "w")) f:write("return 101") f:close() ngx.say("updated") @@ -468,7 +472,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ === TEST 14: no clear builtin lib "string" --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config lua_code_cache off; location /lua { @@ -504,7 +508,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ === TEST 15: do not skip luarocks --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua'; + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; lua_code_cache off;" --- config location /main { @@ -554,7 +558,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ === TEST 16: do not skip luarocks* --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua'; + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; lua_code_cache off;" --- config location /main { @@ -604,7 +608,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/ === TEST 17: clear _G table --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config lua_code_cache off; location /t { @@ -851,6 +855,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/, "decrementing the reference count for Lua VM: 2", "decrementing the reference count for Lua VM: 1", ] +--- skip_eval: 11:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) @@ -962,6 +967,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/, === TEST 27: GC issue with the on_abort thread object +curl: (52) Empty reply from server --- config lua_code_cache off; location = /t { @@ -987,6 +993,8 @@ decrementing the reference count for Lua VM: 3 qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/, "lua close the global Lua VM", ] +--- curl_error eval +qr/curl: \(\d+\) Empty reply from server|curl: \(28\) Operation timed out after \d+ milliseconds with 0 bytes received/ @@ -1050,7 +1058,7 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/, === TEST 29: cosocket connection pool timeout (after Lua VM destroys) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config lua_code_cache off; location = /t { @@ -1092,7 +1100,7 @@ function go(port) ngx.say("failed to receive a line: ", err, " [", part, "]") end - local ok, err = sock:setkeepalive(1) + local ok, err = sock:setkeepalive(10) if not ok then ngx.say("failed to set reusable: ", err) end @@ -1113,12 +1121,13 @@ qq{lua tcp socket keepalive create connection pool for key "127.0.0.1:$ENV{TEST_ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/, qr/\blua tcp socket keepalive: free connection pool [0-9A-F]+ for "127.0.0.1:/, ] +--- skip_eval: 7:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) === TEST 30: cosocket connection pool timeout (before Lua VM destroys) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config lua_code_cache off; location = /t { @@ -1244,3 +1253,634 @@ qr/\[alert\] \S+ lua_code_cache is off; this will hurt performance/, "decrementing the reference count for Lua VM: 1", "lua close the global Lua VM", ] + + + +=== TEST 32: make sure inline code keys are correct +GitHub issue #1428 +--- config +include ../html/a/proxy.conf; +include ../html/b/proxy.conf; +include ../html/c/proxy.conf; + +location /t { + echo_location /a/; + echo_location /b/; + echo_location /a/; + echo_location /c/; +} + +--- user_files +>>> a/proxy.conf +location /a/ { + content_by_lua_block { ngx.say("/a/ is called") } +} + +>>> b/proxy.conf +location /b/ { + content_by_lua_block { ngx.say("/b/ is called") } +} + +>>> c/proxy.conf +location /c/ { + content_by_lua_block { ngx.say("/b/ is called") } +} + +--- request +GET /t +--- response_body +/a/ is called +/b/ is called +/a/ is called +/b/ is called +--- grep_error_log eval: qr/code cache .*/ +--- grep_error_log_out eval +[ +"code cache lookup (key='content_by_lua_nhli_3c7137b8371d10bc148c8f8bb3042ee6', ref=-1) +code cache miss (key='content_by_lua_nhli_3c7137b8371d10bc148c8f8bb3042ee6', ref=-1) +code cache lookup (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=-1) +code cache miss (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=-1) +code cache lookup (key='content_by_lua_nhli_3c7137b8371d10bc148c8f8bb3042ee6', ref=1) +code cache hit (key='content_by_lua_nhli_3c7137b8371d10bc148c8f8bb3042ee6', ref=1) +code cache lookup (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=-1) +code cache setting ref (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=2) +code cache hit (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=2) +", +"code cache lookup (key='content_by_lua_nhli_3c7137b8371d10bc148c8f8bb3042ee6', ref=-1) +code cache miss (key='content_by_lua_nhli_3c7137b8371d10bc148c8f8bb3042ee6', ref=-1) +code cache lookup (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=-1) +code cache miss (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=-1) +code cache lookup (key='content_by_lua_nhli_3c7137b8371d10bc148c8f8bb3042ee6', ref=1) +code cache hit (key='content_by_lua_nhli_3c7137b8371d10bc148c8f8bb3042ee6', ref=1) +code cache lookup (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=-1) +code cache setting ref (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=2) +code cache hit (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=2) +code cache lookup (key='content_by_lua_nhli_3c7137b8371d10bc148c8f8bb3042ee6', ref=1) +code cache hit (key='content_by_lua_nhli_3c7137b8371d10bc148c8f8bb3042ee6', ref=1) +code cache lookup (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=2) +code cache hit (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=2) +code cache lookup (key='content_by_lua_nhli_3c7137b8371d10bc148c8f8bb3042ee6', ref=1) +code cache hit (key='content_by_lua_nhli_3c7137b8371d10bc148c8f8bb3042ee6', ref=1) +code cache lookup (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=2) +code cache hit (key='content_by_lua_nhli_1dfe09105792ef65c8d576cc486d5e04', ref=2) +"] +--- log_level: debug +--- no_error_log +[error] + + + +=== TEST 33: make sure Lua code file keys are correct +GitHub issue #1428 +--- config +include ../html/a/proxy.conf; +include ../html/b/proxy.conf; +include ../html/c/proxy.conf; + +location /t { + echo_location /a/; + echo_location /b/; + echo_location /a/; + echo_location /c/; +} + +--- user_files +>>> a.lua +ngx.say("/a/ is called") + +>>> b.lua +ngx.say("/b/ is called") + +>>> c.lua +ngx.say("/b/ is called") + +>>> a/proxy.conf +location /a/ { + content_by_lua_file html/a.lua; +} + +>>> b/proxy.conf +location /b/ { + content_by_lua_file html/b.lua; +} + +>>> c/proxy.conf +location /c/ { + content_by_lua_file html/c.lua; +} + +--- request +GET /t +--- response_body +/a/ is called +/b/ is called +/a/ is called +/b/ is called +--- grep_error_log eval: qr/code cache .*/ +--- grep_error_log_out eval +[ +"code cache lookup (key='nhlf_48a9a7def61143c003a7de1644e026e4', ref=-1) +code cache miss (key='nhlf_48a9a7def61143c003a7de1644e026e4', ref=-1) +code cache lookup (key='nhlf_68f5f4e946c3efd1cc206452b807e8b6', ref=-1) +code cache miss (key='nhlf_68f5f4e946c3efd1cc206452b807e8b6', ref=-1) +code cache lookup (key='nhlf_48a9a7def61143c003a7de1644e026e4', ref=1) +code cache hit (key='nhlf_48a9a7def61143c003a7de1644e026e4', ref=1) +code cache lookup (key='nhlf_042c9b3a136fbacbbd0e4b9ad10896b7', ref=-1) +code cache miss (key='nhlf_042c9b3a136fbacbbd0e4b9ad10896b7', ref=-1) +", +"code cache lookup (key='nhlf_48a9a7def61143c003a7de1644e026e4', ref=-1) +code cache miss (key='nhlf_48a9a7def61143c003a7de1644e026e4', ref=-1) +code cache lookup (key='nhlf_68f5f4e946c3efd1cc206452b807e8b6', ref=-1) +code cache miss (key='nhlf_68f5f4e946c3efd1cc206452b807e8b6', ref=-1) +code cache lookup (key='nhlf_48a9a7def61143c003a7de1644e026e4', ref=1) +code cache hit (key='nhlf_48a9a7def61143c003a7de1644e026e4', ref=1) +code cache lookup (key='nhlf_042c9b3a136fbacbbd0e4b9ad10896b7', ref=-1) +code cache miss (key='nhlf_042c9b3a136fbacbbd0e4b9ad10896b7', ref=-1) +code cache lookup (key='nhlf_48a9a7def61143c003a7de1644e026e4', ref=1) +code cache hit (key='nhlf_48a9a7def61143c003a7de1644e026e4', ref=1) +code cache lookup (key='nhlf_68f5f4e946c3efd1cc206452b807e8b6', ref=2) +code cache hit (key='nhlf_68f5f4e946c3efd1cc206452b807e8b6', ref=2) +code cache lookup (key='nhlf_48a9a7def61143c003a7de1644e026e4', ref=1) +code cache hit (key='nhlf_48a9a7def61143c003a7de1644e026e4', ref=1) +code cache lookup (key='nhlf_042c9b3a136fbacbbd0e4b9ad10896b7', ref=3) +code cache hit (key='nhlf_042c9b3a136fbacbbd0e4b9ad10896b7', ref=3) +" +] +--- log_level: debug +--- no_error_log +[error] + + + +=== TEST 34: variables in set_by_lua_file's file path +--- config + location ~ ^/lua/(.+)$ { + set_by_lua_file $res html/$1.lua; + echo $res; + } + + location /main { + echo_location /lua/a; + echo_location /lua/b; + echo_location /lua/a; + echo_location /lua/a; + echo_location /lua/b; + } +--- user_files +>>> a.lua +return "a" +>>> b.lua +return "b" +--- request +GET /main +--- response_body +a +b +a +a +b +--- no_error_log +[error] + + + +=== TEST 35: variables in rewrite_by_lua_file's file path +--- config + location ~ ^/lua/(.+)$ { + rewrite_by_lua_file html/$1.lua; + } + + location /main { + echo_location /lua/a; + echo_location /lua/b; + echo_location /lua/a; + echo_location /lua/a; + echo_location /lua/b; + } +--- user_files +>>> a.lua +ngx.say("a") +>>> b.lua +ngx.say("b") +--- request +GET /main +--- response_body +a +b +a +a +b +--- no_error_log +[error] + + + +=== TEST 36: variables in access_by_lua_file's file path +--- config + location ~ ^/lua/(.+)$ { + access_by_lua_file html/$1.lua; + + content_by_lua_block { + return + } + } + + location ~ ^/proxy/(.+)$ { + proxy_pass http://127.0.0.1:$server_port/lua/$1; + } + + location /main { + content_by_lua_block { + local res1, res2, res3, res4, res5 = ngx.location.capture_multi{ + { "/proxy/a" }, + { "/proxy/b" }, + { "/proxy/a" }, + { "/proxy/a" }, + { "/proxy/b" }, + } + + ngx.say(res1.body) + ngx.say(res2.body) + ngx.say(res3.body) + ngx.say(res4.body) + ngx.say(res5.body) + } + } +--- user_files +>>> a.lua +ngx.print("a") +>>> b.lua +ngx.print("b") +--- request +GET /main +--- response_body +a +b +a +a +b +--- no_error_log +[error] + + + +=== TEST 37: variables in content_by_lua_file's file path +--- config + location ~ ^/lua/(.+)$ { + content_by_lua_file html/$1.lua; + } + + location /main { + echo_location /lua/a; + echo_location /lua/b; + echo_location /lua/a; + echo_location /lua/a; + echo_location /lua/b; + } +--- user_files +>>> a.lua +ngx.say("a") +>>> b.lua +ngx.say("b") +--- request +GET /main +--- response_body +a +b +a +a +b +--- no_error_log +[error] + + + +=== TEST 38: variables in header_filter_by_lua_file's file path +--- config + location ~ ^/lua/(.+)$ { + return 200; + + header_filter_by_lua_file html/$1.lua; + } + + location ~ ^/proxy/(.+)$ { + proxy_pass http://127.0.0.1:$server_port/lua/$1; + } + + location /main { + content_by_lua_block { + local res1, res2, res3, res4, res5 = ngx.location.capture_multi{ + { "/proxy/a" }, + { "/proxy/b" }, + { "/proxy/a" }, + { "/proxy/a" }, + { "/proxy/b" }, + } + + ngx.say(res1.header.match) + ngx.say(res2.header.match) + ngx.say(res3.header.match) + ngx.say(res4.header.match) + ngx.say(res5.header.match) + } + } +--- user_files +>>> a.lua +ngx.header.match = "a" +>>> b.lua +ngx.header.match = "b" +--- request +GET /main +--- response_body +a +b +a +a +b +--- no_error_log +[error] + + + +=== TEST 39: variables in body_filter_by_lua_file's file path +--- config + location ~ ^/lua/(.+)$ { + echo hello; + + body_filter_by_lua_file html/$1.lua; + } + + location /main { + echo_location /lua/a; + echo_location /lua/b; + echo_location /lua/a; + echo_location /lua/a; + echo_location /lua/b; + } +--- user_files +>>> a.lua +ngx.arg[1] = "a\n" +ngx.arg[2] = true +>>> b.lua +ngx.arg[1] = "b\n" +ngx.arg[2] = true +--- request +GET /main +--- response_body +a +b +a +a +b +--- no_error_log +[error] + + + +=== TEST 40: variables in log_by_lua_file's file path +--- config + log_subrequest on; + + location ~ ^/lua/(.+)$ { + echo hello; + + log_by_lua_file html/$1.lua; + } + + location /main { + echo_location /lua/a; + echo_location /lua/b; + echo_location /lua/a; + echo_location /lua/a; + echo_location /lua/b; + } +--- user_files +>>> a.lua +ngx.log(ngx.NOTICE, "grep me: a") +>>> b.lua +ngx.log(ngx.NOTICE, "grep me: b") +--- request +GET /main +--- ignore_response_body +--- grep_error_log eval: qr/grep me: ([ab])/ +--- grep_error_log_out eval +[ +"grep me: a +grep me: b +grep me: a +grep me: a +grep me: b +", +"grep me: a +grep me: b +grep me: a +grep me: a +grep me: b +grep me: a +grep me: b +grep me: a +grep me: a +grep me: b +"] +--- no_error_log +[error] + + + +=== TEST 41: same chunk from different directives produces different closures +--- http_config + ssl_session_fetch_by_lua_block { ngx.log(ngx.INFO, "hello") } + + ssl_session_store_by_lua_block { ngx.log(ngx.INFO, "hello") } + + upstream backend { + server unix:$TEST_NGINX_HTML_DIR/nginx.sock; + balancer_by_lua_block { ngx.log(ngx.INFO, "hello") } + } + + server { + server_name test.com; + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_session_tickets off; + + ssl_certificate_by_lua_block { ngx.log(ngx.INFO, "hello") } + + location /lua { + set_by_lua_block $res { ngx.log(ngx.INFO, "hello") } + + rewrite_by_lua_block { ngx.log(ngx.INFO, "hello") } + + access_by_lua_block { ngx.log(ngx.INFO, "hello") } + + content_by_lua_block { ngx.log(ngx.INFO, "hello") } + + header_filter_by_lua_block { ngx.log(ngx.INFO, "hello") } + + body_filter_by_lua_block { ngx.log(ngx.INFO, "hello") } + + log_by_lua_block { ngx.log(ngx.INFO, "hello") } + } + } +--- config + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; + + location = /proxy { + proxy_pass http://backend; + } + + location = /t { + set $html_dir $TEST_NGINX_HTML_DIR; + + content_by_lua_block { + ngx.location.capture("/proxy") + + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect("unix:" .. ngx.var.html_dir .. "/nginx.sock") + if not ok then + ngx.log(ngx.ERR, "failed to connect: ", err) + return + end + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.log(ngx.ERR, "failed to do SSL handshake: ", err) + return + end + package.loaded.session = sess + sock:close() + + local ok, err = sock:connect("unix:" .. ngx.var.html_dir .. "/nginx.sock") + if not ok then + ngx.log(ngx.ERR, "failed to connect: ", err) + return + end + + local sess, err = sock:sslhandshake(package.loaded.session, "test.com", true) + if not sess then + ngx.log(ngx.ERR, "failed to do SSL handshake: ", err) + return + end + + local req = "GET /lua HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.log(ngx.ERR, "failed to send http request: ", err) + return + end + } + } +--- request +GET /t +--- ignore_response_body +--- grep_error_log eval: qr/code cache .*/ +--- grep_error_log_out eval +[ +"code cache lookup (key='content_by_lua_nhli_56ca4388611109b6ecfdeada050c8024', ref=-1) +code cache miss (key='content_by_lua_nhli_56ca4388611109b6ecfdeada050c8024', ref=-1) +code cache lookup (key='balancer_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='balancer_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='ssl_certificate_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='ssl_certificate_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='ssl_session_store_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='ssl_session_store_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='ssl_session_fetch_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='ssl_session_fetch_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='ssl_certificate_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=3) +code cache hit (key='ssl_certificate_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=3) +code cache lookup (key='ssl_session_store_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=4) +code cache hit (key='ssl_session_store_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=4) +code cache lookup (key='set_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='set_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='rewrite_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='rewrite_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='access_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='access_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='content_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='content_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='header_filter_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='header_filter_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='body_filter_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='body_filter_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='log_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='log_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +", +"code cache lookup (key='content_by_lua_nhli_56ca4388611109b6ecfdeada050c8024', ref=-1) +code cache miss (key='content_by_lua_nhli_56ca4388611109b6ecfdeada050c8024', ref=-1) +code cache lookup (key='balancer_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='balancer_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='ssl_certificate_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='ssl_certificate_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='ssl_session_store_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='ssl_session_store_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='ssl_session_fetch_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='ssl_session_fetch_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='ssl_certificate_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=3) +code cache hit (key='ssl_certificate_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=3) +code cache lookup (key='ssl_session_store_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=4) +code cache hit (key='ssl_session_store_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=4) +code cache lookup (key='set_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='set_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='rewrite_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='rewrite_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='access_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='access_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='content_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='content_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='header_filter_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='header_filter_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='body_filter_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='body_filter_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='log_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache miss (key='log_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=-1) +code cache lookup (key='content_by_lua_nhli_56ca4388611109b6ecfdeada050c8024', ref=1) +code cache hit (key='content_by_lua_nhli_56ca4388611109b6ecfdeada050c8024', ref=1) +code cache lookup (key='balancer_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=2) +code cache hit (key='balancer_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=2) +code cache lookup (key='ssl_certificate_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=3) +code cache hit (key='ssl_certificate_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=3) +code cache lookup (key='ssl_session_store_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=4) +code cache hit (key='ssl_session_store_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=4) +code cache lookup (key='ssl_session_fetch_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=5) +code cache hit (key='ssl_session_fetch_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=5) +code cache lookup (key='ssl_certificate_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=3) +code cache hit (key='ssl_certificate_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=3) +code cache lookup (key='ssl_session_store_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=4) +code cache hit (key='ssl_session_store_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=4) +code cache lookup (key='set_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=6) +code cache hit (key='set_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=6) +code cache lookup (key='rewrite_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=7) +code cache hit (key='rewrite_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=7) +code cache lookup (key='access_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=8) +code cache hit (key='access_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=8) +code cache lookup (key='content_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=9) +code cache hit (key='content_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=9) +code cache lookup (key='header_filter_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=10) +code cache hit (key='header_filter_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=10) +code cache lookup (key='body_filter_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=11) +code cache hit (key='body_filter_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=11) +code cache lookup (key='log_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=12) +code cache hit (key='log_by_lua_nhli_8a9441d0a30531ba8bb34ab11c55cfc3', ref=12) +"] +--- error_log eval +[ +qr/balancer_by_lua\(nginx\.conf:\d+\):\d+: hello/, +qr/ssl_session_fetch_by_lua\(nginx\.conf:\d+\):\d+: hello/, +qr/ssl_certificate_by_lua\(nginx\.conf:\d+\):\d+: hello/, +qr/ssl_session_store_by_lua\(nginx\.conf:\d+\):\d+: hello/, +qr/set_by_lua\(nginx\.conf:\d+\):\d+: hello/, +qr/rewrite_by_lua\(nginx\.conf:\d+\):\d+: hello/, +qr/access_by_lua\(nginx\.conf:\d+\):\d+: hello/, +qr/content_by_lua\(nginx\.conf:\d+\):\d+: hello/, +qr/header_filter_by_lua\(nginx\.conf:\d+\):\d+: hello/, +qr/body_filter_by_lua\(nginx\.conf:\d+\):\d+: hello/, +qr/log_by_lua\(nginx.conf:\d+\):\d+: hello/, +] +--- log_level: debug +--- no_error_log +[error] +--- skip_eval: 14:$ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/026-mysql.t b/t/026-mysql.t index 569f96fbc9..e7ab170600 100644 --- a/t/026-mysql.t +++ b/t/026-mysql.t @@ -16,6 +16,7 @@ run_tests(); __DATA__ === TEST 1: when mysql query timed out, kill that query by Lua +--- no_http2 --- http_config upstream backend { drizzle_server 127.0.0.1:$TEST_NGINX_MYSQL_PORT protocol=mysql @@ -69,6 +70,7 @@ kill status = 200 kill body = \{"errcode":0\}$ --- error_log eval qr{upstream timed out \(\d+: Connection timed out\) while sending query to drizzle upstream} +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/027-multi-capture.t b/t/027-multi-capture.t index 9227fe5329..b3cdbaff09 100644 --- a/t/027-multi-capture.t +++ b/t/027-multi-capture.t @@ -151,7 +151,7 @@ res2.body = b location /main { content_by_lua ' - res = ngx.location.capture("/foo?n=1") + local res = ngx.location.capture("/foo?n=1") ngx.say("top res.status = " .. res.status) ngx.say("top res.body = [" .. res.body .. "]") '; @@ -743,7 +743,7 @@ proxy_cache_path conf/cache levels=1:2 keys_zone=STATIC:10m inactive=10m max_siz location = /proxy { proxy_cache STATIC; - proxy_pass http://agentzh.org:12345; + proxy_pass http://127.0.0.2:12345; proxy_cache_key $proxy_host$uri$args; proxy_cache_valid any 1s; #proxy_http_version 1.1; @@ -752,3 +752,75 @@ proxy_cache_path conf/cache levels=1:2 keys_zone=STATIC:10m inactive=10m max_siz GET /foo --- response_body ok + + + +=== TEST 14: capture multi with headers +--- config + location /foo { + content_by_lua_block { + local res1, res2, res3 = ngx.location.capture_multi{ + {"/test", { headers = { ["X-Test-Header"] = "aa"} } }, + {"/test", { headers = { ["X-Test-Header"] = "bb"} } }, + {"/test"}, + } + ngx.say("res1.status = " .. res1.status) + ngx.say("res1.body = " .. res1.body) + ngx.say("res2.status = " .. res2.status) + ngx.say("res2.body = " .. res2.body) + ngx.say("res3.status = " .. res3.status) + ngx.say("res3.body = " .. res3.body) + } + } + + location = /test { + content_by_lua_block { + ngx.print(ngx.var.http_x_test_header) + } + } +--- request + GET /foo +--- response_body +res1.status = 200 +res1.body = aa +res2.status = 200 +res2.body = bb +res3.status = 200 +res3.body = nil + + + +=== TEST 15: capture multi with headers override +--- config + location /foo { + content_by_lua_block { + local res1, res2, res3 = ngx.location.capture_multi{ + {"/test", { headers = { ["X-Test-Header"] = "aa"} } }, + {"/test", { headers = { ["X-Test-Header"] = "bb"} } }, + {"/test"}, + } + ngx.say("res1.status = " .. res1.status) + ngx.say("res1.body = " .. res1.body) + ngx.say("res2.status = " .. res2.status) + ngx.say("res2.body = " .. res2.body) + ngx.say("res3.status = " .. res3.status) + ngx.say("res3.body = " .. res3.body) + } + } + + location = /test { + content_by_lua_block { + ngx.print(ngx.var.http_x_test_header) + } + } +--- request + GET /foo +--- more_headers +X-Test-Header: cc +--- response_body +res1.status = 200 +res1.body = aa +res2.status = 200 +res2.body = bb +res3.status = 200 +res3.body = cc diff --git a/t/028-req-header.t b/t/028-req-header.t index 1ebcdff4e2..21da3fc1ad 100644 --- a/t/028-req-header.t +++ b/t/028-req-header.t @@ -8,10 +8,10 @@ use Test::Nginx::Socket::Lua; repeat_each(2); -plan tests => repeat_each() * (2 * blocks() + 31); +plan tests => repeat_each() * (2 * blocks() + 48); #no_diff(); -#no_long_string(); +no_long_string(); run_tests(); @@ -21,8 +21,14 @@ __DATA__ --- config location /req-header { content_by_lua ' - ngx.say("Foo: ", ngx.req.get_headers()["Foo"] or "nil") - ngx.say("Bar: ", ngx.req.get_headers()["Bar"] or "nil") + local headers, err = ngx.req.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + ngx.say("Foo: ", headers["Foo"] or "nil") + ngx.say("Bar: ", headers["Bar"] or "nil") '; } --- request @@ -43,12 +49,23 @@ lua exceeding request header limit --- config location /req-header { content_by_lua ' + local headers, err = ngx.req.get_headers(nil, true) + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + local h = {} - for k, v in pairs(ngx.req.get_headers(nil, true)) do + for k, v in pairs(headers) do h[k] = v end - ngx.say("Foo: ", h["Foo"] or "nil") - ngx.say("Bar: ", h["Bar"] or "nil") + if (ngx.req.http_version() == 3 or ngx.req.http_version() == 2) then + ngx.say("Foo: ", h["foo"] or "nil") + ngx.say("Bar: ", h["bar"] or "nil") + else + ngx.say("Foo: ", h["Foo"] or "nil") + ngx.say("Bar: ", h["Bar"] or "nil") + end '; } --- request @@ -115,6 +132,8 @@ Foo: --- response_body eval "a" x 2048 --- timeout: 15 +--- skip_eval: 2:$ENV{TEST_NGINX_USE_HTTP3} +--- no_http2 @@ -133,6 +152,8 @@ Foo: --- response_body eval "a" x 2048 --- timeout: 15 +--- skip_eval: 2:$ENV{TEST_NGINX_USE_HTTP3} +--- no_http2 @@ -261,8 +282,13 @@ Content-Type: } --- request GET /bar ---- response_body -Foo: a +--- response_body eval +# Since nginx version 1.23.0, nginx combines same $http_* variable together +$Test::Nginx::Util::NginxVersion >= 1.023000 ? + +"Foo: a, b\n" +: +"Foo: a\n" @@ -289,16 +315,43 @@ GET /bar --- config location /foo { content_by_lua ' - ngx.say("Foo: ", ngx.req.get_headers()["Foo"] or "nil") + local headers, err = ngx.req.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + ngx.say("Foo: ", headers["Foo"] or "nil") ngx.req.set_header("Foo", 32) - ngx.say("Foo 1: ", ngx.req.get_headers()["Foo"] or "nil") + + headers, err = ngx.req.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + ngx.say("Foo 1: ", headers["Foo"] or "nil") ngx.req.set_header("Foo", "abc") - ngx.say("Foo 2: ", ngx.req.get_headers()["Foo"] or "nil") + + headers, err = ngx.req.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + ngx.say("Foo 2: ", headers["Foo"] or "nil") ngx.req.clear_header("Foo") - ngx.say("Foo 3: ", ngx.req.get_headers()["Foo"] or "nil") + + headers, err = ngx.req.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + ngx.say("Foo 3: ", headers["Foo"] or "nil") '; } --- more_headers @@ -319,7 +372,13 @@ Foo 3: nil location /foo { content_by_lua ' collectgarbage() - local vals = ngx.req.get_headers()["Foo"] + local headers, err = ngx.req.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + local vals = headers["Foo"] ngx.say("value is of type ", type(vals), ".") if type(vals) == "table" then ngx.say("Foo takes ", #vals or "nil", " values.") @@ -386,11 +445,15 @@ Content-Encoding: gzip -=== TEST 19: default max 100 headers +=== TEST 19: exceeding default max 100 header limit --- config location /lua { content_by_lua ' - local headers = ngx.req.get_headers() + local headers, err = ngx.req.get_headers() + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(headers) do table.insert(keys, key) @@ -407,7 +470,7 @@ GET /lua --- more_headers eval my $i = 1; my $s; -while ($i <= 102) { +while ($i <= 99) { $s .= "X-$i:$i\n"; $i++; } @@ -427,18 +490,90 @@ for my $k (@k) { $k .= ": $&\n"; } } -CORE::join("", @k); +"err: truncated\n" . CORE::join("", @k); --- timeout: 4 --- error_log -lua exceeding request header limit 100 +lua exceeding request header limit 101 > 100 +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} +--- no_http2 + + + +=== TEST 20: NOT exceeding default max 100 header limit +--- config + location /lua { + content_by_lua ' + local headers, err = ngx.req.get_headers() + if err then + ngx.say("err: ", err) + end + + local keys = {} + for key, val in pairs(headers) do + table.insert(keys, key) + end + + table.sort(keys) + local cnt = 0 + for i, key in ipairs(keys) do + ngx.say(key, ": ", headers[key]) + cnt = cnt + 1 + end + ngx.say("found ", cnt, " headers") + '; + } +--- request +GET /lua +--- more_headers eval +my $i = 1; +my $s; +while ($i <= 98) { + $s .= "X-$i:$i\n"; + $i++; +} +$s +--- response_body eval +my @k; + +if (defined($ENV{TEST_NGINX_USE_HTTP3}) || defined($ENV{TEST_NGINX_USE_HTTP2})) { + push @k, "host: localhost\n"; +} +my $i = 1; +while ($i <= 98) { + push @k, "x-$i"; + $i++; +} + +my $found_headers = "found 99 headers\n"; +if (!defined($ENV{TEST_NGINX_USE_HTTP3}) && !defined($ENV{TEST_NGINX_USE_HTTP2})) { + push @k, "connection: close\n"; + push @k, "host: localhost\n"; + $found_headers = "found 100 headers\n"; +} +@k = sort @k; +for my $k (@k) { + if ($k =~ /\d+/) { + $k .= ": $&\n"; + } +} + +CORE::join("", @k) . $found_headers; +--- timeout: 4 +--- no_error_log +[error] +lua exceeding request header limit -=== TEST 20: custom max 102 headers +=== TEST 21: exceeding custom max 102 header limit --- config location /lua { content_by_lua ' - local headers = ngx.req.get_headers(102) + local headers, err = ngx.req.get_headers(102) + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(headers) do table.insert(keys, key) @@ -455,13 +590,16 @@ GET /lua --- more_headers eval my $i = 1; my $s; -while ($i <= 103) { +while ($i <= 101) { $s .= "X-$i:$i\n"; $i++; } $s --- response_body eval my @k; +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + push @k, "host: localhost\n"; +} my $i = 1; while ($i <= 100) { push @k, "x-$i"; @@ -475,18 +613,84 @@ for my $k (@k) { $k .= ": $&\n"; } } -CORE::join("", @k); +"err: truncated\n" . CORE::join("", @k); --- timeout: 4 --- error_log -lua exceeding request header limit 102 +lua exceeding request header limit 103 > 102 +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} +--- no_http2 -=== TEST 21: custom unlimited headers +=== TEST 22: NOT exceeding custom max 102 header limit --- config location /lua { content_by_lua ' - local headers = ngx.req.get_headers(0) + local headers, err = ngx.req.get_headers(102) + if err then + ngx.say("err: ", err) + end + + local keys = {} + for key, val in pairs(headers) do + table.insert(keys, key) + end + + table.sort(keys) + for i, key in ipairs(keys) do + ngx.say(key, ": ", headers[key]) + end + '; + } +--- request +GET /lua +--- more_headers eval +my $i = 1; +my $s; +while ($i <= 100) { + $s .= "X-$i:$i\n"; + $i++; +} +$s +--- response_body eval +my @k; +if (defined($ENV{TEST_NGINX_USE_HTTP3}) || defined($ENV{TEST_NGINX_USE_HTTP2})) { + push @k, "host: localhost\n"; +} +my $i = 1; +while ($i <= 100) { + push @k, "x-$i"; + $i++; +} + +if (!defined($ENV{TEST_NGINX_USE_HTTP3}) && !defined($ENV{TEST_NGINX_USE_HTTP2})) { + push @k, "connection: close\n"; + push @k, "host: localhost\n"; +} + +@k = sort @k; +for my $k (@k) { + if ($k =~ /\d+/) { + $k .= ": $&\n"; + } +} +CORE::join("", @k); +--- timeout: 4 +--- no_error_log +[error] +lua exceeding request header limit + + + +=== TEST 23: custom unlimited headers +--- config + location /lua { + content_by_lua ' + local headers, err = ngx.req.get_headers(0) + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(headers) do table.insert(keys, key) @@ -510,13 +714,18 @@ while ($i <= 105) { $s --- response_body eval my @k; +if (defined($ENV{TEST_NGINX_USE_HTTP3}) || defined($ENV{TEST_NGINX_USE_HTTP2})) { + push @k, "host: localhost\n"; +} my $i = 1; while ($i <= 105) { push @k, "x-$i"; $i++; } -push @k, "connection: close\n"; -push @k, "host: localhost\n"; +if (!defined($ENV{TEST_NGINX_USE_HTTP3}) && !defined($ENV{TEST_NGINX_USE_HTTP2})) { + push @k, "connection: close\n"; + push @k, "host: localhost\n"; +} @k = sort @k; for my $k (@k) { if ($k =~ /\d+/) { @@ -528,7 +737,7 @@ CORE::join("", @k); -=== TEST 22: modify subrequest req headers should not affect the parent +=== TEST 24: modify subrequest req headers should not affect the parent --- config location = /main { rewrite_by_lua ' @@ -584,7 +793,7 @@ Bar: [] -=== TEST 23: clear_header should clear all the instances of the user custom header +=== TEST 25: clear_header should clear all the instances of the user custom header --- config location = /t { rewrite_by_lua ' @@ -610,7 +819,7 @@ Test-Header: [1] -=== TEST 24: clear_header should clear all the instances of the builtin header +=== TEST 26: clear_header should clear all the instances of the builtin header --- config location = /t { rewrite_by_lua ' @@ -637,7 +846,7 @@ Test-Header: [1] -=== TEST 25: Converting POST to GET - clearing headers (bug found by Matthieu Tourne, 411 error page) +=== TEST 27: Converting POST to GET - clearing headers (bug found by Matthieu Tourne, 411 error page) --- config location /t { rewrite_by_lua ' @@ -659,16 +868,27 @@ hello world Content-Type: application/ocsp-request Test-Header: 1 --- response_body_like eval -qr/Connection: close\r +my $body; + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $body = qr/Connection: close\r +test-header: 1\r +\r +$/; +} else { + $body = qr/Connection: close\r Test-Header: 1\r \r -$/ +$/; +} + +$body; --- no_error_log [error] -=== TEST 26: clear_header() does not duplicate subsequent headers (old bug) +=== TEST 28: clear_header() does not duplicate subsequent headers (old bug) --- config location = /t { rewrite_by_lua ' @@ -710,7 +930,36 @@ Foo20: foo20 Foo21: foo21 Foo22: foo22 --- response_body_like eval -qr/Bah: bah\r +my $headers; + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $headers = qr/bah: bah\r +test-header: 1\r +foo1: foo1\r +foo2: foo2\r +foo3: foo3\r +foo4: foo4\r +foo5: foo5\r +foo6: foo6\r +foo7: foo7\r +foo8: foo8\r +foo9: foo9\r +foo10: foo10\r +foo11: foo11\r +foo12: foo12\r +foo13: foo13\r +foo14: foo14\r +foo15: foo15\r +foo16: foo16\r +foo17: foo17\r +foo18: foo18\r +foo19: foo19\r +foo20: foo20\r +foo21: foo21\r +foo22: foo22\r +/; +} else { + $headers = qr/Bah: bah\r Test-Header: 1\r Foo1: foo1\r Foo2: foo2\r @@ -734,17 +983,26 @@ Foo19: foo19\r Foo20: foo20\r Foo21: foo21\r Foo22: foo22\r -/ +/; +} +$headers; -=== TEST 27: iterating through headers (raw form) + +=== TEST 29: iterating through headers (raw form) --- config location /t { content_by_lua ' local h = {} local arr = {} - for k, v in pairs(ngx.req.get_headers(nil, true)) do + local headers, err = ngx.req.get_headers(nil, true) + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + for k, v in pairs(headers) do h[k] = v table.insert(arr, k) end @@ -759,19 +1017,34 @@ GET /t --- more_headers My-Foo: bar Bar: baz ---- response_body -Bar: baz +--- response_body eval +my $body; +if ($ENV{TEST_NGINX_USE_HTTP3} || $ENV{TEST_NGINX_USE_HTTP2}) { + $body = "bar: baz +host: localhost +my-foo: bar +"; +} else { + $body = "Bar: baz Connection: close Host: localhost My-Foo: bar +"; +} +$body; -=== TEST 28: __index metamethod not working for "raw" mode +=== TEST 30: __index metamethod not working for "raw" mode --- config location /t { content_by_lua ' - local h = ngx.req.get_headers(nil, true) + local h, err = ngx.req.get_headers(nil, true) + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + ngx.say("My-Foo-Header: ", h.my_foo_header) '; } @@ -784,11 +1057,16 @@ My-Foo-Header: nil -=== TEST 29: __index metamethod not working for the default mode +=== TEST 31: __index metamethod not working for the default mode --- config location /t { content_by_lua ' - local h = ngx.req.get_headers() + local h, err = ngx.req.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + ngx.say("My-Foo-Header: ", h.my_foo_header) '; } @@ -801,7 +1079,7 @@ My-Foo-Header: Hello World -=== TEST 30: clear input header (just more than 20 headers) +=== TEST 32: clear input header (just more than 20 headers) --- config location = /t { rewrite_by_lua 'ngx.req.clear_header("R")'; @@ -823,33 +1101,27 @@ for my $i ('a' .. 'r') { } $s --- response_body eval -"GET /back HTTP/1.0\r +my $s = "GET /back HTTP/1.0\r Host: foo\r -Connection: close\r -User-Agent: curl\r -A: a\r -B: b\r -C: c\r -D: d\r -E: e\r -F: f\r -G: g\r -H: h\r -I: i\r -J: j\r -K: k\r -L: l\r -M: m\r -N: n\r -O: o\r -P: p\r -Q: q\r -\r -" +Connection: close\r\n"; +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $s .= "user-agent: curl\r\n"; + for my $i ('a' .. 'q') { + $s .= $i . ": " . "$i\r\n" + } +} else { + $s .= "User-Agent: curl\r\n"; + for my $i ('a' .. 'q') { + $s .= uc($i) . ": " . "$i\r\n" + } +} + +$s . "\r\n"; -=== TEST 31: clear input header (just more than 20 headers, and add more) + +=== TEST 33: clear input header (just more than 20 headers, and add more) --- config location = /t { rewrite_by_lua ' @@ -876,7 +1148,55 @@ for my $i ('a' .. 'r') { } $s --- response_body eval -"GET /back HTTP/1.0\r +my $body; + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $body = "GET /back HTTP/1.0\r +Host: foo\r +Connection: close\r +user-agent: curl\r +a: a\r +b: b\r +c: c\r +d: d\r +e: e\r +f: f\r +g: g\r +h: h\r +i: i\r +j: j\r +k: k\r +l: l\r +m: m\r +n: n\r +o: o\r +p: p\r +q: q\r +foo-1: 1\r +foo-2: 2\r +foo-3: 3\r +foo-4: 4\r +foo-5: 5\r +foo-6: 6\r +foo-7: 7\r +foo-8: 8\r +foo-9: 9\r +foo-10: 10\r +foo-11: 11\r +foo-12: 12\r +foo-13: 13\r +foo-14: 14\r +foo-15: 15\r +foo-16: 16\r +foo-17: 17\r +foo-18: 18\r +foo-19: 19\r +foo-20: 20\r +foo-21: 21\r +\r +"; +} else { + $body = "GET /back HTTP/1.0\r Host: foo\r Connection: close\r User-Agent: curl\r @@ -919,11 +1239,14 @@ foo-19: 19\r foo-20: 20\r foo-21: 21\r \r -" +"; +} + +$body; -=== TEST 32: clear input header (just more than 21 headers) +=== TEST 34: clear input header (just more than 21 headers) --- config location = /t { rewrite_by_lua ' @@ -948,7 +1271,34 @@ for my $i ('a' .. 'r') { } $s --- response_body eval -"GET /back HTTP/1.0\r +my $body; + +if ($ENV{TEST_NGINX_USE_HTTP3}) { + $body = "GET /back HTTP/1.0\r +Host: foo\r +Connection: close\r +user-agent: curl\r +bah: bah\r +a: a\r +b: b\r +c: c\r +d: d\r +e: e\r +f: f\r +g: g\r +h: h\r +i: i\r +j: j\r +k: k\r +l: l\r +m: m\r +n: n\r +o: o\r +p: p\r +\r +" +} else { +$body = "GET /back HTTP/1.0\r Host: foo\r Connection: close\r User-Agent: curl\r @@ -971,10 +1321,13 @@ O: o\r P: p\r \r " +} +$body; -=== TEST 33: clear input header (just more than 21 headers) + +=== TEST 35: clear input header (just more than 21 headers) --- config location = /t { rewrite_by_lua ' @@ -1002,7 +1355,55 @@ for my $i ('a' .. 'r') { } $s --- response_body eval -"GET /back HTTP/1.0\r +my $body; + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $body = "GET /back HTTP/1.0\r +Host: foo\r +Connection: close\r +user-agent: curl\r +bah: bah\r +a: a\r +b: b\r +c: c\r +d: d\r +e: e\r +f: f\r +g: g\r +h: h\r +i: i\r +j: j\r +k: k\r +l: l\r +m: m\r +n: n\r +o: o\r +p: p\r +foo-1: 1\r +foo-2: 2\r +foo-3: 3\r +foo-4: 4\r +foo-5: 5\r +foo-6: 6\r +foo-7: 7\r +foo-8: 8\r +foo-9: 9\r +foo-10: 10\r +foo-11: 11\r +foo-12: 12\r +foo-13: 13\r +foo-14: 14\r +foo-15: 15\r +foo-16: 16\r +foo-17: 17\r +foo-18: 18\r +foo-19: 19\r +foo-20: 20\r +foo-21: 21\r +\r +"; +} else { + $body = "GET /back HTTP/1.0\r Host: foo\r Connection: close\r User-Agent: curl\r @@ -1046,24 +1447,33 @@ foo-20: 20\r foo-21: 21\r \r " +} +$body; -=== TEST 34: raw form + +=== TEST 36: raw form --- config location /t { content_by_lua ' - -- get ALL the raw headers (0 == no limit, not recommended) - local h = {} - local arr = {} - for k, v in pairs(ngx.req.get_headers(0, true)) do - h[k] = v - table.insert(arr, k) - end - table.sort(arr) - for i, k in ipairs(arr) do - ngx.say(k, ": ", h[k]) - end + local headers, err = ngx.req.get_headers(0, true) + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + -- get ALL the raw headers (0 == no limit, not recommended) + local h = {} + local arr = {} + for k, v in pairs(headers) do + h[k] = v + table.insert(arr, k) + end + table.sort(arr) + for i, k in ipairs(arr) do + ngx.say(k, ": ", h[k]) + end '; } --- request @@ -1071,17 +1481,29 @@ GET /t --- more_headers My-Foo: bar Bar: baz ---- response_body -Bar: baz +--- response_body eval +my $body; + +if (defined($ENV{TEST_NGINX_USE_HTTP3})|| defined($ENV{TEST_NGINX_USE_HTTP2})) { + $body="bar: baz +host: localhost +my-foo: bar +"; +} else { + $body="Bar: baz Connection: close Host: localhost My-Foo: bar +"; +} + +$body; --- no_error_log [error] -=== TEST 35: clear X-Real-IP +=== TEST 37: clear X-Real-IP --- config location /t { rewrite_by_lua ' @@ -1127,7 +1549,7 @@ X-Real-IP: -=== TEST 36: set custom X-Real-IP +=== TEST 38: set custom X-Real-IP --- config location /t { rewrite_by_lua ' @@ -1172,7 +1594,7 @@ X-Real-IP: 8.8.4.4 -=== TEST 37: clear Via +=== TEST 39: clear Via --- config location /t { rewrite_by_lua ' @@ -1218,7 +1640,7 @@ Via: -=== TEST 38: set custom Via +=== TEST 40: set custom Via --- config location /t { rewrite_by_lua ' @@ -1263,7 +1685,7 @@ Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) -=== TEST 39: set input header (with underscores in the header name) +=== TEST 41: set input header (with underscores in the header name) --- config location /req-header { rewrite_by_lua ' @@ -1286,12 +1708,18 @@ $} -=== TEST 40: HTTP 0.9 (set & get) +=== TEST 42: HTTP 0.9 (set & get) --- config location /foo { content_by_lua ' ngx.req.set_header("X-Foo", "howdy"); - ngx.say("X-Foo: ", ngx.req.get_headers()["X-Foo"]) + local headers, err = ngx.req.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + ngx.say("X-Foo: ", headers["X-Foo"]) '; } --- raw_request eval @@ -1306,12 +1734,18 @@ X-Foo: nil -=== TEST 41: HTTP 0.9 (clear) +=== TEST 43: HTTP 0.9 (clear) --- config location /foo { content_by_lua ' ngx.req.set_header("X-Foo", "howdy"); - ngx.say("X-Foo: ", ngx.req.get_headers()["X-Foo"]) + local headers, err = ngx.req.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + ngx.say("X-Foo: ", headers["X-Foo"]) '; } --- raw_request eval @@ -1326,7 +1760,7 @@ X-Foo: nil -=== TEST 42: Host header with port and $host (github issue #292) +=== TEST 44: Host header with port and $host (github issue #292) --- config location /bar { rewrite_by_lua ' @@ -1343,7 +1777,7 @@ http_host var: agentzh.org:1984 -=== TEST 43: Host header with upper case letters and $host (github issue #292) +=== TEST 45: Host header with upper case letters and $host (github issue #292) --- config location /bar { rewrite_by_lua ' @@ -1360,11 +1794,16 @@ http_host var: agentZH.org:1984 -=== TEST 44: clear all and re-insert +=== TEST 46: clear all and re-insert --- config location = /t { content_by_lua ' - local headers = ngx.req.get_headers(100, true) + local headers, err = ngx.req.get_headers(100, true) + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + local n = 0 for header, _ in pairs(headers) do n = n + 1 @@ -1377,7 +1816,13 @@ http_host var: agentZH.org:1984 print("1: reinsert header ", header, ": ", i) ngx.req.set_header(header, value) end - local headers = ngx.req.get_headers(100, true) + + headers, err = ngx.req.get_headers(100, true) + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + n = 0 for header, _ in pairs(headers) do n = n + 1 @@ -1417,13 +1862,18 @@ got 8 headers -=== TEST 45: github issue #314: ngx.req.set_header does not override request headers with multiple values +=== TEST 47: github issue #314: ngx.req.set_header does not override request headers with multiple values --- config #lua_code_cache off; location = /t { content_by_lua ' ngx.req.set_header("AAA", "111") - local headers = ngx.req.get_headers() + local headers, err = ngx.req.get_headers() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + ngx.say(headers["AAA"]) '; } @@ -1441,7 +1891,7 @@ AAA: 678 -=== TEST 46: clear If-Match req header +=== TEST 48: clear If-Match req header --- config location /t { content_by_lua ' @@ -1463,7 +1913,7 @@ test -=== TEST 47: clear If-Unmodified-Since req header +=== TEST 49: clear If-Unmodified-Since req header --- config location /t { content_by_lua ' @@ -1486,7 +1936,7 @@ test -=== TEST 48: clear If-None-Match req header +=== TEST 50: clear If-None-Match req header --- config location /t { content_by_lua ' @@ -1509,7 +1959,7 @@ test -=== TEST 49: set the Destination request header for WebDav +=== TEST 51: set the Destination request header for WebDav --- config location = /a.txt { rewrite_by_lua_block { @@ -1535,7 +1985,7 @@ client sent no "Destination" header -=== TEST 50: X-Forwarded-For +=== TEST 52: X-Forwarded-For --- config location = /t { access_by_lua_block { @@ -1559,7 +2009,7 @@ Foo: 8.8.8.8, 127.0.0.1 -=== TEST 51: X-Forwarded-For +=== TEST 53: X-Forwarded-For --- config location = /t { access_by_lua_block { @@ -1585,7 +2035,7 @@ Foo: 127.0.0.1 -=== TEST 52: for bad requests (bad request method letter case) +=== TEST 54: for bad requests (bad request method letter case) --- config error_page 400 = /err; @@ -1605,7 +2055,7 @@ ok -=== TEST 53: for bad requests (bad request method names) +=== TEST 55: for bad requests (bad request method names) --- config error_page 400 = /err; @@ -1625,7 +2075,7 @@ ok -=== TEST 54: for bad requests causing segfaults when setting & getting multi-value headers +=== TEST 56: for bad requests causing segfaults when setting & getting multi-value headers --- config error_page 400 = /err; @@ -1644,3 +2094,322 @@ ok --- no_error_log [error] --- no_check_leak + + + +=== TEST 57: exceeding custom 3 header limit +--- config + location /lua { + content_by_lua ' + local headers, err = ngx.req.get_headers(3) + if err then + ngx.say("err: ", err) + end + + local cnt = 0 + for key, val in pairs(headers) do + cnt = cnt + 1 + end + + ngx.say("found ", cnt, " headers."); + '; + } +--- request +GET /lua +--- more_headers eval +my $i = 1; +my $s; +while ($i <= 2) { + $s .= "X-$i:$i\n"; + $i++; +} +$s +--- response_body +err: truncated +found 3 headers. +--- timeout: 4 +--- error_log +lua exceeding request header limit 4 > 3 +--- no_error_log +[error] +--- skip_eval: 4:$ENV{TEST_NGINX_USE_HTTP3} +--- no_http2 + + + +=== TEST 58: NOT exceeding custom 3 header limit +--- config + location /lua { + content_by_lua ' + local headers, err = ngx.req.get_headers(3) + if err then + ngx.say("err: ", err) + end + + local cnt = 0 + for key, val in pairs(headers) do + cnt = cnt + 1 + end + + ngx.say("found ", cnt, " headers."); + '; + } +--- request +GET /lua +--- more_headers eval +my $i = 1; +my $s; +while ($i <= 1) { + $s .= "X-$i:$i\n"; + $i++; +} +$s +--- response_body +found 3 headers. +--- timeout: 4 +--- no_error_log +lua exceeding request header limit +[error] +--- skip_eval: 4: $ENV{TEST_NGINX_USE_HTTP3} +--- no_http2 + + + +=== TEST 59: exceeding custom 3 header limit (raw) +--- config + location /lua { + content_by_lua ' + local headers, err = ngx.req.get_headers(3, true) + if err then + ngx.say("err: ", err) + end + + local cnt = 0 + for key, val in pairs(headers) do + cnt = cnt + 1 + end + + ngx.say("found ", cnt, " headers."); + '; + } +--- request +GET /lua +--- more_headers eval +my $i = 1; +my $s; +while ($i <= 2) { + $s .= "X-$i:$i\n"; + $i++; +} +$s +--- response_body +err: truncated +found 3 headers. +--- timeout: 4 +--- error_log +lua exceeding request header limit 4 > 3 +--- no_error_log +[error] +--- skip_eval: 4: $ENV{TEST_NGINX_USE_HTTP3} +--- no_http2 + + + +=== TEST 60: NOT exceeding custom 3 header limit (raw) +--- config + location /lua { + content_by_lua ' + local headers, err = ngx.req.get_headers(3, true) + if err then + ngx.say("err: ", err) + end + + local cnt = 0 + for key, val in pairs(headers) do + cnt = cnt + 1 + end + + ngx.say("found ", cnt, " headers."); + '; + } +--- request +GET /lua +--- more_headers eval +my $i = 1; +my $s; +while ($i <= 1) { + $s .= "X-$i:$i\n"; + $i++; +} +$s +--- response_body eval +my $body; +if (!defined $ENV{TEST_NGINX_USE_HTTP2}) { + $body = "found 3 headers. +"; +} else { + $body = "found 2 headers. +"; +} + +$body; +--- timeout: 4 +--- no_error_log +lua exceeding request header limit +[error] +--- skip_eval: 4: $ENV{TEST_NGINX_USE_HTTP3} + + + +=== TEST 61: setting Host header clears cached $host variable +--- config + location /req-header { + # this makes $host indexed and cacheable + set $foo $host; + + content_by_lua_block { + ngx.say(ngx.var.host) + ngx.req.set_header("Host", "new"); + ngx.say(ngx.var.host) + } + } +--- request +GET /req-header +--- response_body +localhost +new +--- no_error_log +[error] + + + +=== TEST 62: unsafe header name (with '\r') +--- config + location /req-header { + rewrite_by_lua_block { + ngx.req.set_header("Foo\rfoo", "new value"); + } + + echo "Foo: $http_foo"; + } +--- request +GET /req-header +--- response_body +Foo: +--- no_error_log +[error] + + + +=== TEST 63: unsafe header value (with '\n') +--- config + location /req-header { + rewrite_by_lua_block { + ngx.req.set_header("Foo", "new\nvalue"); + } + + echo "Foo: $http_foo"; + } +--- request +GET /req-header +--- response_body +Foo: new%0Avalue +--- no_error_log +[error] + + + +=== TEST 64: multiple unsafe header values (with '\n' and '\t') +--- config + location /req-header { + rewrite_by_lua_block { + ngx.req.set_header("Foo", { "new\nvalue", "foo\tbar" } ); + } + + content_by_lua_block { + ngx.say(table.concat(ngx.req.get_headers()["foo"], ", "), ".") + } + } +--- request +GET /req-header +--- response_body +new%0Avalue, foo bar. +--- no_error_log +[error] + + + +=== TEST 65: unsafe names/values logging escapes '"' and '\' characters +--- config + location /req-header { + rewrite_by_lua_block { + ngx.req.set_header("Foo", "\"new\nvalue\\\""); + } + + content_by_lua_block { + ngx.say(ngx.req.get_headers()["foo"]) + } + } +--- request +GET /req-header +--- response_body +"new%0Avalue\" +--- no_error_log +[error] + + + +=== TEST 66: add request headers with '\r\n' +--- config + location /bar { + access_by_lua_block { + ngx.req.set_header("Foo\r", "123\r\n") + } + proxy_pass http://127.0.0.1:$server_port/foo; + } + + location = /foo { + echo $echo_client_request_headers; + } +--- request +GET /bar +--- response_body_like chomp +\bFoo%0D: 123%0D%0A\b + + + +=== TEST 67: add request headers with '\0' +--- config + location /bar { + access_by_lua_block { + ngx.req.set_header("Foo", "\0") + } + proxy_pass http://127.0.0.1:$server_port/foo; + } + + location = /foo { + echo $echo_client_request_headers; + } +--- request +GET /bar +--- response_body_like chomp +\bFoo: %00\b + + + +=== TEST 68: add request headers with '中文' +--- config + location /bar { + access_by_lua_block { + ngx.req.set_header("Foo中文", "ab中文a") + } + proxy_pass http://127.0.0.1:$server_port/foo; + } + + location = /foo { + echo $echo_client_request_headers; + } +--- request +GET /bar +--- response_body_like chomp +\bFoo%E4%B8%AD%E6%96%87: ab中文a\r\n diff --git a/t/030-uri-args-with-ctrl.t b/t/030-uri-args-with-ctrl.t new file mode 100644 index 0000000000..e74da38eeb --- /dev/null +++ b/t/030-uri-args-with-ctrl.t @@ -0,0 +1,137 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use Test::Nginx::Socket::Lua; + +log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 2 + 3); + +no_root_location(); + +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: rewrite args (string with \r) +--- config + location /foo { + rewrite_by_lua_block { + ngx.req.set_uri_args("a\rb") + } + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/echo; + } + location /echo { + content_by_lua_block { + ngx.say(ngx.var.request_uri); + } + } +--- request +GET /foo?world +--- error_code: 200 +--- response_body +/echo?a%0Db + + + +=== TEST 2: rewrite args (string with \n) +--- config + location /foo { + rewrite_by_lua_block { + ngx.req.set_uri_args("a\nb") + } + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/echo; + } + location /echo { + content_by_lua_block { + ngx.say(ngx.var.request_uri); + } + } +--- request +GET /foo?world +--- response_body +/echo?a%0Ab + + + +=== TEST 3: rewrite args (string with \0) +--- config + location /foo { + rewrite_by_lua_block { + ngx.req.set_uri_args("a\0b") + } + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT/echo; + } + location /echo { + content_by_lua_block { + ngx.say(ngx.var.request_uri); + } + } +--- request +GET /foo?world +--- response_body +/echo?a%00b + + + +=== TEST 4: rewrite args (string arg with 'lang=中文') +ngx.req.set_uri_args with string argument should be carefully encoded. +For backward compatibility, we are allowed to pass such parameters. +--- config + location /foo { + rewrite_by_lua_block { + ngx.req.set_uri_args("lang=中文") + } + content_by_lua_block { + ngx.say(ngx.var.arg_lang) + } + } +--- request +GET /foo?world +--- response_body +中文 +--- no_error_log +[error] + + + +=== TEST 5: rewrite args (string arg with '语言=chinese') +ngx.req.set_uri_args with string argument should be carefully encoded. +For backward compatibility, we are allowed to pass such parameters. +--- config + location /foo { + rewrite_by_lua_block { + ngx.req.set_uri_args("语言=chinese") + } + content_by_lua_block { + ngx.say(ngx.var.arg_语言) + } + } +--- request +GET /foo?world +--- response_body +chinese +--- no_error_log +[error] + + + +=== TEST 6: rewrite args (string arg with '语言=中文') +ngx.req.set_uri_args with string argument should be carefully encoded. +For backward compatibility, we are allowed to pass such parameters. +--- config + location /foo { + rewrite_by_lua_block { + ngx.req.set_uri_args("语言=中文") + } + content_by_lua_block { + ngx.say(ngx.var.arg_语言) + } + } +--- request +GET /foo?world +--- response_body +中文 +--- no_error_log +[error] diff --git a/t/030-uri-args.t b/t/030-uri-args.t index 96e216c15b..0633476c8d 100644 --- a/t/030-uri-args.t +++ b/t/030-uri-args.t @@ -9,7 +9,9 @@ log_level('warn'); repeat_each(2); #repeat_each(1); -plan tests => repeat_each() * (blocks() * 2 + 18); + +plan tests => repeat_each() * (blocks() * 2 + 23); + no_root_location(); @@ -24,7 +26,12 @@ __DATA__ --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -49,7 +56,12 @@ c = true --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -74,7 +86,12 @@ foo = true --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -87,7 +104,12 @@ foo = true ngx.say("again...") - args = ngx.req.get_uri_args() + args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -114,7 +136,12 @@ b r = 4a 2 --- config location /t { content_by_lua ' - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -139,7 +166,12 @@ done --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -164,7 +196,12 @@ done --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -195,7 +232,12 @@ done --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -227,7 +269,12 @@ done --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} -- ngx.say(args) for key, val in pairs(args) do @@ -253,7 +300,12 @@ done --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} -- ngx.say(args) for key, val in pairs(args) do @@ -280,7 +332,12 @@ done --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -305,7 +362,12 @@ done --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -320,7 +382,12 @@ done ngx.var.args = "a=3&b=4" - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -356,7 +423,7 @@ done ngx.req.set_uri_args("hello") ngx.req.set_uri("/bar", true); '; - proxy_pass http://agentzh.org:12345; + proxy_pass http://127.0.0.2:12345; } --- request GET /foo?world @@ -503,7 +570,7 @@ HTTP/1.0 ca%20t=%25 ngx.req.set_uri("/bar", true); ngx.exit(503) '; - proxy_pass http://agentzh.org:12345; + proxy_pass http://127.0.0.2:12345; } --- request GET /foo?world @@ -521,7 +588,7 @@ hello #set $args 'hello'; set $err ''; access_by_lua ' - res, err = pcall(ngx.req.set_uri, "/bar", true); + local res, err = pcall(ngx.req.set_uri, "/bar", true); ngx.var.err = err '; echo "err: $err"; @@ -561,7 +628,7 @@ uri: /bar location /foo { #set $args 'hello'; content_by_lua ' - res, err = pcall(ngx.req.set_uri, "/bar", true); + local res, err = pcall(ngx.req.set_uri, "/bar", true); ngx.say("err: ", err) '; } @@ -600,7 +667,7 @@ uri: /bar location /foo { #set $args 'hello'; set_by_lua $err ' - res, err = pcall(ngx.req.set_uri, "/bar", true); + local res, err = pcall(ngx.req.set_uri, "/bar", true); return err '; echo "err: $err"; @@ -623,8 +690,8 @@ err: API disabled in the context of set_by_lua* } --- request GET /lua ---- response_body -a=bar&b=foo +--- response_body eval +qr/a=bar&b=foo|b=foo&a=bar/ @@ -723,7 +790,7 @@ GET /lua location /lua { content_by_lua ' local t = {bar = ngx.shared.dogs, foo = 3} - rc, err = pcall(ngx.encode_args, t) + local rc, err = pcall(ngx.encode_args, t) ngx.say("rc: ", rc, ", err: ", err) '; } @@ -768,7 +835,12 @@ rc: false, err: bad argument #1 to '?' (table expected, got boolean) --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args(2) + local args, err = ngx.req.get_uri_args(2) + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -783,6 +855,7 @@ rc: false, err: bad argument #1 to '?' (table expected, got boolean) --- request GET /lua?foo=3&bar=4&baz=2 --- response_body +err: truncated bar = 4 foo = 3 --- error_log @@ -795,7 +868,12 @@ lua hit query args limit 2 --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args(2) + local args, err = ngx.req.get_uri_args(2) + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -810,6 +888,7 @@ lua hit query args limit 2 --- request GET /lua?foo=3&bar&baz=2 --- response_body +err: truncated bar = true foo = 3 --- error_log @@ -822,7 +901,12 @@ lua hit query args limit 2 --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args(2) + local args, err = ngx.req.get_uri_args(2) + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -839,6 +923,7 @@ lua hit query args limit 2 --- request GET /lua?foo=3&=hello&=world --- response_body +err: truncated foo = 3 done --- error_log @@ -851,7 +936,12 @@ lua hit query args limit 2 --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -887,7 +977,8 @@ for my $k (@k) { $k .= " = $&\n"; } } -CORE::join("", @k); + +"err: truncated\n" . CORE::join("", @k); --- timeout: 4 --- error_log lua hit query args limit 100 @@ -899,7 +990,12 @@ lua hit query args limit 100 --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args(102) + local args, err = ngx.req.get_uri_args(102) + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -935,7 +1031,8 @@ for my $k (@k) { $k .= " = $&\n"; } } -CORE::join("", @k); + +"err: truncated\n" . CORE::join("", @k); --- timeout: 4 --- error_log lua hit query args limit 102 @@ -947,7 +1044,12 @@ lua hit query args limit 102 --- config location /lua { content_by_lua ' - local args = ngx.req.get_uri_args(0) + local args, err = ngx.req.get_uri_args(0) + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -1003,8 +1105,8 @@ CORE::join("", @k); } --- request GET /foo?world ---- response_body -HTTP/1.0 a=3&b=5&b=6 +--- response_body eval +qr/HTTP\/1.0 (a=3&b=5&b=6|b=5&b=6&a=3|b=6&b=5&a=3)/ @@ -1012,8 +1114,14 @@ HTTP/1.0 a=3&b=5&b=6 --- config location /lua { content_by_lua ' + local err local args = "a=bar&b=foo" - args = ngx.decode_args(args) + args, err = ngx.decode_args(args) + + if err then + ngx.say("err: ", err) + end + ngx.say("a = ", args.a) ngx.say("b = ", args.b) '; @@ -1030,8 +1138,14 @@ b = foo --- config location /lua { content_by_lua ' + local err local args = "a=bar&b=foo&a=baz" - args = ngx.decode_args(args) + args, err = ngx.decode_args(args) + + if err then + ngx.say("err: ", err) + end + ngx.say("a = ", table.concat(args.a, ", ")) ngx.say("b = ", args.b) '; @@ -1048,8 +1162,13 @@ b = foo --- config location /lua { content_by_lua ' + local err local args = "" - args = ngx.decode_args(args) + args, err = ngx.decode_args(args) + if err then + ngx.say("err: ", err) + end + ngx.say("n = ", #args) '; } @@ -1064,8 +1183,13 @@ n = 0 --- config location /lua { content_by_lua ' + local err local args = "a&b" - args = ngx.decode_args(args) + args, err = ngx.decode_args(args) + if err then + ngx.say("err: ", err) + end + ngx.say("a = ", args.a) ngx.say("b = ", args.b) '; @@ -1082,8 +1206,14 @@ b = true --- config location /lua { content_by_lua ' + local err local args = "a=&b=" - args = ngx.decode_args(args) + args, err = ngx.decode_args(args) + + if err then + ngx.say("err: ", err) + end + ngx.say("a = ", args.a) ngx.say("b = ", args.b) '; @@ -1100,8 +1230,13 @@ b = --- config location /lua { content_by_lua ' + local err local args = "a=bar&b=foo" - args = ngx.decode_args(args, 1) + args, err = ngx.decode_args(args, 1) + if err then + ngx.say("err: ", err) + end + ngx.say("a = ", args.a) ngx.say("b = ", args.b) '; @@ -1109,6 +1244,7 @@ b = --- request GET /lua --- response_body +err: truncated a = bar b = nil @@ -1118,8 +1254,14 @@ b = nil --- config location /lua { content_by_lua ' + local err local args = "a=bar&b=foo" - args = ngx.decode_args(args, -1) + args, err = ngx.decode_args(args, -1) + + if err then + ngx.say("err: ", err) + end + ngx.say("a = ", args.a) ngx.say("b = ", args.b) '; @@ -1137,7 +1279,11 @@ b = foo location /lua { content_by_lua ' local s = "f+f=bar&B=foo" - args = ngx.decode_args(s) + local args, err = ngx.decode_args(s) + if err then + ngx.say("err: ", err) + end + local arr = {} for k, v in pairs(args) do table.insert(arr, k) @@ -1165,7 +1311,7 @@ s = f+f=bar&B=foo lua_need_request_body on; location /t { content_by_lua ' - function split(s, delimiter) + local function split(s, delimiter) local result = {} local from = 1 local delim_from, delim_to = string.find(s, delimiter, from) @@ -1183,7 +1329,11 @@ s = f+f=bar&B=foo local commands = split(post_data, "||") for _, command in pairs(commands) do --command = ngx.unescape_uri(command) - local request_args = ngx.decode_args(command, 0) + local request_args, err = ngx.decode_args(command, 0) + if err then + ngx.say("err: ", err) + end + local arr = {} for k, v in pairs(request_args) do table.insert(arr, k) @@ -1402,7 +1552,218 @@ GET /lua } --- request GET /lua +--- response_body eval +qr/(\Qargs: foo=%2C%24%40%7C%60&bar=-_.!~*'()\E)|(\Qargs: bar=-_.!~*'()&foo=%2C%24%40%7C%60\E)/ +--- no_error_log +[error] + + + +=== TEST 58: set_uri with unsafe uri (with '\t') +--- config + location /t { + content_by_lua_block { + local new_uri = "/foo\tbar" + ngx.req.set_uri(new_uri) + ngx.say(ngx.var.uri) + } + } +--- request + GET /t +--- response +/foo bar +--- no_error_log + + + +=== TEST 59: set_uri with unsafe uri (with '\0') +--- config + location /t { + content_by_lua_block { + local new_uri = '\0foo' + ngx.req.set_uri(new_uri, false, true) + ngx.say(ngx.var.uri) + } + } +--- request + GET /t +--- error_code: 200 +--- response_body eval +qr/\0foo/ + + + +=== TEST 60: set_uri with safe uri (with ' ') +--- config + location /t { + rewrite_by_lua_block { + local new_uri = "/foo bar" + ngx.req.set_uri(new_uri) + } + + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT; + } + + location /foo { + content_by_lua_block { + ngx.say("request_uri: ", ngx.var.request_uri) + ngx.say("uri: ", ngx.var.uri) + } + } +--- request + GET /t +--- response_body +request_uri: /foo%20bar +uri: /foo bar +--- no_error_log +[error] + + + +=== TEST 61: set_uri_args with boolean +--- config + location /bar { + echo $query_string; + } + location /foo { + #set $args 'hello'; + rewrite_by_lua_block { + ngx.req.set_uri_args(true) + ngx.req.set_uri("/bar", true) + } + proxy_pass http://127.0.0.2:12345; + } +--- request + GET /foo?world +--- response_body_like: 500 Internal Server Error +--- log_level: debug +--- error_code: 500 +--- error_log +bad argument #1 to 'set_uri_args' (string, number, or table expected, but got boolean) + + + +=== TEST 62: set_uri_args with nil +--- config + location /bar { + echo $query_string; + } + location /foo { + #set $args 'hello'; + rewrite_by_lua_block { + ngx.req.set_uri_args(nil) + ngx.req.set_uri("/bar", true) + } + proxy_pass http://127.0.0.2:12345; + } +--- request + GET /foo?world +--- response_body_like: 500 Internal Server Error +--- log_level: debug +--- error_code: 500 +--- error_log +bad argument #1 to 'set_uri_args' (string, number, or table expected, but got nil) + + + +=== TEST 63: set_uri_args with userdata +--- config + location /bar { + echo $query_string; + } + location /foo { + #set $args 'hello'; + rewrite_by_lua_block { + ngx.req.set_uri_args(ngx.null) + ngx.req.set_uri("/bar", true) + } + proxy_pass http://127.0.0.2:12345; + } +--- request + GET /foo?world +--- response_body_like: 500 Internal Server Error +--- log_level: debug +--- error_code: 500 +--- error_log +bad argument #1 to 'set_uri_args' (string, number, or table expected, but got userdata) + + + +=== TEST 64: set_uri binary option with unsafe uri +explicit specify binary option to true +--- config + location /t { + rewrite_by_lua_block { + local new_uri = "/foo\r\nbar" + ngx.req.set_uri(new_uri, false, true) + } + + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT; + } + + location /foo { + content_by_lua_block { + ngx.say("request_uri: ", ngx.var.request_uri) + ngx.say("uri: ", ngx.var.uri) + } + } +--- request + GET /t +--- response_body eval +["request_uri: /foo%0D%0Abar\nuri: /foo\r\nbar\n", "request_uri: /foo%0D%0Abar\nuri: /foo\r\nbar\n"] +--- no_error_log +[error] + + + +=== TEST 65: set_uri binary option with unsafe uri +explicit specify binary option to false +--- config + location /t { + rewrite_by_lua_block { + local new_uri = "/foo\r\nbar" + ngx.req.set_uri(new_uri, false, false) + } + + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT; + } + + location /foo { + content_by_lua_block { + ngx.say("request_uri: ", ngx.var.request_uri) + ngx.say("uri: ", ngx.var.uri) + } + } +--- request + GET /t +--- error_code: 500 +--- error_log eval +qr{\[error\] \d+#\d+: \*\d+ lua entry thread aborted: runtime error: rewrite_by_lua\(nginx.conf:\d+\):\d+: unsafe byte "0x0d" in uri "/foo\\x0D\\x0Abar" \(maybe you want to set the 'binary' argument\?\)} + + + +=== TEST 66: set_uri binary option with safe uri +explicit specify binary option to false +--- config + location /t { + rewrite_by_lua_block { + local new_uri = "/foo bar" + ngx.req.set_uri(new_uri, false, true) + } + + proxy_pass http://127.0.0.1:$TEST_NGINX_SERVER_PORT; + } + + location /foo { + content_by_lua_block { + ngx.say("request_uri: ", ngx.var.request_uri) + ngx.say("uri: ", ngx.var.uri) + } + } +--- request + GET /t --- response_body -args: foo=%2C%24%40%7C%60&bar=-_.!~*'() +request_uri: /foo%20bar +uri: /foo bar --- no_error_log [error] diff --git a/t/031-post-args.t b/t/031-post-args.t index 62c88c1ec7..78805d3723 100644 --- a/t/031-post-args.t +++ b/t/031-post-args.t @@ -11,7 +11,7 @@ repeat_each(2); plan tests => repeat_each() * (blocks() * 2 + 6); #no_diff(); -#no_long_string(); +no_long_string(); run_tests(); __DATA__ @@ -21,7 +21,12 @@ __DATA__ location /lua { lua_need_request_body on; content_by_lua ' - local args = ngx.req.get_post_args() + local args, err = ngx.req.get_post_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -48,7 +53,12 @@ c = true location /lua { lua_need_request_body off; content_by_lua ' - local args = ngx.req.get_post_args() + local args, err = ngx.req.get_post_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -73,7 +83,12 @@ a=3&b=4&c location /lua { lua_need_request_body on; content_by_lua ' - local args = ngx.req.get_post_args() + local args, err = ngx.req.get_post_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -93,6 +108,7 @@ a=3&b=4&c --- request POST /lua --- response_body +--- skip_eval: 2:$ENV{TEST_NGINX_USE_HTTP3} @@ -101,7 +117,12 @@ POST /lua location /lua { content_by_lua ' ngx.req.read_body(); - local args = ngx.req.get_post_args(2) + local args, err = ngx.req.get_post_args(2) + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -117,6 +138,7 @@ POST /lua POST /lua foo=3&bar=4&baz=2 --- response_body +err: truncated bar = 4 foo = 3 --- error_log @@ -129,7 +151,12 @@ lua hit query args limit 2 location /lua { content_by_lua ' ngx.req.read_body(); - local args = ngx.req.get_post_args(2) + local args, err = ngx.req.get_post_args(2) + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -145,6 +172,7 @@ lua hit query args limit 2 POST /lua foo=3&bar&baz=2 --- response_body +err: truncated bar = true foo = 3 --- error_log @@ -157,7 +185,12 @@ lua hit query args limit 2 location /lua { content_by_lua ' ngx.req.read_body(); - local args = ngx.req.get_post_args(2) + local args, err = ngx.req.get_post_args(2) + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -175,6 +208,7 @@ lua hit query args limit 2 POST /lua foo=3&=hello&=world --- response_body +err: truncated foo = 3 done --- error_log @@ -187,7 +221,12 @@ lua hit query args limit 2 location /lua { content_by_lua ' ngx.req.read_body(); - local args = ngx.req.get_post_args() + local args, err = ngx.req.get_post_args() + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -223,7 +262,7 @@ for my $k (@k) { $k .= " = $&\n"; } } -CORE::join("", @k); +"err: truncated\n" . CORE::join("", @k); --- timeout: 4 --- error_log lua hit query args limit 100 @@ -235,7 +274,12 @@ lua hit query args limit 100 location /lua { content_by_lua ' ngx.req.read_body() - local args = ngx.req.get_post_args(102) + local args, err = ngx.req.get_post_args(102) + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -271,7 +315,7 @@ for my $k (@k) { $k .= " = $&\n"; } } -CORE::join("", @k); +"err: truncated\n" . CORE::join("", @k); --- timeout: 4 --- error_log lua hit query args limit 102 @@ -283,7 +327,12 @@ lua hit query args limit 102 location /lua { content_by_lua ' ngx.req.read_body() - local args = ngx.req.get_post_args(0) + local args, err = ngx.req.get_post_args(0) + + if err then + ngx.say("err: ", err) + end + local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -332,9 +381,11 @@ CORE::join("", @k); content_by_lua_block { local args, err = ngx.req.get_post_args() - if not args then - ngx.say(err) - else + if err then + ngx.say("err: ", err) + end + + if args then local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -351,6 +402,6 @@ CORE::join("", @k); POST /lua a=3&b=4&c --- response_body -request body in temp file not supported +err: request body in temp file not supported --- no_error_log [error] diff --git a/t/033-ctx.t b/t/033-ctx.t index 8fc50aa7a0..77bd15f8c1 100644 --- a/t/033-ctx.t +++ b/t/033-ctx.t @@ -278,6 +278,8 @@ GET /t [error] --- error_log ngx.ctx = 32 +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ diff --git a/t/034-match.t b/t/034-match.t index ebe5762be4..fd4d9439e2 100644 --- a/t/034-match.t +++ b/t/034-match.t @@ -9,7 +9,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); -plan tests => repeat_each() * (blocks() * 2 + 15); +plan tests => repeat_each() * (blocks() * 2 + 16); #no_diff(); no_long_string(); @@ -21,7 +21,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "([0-9]+)") + local m = ngx.re.match("hello, 1234", "([0-9]+)") if m then ngx.say(m[0]) else @@ -40,7 +40,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "(\\\\d+)") + local m = ngx.re.match("hello, 1234", "(\\\\d+)") if m then ngx.say(m[0]) else @@ -59,7 +59,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "(\\d+)") + local m = ngx.re.match("hello, 1234", "(\\d+)") if m then ngx.say(m[0]) else @@ -80,7 +80,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "[[\\d+]]") + local m = ngx.re.match("hello, 1234", "[[\\d+]]") if m then ngx.say(m[0]) else @@ -101,7 +101,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "([0-9]{2})[0-9]+") + local m = ngx.re.match("hello, 1234", "([0-9]{2})[0-9]+") if m then ngx.say(m[0]) ngx.say(m[1]) @@ -122,7 +122,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "([a-z]+).*?([0-9]{2})[0-9]+", "") + local m = ngx.re.match("hello, 1234", "([a-z]+).*?([0-9]{2})[0-9]+", "") if m then ngx.say(m[0]) ngx.say(m[1]) @@ -145,7 +145,7 @@ hello --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "([a-z]+).*?([0-9]{2})[0-9]+", "o") + local m = ngx.re.match("hello, 1234", "([a-z]+).*?([0-9]{2})[0-9]+", "o") if m then ngx.say(m[0]) ngx.say(m[1]) @@ -168,7 +168,7 @@ hello --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "foo") + local m = ngx.re.match("hello, 1234", "foo") if m then ngx.say(m[0]) else @@ -187,7 +187,7 @@ not matched: nil --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "HELLO") + local m = ngx.re.match("hello, 1234", "HELLO") if m then ngx.say(m[0]) else @@ -206,7 +206,7 @@ not matched: nil --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "HELLO", "i") + local m = ngx.re.match("hello, 1234", "HELLO", "i") if m then ngx.say(m[0]) else @@ -225,7 +225,7 @@ hello --- config location /re { content_by_lua ' - rc, err = pcall(ngx.re.match, "hello章亦春", "HELLO.{2}", "iu") + local rc, err = pcall(ngx.re.match, "hello章亦春", "HELLO.{2}", "iu") if not rc then ngx.say("FAIL: ", err) return @@ -249,7 +249,7 @@ hello --- config location /re { content_by_lua ' - m = ngx.re.match("hello\\nworld", "^world", "m") + local m = ngx.re.match("hello\\nworld", "^world", "m") if m then ngx.say(m[0]) else @@ -268,7 +268,7 @@ world --- config location /re { content_by_lua ' - m = ngx.re.match("hello\\nworld", ".*", "m") + local m = ngx.re.match("hello\\nworld", ".*", "m") if m then ngx.say(m[0]) else @@ -287,7 +287,7 @@ hello --- config location /re { content_by_lua ' - m = ngx.re.match("hello\\nworld", "^world", "s") + local m = ngx.re.match("hello\\nworld", "^world", "s") if m then ngx.say(m[0]) else @@ -306,7 +306,7 @@ not matched: nil --- config location /re { content_by_lua ' - m = ngx.re.match("hello\\nworld", ".*", "s") + local m = ngx.re.match("hello\\nworld", ".*", "s") if m then ngx.say(m[0]) else @@ -326,7 +326,7 @@ world --- config location /re { content_by_lua ' - m = ngx.re.match("hello\\nworld", "\\\\w \\\\w", "x") + local m = ngx.re.match("hello\\nworld", "\\\\w \\\\w", "x") if m then ngx.say(m[0]) else @@ -361,8 +361,11 @@ he } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] @@ -372,7 +375,7 @@ error: pcre_compile() failed: missing ) in "(abc" --- config location /re { content_by_lua ' - rc, m = pcall(ngx.re.match, "hello\\nworld", ".*", "Hm") + local rc, m = pcall(ngx.re.match, "hello\\nworld", ".*", "Hm") if rc then if m then ngx.say(m[0]) @@ -395,7 +398,7 @@ error: .*?unknown flag "H" \(flags "Hm"\) --- config location /re { content_by_lua ' - m = ngx.re.match("hello, world", "(world)|(hello)", "x") + local m = ngx.re.match("hello, world", "(world)|(hello)", "x") if m then ngx.say(m[0]) ngx.say(m[1]) @@ -418,7 +421,7 @@ hello --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "([0-9]+)(h?)") + local m = ngx.re.match("hello, 1234", "([0-9]+)(h?)") if m then ngx.say(m[0]) ngx.say(m[1]) @@ -442,7 +445,7 @@ hello --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "([0-9]+)", "a") + local m = ngx.re.match("hello, 1234", "([0-9]+)", "a") if m then ngx.say(m[0]) else @@ -461,7 +464,7 @@ not matched! --- config location /re { content_by_lua ' - m = ngx.re.match("1234, hello", "([0-9]+)", "a") + local m = ngx.re.match("1234, hello", "([0-9]+)", "a") if m then ngx.say(m[0]) else @@ -481,7 +484,7 @@ not matched! location /re { content_by_lua ' local ctx = {} - m = ngx.re.match("1234, hello", "([0-9]+)", "", ctx) + local m = ngx.re.match("1234, hello", "([0-9]+)", "", ctx) if m then ngx.say(m[0]) ngx.say(ctx.pos) @@ -504,7 +507,7 @@ not matched! location /re { content_by_lua ' local ctx = { pos = 3 } - m = ngx.re.match("1234, hello", "([0-9]+)", "", ctx) + local m = ngx.re.match("1234, hello", "([0-9]+)", "", ctx) if m then ngx.say(m[0]) ngx.say(ctx.pos) @@ -526,7 +529,7 @@ not matched! --- config location /re { set_by_lua $res ' - m = ngx.re.match("hello, 1234", "([0-9]+)") + local m = ngx.re.match("hello, 1234", "([0-9]+)") if m then return m[0] else @@ -569,7 +572,7 @@ baz } --- user_files >>> a.lua -m = ngx.re.match("hello, 1234", "(\\\s+)") +local m = ngx.re.match("hello, 1234", "(\\\s+)") if m then ngx.say("[", m[0], "]") else @@ -595,7 +598,7 @@ end local uri = "2" local regex = '(?:>[\\w\\s]*)'; ngx.say("regex: ", regex) -m = ngx.re.match(uri, regex, "oi") +local m = ngx.re.match(uri, regex, "oi") if m then ngx.say("[", m[0], "]") else @@ -613,7 +616,7 @@ regex: (?:>[\w\s]*) --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", [[\\d+]]) + local m = ngx.re.match("hello, 1234", [[\\d+]]) if m then ngx.say(m[0]) else @@ -648,8 +651,12 @@ regex: (?:>[\w\s]*) } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "([0-9]+" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile\(\) failed: missing closing parenthesis in \"\([0-9]+\"\n" +: +"error: pcre_compile\(\) failed: missing \) in \"\([0-9]+\"\n" + --- no_error_log [error] @@ -660,7 +667,7 @@ error: pcre_compile() failed: missing ) in "([0-9]+" --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", [[([0-9]+)]]) + local m = ngx.re.match("hello, 1234", [[([0-9]+)]]) if m then ngx.say(m[0]) else @@ -939,8 +946,11 @@ nil } --- request GET /t ---- response_body_like chop -^error: pcre_exec\(\) failed: -10$ +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre_exec\(\) failed: -4\n" +: +"error: pcre_exec\(\) failed: -10\n" --- no_error_log [error] @@ -1027,7 +1037,7 @@ exec opts: 0 >>> a.lua local re = [==[(?i:([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:=|<=>|r?like|sounds\s+like|regexp)([\s'\"`´’‘\(\)]*)?\2|([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:!=|<=|>=|<>|<|>|\^|is\s+not|not\s+like|not\s+regexp)([\s'\"`´’‘\(\)]*)?(?!\6)([\d\w]+))]==] -s = string.rep([[ABCDEFG]], 10) +local s = string.rep([[ABCDEFG]], 10) local start = ngx.now() @@ -1050,8 +1060,14 @@ end --- request GET /re ---- response_body -error: pcre_exec() failed: -8 +--- response_body eval +# lua_regex_match_limit uses pcre_extra->match_limit in the PCRE, +# but PCRE2 replaces this with pcre2_set_match_limit interface, +# which has different effects. +$Test::Nginx::Util::PcreVersion == 2 ? +"failed to match\n" +: +"error: pcre_exec() failed: -8\n" @@ -1067,7 +1083,7 @@ error: pcre_exec() failed: -8 >>> a.lua local re = [==[(?i:([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:=|<=>|r?like|sounds\s+like|regexp)([\s'\"`´’‘\(\)]*)?\2|([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:!=|<=|>=|<>|<|>|\^|is\s+not|not\s+like|not\s+regexp)([\s'\"`´’‘\(\)]*)?(?!\6)([\d\w]+))]==] -s = string.rep([[ABCDEFG]], 10) +local s = string.rep([[ABCDEFG]], 10) local start = ngx.now() @@ -1101,7 +1117,7 @@ failed to match content_by_lua ' local res = {} local s = "hello, 1234" - m = ngx.re.match(s, [[(\\d)(\\d)]], "o", nil, res) + local m = ngx.re.match(s, [[(\\d)(\\d)]], "o", nil, res) if m then ngx.say("1: m size: ", #m) ngx.say("1: res size: ", #res) @@ -1129,26 +1145,33 @@ failed to match -=== TEST 48: init_by_lua +=== TEST 48: init_by_lua_block --- http_config - init_by_lua ' - m = ngx.re.match("hello, 1234", "(\\\\d+)") - '; + init_by_lua_block { + local m, err = ngx.re.match("hello, 1234", [[(\d+)]]) + if not m then + ngx.log(ngx.ERR, "failed to match: ", err) + else + package.loaded.m = m + end + } --- config location /re { - content_by_lua ' + content_by_lua_block { + local m = package.loaded.m if m then ngx.say(m[0]) else ngx.say("not matched!") end - '; + } } --- request GET /re --- response_body 1234 ---- SKIP +--- no_error_log +[error] diff --git a/t/035-gmatch.t b/t/035-gmatch.t index 042699767a..0a4efd2b25 100644 --- a/t/035-gmatch.t +++ b/t/035-gmatch.t @@ -123,7 +123,7 @@ nil --- config location /re { content_by_lua ' - it = ngx.re.gmatch("hello, 1234", "([0-9]+)", "a") + local it = ngx.re.gmatch("hello, 1234", "([0-9]+)", "a") ngx.say(it()) '; } @@ -418,7 +418,7 @@ hello content_by_lua ' local a = {} for i = 1, 3 do - it = ngx.re.gmatch("hello, world", "[a-z]+") + local it = ngx.re.gmatch("hello, world", "[a-z]+") it() collectgarbage() table.insert(a, {"hello", "world"}) @@ -482,9 +482,9 @@ end GET /main --- response_body matched -sr failed: 500 ---- error_log -attempt to use ngx.re.gmatch iterator in a request that did not create it +matched +--- no_error_log +[error] @@ -518,7 +518,7 @@ matched: [] location /re { content_by_lua ' local it = ngx.re.gmatch("1234, 1234", "(?[0-9]+)") - m = it() + local m = it() if m then ngx.say(m[0]) ngx.say(m[1]) @@ -555,7 +555,7 @@ matched: [] content_by_lua ' local it = ngx.re.gmatch("1234, abcd, 1234", "(?[0-9]+)|(?[a-z]+)") - m = it() + local m = it() if m then ngx.say(m[0]) ngx.say(m[1]) @@ -599,7 +599,7 @@ abcd location /re { content_by_lua ' local it = ngx.re.gmatch("hello, 1234", "(?[a-z]+), (?[0-9]+)", "D") - m = it() + local m = it() if m then ngx.say(m[0]) ngx.say(m[1]) @@ -698,8 +698,11 @@ not matched! } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] @@ -735,8 +738,11 @@ error: pcre_compile() failed: missing ) in "(abc" } --- request GET /t ---- response_body_like chop -error: pcre_exec\(\) failed: -10 +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre_exec\(\) failed: -4\n" +: +"error: pcre_exec\(\) failed: -10\n" --- no_error_log [error] @@ -825,7 +831,7 @@ exec opts: 0 >>> a.lua local re = [==[(?i:([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:=|<=>|r?like|sounds\s+like|regexp)([\s'\"`´’‘\(\)]*)?\2|([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:!=|<=|>=|<>|<|>|\^|is\s+not|not\s+like|not\s+regexp)([\s'\"`´’‘\(\)]*)?(?!\6)([\d\w]+))]==] -s = string.rep([[ABCDEFG]], 10) +local s = string.rep([[ABCDEFG]], 10) local start = ngx.now() @@ -854,8 +860,14 @@ end --- request GET /re ---- response_body -error: pcre_exec() failed: -8 +--- response_body eval +# lua_regex_match_limit uses pcre_extra->match_limit in the PCRE, +# but PCRE2 replaces this with pcre2_set_match_limit interface, +# which has different effects. +$Test::Nginx::Util::PcreVersion == 2 ? +"failed to match\n" +: +"error: pcre_exec() failed: -8\n" @@ -871,7 +883,7 @@ error: pcre_exec() failed: -8 >>> a.lua local re = [==[(?i:([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:=|<=>|r?like|sounds\s+like|regexp)([\s'\"`´’‘\(\)]*)?\2|([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:!=|<=|>=|<>|<|>|\^|is\s+not|not\s+like|not\s+regexp)([\s'\"`´’‘\(\)]*)?(?!\6)([\d\w]+))]==] -s = string.rep([[ABCDEFG]], 10) +local s = string.rep([[ABCDEFG]], 10) local start = ngx.now() @@ -881,7 +893,7 @@ if not it then return end -res, err = it() +local res, err = it() --[[ ngx.update_time() diff --git a/t/036-sub.t b/t/036-sub.t index 2b4b07510a..0657ebb2ca 100644 --- a/t/036-sub.t +++ b/t/036-sub.t @@ -480,8 +480,11 @@ a [b c] [b] [c] [] [] d } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] @@ -506,8 +509,11 @@ error: pcre_compile() failed: missing ) in "(abc" } --- request GET /t ---- response_body_like chop -error: pcre_exec\(\) failed: -10 +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre_exec\(\) failed: -4\n" +: +"error: pcre_exec\(\) failed: -10\n" --- no_error_log [error] @@ -590,7 +596,7 @@ s: a好 >>> a.lua local re = [==[(?i:([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:=|<=>|r?like|sounds\s+like|regexp)([\s'\"`´’‘\(\)]*)?\2|([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:!=|<=|>=|<>|<|>|\^|is\s+not|not\s+like|not\s+regexp)([\s'\"`´’‘\(\)]*)?(?!\6)([\d\w]+))]==] -s = string.rep([[ABCDEFG]], 10) +local s = string.rep([[ABCDEFG]], 10) local start = ngx.now() @@ -610,8 +616,14 @@ ngx.say("sub: ", cnt) --- request GET /re ---- response_body -error: pcre_exec() failed: -8 +--- response_body eval +# lua_regex_match_limit uses pcre_extra->match_limit in the PCRE, +# but PCRE2 replaces this with pcre2_set_match_limit interface, +# which has different effects. +$Test::Nginx::Util::PcreVersion == 2 ? +"sub: 0\n" +: +"error: pcre_exec() failed: -8\n" @@ -693,7 +705,7 @@ ab.cd location = /t { content_by_lua ' - function test() + local function test() local data = [[ OUTER {FIRST} ]] diff --git a/t/037-gsub.t b/t/037-gsub.t index 4c5810d527..a6fe579bd4 100644 --- a/t/037-gsub.t +++ b/t/037-gsub.t @@ -423,8 +423,11 @@ n: 1 } --- request GET /t ---- response_body_like chop -error: pcre_exec\(\) failed: -10 +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre_exec\(\) failed: -4\n" +: +"error: pcre_exec\(\) failed: -10\n" --- no_error_log [error] @@ -511,7 +514,7 @@ s: aa >>> a.lua local re = [==[(?i:([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:=|<=>|r?like|sounds\s+like|regexp)([\s'\"`´’‘\(\)]*)?\2|([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:!=|<=|>=|<>|<|>|\^|is\s+not|not\s+like|not\s+regexp)([\s'\"`´’‘\(\)]*)?(?!\6)([\d\w]+))]==] -s = string.rep([[ABCDEFG]], 10) +local s = string.rep([[ABCDEFG]], 10) local start = ngx.now() @@ -531,8 +534,14 @@ ngx.say("gsub: ", cnt) --- request GET /re ---- response_body -error: pcre_exec() failed: -8 +--- response_body eval +# lua_regex_match_limit uses pcre_extra->match_limit in the PCRE, +# but PCRE2 replaces this with pcre2_set_match_limit interface, +# which has different effects. +$Test::Nginx::Util::PcreVersion == 2 ? +"gsub: 0\n" +: +"error: pcre_exec() failed: -8\n" diff --git a/t/038-match-o.t b/t/038-match-o.t index d61ff1fdaa..cc80244e82 100644 --- a/t/038-match-o.t +++ b/t/038-match-o.t @@ -20,7 +20,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "([0-9]+)", "o") + local m = ngx.re.match("hello, 1234", "([0-9]+)", "o") if m then ngx.say(m[0]) else @@ -39,7 +39,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "(\\\\d+)", "o") + local m = ngx.re.match("hello, 1234", "(\\\\d+)", "o") if m then ngx.say(m[0]) else @@ -58,7 +58,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "(\\d+)", "o") + local m = ngx.re.match("hello, 1234", "(\\d+)", "o") if m then ngx.say(m[0]) else @@ -79,7 +79,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "[[\\d+]]", "o") + local m = ngx.re.match("hello, 1234", "[[\\d+]]", "o") if m then ngx.say(m[0]) else @@ -100,7 +100,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "([0-9]{2})[0-9]+", "o") + local m = ngx.re.match("hello, 1234", "([0-9]{2})[0-9]+", "o") if m then ngx.say(m[0]) ngx.say(m[1]) @@ -121,7 +121,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "([a-z]+).*?([0-9]{2})[0-9]+", "o") + local m = ngx.re.match("hello, 1234", "([a-z]+).*?([0-9]{2})[0-9]+", "o") if m then ngx.say(m[0]) ngx.say(m[1]) @@ -144,7 +144,7 @@ hello --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "foo", "o") + local m = ngx.re.match("hello, 1234", "foo", "o") if m then ngx.say(m[0]) else @@ -163,7 +163,7 @@ not matched: nil --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "HELLO", "o") + local m = ngx.re.match("hello, 1234", "HELLO", "o") if m then ngx.say(m[0]) else @@ -182,7 +182,7 @@ not matched: nil --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "HELLO", "oi") + local m = ngx.re.match("hello, 1234", "HELLO", "oi") if m then ngx.say(m[0]) else @@ -224,7 +224,7 @@ this version of PCRE is not compiled with PCRE_UTF8 support|^hello章亦$ --- config location /re { content_by_lua ' - m = ngx.re.match("hello\\nworld", "^world", "mo") + local m = ngx.re.match("hello\\nworld", "^world", "mo") if m then ngx.say(m[0]) else @@ -243,7 +243,7 @@ world --- config location /re { content_by_lua ' - m = ngx.re.match("hello\\nworld", ".*", "om") + local m = ngx.re.match("hello\\nworld", ".*", "om") if m then ngx.say(m[0]) else @@ -262,7 +262,7 @@ hello --- config location /re { content_by_lua ' - m = ngx.re.match("hello\\nworld", "^world", "so") + local m = ngx.re.match("hello\\nworld", "^world", "so") if m then ngx.say(m[0]) else @@ -281,7 +281,7 @@ not matched: nil --- config location /re { content_by_lua ' - m = ngx.re.match("hello\\nworld", ".*", "os") + local m = ngx.re.match("hello\\nworld", ".*", "os") if m then ngx.say(m[0]) else @@ -301,7 +301,7 @@ world --- config location /re { content_by_lua ' - m = ngx.re.match("hello\\nworld", "\\\\w \\\\w", "xo") + local m = ngx.re.match("hello\\nworld", "\\\\w \\\\w", "xo") if m then ngx.say(m[0]) else @@ -336,8 +336,11 @@ he } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] @@ -347,7 +350,7 @@ error: pcre_compile() failed: missing ) in "(abc" --- config location /re { content_by_lua ' - rc, m = pcall(ngx.re.match, "hello\\nworld", ".*", "Ho") + local rc, m = pcall(ngx.re.match, "hello\\nworld", ".*", "Ho") if rc then if m then ngx.say(m[0]) @@ -370,7 +373,7 @@ error: pcre_compile() failed: missing ) in "(abc" --- config location /re { content_by_lua ' - m = ngx.re.match("hello, world", "(world)|(hello)", "xo") + local m = ngx.re.match("hello, world", "(world)|(hello)", "xo") if m then ngx.say(m[0]) ngx.say(m[1]) @@ -393,7 +396,7 @@ hello --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "([0-9]+)(h?)", "o") + local m = ngx.re.match("hello, 1234", "([0-9]+)(h?)", "o") if m then ngx.say(m[0]) ngx.say(m[1]) @@ -417,7 +420,7 @@ hello --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "([0-9]+)", "oa") + local m = ngx.re.match("hello, 1234", "([0-9]+)", "oa") if m then ngx.say(m[0]) else @@ -436,7 +439,7 @@ not matched! --- config location /re { content_by_lua ' - m = ngx.re.match("1234, hello", "([0-9]+)", "ao") + local m = ngx.re.match("1234, hello", "([0-9]+)", "ao") if m then ngx.say(m[0]) else @@ -456,7 +459,7 @@ not matched! location /re { content_by_lua ' local ctx = {} - m = ngx.re.match("1234, hello", "([0-9]+)", "o", ctx) + local m = ngx.re.match("1234, hello", "([0-9]+)", "o", ctx) if m then ngx.say(m[0]) ngx.say(ctx.pos) @@ -479,7 +482,7 @@ not matched! location /re { content_by_lua ' local ctx = { pos = 3 } - m = ngx.re.match("1234, hello", "([0-9]+)", "o", ctx) + local m = ngx.re.match("1234, hello", "([0-9]+)", "o", ctx) if m then ngx.say(m[0]) ngx.say(ctx.pos) @@ -501,7 +504,7 @@ not matched! --- config location /re { set_by_lua $res ' - m = ngx.re.match("hello, 1234", "([0-9]+)", "o") + local m = ngx.re.match("hello, 1234", "([0-9]+)", "o") if m then return m[0] else diff --git a/t/041-header-filter.t b/t/041-header-filter.t index 9cca3b749f..23730815da 100644 --- a/t/041-header-filter.t +++ b/t/041-header-filter.t @@ -10,7 +10,7 @@ log_level('debug'); repeat_each(2); -plan tests => repeat_each() * 94; +plan tests => repeat_each() * (blocks() * 2 + 13); #no_diff(); #no_long_string(); @@ -124,6 +124,8 @@ Hi GET /read --- error_code --- response_body +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -254,7 +256,11 @@ uid: 33 header_filter_by_lua ' local str = ""; - local args = ngx.req.get_uri_args() + local args, err = ngx.req.get_uri_args() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end local keys = {} for key, val in pairs(args) do table.insert(keys, key) @@ -412,7 +418,7 @@ lua release ngx.ctx -=== TEST 20: global got cleared for each single request +=== TEST 20: globals are shared by all requests --- config location /lua { set $foo ''; @@ -424,6 +430,7 @@ lua release ngx.ctx if not foo then foo = 1 else + ngx.log(ngx.INFO, "old foo: ", foo) foo = foo + 1 end ngx.var.foo = foo @@ -431,14 +438,18 @@ lua release ngx.ctx } --- request GET /lua ---- response_body -1 +--- response_body_like +^[12]$ --- no_error_log [error] +--- grep_error_log eval: qr/old foo: \d+/ +--- grep_error_log_out eval +["", "old foo: 1\n"] === TEST 21: lua error (string) +--- no_http2 --- config location /lua { set $foo ''; @@ -454,13 +465,16 @@ GET /lua GET /lua --- ignore_response --- error_log -failed to run header_filter_by_lua*: header_filter_by_lua:2: Something bad +failed to run header_filter_by_lua*: header_filter_by_lua(nginx.conf:47):2: Something bad --- no_error_log [alert] +--- curl_error eval +qr/curl: \(56\) Failure when receiving data from the peer|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(28\) Remote peer returned unexpected data|curl: \(52\) Empty reply from server|curl: \(95\) HTTP\/3 stream 0 reset by server/ === TEST 22: lua error (nil) +--- no_http2 --- config location /lua { set $foo ''; @@ -479,6 +493,8 @@ GET /lua failed to run header_filter_by_lua*: unknown reason --- no_error_log [alert] +--- curl_error eval +qr/curl: \(56\) Failure when receiving data from the peer|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(28\) Remote peer returned unexpected data|curl: \(52\) Empty reply from server|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -493,6 +509,8 @@ GET /lua --- ignore_response --- error_log API disabled in the context of header_filter_by_lua* +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -507,6 +525,8 @@ GET /lua --- ignore_response --- error_log API disabled in the context of header_filter_by_lua* +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -521,6 +541,8 @@ GET /lua --- ignore_response --- error_log API disabled in the context of header_filter_by_lua* +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -535,6 +557,8 @@ GET /lua --- ignore_response --- error_log API disabled in the context of header_filter_by_lua* +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -549,6 +573,8 @@ GET /lua --- ignore_response --- error_log API disabled in the context of header_filter_by_lua* +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -567,6 +593,8 @@ GET /lua --- ignore_response --- error_log API disabled in the context of header_filter_by_lua* +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -585,6 +613,8 @@ GET /lua --- ignore_response --- error_log API disabled in the context of header_filter_by_lua* +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -599,6 +629,8 @@ GET /lua --- ignore_response --- error_log API disabled in the context of header_filter_by_lua* +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -613,6 +645,8 @@ GET /lua --- ignore_response --- error_log API disabled in the context of header_filter_by_lua* +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -627,6 +661,8 @@ GET /lua --- ignore_response --- error_log API disabled in the context of header_filter_by_lua* +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -657,8 +693,10 @@ uri: /blah --- request GET /lua --- ignore_response ---- error_log -API disabled in the context of header_filter_by_lua* +--- error_log eval +qr/API disabled in the context of header_filter_by_lua\*|http3 requests are not supported without content-length header/ms +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -671,8 +709,18 @@ API disabled in the context of header_filter_by_lua* --- request GET /lua --- ignore_response ---- error_log -API disabled in the context of header_filter_by_lua* +--- error_log eval +my $err_log; + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $err_log = "http v3 not supported yet"; +} else { + $err_log = "API disabled in the context of header_filter_by_lua*"; +} + +$err_log; +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -687,6 +735,8 @@ GET /lua --- ignore_response --- error_log API disabled in the context of header_filter_by_lua* +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -701,6 +751,8 @@ GET /lua --- ignore_response --- error_log API disabled in the context of header_filter_by_lua* +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -726,7 +778,8 @@ hello world --- config location /t { header_filter_by_lua ' - function foo() + local bar + local function foo() bar() end @@ -747,6 +800,8 @@ stack traceback: in function 'error' in function 'bar' in function 'foo' +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -765,6 +820,8 @@ GET /lua?a=1&b=2 --- ignore_response --- error_log eval qr/failed to load external Lua file ".*?test2\.lua": cannot open .*? No such file or directory/ +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -790,3 +847,84 @@ GET /t --- error_code: 302 --- no_error_log [error] +--- skip_eval: 3:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) + + + +=== TEST 42: syntax error in header_filter_by_lua_block +--- config + location /lua { + + header_filter_by_lua_block { + 'for end'; + } + content_by_lua_block { + ngx.say("Hello world") + } + } +--- request +GET /lua +--- ignore_response +--- error_log +failed to load inlined Lua code: header_filter_by_lua(nginx.conf:41):2: unexpected symbol near ''for end'' +--- no_error_log +no_such_error +--- curl_error eval +qr/curl: \(56\) Failure when receiving data from the peer|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly: INTERNAL_ERROR \(err 2\)/ + + + +=== TEST 43: syntax error in second content_by_lua_block +--- config + location /foo { + header_filter_by_lua_block { + 'for end'; + } + content_by_lua_block { + ngx.say("Hello world") + } + } + + location /lua { + header_filter_by_lua_block { + 'for end'; + } + content_by_lua_block { + ngx.say("Hello world") + } + } +--- request +GET /lua +--- ignore_response +--- error_log +failed to load inlined Lua code: header_filter_by_lua(nginx.conf:49):2: unexpected symbol near ''for end'' +--- no_error_log +no_such_error +--- curl_error eval +qr/curl: \(56\) Failure when receiving data from the peer|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly: INTERNAL_ERROR \(err 2\)/ + + + +=== TEST 44: syntax error in /tmp/12345678901234567890123456789012345.conf +--- config + location /lua { + content_by_lua_block { + ngx.say("Hello world") + } + + include /tmp/12345678901234567890123456789012345.conf; + } +--- user_files +>>> /tmp/12345678901234567890123456789012345.conf + header_filter_by_lua_block { + 'for end'; + } +--- request +GET /lua +--- ignore_response +--- error_log +failed to load inlined Lua code: header_filter_by_lua(...901234567890123456789012345.conf:1):2: unexpected symbol near ''for end'' +--- no_error_log +[alert] +--- curl_error eval +qr/curl: \(56\) Failure when receiving data from the peer|curl: \(56\) Failure when receiving data from the peer|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly: INTERNAL_ERROR \(err 2\)/ diff --git a/t/043-shdict.t b/t/043-shdict.t index 9183bf5e2e..3c0cc7961a 100644 --- a/t/043-shdict.t +++ b/t/043-shdict.t @@ -158,7 +158,7 @@ hello content_by_lua ' local dogs = ngx.shared.dogs dogs:set("foo", 32, 0.01) - ngx.location.capture("/sleep/0.01") + ngx.location.capture("/sleep/0.011") ngx.say(dogs:get("foo")) '; } @@ -420,7 +420,7 @@ ngx_slab_alloc() failed: no memory in lua_shared_dict zone ngx.say(dogs:get(key)) key = string.rep("a", 65536) - ok, err = dogs:set(key, "world") + local ok, err = dogs:set(key, "world") if not ok then ngx.say("not ok: ", err) return @@ -768,7 +768,7 @@ foo = 10502 dogs:set("bar", 32, 0.001) dogs:set("baz", 32, 0.001) dogs:set("foo", 32, 0.001) - ngx.location.capture("/sleep/0.002") + ngx.location.capture("/sleep/0.003") local res, err, forcible = dogs:add("foo", 10502) ngx.say("add: ", res, " ", err, " ", forcible) ngx.say("foo = ", dogs:get("foo")) @@ -797,7 +797,7 @@ foo = 10502 dogs:set("bar", 32, 0.001) dogs:set("baz", 32, 0.001) dogs:set("foo", "hi", 0.001) - ngx.location.capture("/sleep/0.002") + ngx.location.capture("/sleep/0.003") local res, err, forcible = dogs:add("foo", "hello") ngx.say("add: ", res, " ", err, " ", forcible) ngx.say("foo = ", dogs:get("foo")) @@ -1131,7 +1131,7 @@ nil nil content_by_lua ' local dogs = ngx.shared.dogs dogs:set("foo", 32, 0.01, 255) - ngx.location.capture("/sleep/0.01") + ngx.location.capture("/sleep/0.011") local res, flags = dogs:get("foo") ngx.say("res = ", res, ", flags = ", flags) '; @@ -1184,6 +1184,7 @@ nil nil === TEST 45: flush_expires +--- quic_max_idle_timeout: 1.6 --- http_config lua_shared_dict dogs 1m; --- config @@ -1210,6 +1211,7 @@ GET /t === TEST 46: flush_expires with number +--- quic_max_idle_timeout: 1.6 --- http_config lua_shared_dict dogs 1m; --- config @@ -1337,6 +1339,7 @@ GET /t === TEST 51: list all keys in a shdict with expires +--- quic_max_idle_timeout: 1.6 --- http_config lua_shared_dict dogs 1m; --- config @@ -1426,6 +1429,7 @@ GET /t === TEST 55: list all keys in a shdict with all keys expired +--- quic_max_idle_timeout: 1.6 --- http_config lua_shared_dict dogs 1m; --- config @@ -2048,7 +2052,7 @@ get_stale ok: false, stale: false ngx.say("get not ok: ", err) return end - flags = err + local flags = err ngx.say("get_stale ok: ", data, ", flags: ", flags, ", stale: ", stale) diff --git a/t/044-req-body.t b/t/044-req-body.t index 28fa818657..d72b1eb632 100644 --- a/t/044-req-body.t +++ b/t/044-req-body.t @@ -7,7 +7,7 @@ log_level('warn'); repeat_each(2); -plan tests => repeat_each() * (blocks() * 4 + 52 ); +plan tests => repeat_each() * (blocks() * 4 + 58); #no_diff(); no_long_string(); @@ -351,9 +351,18 @@ hello, world --- user_files >>> a.txt Will you change this world? ---- raw_response_headers_like -X-Old: \S+/client_body_temp/\d+\r -.*?X-New: \S+/html/a\.txt\r +--- raw_response_headers_like eval +my $headers; + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $headers = qr#x-old: \S+/client_body_temp/\d+\r +.*?x-new: \S+/html/a\.txt\r#; +} else { + $headers = qr#X-Old: \S+/client_body_temp/\d+\r +.*?X-New: \S+/html/a\.txt\r#; +} + +$headers; --- response_body Will you change this world? --- no_error_log @@ -390,9 +399,18 @@ hello, world! --- user_files >>> a.txt Will you change this world? ---- raw_response_headers_like -X-Old: \S+/client_body_temp/\d+\r -.*?X-New: \S+/html/a\.txt\r +--- raw_response_headers_like eval +my $headers; + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $headers = qr#x-old: \S+/client_body_temp/\d+\r +.*?x-new: \S+/html/a\.txt\r#; +} else { + $headers = qr#X-Old: \S+/client_body_temp/\d+\r +.*?X-New: \S+/html/a\.txt\r#; +} + +$headers; --- response_body Will you change this world? --- no_error_log @@ -440,7 +458,7 @@ b.txt exists: yes -=== TEST 17: read buffered body to memoary and reset it to a new file (auto-clean) +=== TEST 17: read buffered body to memory and reset it to a new file (auto-clean) --- config client_body_in_file_only off; @@ -476,7 +494,7 @@ qr/500 Internal Server Error/] -=== TEST 18: read buffered body to memoary and reset it to a new file (no auto-clean) +=== TEST 18: read buffered body to memory and reset it to a new file (no auto-clean) --- config client_body_in_file_only off; @@ -727,7 +745,13 @@ hello, world"] } --- user_files >>> myscript.lua - local data, data2 = ngx.req.get_post_args(), {} + local data, err = ngx.req.get_post_args() + if err then + ngx.log(ngx.ERR, "err: ", err) + return ngx.exit(500) + end + + local data2 = {} for k, v in pairs(data) do if type(v) == "table" then for i, val in ipairs(v) do @@ -892,6 +916,7 @@ body: hell --- no_error_log [error] [alert] +--- skip_eval: 4:$ENV{TEST_NGINX_USE_HTTP3} @@ -953,6 +978,7 @@ body file: hello [alert] --- error_log a client request body is buffered to a temporary file +--- skip_eval: 5:$ENV{TEST_NGINX_USE_HTTP3} @@ -979,9 +1005,9 @@ a client request body is buffered to a temporary file --- error_code: 500 --- error_log eval qr/lua entry thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):2: request body not read yet/ - --- no_error_log [alert] +--- skip_eval: 4:$ENV{TEST_NGINX_USE_HTTP3} @@ -1011,8 +1037,8 @@ body: hell --- no_error_log [error] [alert] ---- no_error_log a client request body is buffered to a temporary file +--- skip_eval: 5:$ENV{TEST_NGINX_USE_HTTP3} @@ -1235,8 +1261,8 @@ body: hello, my dear friend! --- no_error_log [error] [alert] ---- no_error_log a client request body is buffered to a temporary file +--- skip_eval: 5:$ENV{TEST_NGINX_USE_HTTP3} @@ -1376,8 +1402,8 @@ failed to get req socket: request body already exists --- no_error_log [error] [alert] ---- no_error_log a client request body is buffered to a temporary file +--- skip_eval: 5:$ENV{TEST_NGINX_USE_HTTP3} @@ -1399,6 +1425,7 @@ Expect: 100-Continue [alert] [error] http finalize request: 500, "/test?" a:1, c:0 +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} @@ -1474,13 +1501,22 @@ probe syscall.fcntl { --- stap_out_unlike fcntl\(O_DIRECT\) ---- raw_response_headers_like -.*?X-New: \S+/html/a\.txt\r +--- raw_response_headers_like eval +my $headers; + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $headers = qr#.*?x-new: \S+/html/a\.txt\r#; +} else { + $headers = qr#.*?X-New: \S+/html/a\.txt\r#; +} + +$headers; --- response_body Will you change this world? --- no_error_log [error] [alert] +--- skip_eval: 6:$ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/045-ngx-var.t b/t/045-ngx-var.t index 6475f1e809..6fe5590d99 100644 --- a/t/045-ngx-var.t +++ b/t/045-ngx-var.t @@ -7,7 +7,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); -plan tests => repeat_each() * (blocks() * 2 + 7); +plan tests => repeat_each() * (blocks() * 2 + 9); #no_diff(); #no_long_string(); @@ -170,10 +170,22 @@ invalid referer: 1 } --- request GET /t ---- raw_response_headers_like -Proxy-Host: 127.0.0.1\:\d+\r +--- raw_response_headers_like eval +my $headers; + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $headers = +qr/proxy-host: 127.0.0.1\:\d+\r +proxy-port: \d+\r +proxy-add-x-forwarded-for: 127.0.0.1\r/; +} else { + $headers = +qr/Proxy-Host: 127.0.0.1\:\d+\r Proxy-Port: \d+\r -Proxy-Add-X-Forwarded-For: 127.0.0.1\r +Proxy-Add-X-Forwarded-For: 127.0.0.1\r/; +} + +$headers; --- response_body hello --- no_error_log @@ -228,3 +240,53 @@ GET /test?hello --- error_log variable "query_string" not changeable --- error_code: 500 + + + +=== TEST 12: get a variable in balancer_by_lua_block +--- http_config + upstream balancer { + server 127.0.0.1; + balancer_by_lua_block { + local balancer = require "ngx.balancer" + local host = "127.0.0.1" + local port = ngx.var.port; + local ok, err = balancer.set_current_peer(host, port) + if not ok then + ngx.log(ngx.ERR, "failed to set the current peer: ", err) + return ngx.exit(500) + end + } + } + server { + # this is the real entry point + listen $TEST_NGINX_RAND_PORT_1; + location / { + content_by_lua_block{ + ngx.print("this is backend peer $TEST_NGINX_RAND_PORT_1") + } + } + } + server { + # this is the real entry point + listen $TEST_NGINX_RAND_PORT_2; + location / { + content_by_lua_block{ + ngx.print("this is backend peer $TEST_NGINX_RAND_PORT_2") + } + } + } +--- config + location =/balancer { + set $port ''; + set_by_lua_block $port { + local args, _ = ngx.req.get_uri_args() + local port = args['port'] + return port + } + proxy_pass http://balancer; + } +--- pipelined_requests eval +["GET /balancer?port=\$TEST_NGINX_RAND_PORT_1", "GET /balancer?port=\$TEST_NGINX_RAND_PORT_2"] +--- response_body eval +["this is backend peer \$TEST_NGINX_RAND_PORT_1", "this is backend peer \$TEST_NGINX_RAND_PORT_2"] diff --git a/t/047-match-jit.t b/t/047-match-jit.t index 2417a63ad3..9b40a889dc 100644 --- a/t/047-match-jit.t +++ b/t/047-match-jit.t @@ -20,7 +20,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "([0-9]+)", "j") + local m = ngx.re.match("hello, 1234", "([0-9]+)", "j") if m then ngx.say(m[0]) else @@ -32,8 +32,11 @@ __DATA__ GET /re --- response_body 1234 ---- error_log -pcre JIT compiling result: 1 +--- error_log eval +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully\n" +: +"pcre JIT compiling result: 1\n" @@ -41,7 +44,7 @@ pcre JIT compiling result: 1 --- config location /re { content_by_lua ' - m = ngx.re.match("hello, world", "([0-9]+)", "j") + local m = ngx.re.match("hello, world", "([0-9]+)", "j") if m then ngx.say(m[0]) else @@ -53,8 +56,11 @@ pcre JIT compiling result: 1 GET /re --- response_body not matched! ---- error_log -pcre JIT compiling result: 1 +--- error_log eval +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully\n" +: +"pcre JIT compiling result: 1\n" @@ -62,7 +68,7 @@ pcre JIT compiling result: 1 --- config location /re { content_by_lua ' - m = ngx.re.match("hello, 1234", "([0-9]+)", "jo") + local m = ngx.re.match("hello, 1234", "([0-9]+)", "jo") if m then ngx.say(m[0]) else @@ -76,9 +82,15 @@ pcre JIT compiling result: 1 1234 --- grep_error_log eval -qr/pcre JIT compiling result: \d+/ +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully" +: +"pcre JIT compiling result: 1" --- grep_error_log_out eval +$Test::Nginx::Util::PcreVersion == 2 ? +["pcre2 JIT compiled successfully\n", ""] +: ["pcre JIT compiling result: 1\n", ""] @@ -87,7 +99,7 @@ qr/pcre JIT compiling result: \d+/ --- config location /re { content_by_lua ' - m = ngx.re.match("hello, world", "([0-9]+)", "jo") + local m = ngx.re.match("hello, world", "([0-9]+)", "jo") if m then ngx.say(m[0]) else @@ -101,9 +113,15 @@ qr/pcre JIT compiling result: \d+/ not matched! --- grep_error_log eval -qr/pcre JIT compiling result: \d+/ +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully" +: +"pcre JIT compiling result: 1" --- grep_error_log_out eval +$Test::Nginx::Util::PcreVersion == 2 ? +["pcre2 JIT compiled successfully\n", ""] +: ["pcre JIT compiling result: 1\n", ""] @@ -128,8 +146,11 @@ qr/pcre JIT compiling result: \d+/ } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] @@ -147,7 +168,7 @@ error: pcre_compile() failed: missing ) in "(abc" >>> a.lua local re = [==[(?i:([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:=|<=>|r?like|sounds\s+like|regexp)([\s'\"`´’‘\(\)]*)?\2|([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:!=|<=|>=|<>|<|>|\^|is\s+not|not\s+like|not\s+regexp)([\s'\"`´’‘\(\)]*)?(?!\6)([\d\w]+))]==] -s = string.rep([[ABCDEFG]], 21) +local s = string.rep([[ABCDEFG]], 21) local start = ngx.now() @@ -170,8 +191,15 @@ end --- request GET /re ---- response_body -error: pcre_exec() failed: -8 +--- response_body eval +# lua_regex_match_limit uses pcre_extra->match_limit in the PCRE, +# but PCRE2 replaces this with pcre2_set_match_limit interface, +# which has different effects. +$Test::Nginx::Util::PcreVersion == 2 ? +# PCRE2_ERROR_MATCHLIMIT (-47) +"error: pcre_exec() failed: -47\n" +: +"error: pcre_exec() failed: -8\n" @@ -187,7 +215,7 @@ error: pcre_exec() failed: -8 >>> a.lua local re = [==[(?i:([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:=|<=>|r?like|sounds\s+like|regexp)([\s'\"`´’‘\(\)]*)?\2|([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:!=|<=|>=|<>|<|>|\^|is\s+not|not\s+like|not\s+regexp)([\s'\"`´’‘\(\)]*)?(?!\6)([\d\w]+))]==] -s = string.rep([[ABCDEFG]], 21) +local s = string.rep([[ABCDEFG]], 21) local start = ngx.now() diff --git a/t/048-match-dfa.t b/t/048-match-dfa.t index 28b5a604c3..023231b5c0 100644 --- a/t/048-match-dfa.t +++ b/t/048-match-dfa.t @@ -20,7 +20,7 @@ __DATA__ --- config location /re { content_by_lua ' - m = ngx.re.match("hello", "(he|hell)", "d") + local m = ngx.re.match("hello", "(he|hell)", "d") if m then ngx.say(m[0]) ngx.say(m[1]) @@ -43,7 +43,7 @@ nil --- config location /re { content_by_lua ' - m = ngx.re.match("hello", "(he|hell)", "do") + local m = ngx.re.match("hello", "(he|hell)", "do") if m then ngx.say(m[0]) ngx.say(m[1]) @@ -66,7 +66,7 @@ nil --- config location /re { content_by_lua ' - m = ngx.re.match("hello", "(he|hell)", "jd") + local m = ngx.re.match("hello", "(he|hell)", "jd") if m then ngx.say(m[0]) else @@ -85,7 +85,7 @@ hell --- config location /re { content_by_lua ' - m = ngx.re.match("world", "(he|hell)", "d") + local m = ngx.re.match("world", "(he|hell)", "d") if m then ngx.say(m[0]) else @@ -104,7 +104,7 @@ not matched! --- config location /re { content_by_lua ' - m = ngx.re.match("hello", "he|hell", "do") + local m = ngx.re.match("hello", "he|hell", "do") if m then ngx.say(m[0]) ngx.say(m[1]) @@ -127,7 +127,7 @@ nil --- config location /re { content_by_lua ' - m = ngx.re.match("world", "([0-9]+)", "do") + local m = ngx.re.match("world", "([0-9]+)", "do") if m then ngx.say(m[0]) else @@ -207,3 +207,41 @@ exec opts: 0 你 --- no_error_log [error] + + + +=== TEST 9: matched with do +--- config + location /re { + content_by_lua ' + local m = ngx.re.match("hello", "(h)(e)(l)", "jo") + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + else + ngx.say("not matched!") + end + local m = ngx.re.match("horld", "(h)(e)?(l)?", "jo") + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + else + ngx.say("not matched!") + end + '; + } +--- request + GET /re +--- response_body +hel +h +e +l +h +h +false +false diff --git a/t/049-gmatch-jit.t b/t/049-gmatch-jit.t index 5134d526b5..b76df35e98 100644 --- a/t/049-gmatch-jit.t +++ b/t/049-gmatch-jit.t @@ -34,8 +34,11 @@ __DATA__ --- response_body hello world ---- error_log -pcre JIT compiling result: 1 +--- error_log eval +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully\n" +: +"pcre JIT compiling result: 1\n" @@ -60,8 +63,11 @@ pcre JIT compiling result: 1 nil nil nil ---- error_log -pcre JIT compiling result: 1 +--- error_log eval +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully\n" +: +"pcre JIT compiling result: 1\n" @@ -77,8 +83,11 @@ pcre JIT compiling result: 1 GET /re --- response_body done ---- error_log -pcre JIT compiling result: 1 +--- error_log eval +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully\n" +: +"pcre JIT compiling result: 1\n" @@ -99,8 +108,11 @@ pcre JIT compiling result: 1 GET /re --- response_body hello ---- error_log -pcre JIT compiling result: 1 +--- error_log eval +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully\n" +: +"pcre JIT compiling result: 1\n" @@ -124,9 +136,15 @@ hello world --- grep_error_log eval -qr/pcre JIT compiling result: \d+/ +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully" +: +"pcre JIT compiling result: 1" --- grep_error_log_out eval +$Test::Nginx::Util::PcreVersion == 2 ? +["pcre2 JIT compiled successfully\n", ""] +: ["pcre JIT compiling result: 1\n", ""] @@ -154,9 +172,15 @@ nil nil --- grep_error_log eval -qr/pcre JIT compiling result: \d+/ +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully" +: +"pcre JIT compiling result: 1" --- grep_error_log_out eval +$Test::Nginx::Util::PcreVersion == 2 ? +["pcre2 JIT compiled successfully\n", ""] +: ["pcre JIT compiling result: 1\n", ""] @@ -175,9 +199,15 @@ qr/pcre JIT compiling result: \d+/ done --- grep_error_log eval -qr/pcre JIT compiling result: \d+/ +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully" +: +"pcre JIT compiling result: 1" --- grep_error_log_out eval +$Test::Nginx::Util::PcreVersion == 2 ? +["pcre2 JIT compiled successfully\n", ""] +: ["pcre JIT compiling result: 1\n", ""] @@ -201,9 +231,15 @@ qr/pcre JIT compiling result: \d+/ hello --- grep_error_log eval -qr/pcre JIT compiling result: \d+/ +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully" +: +"pcre JIT compiling result: 1" --- grep_error_log_out eval +$Test::Nginx::Util::PcreVersion == 2 ? +["pcre2 JIT compiled successfully\n", ""] +: ["pcre JIT compiling result: 1\n", ""] @@ -222,7 +258,10 @@ qr/pcre JIT compiling result: \d+/ } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] diff --git a/t/050-gmatch-dfa.t b/t/050-gmatch-dfa.t index d2ac2bc666..4c5f820e49 100644 --- a/t/050-gmatch-dfa.t +++ b/t/050-gmatch-dfa.t @@ -214,8 +214,11 @@ hello } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] diff --git a/t/051-sub-jit.t b/t/051-sub-jit.t index c8a49c0516..7aec7ccb79 100644 --- a/t/051-sub-jit.t +++ b/t/051-sub-jit.t @@ -32,8 +32,11 @@ __DATA__ GET /re --- response_body hello, world 5678: 1 ---- error_log -pcre JIT compiling result: 1 +--- error_log eval +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully\n" +: +"pcre JIT compiling result: 1\n" @@ -53,8 +56,11 @@ pcre JIT compiling result: 1 GET /re --- response_body hello, world: 0 ---- error_log -pcre JIT compiling result: 1 +--- error_log eval +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully\n" +: +"pcre JIT compiling result: 1\n" @@ -76,9 +82,15 @@ pcre JIT compiling result: 1 hello, world 5678: 1 --- grep_error_log eval -qr/pcre JIT compiling result: \d+/ +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully" +: +"pcre JIT compiling result: 1" --- grep_error_log_out eval +$Test::Nginx::Util::PcreVersion == 2 ? +["pcre2 JIT compiled successfully\n", ""] +: ["pcre JIT compiling result: 1\n", ""] @@ -101,9 +113,15 @@ qr/pcre JIT compiling result: \d+/ hello, world: 0 --- grep_error_log eval -qr/pcre JIT compiling result: \d+/ +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully" +: +"pcre JIT compiling result: 1" --- grep_error_log_out eval +$Test::Nginx::Util::PcreVersion == 2 ? +["pcre2 JIT compiled successfully\n", ""] +: ["pcre JIT compiling result: 1\n", ""] @@ -122,8 +140,11 @@ qr/pcre JIT compiling result: \d+/ } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] @@ -143,7 +164,10 @@ error: pcre_compile() failed: missing ) in "(abc" } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] diff --git a/t/052-sub-dfa.t b/t/052-sub-dfa.t index 254913b3e0..2329c77aa3 100644 --- a/t/052-sub-dfa.t +++ b/t/052-sub-dfa.t @@ -107,8 +107,11 @@ hello, world: 0 } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] @@ -129,8 +132,11 @@ error: pcre_compile() failed: missing ) in "(abc" } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] diff --git a/t/053-gsub-jit.t b/t/053-gsub-jit.t index 3efc0a24a5..c7c87ee29c 100644 --- a/t/053-gsub-jit.t +++ b/t/053-gsub-jit.t @@ -32,8 +32,11 @@ __DATA__ GET /re --- response_body hello, world world: 2 ---- error_log -pcre JIT compiling result: 1 +--- error_log eval +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully\n" +: +"pcre JIT compiling result: 1\n" @@ -53,8 +56,11 @@ pcre JIT compiling result: 1 GET /re --- response_body hello, world: 0 ---- error_log -pcre JIT compiling result: 1 +--- error_log eval +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully\n" +: +"pcre JIT compiling result: 1\n" @@ -76,9 +82,15 @@ pcre JIT compiling result: 1 hello, world world: 2 --- grep_error_log eval -qr/pcre JIT compiling result: \d+/ +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully" +: +"pcre JIT compiling result: 1" --- grep_error_log_out eval +$Test::Nginx::Util::PcreVersion == 2 ? +["pcre2 JIT compiled successfully\n", ""] +: ["pcre JIT compiling result: 1\n", ""] @@ -101,9 +113,15 @@ qr/pcre JIT compiling result: \d+/ hello, world: 0 --- grep_error_log eval -qr/pcre JIT compiling result: \d+/ +$Test::Nginx::Util::PcreVersion == 2 ? +"pcre2 JIT compiled successfully" +: +"pcre JIT compiling result: 1" --- grep_error_log_out eval +$Test::Nginx::Util::PcreVersion == 2 ? +["pcre2 JIT compiled successfully\n", ""] +: ["pcre JIT compiling result: 1\n", ""] @@ -122,8 +140,11 @@ qr/pcre JIT compiling result: \d+/ } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] @@ -143,7 +164,10 @@ error: pcre_compile() failed: missing ) in "(abc" } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] diff --git a/t/054-gsub-dfa.t b/t/054-gsub-dfa.t index 937aa1c421..bd0825d4ba 100644 --- a/t/054-gsub-dfa.t +++ b/t/054-gsub-dfa.t @@ -107,8 +107,11 @@ hello, world: 0 } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" @@ -126,8 +129,11 @@ error: pcre_compile() failed: missing ) in "(abc" } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] diff --git a/t/055-subreq-vars.t b/t/055-subreq-vars.t index eb5e24d447..1369992f2b 100644 --- a/t/055-subreq-vars.t +++ b/t/055-subreq-vars.t @@ -28,7 +28,7 @@ __DATA__ location /lua { content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { vars = { dog = "hello", cat = 32 }}); ngx.print(res.body) @@ -82,7 +82,7 @@ qr/variable "(dog|cat)" cannot be assigned a value \(maybe you forgot to define location /lua { set $dog ''; content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { vars = { dog = "hello", cat = 32 }}); ngx.print(res.body) @@ -110,7 +110,7 @@ variable "cat" cannot be assigned a value (maybe you forgot to define it first?) set $dog ''; set $cat ''; content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { vars = { dog = "hello", cat = 32 }}); ngx.print(res.body) @@ -137,7 +137,7 @@ cat = 32 set $dog ''; set $cat ''; content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { vars = "hello" }); ngx.print(res.body) @@ -165,7 +165,7 @@ Bad vars option value set $dog ''; set $cat ''; content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { vars = { cat = true } }); ngx.print(res.body) @@ -188,7 +188,7 @@ attempt to use bad variable value type boolean location /lua { content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { vars = { args = "a=hello&b=32" }}); ngx.print(res.body) @@ -234,7 +234,7 @@ variable "query_string" not changeable location /lua { set $dog 'hello'; content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { copy_all_vars = true }); ngx.print(res.body) @@ -259,7 +259,7 @@ GET /lua location /lua { set $dog 'hello'; content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { share_all_vars = true }); ngx.print(res.body) @@ -284,7 +284,7 @@ GET /lua location /lua { set $dog 'hello'; content_by_lua ' - res = ngx.location.capture("/other", + local res = ngx.location.capture("/other", { vars = { dog = "hiya" }, copy_all_vars = true }); ngx.print(res.body) diff --git a/t/056-flush.t b/t/056-flush.t index 6b697a4233..bb81c1ecf3 100644 --- a/t/056-flush.t +++ b/t/056-flush.t @@ -43,6 +43,7 @@ hiya [error] --- error_log lua reuse free buf chain, but reallocate memory because 5 >= 0 +--- skip_eval: 4:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) @@ -317,7 +318,7 @@ lua http 1.0 buffering makes ngx.flush() a no-op --- config location /test { content_by_lua ' - function f() + local function f() ngx.say("hello, world") ngx.flush(true) coroutine.yield() @@ -491,6 +492,7 @@ GET /test === TEST 17: limit_rate +--- quic_max_idle_timeout: 2 --- config location /test { limit_rate 150; @@ -512,11 +514,19 @@ GET /test --- response_body eval "a" x 200 --- error_log eval -[ +my @errlog; +if (defined $ENV{TEST_NGINX_USE_HTTP2}) { + @errlog = [ +qr/lua writes elapsed (?:0\.[7-9]\d+|[12]\.\d+) sec/, +qr/lua flush requires waiting: buffered 0x[0-9a-f]+, delayed:1/, +]; +} else { + @errlog = [ qr/lua writes elapsed [12](?:\.\d+)? sec/, qr/lua flush requires waiting: buffered 0x[0-9a-f]+, delayed:1/, -] - +]; +} +@errlog; --- no_error_log [error] --- timeout: 4 diff --git a/t/057-flush-timeout.t b/t/057-flush-timeout.t index a04653991e..8f0b7790a0 100644 --- a/t/057-flush-timeout.t +++ b/t/057-flush-timeout.t @@ -19,9 +19,14 @@ BEGIN { $ENV{TEST_NGINX_EVENT_TYPE} = 'poll'; $ENV{MOCKEAGAIN_WRITE_TIMEOUT_PATTERN} = 'hello, world'; $ENV{TEST_NGINX_POSTPONE_OUTPUT} = 1; + delete($ENV{TEST_NGINX_USE_HTTP2}); + + if ($ENV{TEST_NGINX_USE_HTTP3}) { + $SkipReason = "HTTP3 does not support mockeagain"; + } } -use Test::Nginx::Socket::Lua; +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); use t::StapThread; our $GCScript = $t::StapThread::GCScript; @@ -127,7 +132,7 @@ del timer 1234 send_timeout 200ms; location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) diff --git a/t/058-tcp-socket.t b/t/058-tcp-socket.t index f0c2b481cd..45d3073f5e 100644 --- a/t/058-tcp-socket.t +++ b/t/058-tcp-socket.t @@ -1,15 +1,17 @@ # vim:set ft= ts=4 sw=4 et fdm=marker: use Test::Nginx::Socket::Lua; +use Test::Nginx::Socket::Lua::Stream; repeat_each(2); -plan tests => repeat_each() * 196; +plan tests => repeat_each() * (blocks() * 3 + 21); our $HtmlDir = html_dir; $ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); #log_level 'warn'; log_level 'debug'; @@ -21,6 +23,7 @@ run_tests(); __DATA__ === TEST 1: sanity +--- no_http2 --- config server_tokens off; location /t { @@ -90,6 +93,7 @@ close: 1 nil === TEST 2: no trailing newline +--- no_http2 --- config server_tokens off; location /t { @@ -245,18 +249,20 @@ attempt to send data on a closed socket: --- request GET /t ---- response_body +--- response_body_like connected: 1 request sent: 56 -first line received: HTTP/1.1 200 OK -second line received: Server: openresty +first line received: HTTP\/1\.1 200 OK +second line received: (?:Date|Server): .*? --- no_error_log [error] --- timeout: 10 +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} === TEST 5: connection refused (tcp) +--- no_http2 --- config location /test { content_by_lua ' @@ -298,7 +304,7 @@ qr/connect\(\) failed \(\d+: Connection refused\)/ location /test { content_by_lua ' local sock = ngx.socket.tcp() - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) ngx.say("connect: ", ok, " ", err) local bytes @@ -321,12 +327,13 @@ send: nil closed receive: nil closed close: nil closed --- error_log -lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 +lua tcp socket connect timed out, when connecting to 127.0.0.2:12345 --- timeout: 10 === TEST 7: not closed manually +--- no_http2 --- config server_tokens off; location /t { @@ -441,6 +448,7 @@ attempt to send data on a closed socket === TEST 10: explicit *l pattern for receive +--- no_http2 --- config server_tokens off; location /t { @@ -509,6 +517,7 @@ close: 1 nil === TEST 11: *a pattern for receive +--- no_http2 --- config server_tokens off; location /t { @@ -577,6 +586,7 @@ close: 1 nil === TEST 12: mixing *a and *l patterns for receive +--- no_http2 --- config server_tokens off; location /t { @@ -656,6 +666,7 @@ close: 1 nil === TEST 13: receive by chunks +--- no_http2 --- timeout: 5 --- config server_tokens off; @@ -731,6 +742,7 @@ close: 1 nil === TEST 14: receive by chunks (very small buffer) +--- no_http2 --- timeout: 5 --- config server_tokens off; @@ -807,6 +819,7 @@ close: 1 nil === TEST 15: line reading (very small buffer) +--- no_http2 --- config server_tokens off; lua_socket_buffer_size 1; @@ -876,6 +889,7 @@ close: 1 nil === TEST 16: ngx.socket.connect (working) +--- no_http2 --- config server_tokens off; location /t { @@ -914,7 +928,7 @@ close: 1 nil end end - ok, err = sock:close() + local ok, err = sock:close() ngx.say("close: ", ok, " ", err) '; } @@ -961,7 +975,7 @@ close: 1 nil line, err = sock:receive() ngx.say("receive: ", line, " ", err) - ok, err = sock:close() + local ok, err = sock:close() ngx.say("close: ", ok, " ", err) '; } @@ -982,6 +996,7 @@ qr/connect\(\) failed \(\d+: Connection refused\)/ === TEST 18: receive by chunks (stringified size) +--- no_http2 --- config server_tokens off; location /t { @@ -1056,8 +1071,9 @@ close: 1 nil === TEST 19: cannot survive across request boundary (send) +--- no_http2 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location /t { @@ -1115,8 +1131,9 @@ received: OK|failed to send request: closed)\$" === TEST 20: cannot survive across request boundary (receive) +--- no_http2 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location /t { @@ -1190,8 +1207,9 @@ received: OK|failed to receive a line: closed \[nil\])$/ === TEST 21: cannot survive across request boundary (close) +--- no_http2 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location /t { @@ -1259,8 +1277,9 @@ received: OK|failed to close: closed)$/ === TEST 22: cannot survive across request boundary (connect) +--- no_http2 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location /t { @@ -1335,6 +1354,7 @@ lua reuse socket upstream ctx === TEST 23: connect again immediately +--- no_http2 --- config server_tokens off; location /t { @@ -1403,6 +1423,7 @@ close: 1 nil === TEST 24: two sockets mix together +--- no_http2 --- config server_tokens off; location /t { @@ -1493,7 +1514,8 @@ GET /t -=== TEST 25: send tables of string fragments +=== TEST 25: send tables of string fragments (with integers too) +--- no_http2 --- config server_tokens off; location /t { @@ -1564,6 +1586,7 @@ close: 1 nil === TEST 26: send tables of string fragments (bad type "nil") +--- no_http2 --- config server_tokens off; location /t { @@ -1619,10 +1642,13 @@ GET /t --- ignore_response --- error_log bad argument #1 to 'send' (bad data type nil found) +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server# === TEST 27: send tables of string fragments (bad type "boolean") +--- no_http2 --- config server_tokens off; location /t { @@ -1678,10 +1704,13 @@ GET /t --- ignore_response --- error_log bad argument #1 to 'send' (bad data type boolean found) +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server# === TEST 28: send tables of string fragments (bad type ngx.null) +--- no_http2 --- config server_tokens off; location /t { @@ -1737,10 +1766,13 @@ GET /t --- ignore_response --- error_log bad argument #1 to 'send' (bad data type userdata found) +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server# === TEST 29: cosocket before location capture (tcpsock:send did not clear u->waiting) +--- no_http2 --- config server_tokens off; location /t { @@ -1808,6 +1840,7 @@ subrequest: 200, OK\r === TEST 30: CR in a line +--- no_http2 --- config server_tokens off; location /t { @@ -1877,6 +1910,7 @@ close: nil closed === TEST 31: receive(0) +--- no_http2 --- config server_tokens off; location /t { @@ -1935,6 +1969,7 @@ close: 1 nil === TEST 32: send("") +--- no_http2 --- config server_tokens off; location /t { @@ -2176,6 +2211,7 @@ lua tcp socket read timed out === TEST 37: successful reread after a read time out happen (receive -> receive) +--- no_http2 --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -2253,6 +2289,7 @@ lua tcp socket read timed out === TEST 38: successful reread after a read time out happen (receive -> receiveuntil) +--- no_http2 --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -2333,6 +2370,7 @@ lua tcp socket read timed out === TEST 39: successful reread after a read time out happen (receiveuntil -> receiveuntil) +--- no_http2 --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -2415,6 +2453,7 @@ lua tcp socket read timed out === TEST 40: successful reread after a read time out happen (receiveuntil -> receive) +--- no_http2 --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -2495,6 +2534,7 @@ lua tcp socket read timed out === TEST 41: receive(0) +--- no_http2 --- config server_tokens off; location /t { @@ -2542,6 +2582,7 @@ close: 1 nil === TEST 42: empty options table +--- no_http2 --- config server_tokens off; location /t { @@ -2580,6 +2621,7 @@ close: 1 nil === TEST 43: u->coctx left over bug +--- no_http2 --- config server_tokens off; location = /t { @@ -2611,7 +2653,7 @@ close: 1 nil local ready = false local fatal = false - function f() + local function f() local line, err, part = sock:receive() if not line then ngx.say("failed to receive the 1st line: ", err, " [", part, "]") @@ -2671,8 +2713,9 @@ lua clean up the timer for pending ngx.sleep === TEST 44: bad request tries to connect +--- no_http2 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -2725,8 +2768,9 @@ qr/runtime error: content_by_lua\(nginx\.conf:\d+\):7: bad request/ === TEST 45: bad request tries to receive +--- no_http2 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -2782,8 +2826,9 @@ qr/runtime error: content_by_lua\(nginx\.conf:\d+\):14: bad request/ === TEST 46: bad request tries to send +--- no_http2 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -2839,8 +2884,9 @@ qr/runtime error: content_by_lua\(nginx\.conf:\d+\):14: bad request/ === TEST 47: bad request tries to close +--- no_http2 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -2896,8 +2942,9 @@ qr/runtime error: content_by_lua\(nginx\.conf:\d+\):14: bad request/ === TEST 48: bad request tries to set keepalive +--- no_http2 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -2953,8 +3000,9 @@ qr/runtime error: content_by_lua\(nginx\.conf:\d+\):14: bad request/ === TEST 49: bad request tries to receiveuntil +--- no_http2 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -3029,7 +3077,7 @@ qr/runtime error: content_by_lua\(nginx\.conf:\d+\):16: bad request/ --- user_files >>> myfoo.lua local sock = ngx.socket.tcp() -local ok, err = sock:connect("agentzh.org") +local ok, err = sock:connect("agentzh.org", 12345) if not ok then ngx.log(ngx.ERR, "failed to connect: ", err) return @@ -3059,7 +3107,7 @@ runtime error: attempt to yield across C-call boundary end local function err() local sock = ngx.socket.tcp() - local ok, err = sock:connect("agentzh.org") + local ok, err = sock:connect("agentzh.org", 12345) if not ok then ngx.log(ngx.ERR, "failed to connect: ", err) return @@ -3084,6 +3132,7 @@ could not cancel === TEST 52: tcp_nodelay on +--- no_http2 --- config tcp_nodelay on; server_tokens off; @@ -3157,6 +3206,7 @@ lua socket tcp_nodelay === TEST 53: tcp_nodelay off +--- no_http2 --- config tcp_nodelay off; server_tokens off; @@ -3315,7 +3365,7 @@ close: 1 nil local thr = ngx.thread.spawn(function () sock = ngx.socket.tcp() - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -3348,6 +3398,7 @@ lua tcp socket connect timeout: 100 === TEST 56: reuse cleanup +--- no_http2 --- config server_tokens off; location /t { @@ -3412,6 +3463,7 @@ lua http cleanup reuse === TEST 57: reuse cleanup in ngx.timer (fake_request) +--- no_http2 --- config server_tokens off; location /t { @@ -3464,7 +3516,7 @@ lua http cleanup reuse ngx.say("failed to create timer: ", err) end - i = 1 + local i = 1 while not done do local time = 0.005 * i if time > 0.1 then @@ -3495,6 +3547,7 @@ lua http cleanup reuse === TEST 58: free cleanup in ngx.timer (without sock:close) +--- no_http2 --- config server_tokens off; location /t { @@ -3545,7 +3598,7 @@ lua http cleanup reuse ngx.say("failed to create timer: ", err) end - i = 1 + local i = 1 while not done do local time = 0.005 * i if time > 0.1 then @@ -3576,6 +3629,7 @@ total_send_bytes: 114 === TEST 59: reuse cleanup in subrequest +--- no_http2 --- config server_tokens off; location /t { @@ -3643,7 +3697,47 @@ lua http cleanup reuse -=== TEST 60: options_table is nil +=== TEST 60: setkeepalive on socket already shutdown +--- no_http2 +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local sock = ngx.socket.tcp() + local port = ngx.var.port + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local ok, err = sock:close() + if not ok then + ngx.log(ngx.ERR, "failed to close socket: ", err) + return + end + + local ok, err = sock:setkeepalive() + if not ok then + ngx.log(ngx.ERR, "failed to setkeepalive: ", err) + end + } + } +--- request +GET /t +--- response_body +connected: 1 +--- error_log +failed to setkeepalive: closed + + + +=== TEST 61: options_table is nil +--- no_http2 --- config location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -3693,7 +3787,7 @@ close: 1 nil -=== TEST 61: resolver send query failing immediately in connect() +=== TEST 62: resolver send query failing immediately in connect() this case did not clear coctx->cleanup properly and would lead to memory invalid accesses. this test case requires the following iptables rule to work properly: @@ -3720,17 +3814,17 @@ sudo iptables -I OUTPUT 1 -p udp --dport 10086 -j REJECT } --- request GET /t ---- response_body -failed to connect: www.google.com could not be resolved -failed to connect: www.google.com could not be resolved -failed to connect: www.google.com could not be resolved +--- response_body_like +failed to connect: www.google.com could not be resolved(?: \(\d+: Operation timed out\))? +failed to connect: www.google.com could not be resolved(?: \(\d+: Operation timed out\))? +failed to connect: www.google.com could not be resolved(?: \(\d+: Operation timed out\))? hello! --- error_log eval qr{\[alert\] .*? send\(\) failed \(\d+: Operation not permitted\) while resolving} -=== TEST 62: the upper bound of port range should be 2^16 - 1 +=== TEST 63: the upper bound of port range should be 2^16 - 1 --- config location /t { content_by_lua_block { @@ -3746,3 +3840,805 @@ GET /t failed to connect: bad port number: 65536 --- no_error_log [error] + + + +=== TEST 64: send boolean and nil +--- no_http2 +--- config + location /t { + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + local sock = ngx.socket.tcp() + local port = ngx.var.port + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local function send(data) + local bytes, err = sock:send(data) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + end + + local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\nTest: " + send(req) + send(true) + send(false) + send(nil) + send("\r\n\r\n") + + while true do + local line, err, part = sock:receive() + if line then + ngx.say("received: ", line) + else + break + end + end + + ok, err = sock:close() + } + } + + location /foo { + server_tokens off; + more_clear_headers Date; + echo $http_test; + } + +--- request +GET /t +--- response_body +received: HTTP/1.1 200 OK +received: Server: nginx +received: Content-Type: text/plain +received: Connection: close +received: +received: truefalsenil +--- no_error_log +[error] + + + +=== TEST 65: receiveany method in cosocket +--- no_http2 +--- config + server_tokens off; + location = /t { + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(500) + assert(sock:connect("127.0.0.1", ngx.var.port)) + local req = { + 'GET /foo HTTP/1.0\r\n', + 'Host: localhost\r\n', + 'Connection: close\r\n\r\n', + } + local ok, err = sock:send(req) + if not ok then + ngx.say("send request failed: ", err) + return + end + + -- skip http header + while true do + local data, err, _ = sock:receive('*l') + if err then + ngx.say('unexpected error occurs when receiving http head: ', err) + return + end + + if #data == 0 then -- read last line of head + break + end + end + + -- receive http body + while true do + local data, err = sock:receiveany(1024) + if err then + if err ~= 'closed' then + ngx.say('unexpected err: ', err) + end + break + end + ngx.say(data) + end + + sock:close() + } + } + + location = /foo { + content_by_lua_block { + local resp = { + '1', + '22', + 'hello world', + } + + local length = 0 + for _, v in ipairs(resp) do + length = length + #v + end + + -- flush http header + ngx.header['Content-Length'] = length + ngx.flush(true) + ngx.sleep(0.01) + + -- send http body + for _, v in ipairs(resp) do + ngx.print(v) + ngx.flush(true) + ngx.sleep(0.01) + end + } + } + +--- request +GET /t +--- response_body +1 +22 +hello world +--- no_error_log +[error] +--- error_log +lua tcp socket read any +--- skip_eval: 4:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} ne "") + + + +=== TEST 66: receiveany send data after read side closed +--- config + server_tokens off; + location = /t { + set $port $TEST_NGINX_RAND_PORT_1; + + content_by_lua_block { + local sock = ngx.socket.tcp() + local port = ngx.var.port + sock:settimeout(500) + assert(sock:connect("127.0.0.1", port)) + + while true do + local data, err = sock:receiveany(1024) + if err then + if err ~= 'closed' then + ngx.say('unexpected err: ', err) + break + end + + local data = "send data after read side closed" + local bytes, err = sock:send(data) + if not bytes then + ngx.say(err) + end + + break + end + ngx.say(data) + end + + sock:close() + } + } + +--- request +GET /t +--- tcp_listen: $TEST_NGINX_RAND_PORT_1 +--- tcp_shutdown: 1 +--- tcp_query eval: "send data after read side closed" +--- tcp_query_len: 32 +--- response_body +--- no_error_log +[error] + + + +=== TEST 67: receiveany with limited, max <= 0 +--- no_http2 +--- config + location = /t { + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(500) + assert(sock:connect("127.0.0.1", ngx.var.port)) + + local function receiveany_say_err(...) + local ok, err = pcall(sock.receiveany, sock, ...) + if not ok then + ngx.say(err) + end + end + + + receiveany_say_err(0) + receiveany_say_err(-1) + receiveany_say_err() + receiveany_say_err(nil) + } + } + +--- response_body +bad argument #2 to '?' (bad max argument) +bad argument #2 to '?' (bad max argument) +expecting 2 arguments (including the object), but got 1 +bad argument #2 to '?' (bad max argument) +--- request +GET /t +--- no_error_log +[error] + + + +=== TEST 68: receiveany with limited, max is larger than data +--- no_http2 +--- config + server_tokens off; + location = /t { + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(500) + assert(sock:connect("127.0.0.1", ngx.var.port)) + local req = { + 'GET /foo HTTP/1.0\r\n', + 'Host: localhost\r\n', + 'Connection: close\r\n\r\n', + } + local ok, err = sock:send(req) + if not ok then + ngx.say("send request failed: ", err) + return + end + + while true do + local data, err, _ = sock:receive('*l') + if err then + ngx.say('unexpected error occurs when receiving http head: ', err) + return + end + + if #data == 0 then -- read last line of head + break + end + end + + local data, err = sock:receiveany(128) + if err then + if err ~= 'closed' then + ngx.say('unexpected err: ', err) + end + else + ngx.say(data) + end + + sock:close() + } + } + + location = /foo { + content_by_lua_block { + local resp = 'hello world' + local length = #resp + + ngx.header['Content-Length'] = length + ngx.flush(true) + ngx.sleep(0.01) + + ngx.print(resp) + } + } + +--- request +GET /t +--- response_body +hello world +--- no_error_log +[error] +--- error_log +lua tcp socket calling receiveany() method to read at most 128 bytes +--- skip_eval: 4:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} ne "") + + + +=== TEST 69: receiveany with limited, max is smaller than data +--- no_http2 +--- config + server_tokens off; + location = /t { + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(500) + assert(sock:connect("127.0.0.1", ngx.var.port)) + local req = { + 'GET /foo HTTP/1.0\r\n', + 'Host: localhost\r\n', + 'Connection: close\r\n\r\n', + } + local ok, err = sock:send(req) + if not ok then + ngx.say("send request failed: ", err) + return + end + + while true do + local data, err, _ = sock:receive('*l') + if err then + ngx.say('unexpected error occurs when receiving http head: ', err) + return + end + + if #data == 0 then -- read last line of head + break + end + end + + while true do + local data, err = sock:receiveany(7) + if err then + if err ~= 'closed' then + ngx.say('unexpected err: ', err) + end + break + + else + ngx.say(data) + end + end + + sock:close() + } + } + + location = /foo { + content_by_lua_block { + local resp = 'hello world' + local length = #resp + + ngx.header['Content-Length'] = length + ngx.flush(true) + ngx.sleep(0.01) + + ngx.print(resp) + } + } + +--- request +GET /t +--- response_body +hello w +orld +--- no_error_log +[error] +--- error_log +lua tcp socket calling receiveany() method to read at most 7 bytes +--- skip_eval: 4:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} ne "") + + + +=== TEST 70: send tables of string fragments (with floating point number too) +--- no_http2 +--- config + server_tokens off; + location /t { + #set $port 5000; + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + local sock = ngx.socket.tcp() + local port = ngx.var.port + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = {"GET", " ", "/foo", " HTTP/", 1, ".", 0, "\r\n", + "Host: localhost\r\n", "Connection: close\r\n", + "Foo: ", 3.1415926, "\r\n", + "\r\n"} + -- req = "OK" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + ngx.say("request sent: ", bytes) + + while true do + local line, err, part = sock:receive() + if line then + ngx.say("received: ", line) + + else + ngx.say("failed to receive a line: ", err, " [", part, "]") + break + end + end + + ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + } + } + + location /foo { + content_by_lua_block { + ngx.say(ngx.req.get_headers()["Foo"]) + } + more_clear_headers Date; + } +--- request +GET /t +--- response_body +connected: 1 +request sent: 73 +received: HTTP/1.1 200 OK +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 10 +received: Connection: close +received: +received: 3.1415926 +failed to receive a line: closed [] +close: 1 nil +--- no_error_log +[error] + + + +=== TEST 71: send numbers +the maximum number of significant digits is 14 in lua +--- no_http2 +--- config + server_tokens off; + location /t { + #set $port 5000; + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + local sock = ngx.socket.tcp() + local port = ngx.var.port + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = {"GET", " ", "/foo", " HTTP/", 1, ".", 0, "\r\n", + "Host: localhost\r\n", "Connection: close\r\n", + "Foo: "} + -- req = "OK" + + local total_bytes = 0; + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + total_bytes = total_bytes + bytes; + + bytes, err = sock:send(3.14159265357939723846) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + total_bytes = total_bytes + bytes; + + bytes, err = sock:send(31415926) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + total_bytes = total_bytes + bytes; + + bytes, err = sock:send("\r\n\r\n") + if not bytes then + ngx.say("failed to send request: ", err) + return + end + total_bytes = total_bytes + bytes; + + ngx.say("request sent: ", total_bytes) + + while true do + local line, err, part = sock:receive() + if line then + ngx.say("received: ", line) + + else + ngx.say("failed to receive a line: ", err, " [", part, "]") + break + end + end + + ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + } + } + + location /foo { + content_by_lua_block { + ngx.say(ngx.req.get_headers()["Foo"]) + } + more_clear_headers Date; + } +--- request +GET /t +--- response_body +connected: 1 +request sent: 87 +received: HTTP/1.1 200 OK +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 24 +received: Connection: close +received: +received: 3.141592653579431415926 +failed to receive a line: closed [] +close: 1 nil +--- no_error_log +[error] + + + +=== TEST 72: port is not number +--- config + server_tokens off; + location = /t { + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(500) + + local ok, err = sock:connect("127.0.0.1") + if not ok then + ngx.say("connect failed: ", err) + end + + local ok, err = sock:connect("127.0.0.1", nil) + if not ok then + ngx.say("connect failed: ", err) + end + + local ok, err = sock:connect("127.0.0.1", {}) + if not ok then + ngx.say("connect failed: ", err) + end + + ngx.say("finish") + } + } + +--- request +GET /t +--- response_body +connect failed: missing the port number +connect failed: missing the port number +connect failed: missing the port number +finish +--- no_error_log +[error] + + + +=== TEST 73: reset the buffer pos when keepalive +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + for i = 1, 10 + do + local sock = ngx.socket.tcp() + local port = ngx.var.port + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local req = "GET /hi HTTP/1.1\r\nHost: localhost\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + local line, err, part = sock:receive() + if not line then + ngx.say("receive err: ", err) + return + end + + data, err = sock:receiveany(4096) + if not data then + ngx.say("receiveany er: ", err) + return + end + + ok, err = sock:setkeepalive(10000, 32) + if not ok then + ngx.say("reused times: ", i, ", setkeepalive err: ", err) + return + end + end + ngx.say("END") + } + } + + location /hi { + keepalive_requests 3; + content_by_lua_block { + ngx.say("Hello") + } + + more_clear_headers Date; + } + +--- request +GET /t +--- response_body +reused times: 3, setkeepalive err: closed +--- no_error_log +[error] +--- skip_eval: 3: $ENV{TEST_NGINX_EVENT_TYPE} && $ENV{TEST_NGINX_EVENT_TYPE} ne 'epoll' + + + +=== TEST 74: setkeepalive with TLSv1.3 +--- skip_openssl: 3: < 1.1.1 +--- stream_server_config + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.3; + + content_by_lua_block { + local sock = assert(ngx.req.socket(true)) + local data + while true do + data = assert(sock:receive()) + assert(data == "hello") + end + } +--- config + location /test { + lua_ssl_protocols TLSv1.3; + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local ok, err = sock:sslhandshake(false, nil, false) + if not ok then + ngx.say("failed to sslhandshake: ", err) + return + end + + local ok, err = sock:send("hello\n") + if not ok then + ngx.say("failed to send: ", err) + return + end + + -- sleep a while to make sure the NewSessionTicket message has arrived + ngx.sleep(1) + + local ok, err = sock:setkeepalive() + if not ok then + ngx.say("failed to setkeepalive: ", err) + else + ngx.say("setkeepalive: ", ok) + end + } + } +--- request +GET /test +--- response_body +connected: 1 +setkeepalive: 1 +--- no_error_log +[error] + + + +=== TEST 75: getfd() +--- no_http2 +--- http_config + lua_package_path "../lua-resty-core/lib/?.lua;;"; +--- config + server_tokens off; + location /t { + #set $port 5000; + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + local sock = ngx.socket.tcp() + local port = ngx.var.port + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local s, err = sock:getfd() + ngx.say("fd: ", s) + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n" + -- req = "OK" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + ngx.say("request sent: ", bytes) + + while true do + local line, err, part = sock:receive() + if line then + ngx.say("received: ", line) + + else + ngx.say("failed to receive a line: ", err, " [", part, "]") + break + end + end + + ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + } + } + + location /foo { + content_by_lua 'ngx.say("foo")'; + more_clear_headers Date; + } + +--- request +GET /t +--- response_body eval +qr/fd: \d+ +connected: 1 +request sent: 57 +received: HTTP\/1.1 200 OK +received: Server: nginx +received: Content-Type: text\/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +failed to receive a line: closed \[\] +close: 1 nil +/ms +--- no_error_log +[error] diff --git a/t/059-unix-socket.t b/t/059-unix-socket.t index b06ba6e2ee..bd83006baf 100644 --- a/t/059-unix-socket.t +++ b/t/059-unix-socket.t @@ -64,7 +64,7 @@ qr{\[crit\] .*? connect\(\) to unix:/tmp/nosuchfile\.sock failed} --- request GET /test --- response_body -failed to connect: failed to parse host name "/tmp/test-nginx.sock": invalid host +failed to connect: missing the port number @@ -201,3 +201,139 @@ received: received: foo failed to receive a line: closed close: 1 nil + + + +=== TEST 5: port will be ignored +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; + default_type 'text/plain'; + + server_tokens off; + location /foo { + content_by_lua 'ngx.say("foo")'; + more_clear_headers Date; + } + } +--- config + location /test { + content_by_lua ' + local sock = ngx.socket.tcp() + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock", 80) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.0\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n" + -- req = "OK" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + ngx.say("request sent: ", bytes) + + while true do + print("calling receive") + local line, err = sock:receive() + if line then + ngx.say("received: ", line) + + else + ngx.say("failed to receive a line: ", err) + break + end + end + + ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + '; + } +--- request + GET /test +--- response_body +connected: 1 +request sent: 57 +received: HTTP/1.1 200 OK +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +failed to receive a line: closed +close: 1 nil + + + +=== TEST 6: second parameter is nil +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; + default_type 'text/plain'; + + server_tokens off; + location /foo { + content_by_lua 'ngx.say("foo")'; + more_clear_headers Date; + } + } +--- config + location /test { + content_by_lua ' + local sock = ngx.socket.tcp() + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock", nil) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.0\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n" + -- req = "OK" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + ngx.say("request sent: ", bytes) + + while true do + print("calling receive") + local line, err = sock:receive() + if line then + ngx.say("received: ", line) + + else + ngx.say("failed to receive a line: ", err) + break + end + end + + ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + '; + } +--- request + GET /test +--- response_body +connected: 1 +request sent: 57 +received: HTTP/1.1 200 OK +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +failed to receive a line: closed +close: 1 nil diff --git a/t/062-count.t b/t/062-count.t index a69c33edb9..3fda3b9547 100644 --- a/t/062-count.t +++ b/t/062-count.t @@ -34,7 +34,7 @@ __DATA__ --- request GET /test --- response_body -ngx: 116 +ngx: 117 --- no_error_log [error] @@ -55,7 +55,7 @@ ngx: 116 --- request GET /test --- response_body -116 +117 --- no_error_log [error] @@ -83,7 +83,7 @@ GET /test --- request GET /test --- response_body -n = 116 +n = 117 --- no_error_log [error] @@ -123,7 +123,7 @@ n = 1 --- request GET /test --- response_body -n = 24 +n = 23 --- no_error_log [error] @@ -145,7 +145,7 @@ n = 24 --- request GET /test --- response_body -n = 24 +n = 23 --- no_error_log [error] @@ -172,7 +172,7 @@ n = 24 --- request GET /test --- response_body -n = 24 +n = 23 --- no_error_log [error] @@ -259,9 +259,10 @@ n = 10 POST /test hello world --- response_body -n = 5 +n = 7 --- no_error_log [error] +--- skip_eval: 3: $ENV{TEST_NGINX_USE_HTTP3} @@ -283,7 +284,7 @@ n = 5 --- request GET /test --- response_body -n = 18 +n = 22 --- no_error_log [error] @@ -305,7 +306,7 @@ GET /t --- response_body_like: 404 Not Found --- error_code: 404 --- error_log -ngx. entry count: 116 +ngx. entry count: 117 @@ -391,7 +392,7 @@ probe process("$LIBLUA_PATH").function("rehashtab") { --- stap_out2 3 --- response_body -coroutine: 14 +coroutine: 16 --- no_error_log [error] @@ -438,13 +439,34 @@ thread: 3 --- request GET /test --- response_body -worker: 4 +worker: 5 --- no_error_log [error] -=== TEST 20: entries under the metatable of udp sockets +=== TEST 20: entries under the metatable of tcp sockets +--- config + location = /test { + content_by_lua_block { + local n = 0 + local sock = ngx.socket.tcp() + for k, v in pairs(getmetatable(sock)) do + n = n + 1 + end + ngx.say("n = ", n) + } + } +--- request +GET /test +--- response_body +n = 17 +--- no_error_log +[error] + + + +=== TEST 21: entries under the metatable of udp sockets --- config location = /test { content_by_lua ' @@ -459,13 +481,13 @@ worker: 4 --- request GET /test --- response_body -n = 6 +n = 7 --- no_error_log [error] -=== TEST 21: entries under the metatable of req raw sockets +=== TEST 22: entries under the metatable of req raw sockets --- config location = /test { content_by_lua ' @@ -491,6 +513,83 @@ n = 6 --- request GET /test --- response_body -n = 6 +n = 8 +--- no_error_log +[error] +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} + + + +=== TEST 23: entries under the req raw sockets +--- config + location = /test { + content_by_lua_block { + local narr = 0 + local nrec = 0 + ngx.req.read_body() + local sock, err = ngx.req.socket(true) + if not sock then + ngx.log(ngx.ERR, "server: failed to get raw req socket: ", err) + return + end + sock:settimeouts(1000, 2000, 3000) + for k, v in ipairs(sock) do + narr = narr + 1 + end + for k, v in pairs(sock) do + nrec = nrec + 1 + end + -- include '__index' + nrec = nrec - narr + 1 + + local ok, err = sock:send("HTTP/1.1 200 OK\r\n\r\nnarr = "..narr.."\nnrec = "..nrec.."\n") + if not ok then + ngx.log(ngx.ERR, "failed to send: ", err) + return + end + } + } +--- request +GET /test +--- response_body +narr = 2 +nrec = 3 +--- no_error_log +[error] +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} + + + +=== TEST 24: entries under the req sockets +--- config + location = /test { + content_by_lua_block { + local narr = 0 + local nrec = 0 + local sock, err = ngx.req.socket() + if not sock then + ngx.log(ngx.ERR, "server: failed to get req socket: ", err) + return + end + sock:settimeouts(1000, 2000, 3000) + for k, v in ipairs(sock) do + narr = narr + 1 + end + for k, v in pairs(sock) do + nrec = nrec + 1 + end + -- include '__index' + nrec = nrec - narr + 1 + + ngx.say("narr = "..narr.."\nnrec = "..nrec) + } + } +--- request +POST /test +hello world +--- response_body +narr = 2 +nrec = 3 --- no_error_log [error] +--- skip_eval: 3: $ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/063-abort.t b/t/063-abort.t index 411a07eee9..7c90eaafae 100644 --- a/t/063-abort.t +++ b/t/063-abort.t @@ -22,7 +22,7 @@ __DATA__ === TEST 1: ngx.exit(400) should abort print --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /memc_query { internal; @@ -68,7 +68,7 @@ GET /test?a === TEST 2: ngx.exit(400) should abort ngx.log --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /memc_query { internal; @@ -116,7 +116,7 @@ GET /test?a === TEST 3: ngx.exit(400) should abort ngx.location.capture --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /memc_query { internal; @@ -161,7 +161,7 @@ the "$memc_key" variable is not set === TEST 4: ngx.exit(400) should abort ngx.location.capture_multi --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /memc_query { internal; @@ -206,7 +206,7 @@ the "$memc_key" variable is not set === TEST 5: ngx.exit(400) should abort ngx.redirect --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -233,7 +233,7 @@ lua redirect to "/blah" with code 302 === TEST 6: ngx.exit(400) should abort ngx.exit --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -260,7 +260,7 @@ lua exit with code 503 === TEST 7: ngx.exit(400) should abort ngx.exec --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -287,7 +287,7 @@ lua exec "/blah?" === TEST 8: ngx.exit(400) should abort ngx.send_headers --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -314,7 +314,7 @@ lua send headers === TEST 9: ngx.exit(400) should abort ngx.print --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -341,7 +341,7 @@ lua print response === TEST 10: ngx.exit(400) should abort ngx.say --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -368,7 +368,7 @@ lua say response === TEST 11: ngx.exit(400) should abort ngx.flush --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -395,7 +395,7 @@ lua flush asynchronously === TEST 12: ngx.exit(400) should abort ngx.eof --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -422,7 +422,7 @@ lua send eof === TEST 13: ngx.exit(400) should abort ngx.re.match --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -449,7 +449,7 @@ lua compiling match regex "a" with options "jo" === TEST 14: ngx.exit(400) should abort ngx.re.gmatch --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -476,7 +476,7 @@ lua compiling gmatch regex "a" with options "jo" === TEST 15: ngx.exit(400) should abort ngx.re.sub --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -503,7 +503,7 @@ lua compiling sub regex "a" with options "jo" === TEST 16: ngx.exit(400) should abort ngx.re.gsub --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -530,7 +530,7 @@ lua compiling gsub regex "a" with options "jo" === TEST 17: ngx.exit(400) should abort ngx.shared.DICT (set) --- http_config eval - "lua_shared_dict dogs 1m; lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_shared_dict dogs 1m; lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -560,7 +560,7 @@ foo = 56 === TEST 18: ngx.exit(400) should abort ngx.shared.DICT (replace) --- http_config eval - "lua_shared_dict dogs 1m; lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_shared_dict dogs 1m; lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -590,7 +590,7 @@ foo = 56 === TEST 19: ngx.exit(400) should abort ngx.shared.DICT (incr) --- http_config eval - "lua_shared_dict dogs 1m; lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_shared_dict dogs 1m; lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -620,7 +620,7 @@ foo = 88 === TEST 20: ngx.exit(400) should abort ngx.shared.DICT (get) --- http_config eval - "lua_shared_dict dogs 1m; lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_shared_dict dogs 1m; lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -649,7 +649,7 @@ fetching key "foo" in shared dict "dogs" === TEST 21: ngx.exit(400) should skip os.execute --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -679,7 +679,7 @@ GET /test === TEST 22: ngx.exit(400) should break pcall and skip os.execute --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -709,7 +709,7 @@ fetching key "foo" in shared dict "dogs" === TEST 23: ngx.exit(400) should break pcall and skip os.execute (all in user module) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -739,7 +739,7 @@ GET /test === TEST 24: ngx.redirect() should break pcall and skip os.execute (all in user module) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -769,7 +769,7 @@ GET /test === TEST 25: ngx.redirect() should skip os.execute (all in user module) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -799,7 +799,7 @@ GET /test === TEST 26: ngx.exec() should break pcall and skip os.execute (all in user module) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -832,7 +832,7 @@ foo === TEST 27: ngx.exec() should skip os.execute (all in user module) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -865,7 +865,7 @@ foo === TEST 28: ngx.set_uri(uri, true) should break pcall and skip os.execute (all in user module) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { rewrite_by_lua ' @@ -921,7 +921,7 @@ hello world === TEST 30: ngx.exit(400) should break xpcall and skip os.execute (all in user module) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -955,7 +955,7 @@ GET /test === TEST 31: ngx.exec() should skip os.execute (all in user module) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' @@ -988,7 +988,7 @@ foo === TEST 32: ngx.exec() should break pcall and skip os.execute (all in user module) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' diff --git a/t/064-pcall.t b/t/064-pcall.t index 3011f3e46a..9af2de7d92 100644 --- a/t/064-pcall.t +++ b/t/064-pcall.t @@ -22,11 +22,11 @@ __DATA__ === TEST 1: pcall works --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' - function f(a, b) + local function f(a, b) if a == 0 and b == 0 then error("zero error") end @@ -58,11 +58,11 @@ $/s === TEST 2: xpcall works --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /test { content_by_lua ' - function f(a, b) + local function f(a, b) if a == 0 and b == 0 then error("zero error") end @@ -70,15 +70,15 @@ $/s return 23, "hello", true end - function g() + local function g() return f(0, 0) end - function h() + local function h() return f(0) end - function err(...) + local function err(...) ngx.say("error handler called: ", ...) return "this is the new err" end diff --git a/t/065-tcp-socket-timeout.t b/t/065-tcp-socket-timeout.t index 94ade07b0c..14563f70c6 100644 --- a/t/065-tcp-socket-timeout.t +++ b/t/065-tcp-socket-timeout.t @@ -16,6 +16,7 @@ BEGIN { $ENV{MOCKEAGAIN} = 'w'; } + delete($ENV{TEST_NGINX_USE_HTTP2}); $ENV{TEST_NGINX_EVENT_TYPE} = 'poll'; $ENV{MOCKEAGAIN_WRITE_TIMEOUT_PATTERN} = 'get helloworld'; } @@ -51,7 +52,7 @@ __DATA__ location /t { content_by_lua ' local sock = ngx.socket.tcp() - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -66,7 +67,7 @@ GET /t failed to connect: timeout --- error_log lua tcp socket connect timeout: 100 -lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 +lua tcp socket connect timed out, when connecting to 127.0.0.2:12345 --- timeout: 10 @@ -81,7 +82,7 @@ lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 content_by_lua ' local sock = ngx.socket.tcp() sock:settimeout(150) - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -96,7 +97,7 @@ GET /t failed to connect: timeout --- error_log lua tcp socket connect timeout: 150 -lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 +lua tcp socket connect timed out, when connecting to 127.0.0.2:12345 --- timeout: 10 @@ -110,7 +111,7 @@ lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 content_by_lua ' local sock = ngx.socket.tcp() sock:settimeout(nil) - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -125,7 +126,7 @@ GET /t failed to connect: timeout --- error_log lua tcp socket connect timeout: 102 -lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 +lua tcp socket connect timed out, when connecting to 127.0.0.2:12345 @@ -139,7 +140,7 @@ lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 content_by_lua ' local sock = ngx.socket.tcp() sock:settimeout(0) - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -154,7 +155,7 @@ GET /t failed to connect: timeout --- error_log lua tcp socket connect timeout: 102 -lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 +lua tcp socket connect timed out, when connecting to 127.0.0.2:12345 --- timeout: 10 @@ -168,7 +169,7 @@ lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 content_by_lua ' local sock = ngx.socket.tcp() sock:settimeout(-1) - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -584,7 +585,7 @@ bad timeout value --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -683,7 +684,7 @@ after location /t { content_by_lua ' local sock = ngx.socket.tcp() - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("1: failed to connect: ", err) @@ -707,7 +708,7 @@ GET /t 2: connected: 1 --- error_log lua tcp socket connect timeout: 100 -lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 +lua tcp socket connect timed out, when connecting to 127.0.0.2:12345 --- timeout: 10 @@ -873,6 +874,7 @@ quitting request now --- no_error_log lua tcp socket write timed out [alert] +--- skip_eval: 4: $ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/066-socket-receiveuntil.t b/t/066-socket-receiveuntil.t index ffe74aa354..43744a3721 100644 --- a/t/066-socket-receiveuntil.t +++ b/t/066-socket-receiveuntil.t @@ -71,6 +71,7 @@ close: 1 nil === TEST 2: http read lines +--- no_http2 --- config server_tokens off; location /t { @@ -140,6 +141,7 @@ close: 1 nil === TEST 3: http read all the headers in a single run +--- no_http2 --- config server_tokens off; location /t { @@ -208,6 +210,7 @@ close: 1 nil === TEST 4: ambiguous boundary patterns (abcabd) +--- no_http2 --- config server_tokens off; location /t { @@ -245,7 +248,7 @@ close: 1 nil local reader = sock:receiveuntil("abcabd") for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -279,6 +282,7 @@ close: 1 nil === TEST 5: ambiguous boundary patterns (aa) +--- no_http2 --- config server_tokens off; location /t { @@ -316,7 +320,7 @@ close: 1 nil local reader = sock:receiveuntil("aa") for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -350,6 +354,7 @@ close: 1 nil === TEST 6: ambiguous boundary patterns (aaa) +--- no_http2 --- config server_tokens off; location /t { @@ -387,7 +392,7 @@ close: 1 nil local reader = sock:receiveuntil("aaa") for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -421,6 +426,7 @@ close: 1 nil === TEST 7: ambiguous boundary patterns (aaaaad) +--- no_http2 --- config server_tokens off; location /t { @@ -458,7 +464,7 @@ close: 1 nil local reader = sock:receiveuntil("aaaaad") for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -492,6 +498,7 @@ close: 1 nil === TEST 8: ambiguous boundary patterns (aaaaad), small buffer, 2 bytes +--- no_http2 --- config server_tokens off; lua_socket_buffer_size 2; @@ -530,7 +537,7 @@ close: 1 nil local reader = sock:receiveuntil("aaaaad") for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -564,6 +571,7 @@ close: 1 nil === TEST 9: ambiguous boundary patterns (aaaaad), small buffer, 1 byte +--- no_http2 --- config server_tokens off; lua_socket_buffer_size 1; @@ -602,7 +610,7 @@ close: 1 nil local reader = sock:receiveuntil("aaaaad") for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -636,6 +644,7 @@ close: 1 nil === TEST 10: ambiguous boundary patterns (abcabdabcabe) +--- no_http2 --- config server_tokens off; location /t { @@ -673,7 +682,7 @@ close: 1 nil local reader = sock:receiveuntil("abcabdabcabe") for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -707,6 +716,7 @@ close: 1 nil === TEST 11: ambiguous boundary patterns (abcabdabcabe 2) +--- no_http2 --- config server_tokens off; location /t { @@ -744,7 +754,7 @@ close: 1 nil local reader = sock:receiveuntil("abcabdabcabe") for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -778,6 +788,7 @@ close: 1 nil === TEST 12: ambiguous boundary patterns (abcabdabcabe 3) +--- no_http2 --- config server_tokens off; location /t { @@ -815,7 +826,7 @@ close: 1 nil local reader = sock:receiveuntil("abcabdabcabe") for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -849,6 +860,7 @@ close: 1 nil === TEST 13: ambiguous boundary patterns (abcabdabcabe 4) +--- no_http2 --- config server_tokens off; location /t { @@ -886,7 +898,7 @@ close: 1 nil local reader = sock:receiveuntil("abcabdabcabe") for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -920,6 +932,7 @@ close: 1 nil === TEST 14: ambiguous boundary patterns (--abc) +--- no_http2 --- config server_tokens off; location /t { @@ -957,7 +970,7 @@ close: 1 nil local reader = sock:receiveuntil("--abc") for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -991,6 +1004,7 @@ close: 1 nil === TEST 15: ambiguous boundary patterns (--abc) +--- no_http2 --- config server_tokens off; location /t { @@ -1027,8 +1041,8 @@ close: 1 nil local reader = sock:receiveuntil("--abc") - for i = 1, 7 do - line, err, part = reader(4) + for i = 1, 6 do + local line, err, part = reader(4) if line then ngx.say("read: ", line) @@ -1055,7 +1069,6 @@ read: hell read: o, w read: orld read: -- -read: failed to read a line: nil [nil] failed to read a line: closed [ ] @@ -1067,6 +1080,7 @@ close: 1 nil === TEST 16: ambiguous boundary patterns (--abc), small buffer +--- no_http2 --- config server_tokens off; location /t { @@ -1104,8 +1118,8 @@ close: 1 nil local reader = sock:receiveuntil("--abc") - for i = 1, 7 do - line, err, part = reader(4) + for i = 1, 6 do + local line, err, part = reader(4) if line then ngx.say("read: ", line) @@ -1132,7 +1146,6 @@ read: hell read: o, w read: orld read: -- -read: failed to read a line: nil [nil] failed to read a line: closed [ ] @@ -1144,6 +1157,7 @@ close: 1 nil === TEST 17: ambiguous boundary patterns (--abc), small buffer, mixed by other reading calls +--- no_http2 --- config server_tokens off; location /t { @@ -1182,7 +1196,7 @@ close: 1 nil local reader = sock:receiveuntil("--abc") for i = 1, 7 do - line, err, part = reader(4) + local line, err, part = reader(4) if line then ngx.say("read: ", line) @@ -1232,6 +1246,7 @@ close: 1 nil === TEST 18: ambiguous boundary patterns (abcabd), small buffer +--- no_http2 --- config server_tokens off; lua_socket_buffer_size 3; @@ -1270,7 +1285,7 @@ close: 1 nil local reader = sock:receiveuntil("abcabd") for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -1329,3 +1344,671 @@ this exposed a memory leak in receiveuntil ok --- no_error_log [error] +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} + + + +=== TEST 20: add pending bytes +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + lua_socket_buffer_size 1; + + content_by_lua ' + -- collectgarbage("collect") + + local sock = ngx.socket.tcp() + local port = ngx.var.port + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.0\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + ngx.say("request sent: ", bytes) + + local read_headers = sock:receiveuntil("\\r\\n\\r\\n") + local headers, err, part = read_headers() + if not headers then + ngx.say("failed to read headers: ", err, " [", part, "]") + end + + local reader = sock:receiveuntil("--abc") + + for i = 1, 4 do + local line, err, part = reader(2) + if line then + ngx.say("read: ", line) + + else + ngx.say("failed to read a line: ", err, " [", part, "]") + end + end + + ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + '; + } + + location /foo { + echo -- -----abc; + more_clear_headers Date; + } +--- request +GET /t + +--- response_body eval +qq{connected: 1 +request sent: 57 +read: -- +read: - +failed to read a line: nil [nil] +failed to read a line: closed [ +] +close: 1 nil +} +--- no_error_log +[error] + + + +=== TEST 21: ambiguous boundary patterns (--abc), mixed by other reading calls consume boundary +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + -- collectgarbage("collect") + + local sock = ngx.socket.tcp() + local port = ngx.var.port + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + ngx.say("request sent: ", bytes) + + local read_headers = sock:receiveuntil("\r\n\r\n") + local headers, err, part = read_headers() + if not headers then + ngx.say("failed to read headers: ", err, " [", part, "]") + end + + local reader = sock:receiveuntil("--abc") + + for i = 1, 5 do + local line, err, part = reader(2) + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + break + + else + ngx.say("read: ", line) + end + + local data, err, part = sock:receive(1) + if not data then + ngx.say("failed to read a byte: ", err, " [", part, "]") + break + + else + ngx.say("read one byte: ", data) + end + end + + local line, err, part = reader(2) + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + + ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + } + } + + location /foo { + echo -- ----abc----abc-; + more_clear_headers Date; + } +--- request +GET /t + +--- response_body eval +qq{connected: 1 +request sent: 57 +read: -- +read one byte: - +read: -a +read one byte: b +read: c- +read one byte: - +read: +read one byte: - +failed to read a line: nil [nil] +failed to read a line: closed [ +] +close: 1 nil +} +--- no_error_log +[error] + + + +=== TEST 22: ambiguous boundary patterns (--abc), mixed by other reading calls (including receiveuntil) consume boundary +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + -- collectgarbage("collect") + + local sock = ngx.socket.tcp() + local port = ngx.var.port + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + ngx.say("request sent: ", bytes) + + local read_headers = sock:receiveuntil("\r\n\r\n") + local headers, err, part = read_headers() + if not headers then + ngx.say("failed to read headers: ", err, " [", part, "]") + end + + local reader1 = sock:receiveuntil("--abc") + local reader2 = sock:receiveuntil("-ab") + + local line, err, part = reader1(2) + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + + local data, err, part = sock:receive(1) + if not data then + ngx.say("failed to read a byte: ", err, " [", part, "]") + + else + ngx.say("read one byte: ", data) + end + + local line, err, part = reader1(1) + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + + local line, err, part = reader2(2) + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + + local line, err, part = reader1() + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + + local line, err, part = reader1() + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + + ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + } + } + + location /foo { + echo -- ------abd----abc; + more_clear_headers Date; + } +--- request +GET /t + +--- response_body eval +qq{connected: 1 +request sent: 57 +read: -- +read one byte: - +read: - +read: - +read: d-- +failed to read a line: closed [ +] +close: 1 nil +} +--- no_error_log +[error] + + + +=== TEST 23: ambiguous boundary patterns (--abc), mixed by other reading calls consume boundary, small buffer +--- config + lua_socket_buffer_size 3; + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + -- collectgarbage("collect") + + local sock = ngx.socket.tcp() + local port = ngx.var.port + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + ngx.say("request sent: ", bytes) + + local read_headers = sock:receiveuntil("\r\n\r\n") + local headers, err, part = read_headers() + if not headers then + ngx.say("failed to read headers: ", err, " [", part, "]") + end + + local reader = sock:receiveuntil("--abc") + + for i = 1, 5 do + local line, err, part = reader(2) + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + break + + else + ngx.say("read: ", line) + end + + local data, err, part = sock:receive(1) + if not data then + ngx.say("failed to read a byte: ", err, " [", part, "]") + break + + else + ngx.say("read one byte: ", data) + end + end + + local line, err, part = reader(2) + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + + ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + } + } + + location /foo { + echo -- ----abc----abc-; + more_clear_headers Date; + } +--- request +GET /t + +--- response_body eval +qq{connected: 1 +request sent: 57 +read: -- +read one byte: - +read: -a +read one byte: b +read: c- +read one byte: - +read: +read one byte: - +failed to read a line: nil [nil] +failed to read a line: closed [ +] +close: 1 nil +} +--- no_error_log +[error] + + + +=== TEST 24: ambiguous boundary patterns (--abc), mixed by other reading calls (including receiveuntil) consume boundary, small buffer +--- config + lua_socket_buffer_size 3; + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + -- collectgarbage("collect") + + local sock = ngx.socket.tcp() + local port = ngx.var.port + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + ngx.say("request sent: ", bytes) + + local read_headers = sock:receiveuntil("\r\n\r\n") + local headers, err, part = read_headers() + if not headers then + ngx.say("failed to read headers: ", err, " [", part, "]") + end + + local reader1 = sock:receiveuntil("--abc") + local reader2 = sock:receiveuntil("-ab") + + local line, err, part = reader1(2) + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + + local data, err, part = sock:receive(1) + if not data then + ngx.say("failed to read a byte: ", err, " [", part, "]") + + else + ngx.say("read one byte: ", data) + end + + local line, err, part = reader1(1) + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + + local line, err, part = reader2(2) + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + + local line, err, part = reader1() + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + + local line, err, part = reader1() + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + + ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + } + } + + location /foo { + echo -- ------abd----abc; + more_clear_headers Date; + } +--- request +GET /t + +--- response_body eval +qq{connected: 1 +request sent: 57 +read: -- +read one byte: - +read: - +read: - +read: d-- +failed to read a line: closed [ +] +close: 1 nil +} +--- no_error_log +[error] + + + +=== TEST 25: ambiguous boundary patterns (ab1ab2), ends half way +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + -- collectgarbage("collect") + + local sock = ngx.socket.tcp() + local port = ngx.var.port + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + ngx.say("request sent: ", bytes) + + local read_headers = sock:receiveuntil("\r\n\r\n") + local headers, err, part = read_headers() + if not headers then + ngx.say("failed to read headers: ", err, " [", part, "]") + end + + if true then + local reader = sock:receiveuntil("ab1ab2") + + local line, err, part = reader(2) + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + end + + collectgarbage("collect") + + local data, err, part = sock:receive(3) + if not data then + ngx.say("failed to read three bytes: ", err, " [", part, "]") + + else + ngx.say("read three bytes: ", data) + end + + ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + } + } + + location /foo { + echo -- ab1ab1; + more_clear_headers Date; + } +--- request +GET /t + +--- response_body eval +qq{connected: 1 +request sent: 57 +read: ab1 +read three bytes: ab1 +close: 1 nil +} +--- no_error_log +[error] + + + +=== TEST 26: ambiguous boundary patterns (ab1ab2), ends half way, small buffer +--- config + lua_socket_buffer_size 3; + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + -- collectgarbage("collect") + + local sock = ngx.socket.tcp() + local port = ngx.var.port + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + ngx.say("request sent: ", bytes) + + local read_headers = sock:receiveuntil("\r\n\r\n") + local headers, err, part = read_headers() + if not headers then + ngx.say("failed to read headers: ", err, " [", part, "]") + end + + if true then + local reader = sock:receiveuntil("ab1ab2") + + local line, err, part = reader(2) + if not line then + ngx.say("failed to read a line: ", err, " [", part, "]") + + else + ngx.say("read: ", line) + end + end + + collectgarbage("collect") + + local data, err, part = sock:receive(3) + if not data then + ngx.say("failed to read three bytes: ", err, " [", part, "]") + + else + ngx.say("read three bytes: ", data) + end + + ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + } + } + + location /foo { + echo -- ab1ab1; + more_clear_headers Date; + } +--- request +GET /t + +--- response_body eval +qq{connected: 1 +request sent: 57 +read: ab1 +read three bytes: ab1 +close: 1 nil +} +--- no_error_log +[error] +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/067-req-socket.t b/t/067-req-socket.t index 229d5ccf41..ae6bc3f3b6 100644 --- a/t/067-req-socket.t +++ b/t/067-req-socket.t @@ -1,6 +1,16 @@ # vim:set ft= ts=4 sw=4 et fdm=marker: -use Test::Nginx::Socket::Lua; +our $SkipReason; + +BEGIN { + if ($ENV{TEST_NGINX_USE_HTTP3}) { + $SkipReason = "http3 does not support ngx.req.socket"; + } elsif ($ENV{TEST_NGINX_USE_HTTP2}) { + $SkipReason = "http2 does not support ngx.req.socket"; + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); repeat_each(2); @@ -320,7 +330,7 @@ found the end of the stream === TEST 4: attempt to use the req socket across request boundary --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { content_by_lua ' @@ -369,7 +379,7 @@ hello world === TEST 5: receive until on request_body - receiveuntil(1) on the last byte of the body See https://groups.google.com/group/openresty/browse_thread/thread/43cf01da3c681aba for details --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { content_by_lua ' @@ -431,7 +441,7 @@ done === TEST 6: pipelined POST requests --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { content_by_lua ' @@ -519,6 +529,7 @@ Expect: 100-Continue \breceived: hello\b.*?\breceived: worl\b --- no_error_log [error] +--- skip_eval: 3:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) @@ -1096,3 +1107,102 @@ done --- grep_error_log_out lua finalize socket GC cycle done + + + +=== TEST 18: receiveany +--- config + location = /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect("127.0.0.1", ngx.var.server_port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local bytes, err = sock:send("POST /back HTTP/1.0\r\nHost: localhost\r\nContent-Length: 1024\r\n\r\nabc") + if not bytes then + ngx.say("failed to send: ", err) + end + + ngx.sleep(0.2) + + local bytes, err = sock:send("hello world\n") + if not bytes then + ngx.say("failed to send: ", err) + end + + local reader = sock:receiveuntil("\r\n\r\n") + local header, err = reader() + if not header then + ngx.say("failed to receive header: ", err) + return + end + + local line, err = sock:receive() + if not line then + ngx.say("failed to receive line: ", err) + return + end + ngx.say("received: ", line) + } + } + + location = /back { + content_by_lua_block { + ngx.send_headers() + ngx.flush(true) + + local sock, err = ngx.req.socket() + + if not sock then + ngx.say("failed to get socket: ", err) + return nil + end + + local data, err = sock:receiveany(4096) + if not data then + ngx.say("err: ", err) + return nil + end + + ngx.say("received: ", data) + } + } + +--- request +GET /t +--- response_body +received: received: abc +--- no_error_log +[error] +--- skip_eval: 3:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} ne "") + + + +=== TEST 19: getfd +--- http_config + lua_package_path "../lua-resty-core/lib/?.lua;;"; +--- config + location /t { + content_by_lua_block { + local sock, err = ngx.req.socket() + if sock then + ngx.say("got the request socket") + else + ngx.say("failed to get the request socket: ", err) + end + + ngx.say(sock:getfd()) + } + } +--- request +POST /t +hello world +--- response_body eval +qr/\Agot the request socket +\d+ +\z/ms +--- no_error_log +[error] diff --git a/t/068-socket-keepalive.t b/t/068-socket-keepalive.t index f052e9afce..423d391799 100644 --- a/t/068-socket-keepalive.t +++ b/t/068-socket-keepalive.t @@ -4,13 +4,14 @@ use Test::Nginx::Socket::Lua; #repeat_each(2); -plan tests => repeat_each() * (blocks() * 5 + 9); +plan tests => repeat_each() * (blocks() * 4 + 34); our $HtmlDir = html_dir; $ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; $ENV{TEST_NGINX_HTML_DIR} = $HtmlDir; $ENV{TEST_NGINX_REDIS_PORT} ||= 6379; +$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; $ENV{LUA_PATH} ||= '/usr/local/openresty-debug/lualib/?.lua;/usr/local/openresty/lualib/?.lua;;'; @@ -29,7 +30,7 @@ __DATA__ === TEST 1: sanity --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -97,7 +98,7 @@ lua tcp socket keepalive create connection pool for key "127.0.0.1:$ENV{TEST_NGI === TEST 2: free up the whole connection pool if no active connections --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -167,8 +168,9 @@ received: OK === TEST 3: upstream sockets close prematurely +--- no_http3 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; keepalive_timeout 100ms; @@ -242,8 +244,9 @@ done === TEST 4: http keepalive +--- quic_max_idle_timeout: 1.2 --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location /t { @@ -317,6 +320,7 @@ done === TEST 5: lua_socket_keepalive_timeout +--- quic_max_idle_timeout: 1.2 --- config server_tokens off; location /t { @@ -394,6 +398,7 @@ qr/lua tcp socket connection pool size: 30\b/] === TEST 6: lua_socket_pool_size +--- quic_max_idle_timeout: 1.2 --- config server_tokens off; location /t { @@ -472,6 +477,7 @@ qr/lua tcp socket connection pool size: 1\b/] === TEST 7: "lua_socket_keepalive_timeout 0" means unlimited +--- quic_max_idle_timeout: 1.2 --- config server_tokens off; location /t { @@ -547,6 +553,7 @@ qr/lua tcp socket connection pool size: 30\b/] === TEST 8: setkeepalive(timeout) overrides lua_socket_keepalive_timeout +--- quic_max_idle_timeout: 1.2 --- config server_tokens off; location /t { @@ -624,6 +631,7 @@ qr/lua tcp socket connection pool size: 30\b/] === TEST 9: sock:setkeepalive(timeout, size) overrides lua_socket_pool_size +--- quic_max_idle_timeout: 1.2 --- config server_tokens off; location /t { @@ -701,7 +709,36 @@ qr/lua tcp socket connection pool size: 25\b/] -=== TEST 10: sock:keepalive_timeout(0) means unlimited +=== TEST 10: setkeepalive() 'pool_size' should be greater than zero +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + content_by_lua_block { + local sock, err = ngx.socket.connect("127.0.0.1", ngx.var.port) + if not sock then + ngx.say(err) + return + end + + local ok, err = pcall(sock.setkeepalive, sock, 0, 0) + if not ok then + ngx.say(err) + return + end + ngx.say(ok) + } + } +--- request +GET /t +--- response_body +bad argument #3 to '?' (bad "pool_size" option value: 0) +--- no_error_log +[error] + + + +=== TEST 11: sock:keepalive_timeout(0) means unlimited +--- quic_max_idle_timeout: 1.2 --- config server_tokens off; location /t { @@ -776,10 +813,10 @@ qr/lua tcp socket connection pool size: 30\b/] -=== TEST 11: sanity (uds) +=== TEST 12: sanity (uds) --- http_config eval " - lua_package_path '$::HtmlDir/?.lua;./?.lua'; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; server { listen unix:$::HtmlDir/nginx.sock; default_type 'text/plain'; @@ -858,7 +895,7 @@ received response of 119 bytes -=== TEST 12: github issue #108: ngx.locaiton.capture + redis.set_keepalive +=== TEST 13: github issue #108: ngx.location.capture + redis.set_keepalive --- http_config eval qq{ lua_package_path "$::HtmlDir/?.lua;;"; @@ -905,9 +942,9 @@ lua tcp socket get keepalive peer: using connection -=== TEST 13: github issue #110: ngx.exit with HTTP_NOT_FOUND causes worker process to exit +=== TEST 14: github issue #110: ngx.exit with HTTP_NOT_FOUND causes worker process to exit --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config error_page 404 /404.html; location /t { @@ -964,9 +1001,9 @@ Not found, dear... -=== TEST 14: custom pools (different pool for the same host:port) - tcp +=== TEST 15: custom pools (different pool for the same host:port) - tcp --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -1012,9 +1049,9 @@ lua tcp socket keepalive create connection pool for key "B" -=== TEST 15: custom pools (same pool for different host:port) - tcp +=== TEST 16: custom pools (same pool for different host:port) - tcp --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -1059,10 +1096,10 @@ lua tcp socket get keepalive peer: using connection -=== TEST 16: custom pools (different pool for the same host:port) - unix +=== TEST 17: custom pools (different pool for the same host:port) - unix --- http_config eval " - lua_package_path '$::HtmlDir/?.lua;./?.lua'; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; server { listen unix:$::HtmlDir/nginx.sock; default_type 'text/plain'; @@ -1119,10 +1156,10 @@ lua tcp socket keepalive create connection pool for key "B" -=== TEST 17: custom pools (same pool for the same path) - unix +=== TEST 18: custom pools (same pool for the same path) - unix --- http_config eval " - lua_package_path '$::HtmlDir/?.lua;./?.lua'; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; server { listen unix:$::HtmlDir/nginx.sock; default_type 'text/plain'; @@ -1174,9 +1211,9 @@ lua tcp socket get keepalive peer: using connection -=== TEST 18: numeric pool option value +=== TEST 19: numeric pool option value --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -1221,9 +1258,9 @@ lua tcp socket get keepalive peer: using connection -=== TEST 19: nil pool option value +=== TEST 20: nil pool option value --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -1264,9 +1301,9 @@ connected: 1, reused: 0 -=== TEST 20: (bad) table pool option value +=== TEST 21: (bad) table pool option value --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -1305,9 +1342,9 @@ bad argument #3 to 'connect' (bad "pool" option type: table) -=== TEST 21: (bad) boolean pool option value +=== TEST 22: (bad) boolean pool option value --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -1346,7 +1383,8 @@ bad argument #3 to 'connect' (bad "pool" option type: boolean) -=== TEST 22: clear the redis store +=== TEST 23: clear the redis store +--- no_http2 --- config location /t { redis2_query flushall; @@ -1363,9 +1401,9 @@ bad argument #3 to 'connect' (bad "pool" option type: boolean) -=== TEST 23: bug in send(): clear the chain writer ctx +=== TEST 24: bug in send(): clear the chain writer ctx --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location /t { set $port $TEST_NGINX_REDIS_PORT; @@ -1478,7 +1516,8 @@ done -=== TEST 24: setkeepalive() with explicit nil args +=== TEST 25: setkeepalive() with explicit nil args +--- quic_max_idle_timeout: 1.2 --- config server_tokens off; location /t { @@ -1551,3 +1590,1609 @@ done "lua tcp socket keepalive timeout: 100 ms", qr/lua tcp socket connection pool size: 30\b/] --- timeout: 4 + + + +=== TEST 26: conn queuing: connect() verifies the options for connection pool +--- config + location /t { + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + local sock = ngx.socket.tcp() + local function check_opts_for_connect(opts) + local ok, err = pcall(function() + sock:connect("127.0.0.1", ngx.var.port, opts) + end) + if not ok then + ngx.say(err) + else + ngx.say("ok") + end + end + + check_opts_for_connect({pool_size = 'a'}) + check_opts_for_connect({pool_size = 0}) + check_opts_for_connect({backlog = -1}) + check_opts_for_connect({backlog = 0}) + } + } +--- request +GET /t +--- response_body_like +.+ 'connect' \(bad "pool_size" option type: string\) +.+ 'connect' \(bad "pool_size" option value: 0\) +.+ 'connect' \(bad "backlog" option value: -1\) +ok +--- no_error_log +[error] + + + +=== TEST 27: conn queuing: connect() can specify 'pool_size' which overrides setkeepalive() +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local function go() + local sock = ngx.socket.tcp() + local ok, err = sock:connect("127.0.0.1", port, {pool_size = 1}) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok, ", reused: ", sock:getreusedtimes()) + + local req = "flush_all\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + ngx.say("request sent: ", bytes) + + local line, err, part = sock:receive() + if line then + ngx.say("received: ", line) + else + ngx.say("failed to receive a line: ", err, " [", part, "]") + end + + local ok, err = sock:setkeepalive(0, 20) + if not ok then + ngx.say("failed to set reusable: ", err) + end + end + + -- reuse ok + go() + go() + + local sock1 = ngx.socket.connect("127.0.0.1", port) + local sock2 = ngx.socket.connect("127.0.0.1", port) + local ok, err = sock1:setkeepalive(0, 20) + if not ok then + ngx.say(err) + end + local ok, err = sock2:setkeepalive(0, 20) + if not ok then + ngx.say(err) + end + + -- the pool_size is 1 instead of 20 + sock1 = ngx.socket.connect("127.0.0.1", port) + sock2 = ngx.socket.connect("127.0.0.1", port) + ngx.say("reused: ", sock1:getreusedtimes()) + ngx.say("reused: ", sock2:getreusedtimes()) + sock1:setkeepalive(0, 20) + sock2:setkeepalive(0, 20) + } + } +--- request +GET /t +--- response_body +connected: 1, reused: 0 +request sent: 11 +received: OK +connected: 1, reused: 1 +request sent: 11 +received: OK +reused: 1 +reused: 0 +--- no_error_log eval +["[error]", +"lua tcp socket keepalive: free connection pool for ", +"lua tcp socket connection pool size: 20"] +--- error_log eval +[qq{lua tcp socket keepalive create connection pool for key "127.0.0.1:$ENV{TEST_NGINX_MEMCACHED_PORT}"}, +"lua tcp socket connection pool size: 1", +] + + + +=== TEST 28: conn queuing: connect() can specify 'pool_size' for unix domain socket +--- http_config eval +" + server { + listen unix:$::HtmlDir/nginx.sock; + } +" +--- config + location /t { + content_by_lua_block { + local path = "unix:" .. "$TEST_NGINX_HTML_DIR/nginx.sock"; + local function go() + local sock = ngx.socket.tcp() + local ok, err = sock:connect(path, {pool_size = 1}) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok, ", reused: ", sock:getreusedtimes()) + + local ok, err = sock:setkeepalive(0, 20) + if not ok then + ngx.say("failed to set reusable: ", err) + end + end + + go() + go() + + local sock1 = ngx.socket.connect(path) + local sock2 = ngx.socket.connect(path) + local ok, err = sock1:setkeepalive(0, 20) + if not ok then + ngx.say(err) + end + local ok, err = sock2:setkeepalive(0, 20) + if not ok then + ngx.say(err) + end + + -- the pool_size is 1 instead of 20 + sock1 = ngx.socket.connect(path) + sock2 = ngx.socket.connect(path) + ngx.say("reused: ", sock1:getreusedtimes()) + ngx.say("reused: ", sock2:getreusedtimes()) + sock1:setkeepalive(0, 20) + sock2:setkeepalive(0, 20) + } + } +--- request +GET /t +--- response_body +connected: 1, reused: 0 +connected: 1, reused: 1 +reused: 1 +reused: 0 +--- no_error_log eval +["[error]", +"lua tcp socket keepalive: free connection pool for ", +"lua tcp socket connection pool size: 20"] +--- error_log eval +["lua tcp socket get keepalive peer: using connection", +'lua tcp socket keepalive create connection pool for key "unix:', +"lua tcp socket connection pool size: 1", +] + + + +=== TEST 29: conn queuing: connect() can specify 'pool_size' for custom pool +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local function go(pool) + local sock = ngx.socket.tcp() + local ok, err = sock:connect("127.0.0.1", port, {pool = pool, pool_size = 1}) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", pool, ", reused: ", sock:getreusedtimes()) + + local ok, err = sock:setkeepalive(0, 20) + if not ok then + ngx.say("failed to set reusable: ", err) + end + end + + go('A') + go('B') + go('A') + go('B') + + local sock1 = ngx.socket.connect("127.0.0.1", port, {pool = 'A'}) + local sock2 = ngx.socket.connect("127.0.0.1", port, {pool = 'A'}) + local ok, err = sock1:setkeepalive(0, 20) + if not ok then + ngx.say(err) + end + local ok, err = sock2:setkeepalive(0, 20) + if not ok then + ngx.say(err) + end + + -- the pool_size is 1 instead of 20 + sock1 = ngx.socket.connect("127.0.0.1", port, {pool = 'A'}) + sock2 = ngx.socket.connect("127.0.0.1", port, {pool = 'A'}) + ngx.say("reused: ", sock1:getreusedtimes()) + ngx.say("reused: ", sock2:getreusedtimes()) + sock1:setkeepalive(0, 20) + sock2:setkeepalive(0, 20) + } + } +--- request +GET /t +--- response_body +connected: A, reused: 0 +connected: B, reused: 0 +connected: A, reused: 1 +connected: B, reused: 1 +reused: 1 +reused: 0 +--- no_error_log eval +["[error]", +"lua tcp socket keepalive: free connection pool for ", +"lua tcp socket connection pool size: 20"] +--- error_log eval +[qq{lua tcp socket keepalive create connection pool for key "A"}, +qq{lua tcp socket keepalive create connection pool for key "B"}, +"lua tcp socket connection pool size: 1", +] + + + +=== TEST 30: conn queuing: connect() uses lua_socket_pool_size as default if 'backlog' is given +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + lua_socket_pool_size 1234; + + content_by_lua_block { + local port = ngx.var.port + local opts = {backlog = 0} + local sock, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock then + ngx.say(err) + else + ngx.say("ok") + end + } + } +--- request +GET /t +--- response_body +ok +--- error_log +lua tcp socket connection pool size: 1234 +--- no_error_log +[error] + + + +=== TEST 31: conn queuing: more connect operations than 'backlog' size +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool_size = 2, backlog = 0} + local sock = ngx.socket.connect("127.0.0.1", port, opts) + local not_reused_socket, err = ngx.socket.connect("127.0.0.1", port, opts) + if not not_reused_socket then + ngx.say(err) + return + end + -- burst + local ok, err = ngx.socket.connect("127.0.0.1", port, opts) + if not ok then + ngx.say(err) + end + + local ok, err = sock:setkeepalive() + if not ok then + ngx.say(err) + return + end + + ok, err = sock:connect("127.0.0.1", port, opts) + if not ok then + ngx.say(err) + end + ngx.say("reused: ", sock:getreusedtimes()) + -- both queue and pool is full + ok, err = ngx.socket.connect("127.0.0.1", port, opts) + if not ok then + ngx.say(err) + end + } + } +--- request +GET /t +--- response_body +too many waiting connect operations +reused: 1 +too many waiting connect operations +--- no_error_log +[error] + + + +=== TEST 32: conn queuing: once 'pool_size' is reached and pool has 'backlog' +--- quic_max_idle_timeout: 1.2 +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool_size = 2, backlog = 2} + local sock1 = ngx.socket.connect("127.0.0.1", port, opts) + + ngx.timer.at(0, function(premature) + local sock2, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock2 then + ngx.log(ngx.ERR, err) + return + end + + ngx.log(ngx.WARN, "start to handle timer") + ngx.sleep(0.1) + sock2:close() + -- resume connect operation + ngx.log(ngx.WARN, "continue to handle timer") + end) + + ngx.sleep(0.05) + ngx.log(ngx.WARN, "start to handle cosocket") + local sock3, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock3 then + ngx.say(err) + return + end + ngx.log(ngx.WARN, "continue to handle cosocket") + + local req = "flush_all\r\n" + local bytes, err = sock3:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + ngx.say("request sent: ", bytes) + + local line, err, part = sock3:receive() + if line then + ngx.say("received: ", line) + else + ngx.say("failed to receive a line: ", err, " [", part, "]") + end + + local ok, err = sock3:setkeepalive() + if not ok then + ngx.say("failed to set reusable: ", err) + end + ngx.say("setkeepalive: OK") + } + } +--- request +GET /t +--- response_body +request sent: 11 +received: OK +setkeepalive: OK +--- no_error_log +[error] +--- error_log +lua tcp socket queue connect operation for connection pool "127.0.0.1 +--- grep_error_log eval: qr/(start|continue) to handle \w+/ +--- grep_error_log_out +start to handle timer +start to handle cosocket +continue to handle timer +continue to handle cosocket + + + +=== TEST 33: conn queuing: do not count failed connect operations +--- config + resolver $TEST_NGINX_RESOLVER ipv6=off; + resolver_timeout 3s; + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool = "test", pool_size = 1, backlog = 0} + + local sock = ngx.socket.tcp() + sock:settimeouts(100, 3000, 3000) + local ok, err = sock:connect("127.0.0.2", 12345, opts) + if not ok then + ngx.say(err) + end + + local sock, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock then + ngx.say(err) + end + ngx.say("ok") + } + } +--- request +GET /t +--- error_log +lua tcp socket connect timed out, when connecting to +--- response_body +timeout +ok + + + +=== TEST 34: conn queuing: connect until backlog is reached +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool_size = 1, backlog = 1} + local sock1 = ngx.socket.connect("127.0.0.1", port, opts) + + ngx.timer.at(0.01, function(premature) + ngx.log(ngx.WARN, "start to handle timer") + local sock2, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock2 then + ngx.log(ngx.ERR, err) + return + end + + ngx.sleep(0.02) + local ok, err = sock2:close() + if not ok then + ngx.log(ngx.ERR, err) + end + ngx.log(ngx.WARN, "continue to handle timer") + end) + + ngx.sleep(0.02) + local sock3, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock3 then + ngx.say(err) + end + local ok, err = sock1:setkeepalive() + if not ok then + ngx.say(err) + return + end + ngx.sleep(0.01) -- run sock2 + + ngx.log(ngx.WARN, "start to handle cosocket") + local sock3, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock3 then + ngx.say(err) + return + end + ngx.log(ngx.WARN, "continue to handle cosocket") + + local ok, err = sock3:setkeepalive() + if not ok then + ngx.say(err) + end + } + } +--- request +GET /t +--- response_body +too many waiting connect operations +--- no_error_log +[error] +--- error_log +lua tcp socket queue connect operation for connection pool "127.0.0.1 +--- grep_error_log eval: qr/queue connect operation for connection pool|(start|continue) to handle \w+/ +--- grep_error_log_out +start to handle timer +queue connect operation for connection pool +start to handle cosocket +queue connect operation for connection pool +continue to handle timer +continue to handle cosocket + + + +=== TEST 35: conn queuing: memory reuse for host in queueing connect operation ctx +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool = "test", pool_size = 1, backlog = 3} + local sock = ngx.socket.connect("127.0.0.1", port, opts) + + ngx.timer.at(0.01, function(premature) + local sock, err = ngx.socket.connect("0.0.0.0", port, opts) + if not sock then + ngx.log(ngx.ERR, err) + return + end + + local ok, err = sock:close() + if not ok then + ngx.log(ngx.ERR, err) + end + end) + + ngx.timer.at(0.015, function(premature) + local sock, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock then + ngx.log(ngx.ERR, err) + return + end + + local ok, err = sock:close() + if not ok then + ngx.log(ngx.ERR, err) + end + end) + + ngx.timer.at(0.02, function(premature) + local sock, err = ngx.socket.connect("0.0.0.0", port, opts) + if not sock then + ngx.log(ngx.ERR, err) + return + end + + local ok, err = sock:close() + if not ok then + ngx.log(ngx.ERR, err) + end + end) + + ngx.sleep(0.03) + local ok, err = sock:setkeepalive() + if not ok then + ngx.say(err) + return + end + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +ok +--- no_error_log +[error] +--- grep_error_log eval: qr/queue connect operation for connection pool/ +--- grep_error_log_out +queue connect operation for connection pool +queue connect operation for connection pool +queue connect operation for connection pool + + + +=== TEST 36: conn queuing: connect() returns error after connect operation resumed +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool = "test", pool_size = 1, backlog = 1} + local sock = ngx.socket.connect("127.0.0.1", port, opts) + + ngx.timer.at(0, function(premature) + local sock, err = ngx.socket.connect("", port, opts) + if not sock then + ngx.log(ngx.WARN, err) + end + end) + + ngx.sleep(0.01) + -- use 'close' to force parsing host instead of reusing conn + local ok, err = sock:close() + if not ok then + ngx.say(err) + return + end + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +ok +--- no_error_log +[error] +--- error_log +failed to parse host name +--- grep_error_log eval: qr/queue connect operation for connection pool/ +--- grep_error_log_out +queue connect operation for connection pool + + + +=== TEST 37: conn queuing: in uthread +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool_size = 1, backlog = 2} + + local conn_sock = function() + local sock, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock then + ngx.say(err) + return + end + ngx.say("start to handle uthread") + + ngx.sleep(0.01) + sock:close() + ngx.say("continue to handle other uthread") + end + + local sock, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock then + ngx.log(ngx.ERR, err) + return + end + + local co1 = ngx.thread.spawn(conn_sock) + local co2 = ngx.thread.spawn(conn_sock) + local co3 = ngx.thread.spawn(conn_sock) + + local ok, err = sock:setkeepalive() + if not ok then + ngx.log(ngx.ERR, err) + end + + ngx.thread.wait(co1) + ngx.thread.wait(co2) + ngx.thread.wait(co3) + ngx.say("all uthreads ok") + } + } +--- request +GET /t +--- response_body +too many waiting connect operations +start to handle uthread +continue to handle other uthread +start to handle uthread +continue to handle other uthread +all uthreads ok +--- no_error_log +[error] +--- grep_error_log eval: qr/queue connect operation for connection pool/ +--- grep_error_log_out +queue connect operation for connection pool +queue connect operation for connection pool + + + +=== TEST 38: conn queuing: in access_by_lua +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + access_by_lua_block { + local port = ngx.var.port + local opts = {pool_size = 1, backlog = 2} + + local conn_sock = function() + local sock, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock then + ngx.say(err) + return + end + ngx.say("start to handle uthread") + + ngx.sleep(0.01) + sock:close() + ngx.say("continue to handle other uthread") + end + + local sock, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock then + ngx.log(ngx.ERR, err) + return + end + + local co1 = ngx.thread.spawn(conn_sock) + local co2 = ngx.thread.spawn(conn_sock) + local co3 = ngx.thread.spawn(conn_sock) + + local ok, err = sock:setkeepalive() + if not ok then + ngx.log(ngx.ERR, err) + end + + ngx.thread.wait(co1) + ngx.thread.wait(co2) + ngx.thread.wait(co3) + ngx.say("all uthreads ok") + } + } +--- request +GET /t +--- response_body +too many waiting connect operations +start to handle uthread +continue to handle other uthread +start to handle uthread +continue to handle other uthread +all uthreads ok +--- no_error_log +[error] +--- grep_error_log eval: qr/queue connect operation for connection pool/ +--- grep_error_log_out +queue connect operation for connection pool +queue connect operation for connection pool + + + +=== TEST 39: conn queuing: in rewrite_by_lua +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + rewrite_by_lua_block { + local port = ngx.var.port + local opts = {pool_size = 1, backlog = 2} + + local conn_sock = function() + local sock, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock then + ngx.say(err) + return + end + ngx.say("start to handle uthread") + + ngx.sleep(0.01) + sock:close() + ngx.say("continue to handle other uthread") + end + + local sock, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock then + ngx.log(ngx.ERR, err) + return + end + + local co1 = ngx.thread.spawn(conn_sock) + local co2 = ngx.thread.spawn(conn_sock) + local co3 = ngx.thread.spawn(conn_sock) + + local ok, err = sock:setkeepalive() + if not ok then + ngx.log(ngx.ERR, err) + end + + ngx.thread.wait(co1) + ngx.thread.wait(co2) + ngx.thread.wait(co3) + ngx.say("all uthreads ok") + } + } +--- request +GET /t +--- response_body +too many waiting connect operations +start to handle uthread +continue to handle other uthread +start to handle uthread +continue to handle other uthread +all uthreads ok +--- no_error_log +[error] +--- grep_error_log eval: qr/queue connect operation for connection pool/ +--- grep_error_log_out +queue connect operation for connection pool +queue connect operation for connection pool + + + +=== TEST 40: conn queuing: in subrequest +--- config + set $port $TEST_NGINX_MEMCACHED_PORT; + + location /t { + content_by_lua_block { + local port = ngx.var.port + ngx.timer.at(0, function() + local opts = {pool_size = 1, backlog = 2} + local sock, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock then + ngx.log(ngx.ERR, err) + return + end + + ngx.sleep(0.1) + local ok, err = sock:setkeepalive() + if not ok then + ngx.log(ngx.ERR, err) + end + end) + + ngx.sleep(0.01) + local res1, res2, res3 = ngx.location.capture_multi{ + {"/conn"}, {"/conn"}, {"/conn"} + } + ngx.say(res1.body) + ngx.say(res2.body) + ngx.say(res3.body) + } + } + + location /conn { + content_by_lua_block { + local port = ngx.var.port + local sock, err = ngx.socket.connect("127.0.0.1", port) + if not sock then + ngx.print(err) + return + end + local ok, err = sock:setkeepalive() + if not ok then + ngx.print(err) + else + ngx.print("ok") + end + } + } +--- request +GET /t +--- response_body +ok +ok +too many waiting connect operations +--- no_error_log +[error] +--- grep_error_log eval: qr/queue connect operation for connection pool/ +--- grep_error_log_out +queue connect operation for connection pool +queue connect operation for connection pool + + + +=== TEST 41: conn queuing: timeouts when 'connect_timeout' is reached +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool_size = 1, backlog = 1} + local sock1 = ngx.socket.connect("127.0.0.1", port, opts) + + local sock2 = ngx.socket.tcp() + sock2:settimeouts(10, 3000, 3000) + local ok, err = sock2:connect("127.0.0.1", port, opts) + if not ok then + ngx.say(err) + end + } + } +--- request +GET /t +--- response_body +timeout +--- error_log eval +"lua tcp socket queued connect timed out, when trying to connect to 127.0.0.1:$ENV{TEST_NGINX_MEMCACHED_PORT}" + + + +=== TEST 42: conn queuing: set timeout via lua_socket_connect_timeout +--- config + lua_socket_connect_timeout 10ms; + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool_size = 1, backlog = 1} + local sock1 = ngx.socket.connect("127.0.0.1", port, opts) + + local sock2 = ngx.socket.tcp() + local ok, err = sock2:connect("127.0.0.1", port, opts) + if not ok then + ngx.say(err) + end + } + } +--- request +GET /t +--- response_body +timeout +--- error_log eval +"lua tcp socket queued connect timed out, when trying to connect to 127.0.0.1:$ENV{TEST_NGINX_MEMCACHED_PORT}" + + + +=== TEST 43: conn queuing: client aborting while connect operation is queued +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool_size = 1, backlog = 1} + local sock1 = ngx.socket.connect("127.0.0.1", port, opts) + + local sock2 = ngx.socket.tcp() + sock2:settimeouts(3000, 3000, 3000) + local ok, err = sock2:connect("127.0.0.1", port, opts) + if not ok then + ngx.say(err) + end + } + } +--- request +GET /t +--- ignore_response +--- timeout: 0.1 +--- abort +--- no_error_log +[error] +--- curl_error eval +qr/curl: \(28\) Operation timed out after \d+ milliseconds with 0 bytes received/ + + + +=== TEST 44: conn queuing: resume next connect operation if resumed connect failed immediately +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool = "test", pool_size = 1, backlog = 2} + + local conn_sock = function(should_timeout) + local sock = ngx.socket.tcp() + local ok, err + if should_timeout then + ok, err = sock:connect("", port, opts) + else + ok, err = sock:connect("127.0.0.1", port, opts) + end + if not ok then + ngx.say(err) + return + end + ngx.say("connected in uthread") + sock:close() + end + + local sock, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock then + ngx.log(ngx.ERR, err) + return + end + + local co1 = ngx.thread.spawn(conn_sock, true) + local co2 = ngx.thread.spawn(conn_sock) + + local ok, err = sock:close() + if not ok then + ngx.log(ngx.ERR, err) + end + + ngx.thread.wait(co1) + ngx.thread.wait(co2) + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +failed to parse host name "": no host +connected in uthread +ok +--- no_error_log +[error] + + + +=== TEST 45: conn queuing: resume connect operation if resumed connect failed (timeout) +--- config + resolver $TEST_NGINX_RESOLVER ipv6=off; + resolver_timeout 3s; + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool = "test", pool_size = 1, backlog = 1} + + local conn_sock = function(should_timeout) + local sock = ngx.socket.tcp() + local ok, err + if should_timeout then + sock:settimeouts(100, 3000, 3000) + ok, err = sock:connect("127.0.0.2", 12345, opts) + else + ok, err = sock:connect("127.0.0.1", port, opts) + end + if not ok then + ngx.say(err) + return + end + ngx.say("connected in uthread") + sock:close() + end + + local co1 = ngx.thread.spawn(conn_sock, true) + local co2 = ngx.thread.spawn(conn_sock) + + ngx.thread.wait(co1) + ngx.thread.wait(co2) + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +timeout +connected in uthread +ok +--- error_log +queue connect operation for connection pool "test" +lua tcp socket connect timed out, when connecting to + + + +=== TEST 46: conn queuing: resume connect operation if resumed connect failed (could not be resolved) +--- quic_max_idle_timeout: 1.2 +--- config + resolver 127.0.0.2:12345 ipv6=off; + resolver_timeout 1s; + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool = "test", pool_size = 1, backlog = 1} + + local conn_sock = function(should_timeout) + local sock = ngx.socket.tcp() + local ok, err + if should_timeout then + sock:settimeouts(1, 3000, 3000) + ok, err = sock:connect("agentzh.org", 80, opts) + else + ok, err = sock:connect("127.0.0.1", port, opts) + end + if not ok then + ngx.say(err) + return + end + ngx.say("connected in uthread") + sock:close() + end + + local co1 = ngx.thread.spawn(conn_sock, true) + local co2 = ngx.thread.spawn(conn_sock) + + ngx.thread.wait(co1) + ngx.thread.wait(co2) + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +agentzh.org could not be resolved (110: Operation timed out) +connected in uthread +ok +--- error_log +queue connect operation for connection pool "test" + + + +=== TEST 47: conn queuing: resume connect operation if resumed connect failed (connection refused) +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local port = ngx.var.port + local opts = {pool = "test", pool_size = 1, backlog = 1} + + local conn_sock = function(should_timeout) + local sock = ngx.socket.tcp() + local ok, err + if should_timeout then + sock:settimeouts(100, 3000, 3000) + ok, err = sock:connect("127.0.0.1", 62345, opts) + else + ok, err = sock:connect("127.0.0.1", port, opts) + end + if not ok then + ngx.say(err) + return + end + ngx.say("connected in uthread") + sock:close() + end + + local co1 = ngx.thread.spawn(conn_sock, true) + local co2 = ngx.thread.spawn(conn_sock) + + ngx.thread.wait(co1) + ngx.thread.wait(co2) + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +connection refused +connected in uthread +ok +--- error_log +queue connect operation for connection pool "test" + + + +=== TEST 48: conn queuing: resume connect operation if resumed connect failed (uthread aborted while resolving) +--- http_config + lua_package_path '../lua-resty-core/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;'; +--- config + resolver 127.0.0.1 ipv6=off; + resolver_timeout 100s; + set $port $TEST_NGINX_MEMCACHED_PORT; + + location /sub { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + + local function f() + sem:wait(0.1) + ngx.exit(0) + end + + local opts = {pool = "test", pool_size = 1, backlog = 1} + local port = ngx.var.port + ngx.timer.at(0, function() + sem:post() + local sock2, err = ngx.socket.connect("127.0.0.1", port, opts) + package.loaded.for_timer_to_resume:post() + if not sock2 then + ngx.log(ngx.ALERT, "resume connect failed: ", err) + return + end + + ngx.log(ngx.INFO, "resume success") + end) + + ngx.thread.spawn(f) + local sock1, err = ngx.socket.connect("openresty.org", 80, opts) + if not sock1 then + ngx.say(err) + return + end + } + } + + location /t { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local for_timer_to_resume = semaphore.new() + package.loaded.for_timer_to_resume = for_timer_to_resume + + ngx.location.capture("/sub") + for_timer_to_resume:wait(0.1) + } + } +--- request +GET /t +--- no_error_log +[alert] +--- error_log +resume success + + + +=== TEST 49: conn queuing: resume connect operation if resumed connect failed (uthread killed while resolving) +--- config + resolver 127.0.0.1 ipv6=off; + resolver_timeout 100s; + set $port $TEST_NGINX_MEMCACHED_PORT; + + location /t { + content_by_lua_block { + local opts = {pool = "test", pool_size = 1, backlog = 1} + local port = ngx.var.port + + local function resolve() + local sock1, err = ngx.socket.connect("openresty.org", 80, opts) + if not sock1 then + ngx.say(err) + return + end + end + + local th = ngx.thread.spawn(resolve) + local ok, err = ngx.thread.kill(th) + if not ok then + ngx.log(ngx.ALERT, "kill thread failed: ", err) + return + end + + local sock2, err = ngx.socket.connect("127.0.0.1", port, opts) + if not sock2 then + ngx.log(ngx.ALERT, "resume connect failed: ", err) + return + end + + ngx.log(ngx.INFO, "resume success") + } + } +--- request +GET /t +--- no_error_log +[alert] +--- error_log +resume success + + + +=== TEST 50: conn queuing: increase the counter for connections created before creating the pool with setkeepalive() +--- config + set $port $TEST_NGINX_MEMCACHED_PORT; + + location /t { + content_by_lua_block { + local function connect() + local sock, err = ngx.socket.connect("127.0.0.1", ngx.var.port) + if not sock then + error("connect failed: " .. err) + end + + return sock + end + + local sock1 = connect() + local sock2 = connect() + assert(sock1:setkeepalive()) + assert(sock2:setkeepalive()) + + local sock1 = connect() + local sock2 = connect() + assert(sock1:close()) + assert(sock2:close()) + + ngx.say("ok") + } + } +--- request +GET /t +--- no_error_log +[error] +--- response_body +ok + + + +=== TEST 51: conn queuing: only decrease the counter for connections which were counted by the pool +--- config + set $port $TEST_NGINX_MEMCACHED_PORT; + + location /t { + content_by_lua_block { + local function connect() + local sock, err = ngx.socket.connect("127.0.0.1", ngx.var.port) + if not sock then + error("connect failed: " .. err) + end + + return sock + end + + local sock1 = connect() + local sock2 = connect() + assert(sock1:setkeepalive(1000, 1)) + assert(sock2:setkeepalive(1000, 1)) + + local sock1 = connect() + local sock2 = connect() + assert(sock1:close()) + assert(sock2:close()) + + ngx.say("ok") + } + } +--- request +GET /t +--- no_error_log +[error] +--- response_body +ok + + + +=== TEST 52: conn queuing: clean up pending connect operations which are in queue +--- config + set $port $TEST_NGINX_MEMCACHED_PORT; + + location /sub { + content_by_lua_block { + local opts = {pool = "test", pool_size = 1, backlog = 1} + local sock, err = ngx.socket.connect("127.0.0.1", ngx.var.port, opts) + if not sock then + ngx.say("connect failed: " .. err) + return + end + + local function f() + assert(ngx.socket.connect("127.0.0.1", ngx.var.port, opts)) + end + + local th = ngx.thread.spawn(f) + local ok, err = ngx.thread.kill(th) + if not ok then + ngx.log(ngx.ERR, "kill thread failed: ", err) + return + end + + sock:close() + } + } + + location /t { + content_by_lua_block { + ngx.location.capture("/sub") + -- let pending connect operation resumes first + ngx.sleep(0) + ngx.say("ok") + } + } +--- request +GET /t +--- no_error_log +[error] +--- error_log +lua tcp socket abort queueing +--- response_body +ok + + + +=== TEST 53: custom pools in third parameters for unix domain socket +--- http_config eval +" + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; + server { + listen unix:$::HtmlDir/nginx.sock; + default_type 'text/plain'; + + server_tokens off; + location /foo { + echo foo; + more_clear_headers Date; + } + } +" +--- config + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + content_by_lua ' + local test = require "test" + local path = "$TEST_NGINX_HTML_DIR/nginx.sock"; + test.go(path, "A") + test.go(path, "B") + '; + } +--- user_files +>>> test.lua +module("test", package.seeall) + +function go(path, pool) + local sock = ngx.socket.tcp() + local ok, err = sock:connect("unix:" .. path, nil, {pool = pool}) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok, ", reused: ", sock:getreusedtimes()) + + local ok, err = sock:setkeepalive() + if not ok then + ngx.say("failed to set reusable: ", err) + end +end +--- request +GET /t +--- response_body +connected: 1, reused: 0 +connected: 1, reused: 0 +--- no_error_log eval +["[error]", +"lua tcp socket keepalive: free connection pool for ", +"lua tcp socket get keepalive peer: using connection" +] +--- error_log +lua tcp socket keepalive create connection pool for key "A" +lua tcp socket keepalive create connection pool for key "B" + + + +=== TEST 54: wrong first argument for setkeepalive +--- no_http2 +--- quic_max_idle_timeout: 1.2 +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config + server_tokens off; + location /t { + keepalive_timeout 60s; + + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local port = ngx.var.port + + local sock = ngx.socket.tcp() + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.1\r\nHost: localhost\r\nConnection: keepalive\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + ngx.say("request sent: ", bytes) + + local reader = sock:receiveuntil("\r\n0\r\n\r\n") + local data, err = reader() + + if not data then + ngx.say("failed to receive response body: ", err) + return + end + + ngx.say("received response of ", #data, " bytes") + + ok, err = sock:setkeepalive() + if not ok then + ngx.say("failed to set reusable: ", err) + end + + ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ok, err = sock:setkeepalive("not a number", "not a number") + if not ok then + ngx.say("failed to set reusable: ", err) + end + } + } + + location /foo { + echo foo; + } + + location /sleep { + echo_sleep 1; + } +--- request +GET /t +--- error_code: +--- response_body +--- error_log eval +qr/\Qbad argument #1 to 'setkeepalive' (number expected, got string)\E/ +--- no_error_log +[crit] +--- timeout: 4 +--- curl_error eval +qr{HTTP/3 stream 0 reset by server} + + + +=== TEST 55: wrong second argument for setkeepalive +--- no_http2 +--- quic_max_idle_timeout: 1.2 +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config + server_tokens off; + location /t { + keepalive_timeout 60s; + + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local port = ngx.var.port + + local sock = ngx.socket.tcp() + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.1\r\nHost: localhost\r\nConnection: keepalive\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + ngx.say("request sent: ", bytes) + + local reader = sock:receiveuntil("\r\n0\r\n\r\n") + local data, err = reader() + + if not data then + ngx.say("failed to receive response body: ", err) + return + end + + ngx.say("received response of ", #data, " bytes") + + ok, err = sock:setkeepalive() + if not ok then + ngx.say("failed to set reusable: ", err) + end + + ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ok, err = sock:setkeepalive(10, "not a number") + if not ok then + ngx.say("failed to set reusable: ", err) + end + } + } + + location /foo { + echo foo; + } + + location /sleep { + echo_sleep 1; + } +--- request +GET /t +--- error_code: +--- response_body +--- error_log eval +qr/\Qbad argument #2 to 'setkeepalive' (number expected, got string)\E/ +--- no_error_log +[crit] +--- timeout: 4 +--- curl_error eval +qr{HTTP/3 stream 0 reset by server} diff --git a/t/071-idle-socket.t b/t/071-idle-socket.t index c9002be8ea..46ad84f317 100644 --- a/t/071-idle-socket.t +++ b/t/071-idle-socket.t @@ -367,7 +367,7 @@ Transfer-Encoding: chunked\r Connection: close\r \r 6\r -failed to set keepalive: (?:unread data in buffer|connection in dubious state) +failed to set keepalive: (?:unread data in buffer|closed|connection in dubious state) } --- no_error_log [error] @@ -431,3 +431,4 @@ failed to set keepalive: unread data in buffer } --- no_error_log [error] +--- skip_eval: 3:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} ne "") diff --git a/t/072-conditional-get.t b/t/072-conditional-get.t index 7cf2dcd22d..d3400c613a 100644 --- a/t/072-conditional-get.t +++ b/t/072-conditional-get.t @@ -88,3 +88,4 @@ delete thread 1 say failed: nginx output filter error --- no_error_log [error] +--- skip_eval: 5:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) diff --git a/t/073-backtrace.t b/t/073-backtrace.t index 6c5469218d..663c3afece 100644 --- a/t/073-backtrace.t +++ b/t/073-backtrace.t @@ -21,10 +21,10 @@ __DATA__ --- config location /lua { content_by_lua - ' function bar() + ' local function bar() return lua_concat(3) end - function foo() + local function foo() bar() end foo() @@ -37,7 +37,7 @@ GET /lua attempt to call global 'lua_concat' : in function 'bar' :5: in function 'foo' -:7: in function +:7: in main chunk @@ -45,10 +45,10 @@ attempt to call global 'lua_concat' --- config location /lua { content_by_lua - ' function bar() + ' local function bar() error(nil) end - function foo() + local function foo() bar() end foo() @@ -64,7 +64,7 @@ GET /lua " in function 'error'", ": in function 'bar'", ":5: in function 'foo'", -qr/:7: in function /, +qr/:7: in main chunk/, ] @@ -125,7 +125,7 @@ probe process("$LIBLUA_PATH").function("lua_concat") { :63: in function 'func16' :67: in function 'func17' :71: in function 'func18' -:74: in function +:74: in main chunk diff --git a/t/075-logby.t b/t/075-logby.t index 520c62e314..3e9743584a 100644 --- a/t/075-logby.t +++ b/t/075-logby.t @@ -220,7 +220,7 @@ failed to run log_by_lua*: unknown reason -=== TEST 11: globals get cleared for every single request +=== TEST 11: globals sharing --- config location /lua { echo ok; @@ -228,6 +228,7 @@ failed to run log_by_lua*: unknown reason if not foo then foo = 1 else + ngx.log(ngx.INFO, "old foo: ", foo) foo = foo + 1 end ngx.log(ngx.WARN, "foo = ", foo) @@ -237,8 +238,9 @@ failed to run log_by_lua*: unknown reason GET /lua --- response_body ok ---- error_log -foo = 1 +--- grep_error_log eval: qr/old foo: \d+/ +--- grep_error_log_out eval +["", "old foo: 1\n"] @@ -442,6 +444,7 @@ GET /lua ok --- error_log API disabled in the context of log_by_lua* +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} @@ -455,8 +458,16 @@ API disabled in the context of log_by_lua* GET /lua --- response_body ok ---- error_log -API disabled in the context of log_by_lua* +--- error_log eval +my $err_log; + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $err_log = "http v3 not supported yet"; +} else { + $err_log = "API disabled in the context of log_by_lua*"; +} + +$err_log; @@ -495,7 +506,8 @@ API disabled in the context of log_by_lua* location /t { echo ok; log_by_lua ' - function foo() + local bar + local function foo() bar() end diff --git a/t/077-sleep.t b/t/077-sleep.t index 96f04bd98e..4867b9ca7f 100644 --- a/t/077-sleep.t +++ b/t/077-sleep.t @@ -105,6 +105,7 @@ bad argument #1 to 'sleep' === TEST 5: sleep 0.33 - multi-times in content +--- quic_max_idle_timeout: 1.1 --- config location /test { content_by_lua ' @@ -129,6 +130,7 @@ lua sleep timer expired: "/test?" === TEST 6: sleep 0.5 - interleaved by ngx.say() - ended by ngx.sleep +--- quic_max_idle_timeout: 2.2 --- config location /test { content_by_lua ' @@ -156,6 +158,7 @@ lua sleep timer expired: "/test?" === TEST 7: sleep 0.5 - interleaved by ngx.say() - not ended by ngx.sleep +--- quic_max_idle_timeout: 0.9 --- config location /test { content_by_lua ' diff --git a/t/081-bytecode.t b/t/081-bytecode.t index cb50e945d3..92383bcb92 100644 --- a/t/081-bytecode.t +++ b/t/081-bytecode.t @@ -24,7 +24,7 @@ __DATA__ content_by_lua ' ngx.req.read_body(); local b = ngx.req.get_body_data(); - f = io.open(ngx.var.realpath_root.."/test.lua", "w"); + local f = io.open(ngx.var.realpath_root.."/test.lua", "w"); -- luajit bytecode: sub(149,-1), lua bytecode: sub(1,147) if jit then if not string.find(jit.version, "LuaJIT 2.0") then @@ -35,7 +35,8 @@ __DATA__ else f:write(string.sub(b, 1, 147)); end - f:close(); res = ngx.location.capture("/call"); + f:close(); + local res = ngx.location.capture("/call"); ngx.print(res.body) '; } @@ -60,14 +61,15 @@ __DATA__ content_by_lua ' ngx.req.read_body(); local b = ngx.req.get_body_data(); - f = io.open(ngx.var.realpath_root.."/test.lua", "w"); + local f = io.open(ngx.var.realpath_root.."/test.lua", "w"); -- luajit bytecode: sub(149,-1), lua bytecode: sub(1,147) if not package.loaded["jit"] then f:write(string.sub(b, 149)); else f:write(string.sub(b, 1, 147)); end - f:close(); res = ngx.location.capture("/call"); + f:close(); + local res = ngx.location.capture("/call"); if res.status == 200 then ngx.print(res.body) else @@ -85,7 +87,7 @@ __DATA__ --- response_body error --- error_log eval -qr/failed to load external Lua file ".*?test\.lua": bad byte-code header/ +qr/failed to load external Lua file ".*?test\.lua": .* cannot load incompatible bytecode/ @@ -96,14 +98,15 @@ qr/failed to load external Lua file ".*?test\.lua": bad byte-code header/ content_by_lua ' ngx.req.read_body(); local b = ngx.req.get_body_data(); - f = io.open(ngx.var.realpath_root.."/test.lua", "w"); + local f = io.open(ngx.var.realpath_root.."/test.lua", "w"); -- luajit bytecode: sub(149,-1), lua bytecode: sub(1,147) if package.loaded["jit"] then f:write(string.sub(b, 149)); else f:write(string.sub(b, 1, 147)); end - f:close(); res = ngx.location.capture("/call"); + f:close(); + local res = ngx.location.capture("/call"); if res.status == 200 then ngx.print(res.body) else @@ -121,7 +124,7 @@ qr/failed to load external Lua file ".*?test\.lua": bad byte-code header/ --- response_body error --- error_log -bytecode format version unsupported +cannot load incompatible bytecode @@ -132,7 +135,7 @@ bytecode format version unsupported content_by_lua ' ngx.req.read_body(); local b = ngx.req.get_body_data(); - f = io.open(ngx.var.realpath_root.."/test.lua", "w"); + local f = io.open(ngx.var.realpath_root.."/test.lua", "w"); -- luajit bytecode: sub(149,-1), lua bytecode: sub(1,147) local do_jit if jit then @@ -176,7 +179,7 @@ bytecode format version unsupported content_by_lua ' ngx.req.read_body(); local b = ngx.req.get_body_data(); - f = io.open(ngx.var.realpath_root.."/test.lua", "w"); + local f = io.open(ngx.var.realpath_root.."/test.lua", "w"); -- luajit bytecode: sub(149,-1), lua bytecode: sub(1,147) local jit; if package.loaded["jit"] then @@ -220,7 +223,7 @@ error content_by_lua ' ngx.req.read_body(); local b = ngx.req.get_body_data(); - f = io.open(ngx.var.realpath_root.."/test.lua", "w"); + local f = io.open(ngx.var.realpath_root.."/test.lua", "w"); -- luajit bytecode: sub(149,-1), lua bytecode: sub(1,147) if jit then if not string.find(jit.version, "LuaJIT 2.0") then @@ -256,9 +259,9 @@ error content_by_lua ' local bcsave = require "jit.bcsave" if jit then - local prefix = ngx.config.prefix() - local infile = prefix .. "html/a.lua" - local outfile = prefix .. "html/a.luac" + local prefix = "$TEST_NGINX_SERVER_ROOT" + local infile = prefix .. "/html/a.lua" + local outfile = prefix .. "/html/a.luac" bcsave.start("-s", infile, outfile) return ngx.exec("/call") end @@ -289,9 +292,9 @@ ngx.status = 201 ngx.say("hello from Lua!") content_by_lua ' local bcsave = require "jit.bcsave" if jit then - local prefix = ngx.config.prefix() - local infile = prefix .. "html/a.lua" - local outfile = prefix .. "html/a.luac" + local prefix = "$TEST_NGINX_SERVER_ROOT" + local infile = prefix .. "/html/a.lua" + local outfile = prefix .. "/html/a.luac" bcsave.start("-g", infile, outfile) return ngx.exec("/call") end @@ -320,9 +323,9 @@ ngx.status = 201 ngx.say("hello from Lua!") --- config location = /t { content_by_lua_block { - local f = assert(loadstring("a = a and a + 1 or 1 ngx.say('a = ', a)", "=code")) + local f = assert(loadstring("local a = 1 ngx.say('a = ', a)", "=code")) local bc = string.dump(f) - local f = assert(io.open("t/servroot/html/a.luac", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/a.luac", "w")) f:write(bc) f:close() } @@ -349,9 +352,9 @@ a = 1 --- config location = /t { content_by_lua_block { - local f = assert(loadstring("a = a and a + 1 or 1 ngx.say('a = ', a)", "=code")) + local f = assert(loadstring("local a = 1 ngx.say('a = ', a)", "=code")) local bc = string.dump(f, true) - local f = assert(io.open("t/servroot/html/a.luac", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/a.luac", "w")) f:write(bc) f:close() } diff --git a/t/082-body-filter-2.t b/t/082-body-filter-2.t new file mode 100644 index 0000000000..3c9b9797d5 --- /dev/null +++ b/t/082-body-filter-2.t @@ -0,0 +1,271 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +our $SkipReason; + +BEGIN { + if ($ENV{TEST_NGINX_EVENT_TYPE} && $ENV{TEST_NGINX_EVENT_TYPE} ne 'poll') { + $SkipReason = "unavailable for the event type '$ENV{TEST_NGINX_EVENT_TYPE}'"; + + } elsif ($ENV{TEST_NGINX_USE_HTTP3}) { + $SkipReason = "http3 does not support mockeagain"; + + } elsif ($ENV{TEST_NGINX_USE_HTTP2}) { + $SkipReason = "http2 does not support mockeagain"; + + } else { + if ($ENV{LD_PRELOAD} && $ENV{LD_PRELOAD} =~ /\bmockeagain\.so\b/) { + $ENV{TEST_NGINX_POSTPONE_OUTPUT} = 1; + $ENV{TEST_NGINX_EVENT_TYPE} = 'poll'; + $ENV{MOCKEAGAIN}='w' + } else { + $SkipReason = "'mockeagain.so' does not appear to be preloaded " + . "with 'LD_PRELOAD'"; + } + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +log_level('debug'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +#no_diff(); +no_long_string(); + +run_tests(); + +__DATA__ + +=== TEST 1: check ctx->busy_bufs +--- config + location /t { + postpone_output 1; + content_by_lua_block { + for i = 1, 5 do + ngx.say(i, ": Hello World!") + end + } + + body_filter_by_lua_block { + ngx.arg[1] = ngx.arg[1] + } + } +--- request +GET /t +--- response_body +1: Hello World! +2: Hello World! +3: Hello World! +4: Hello World! +5: Hello World! + +--- error_log +waiting body filter busy buffer to be sent +lua say response has busy bufs +--- no_error_log +[error] + + + +=== TEST 2: arg[1] not change and say long string +--- config + location /t { + header_filter_by_lua_block {ngx.header.content_length = nil} + body_filter_by_lua_block { + local function anyting_not_change_arg1() + return + end + anyting_not_change_arg1() + } + content_by_lua_block { + for i = 1, 100 do + ngx.say("12345678901234567890") + end + } + } +--- request +GET /t +--- response_body eval +("12345678901234567890\n" x 100) +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 3: arg[1] not change and chunked_transfer_encoding off +--- config + location /t { + header_filter_by_lua_block {ngx.header.content_length = nil} + body_filter_by_lua_block { + local function anyting_not_change_arg1() + return + end + anyting_not_change_arg1() + } + chunked_transfer_encoding off; + content_by_lua_block { + for i = 1, 100 do + ngx.say("12345678901234567890123456789012345678901234567890".."_"..tostring(i/3)) + end + } + } +--- request +GET /t +--- response_body +12345678901234567890123456789012345678901234567890_0.33333333333333 +12345678901234567890123456789012345678901234567890_0.66666666666667 +12345678901234567890123456789012345678901234567890_1 +12345678901234567890123456789012345678901234567890_1.3333333333333 +12345678901234567890123456789012345678901234567890_1.6666666666667 +12345678901234567890123456789012345678901234567890_2 +12345678901234567890123456789012345678901234567890_2.3333333333333 +12345678901234567890123456789012345678901234567890_2.6666666666667 +12345678901234567890123456789012345678901234567890_3 +12345678901234567890123456789012345678901234567890_3.3333333333333 +12345678901234567890123456789012345678901234567890_3.6666666666667 +12345678901234567890123456789012345678901234567890_4 +12345678901234567890123456789012345678901234567890_4.3333333333333 +12345678901234567890123456789012345678901234567890_4.6666666666667 +12345678901234567890123456789012345678901234567890_5 +12345678901234567890123456789012345678901234567890_5.3333333333333 +12345678901234567890123456789012345678901234567890_5.6666666666667 +12345678901234567890123456789012345678901234567890_6 +12345678901234567890123456789012345678901234567890_6.3333333333333 +12345678901234567890123456789012345678901234567890_6.6666666666667 +12345678901234567890123456789012345678901234567890_7 +12345678901234567890123456789012345678901234567890_7.3333333333333 +12345678901234567890123456789012345678901234567890_7.6666666666667 +12345678901234567890123456789012345678901234567890_8 +12345678901234567890123456789012345678901234567890_8.3333333333333 +12345678901234567890123456789012345678901234567890_8.6666666666667 +12345678901234567890123456789012345678901234567890_9 +12345678901234567890123456789012345678901234567890_9.3333333333333 +12345678901234567890123456789012345678901234567890_9.6666666666667 +12345678901234567890123456789012345678901234567890_10 +12345678901234567890123456789012345678901234567890_10.333333333333 +12345678901234567890123456789012345678901234567890_10.666666666667 +12345678901234567890123456789012345678901234567890_11 +12345678901234567890123456789012345678901234567890_11.333333333333 +12345678901234567890123456789012345678901234567890_11.666666666667 +12345678901234567890123456789012345678901234567890_12 +12345678901234567890123456789012345678901234567890_12.333333333333 +12345678901234567890123456789012345678901234567890_12.666666666667 +12345678901234567890123456789012345678901234567890_13 +12345678901234567890123456789012345678901234567890_13.333333333333 +12345678901234567890123456789012345678901234567890_13.666666666667 +12345678901234567890123456789012345678901234567890_14 +12345678901234567890123456789012345678901234567890_14.333333333333 +12345678901234567890123456789012345678901234567890_14.666666666667 +12345678901234567890123456789012345678901234567890_15 +12345678901234567890123456789012345678901234567890_15.333333333333 +12345678901234567890123456789012345678901234567890_15.666666666667 +12345678901234567890123456789012345678901234567890_16 +12345678901234567890123456789012345678901234567890_16.333333333333 +12345678901234567890123456789012345678901234567890_16.666666666667 +12345678901234567890123456789012345678901234567890_17 +12345678901234567890123456789012345678901234567890_17.333333333333 +12345678901234567890123456789012345678901234567890_17.666666666667 +12345678901234567890123456789012345678901234567890_18 +12345678901234567890123456789012345678901234567890_18.333333333333 +12345678901234567890123456789012345678901234567890_18.666666666667 +12345678901234567890123456789012345678901234567890_19 +12345678901234567890123456789012345678901234567890_19.333333333333 +12345678901234567890123456789012345678901234567890_19.666666666667 +12345678901234567890123456789012345678901234567890_20 +12345678901234567890123456789012345678901234567890_20.333333333333 +12345678901234567890123456789012345678901234567890_20.666666666667 +12345678901234567890123456789012345678901234567890_21 +12345678901234567890123456789012345678901234567890_21.333333333333 +12345678901234567890123456789012345678901234567890_21.666666666667 +12345678901234567890123456789012345678901234567890_22 +12345678901234567890123456789012345678901234567890_22.333333333333 +12345678901234567890123456789012345678901234567890_22.666666666667 +12345678901234567890123456789012345678901234567890_23 +12345678901234567890123456789012345678901234567890_23.333333333333 +12345678901234567890123456789012345678901234567890_23.666666666667 +12345678901234567890123456789012345678901234567890_24 +12345678901234567890123456789012345678901234567890_24.333333333333 +12345678901234567890123456789012345678901234567890_24.666666666667 +12345678901234567890123456789012345678901234567890_25 +12345678901234567890123456789012345678901234567890_25.333333333333 +12345678901234567890123456789012345678901234567890_25.666666666667 +12345678901234567890123456789012345678901234567890_26 +12345678901234567890123456789012345678901234567890_26.333333333333 +12345678901234567890123456789012345678901234567890_26.666666666667 +12345678901234567890123456789012345678901234567890_27 +12345678901234567890123456789012345678901234567890_27.333333333333 +12345678901234567890123456789012345678901234567890_27.666666666667 +12345678901234567890123456789012345678901234567890_28 +12345678901234567890123456789012345678901234567890_28.333333333333 +12345678901234567890123456789012345678901234567890_28.666666666667 +12345678901234567890123456789012345678901234567890_29 +12345678901234567890123456789012345678901234567890_29.333333333333 +12345678901234567890123456789012345678901234567890_29.666666666667 +12345678901234567890123456789012345678901234567890_30 +12345678901234567890123456789012345678901234567890_30.333333333333 +12345678901234567890123456789012345678901234567890_30.666666666667 +12345678901234567890123456789012345678901234567890_31 +12345678901234567890123456789012345678901234567890_31.333333333333 +12345678901234567890123456789012345678901234567890_31.666666666667 +12345678901234567890123456789012345678901234567890_32 +12345678901234567890123456789012345678901234567890_32.333333333333 +12345678901234567890123456789012345678901234567890_32.666666666667 +12345678901234567890123456789012345678901234567890_33 +12345678901234567890123456789012345678901234567890_33.333333333333 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 4: set resp body nil with ngx.arg[1] first +--- config + location /t { + content_by_lua_block { + ngx.say("Hello World!") + } + + body_filter_by_lua_block { + ngx.arg[1] = "" + ngx.arg[2] = true + } + } +--- request +GET /t +--- response_body +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 5: set resp body nil with ngx.arg[2] first +--- config + location /t { + content_by_lua_block { + ngx.say("Hello World!") + } + + body_filter_by_lua_block { + ngx.arg[2] = true + ngx.arg[1] = "" + } + } +--- request +GET /t +--- response_body +--- no_error_log +[error] +[alert] +[crit] diff --git a/t/082-body-filter.t b/t/082-body-filter.t index 98efa84847..4033bac8ce 100644 --- a/t/082-body-filter.t +++ b/t/082-body-filter.t @@ -317,7 +317,7 @@ hiya globe GET /t --- ignore_response --- error_log -failed to run body_filter_by_lua*: body_filter_by_lua:4: something bad happened! +failed to run body_filter_by_lua*: body_filter_by_lua(nginx.conf:49):4: something bad happened! @@ -460,7 +460,8 @@ GET /t --- config location /t { body_filter_by_lua ' - function foo() + local bar + local function foo() bar() end @@ -481,6 +482,8 @@ stack traceback: in function 'error' in function 'bar' in function 'foo' +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP\/3 stream 0 reset by server# @@ -523,6 +526,8 @@ GET /lua?a=1&b=2 --- ignore_response --- error_log eval qr/failed to load external Lua file ".*?test2\.lua": cannot open .*? No such file or directory/ +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP\/3 stream 0 reset by server# @@ -837,3 +842,61 @@ GET /lua --- ignore_response --- error_log API disabled in the context of body_filter_by_lua* +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP\/3 stream 0 reset by server# + + + +=== TEST 27: syntax error in body_filter_by_lua_block +--- config + location /lua { + + body_filter_by_lua_block { + 'for end'; + } + content_by_lua_block { + ngx.say("Hello world") + } + } +--- request +GET /lua +--- ignore_response +--- error_log +failed to load inlined Lua code: body_filter_by_lua(nginx.conf:41):2: unexpected symbol near ''for end'' +--- no_error_log +no_such_error1 +no_such_error2 +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP\/3 stream 0 reset by server# + + + +=== TEST 28: syntax error in second body_by_lua_block +--- config + location /foo { + body_filter_by_lua_block { + 'for end'; + } + content_by_lua_block { + ngx.say("Hello world") + } + } + + location /lua { + body_filter_by_lua_block { + 'for end'; + } + content_by_lua_block { + ngx.say("Hello world") + } + } +--- request +GET /lua +--- ignore_response +--- error_log +failed to load inlined Lua code: body_filter_by_lua(nginx.conf:49):2: unexpected symbol near ''for end'' +--- no_error_log +no_such_error1 +no_such_error2 +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP\/3 stream 0 reset by server# diff --git a/t/083-bad-sock-self.t b/t/083-bad-sock-self.t index 7a76752d9f..206de8b990 100644 --- a/t/083-bad-sock-self.t +++ b/t/083-bad-sock-self.t @@ -33,6 +33,7 @@ __DATA__ --- error_code: 500 --- error_log bad argument #1 to 'receive' (table expected, got string) +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} @@ -51,6 +52,7 @@ bad argument #1 to 'receive' (table expected, got string) --- error_code: 500 --- error_log bad argument #1 to 'receiveuntil' (table expected, got number) +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/084-inclusive-receiveuntil.t b/t/084-inclusive-receiveuntil.t index f7d5d3d189..60f8363f92 100644 --- a/t/084-inclusive-receiveuntil.t +++ b/t/084-inclusive-receiveuntil.t @@ -19,6 +19,7 @@ run_tests(); __DATA__ === TEST 1: ambiguous boundary patterns (abcabd) - inclusive mode +--- no_http2 --- config server_tokens off; location /t { @@ -56,7 +57,7 @@ __DATA__ local reader = sock:receiveuntil("abcabd", { inclusive = true }) for i = 1, 3 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -91,6 +92,7 @@ close: 1 nil === TEST 2: ambiguous boundary patterns (abcabdabcabe 4) - inclusive mode +--- no_http2 --- config server_tokens off; location /t { @@ -128,7 +130,7 @@ close: 1 nil local reader = sock:receiveuntil("abcabdabcabe", { inclusive = true }) for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -162,6 +164,7 @@ close: 1 nil === TEST 3: ambiguous boundary patterns (abcabd) - inclusive mode - small buffers +--- no_http2 --- config server_tokens off; lua_socket_buffer_size 1; @@ -200,7 +203,7 @@ close: 1 nil local reader = sock:receiveuntil("abcabd", { inclusive = true }) for i = 1, 3 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -235,6 +238,7 @@ close: 1 nil === TEST 4: inclusive option value nil +--- no_http2 --- config server_tokens off; location /t { @@ -272,7 +276,7 @@ close: 1 nil local reader = sock:receiveuntil("aa", { inclusive = nil }) for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -306,6 +310,7 @@ close: 1 nil === TEST 5: inclusive option value false +--- no_http2 --- config server_tokens off; location /t { @@ -343,7 +348,7 @@ close: 1 nil local reader = sock:receiveuntil("aa", { inclusive = false }) for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -377,6 +382,7 @@ close: 1 nil === TEST 6: inclusive option value true (aa) +--- no_http2 --- config server_tokens off; location /t { @@ -414,7 +420,7 @@ close: 1 nil local reader = sock:receiveuntil("aa", { inclusive = true }) for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -448,6 +454,7 @@ close: 1 nil === TEST 7: bad inclusive option value type +--- no_http2 --- config server_tokens off; location /t { @@ -485,7 +492,7 @@ close: 1 nil local reader = sock:receiveuntil("aa", { inclusive = "true" }) for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -511,10 +518,13 @@ bad "inclusive" option value type: string --- no_error_log [alert] [warn] +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP\/3 stream 0 reset by server# === TEST 8: bad option table +--- no_http2 --- config server_tokens off; location /t { @@ -552,7 +562,7 @@ bad "inclusive" option value type: string local reader = sock:receiveuntil("aa", { inclusive = "true" }) for i = 1, 2 do - line, err, part = reader() + local line, err, part = reader() if line then ngx.say("read: ", line) @@ -578,10 +588,13 @@ bad "inclusive" option value type: string --- no_error_log [alert] [warn] +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP\/3 stream 0 reset by server# === TEST 9: ambiguous boundary patterns (--abc), small buffer +--- no_http2 --- config server_tokens off; location /t { @@ -619,8 +632,8 @@ bad "inclusive" option value type: string local reader = sock:receiveuntil("--abc", { inclusive = true }) - for i = 1, 7 do - line, err, part = reader(4) + for i = 1, 6 do + local line, err, part = reader(4) if line then ngx.say("read: ", line) @@ -646,8 +659,7 @@ request sent: 57 read: hell read: o, w read: orld -read: -- -read: --abc +read: ----abc failed to read a line: nil [nil] failed to read a line: closed [ ] @@ -659,6 +671,7 @@ close: 1 nil === TEST 10: ambiguous boundary patterns (--abc), small buffer, mixed by other reading calls +--- no_http2 --- config server_tokens off; location /t { @@ -697,7 +710,7 @@ close: 1 nil local reader = sock:receiveuntil("--abc", { inclusive = true }) for i = 1, 7 do - line, err, part = reader(4) + local line, err, part = reader(4) if line then ngx.say("read: ", line) diff --git a/t/086-init-by.t b/t/086-init-by.t index bea34a461a..04f8160eb1 100644 --- a/t/086-init-by.t +++ b/t/086-init-by.t @@ -321,3 +321,47 @@ INIT 2: foo = 3 failed to init --- error_log [error] + + + +=== TEST 13: syntax error in init_by_lua_block +--- http_config + init_by_lua_block { + ngx.log(ngx.debug, "pass") + error("failed to init" + ngx.log(ngx.debug, "unreachable") + } +--- config + location /lua { + content_by_lua_block { + ngx.say("hello world") + } + } +--- must_die +--- error_log +init_by_lua error: init_by_lua(nginx.conf:25):4: ')' expected (to close '(' at line 3) near 'ngx' +--- no_error_log +no_such_error_log + + + +=== TEST 14: syntax error in init_by_lua_file +--- http_config + init_by_lua_file html/init.lua; +--- config + location /lua { + content_by_lua_block { + ngx.say("hello world") + } + } +--- user_files +>>> init.lua + ngx.log(ngx.debug, "pass") + error("failed to init" + ngx.log(ngx.debug, "unreachable") + +--- must_die +--- error_log eval +qr|init_by_lua_file error: .*?/t/servroot\w*?/html/init.lua:3: '\)' expected \(to close '\(' at line 2\) near 'ngx'| +--- no_error_log +no_such_error_log diff --git a/t/087-udp-socket.t b/t/087-udp-socket.t index ab41622eee..d1bc654365 100644 --- a/t/087-udp-socket.t +++ b/t/087-udp-socket.t @@ -4,7 +4,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); -plan tests => repeat_each() * (3 * blocks() + 13); +plan tests => repeat_each() * (3 * blocks() + 16); our $HtmlDir = html_dir; @@ -69,7 +69,7 @@ GET /t [error] --- log_level: debug --- error_log -lua udp socket receive buffer size: 8192 +lua udp socket receive buffer size: 65536 @@ -143,12 +143,15 @@ GET /t === TEST 3: access a TCP interface +test-nginx use the same port for tcp(http) and udp(http3) +so need to change to a port that is not listen by any app. +default port range: +net.ipv4.ip_local_port_range = 32768 60999 +choose a port greater than 61000 should be less race. --- config server_tokens off; location /t { - #set $port 5000; - set $port $TEST_NGINX_SERVER_PORT; - #set $port 1234; + set $port 65432; content_by_lua ' local socket = ngx.socket @@ -555,7 +558,7 @@ lua udp socket read timed out local udp = socket.udp() - udp:settimeout(2000) -- 2 sec + udp:settimeout(5000) -- 5 sec local ok, err = udp:setpeername("$TEST_NGINX_RESOLVER", 53) if not ok then @@ -595,7 +598,7 @@ received a good response. [error] --- log_level: debug --- error_log -lua udp socket receive buffer size: 8192 +lua udp socket receive buffer size: 65536 --- no_check_leak @@ -662,7 +665,7 @@ received a good response. [error] --- log_level: debug --- error_log -lua udp socket receive buffer size: 8192 +lua udp socket receive buffer size: 65536 --- no_check_leak @@ -824,7 +827,7 @@ probe syscall.socket.return, syscall.connect.return { === TEST 15: bad request tries to setpeer --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -881,7 +884,7 @@ qr/runtime error: content_by_lua\(nginx\.conf:\d+\):14: bad request/ === TEST 16: bad request tries to send --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -938,7 +941,7 @@ qr/runtime error: content_by_lua\(nginx\.conf:\d+\):14: bad request/ === TEST 17: bad request tries to receive --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -995,7 +998,7 @@ qr/runtime error: content_by_lua\(nginx\.conf:\d+\):14: bad request/ === TEST 18: bad request tries to close --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config server_tokens off; location = /main { @@ -1019,7 +1022,7 @@ qr/runtime error: content_by_lua\(nginx\.conf:\d+\):14: bad request/ return end local sock = test.get_sock() - sock:send("a") + sock:close() '; } --- user_files @@ -1050,77 +1053,282 @@ qr/runtime error: content_by_lua\(nginx\.conf:\d+\):14: bad request/ -=== TEST 19: bad request tries to receive ---- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" +=== TEST 19: the upper bound of port range should be 2^16 - 1 --- config - server_tokens off; - location = /main { - echo_location /t?reset=1; - echo_location /t; + location /t { + content_by_lua_block { + local sock = ngx.socket.udp() + local ok, err = sock:setpeername("127.0.0.1", 65536) + if not ok then + ngx.say("failed to connect: ", err) + end + } } +--- request +GET /t +--- response_body +failed to connect: bad port number: 65536 +--- no_error_log +[error] + + + +=== TEST 20: send boolean and nil +--- config + server_tokens off; location /t { - #set $port 5000; set $port $TEST_NGINX_MEMCACHED_PORT; - content_by_lua ' - local test = require "test" - if ngx.var.arg_reset then - local sock = test.new_sock() - local ok, err = sock:setpeername("127.0.0.1", ngx.var.port) - if not ok then - ngx.say("failed to set peer: ", err) - else - ngx.say("peer set") + content_by_lua_block { + local socket = ngx.socket + local udp = socket.udp() + local port = ngx.var.port + udp:settimeout(1000) -- 1 sec + + local ok, err = udp:setpeername("127.0.0.1", ngx.var.port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local function send(data) + local bytes, err = udp:send(data) + if not bytes then + ngx.say("failed to send: ", err) + return end + ngx.say("sent ok") + end + + send(true) + send(false) + send(nil) + } + } +--- request +GET /t +--- response_body +sent ok +sent ok +sent ok +--- no_error_log +[error] +--- grep_error_log eval +qr/send: fd:\d+ \d+ of \d+/ +--- grep_error_log_out eval +qr/send: fd:\d+ 4 of 4 +send: fd:\d+ 5 of 5 +send: fd:\d+ 3 of 3/ +--- log_level: debug + + + +=== TEST 21: send numbers +Note: maximum number of digits after the decimal-point character is 13 +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local socket = ngx.socket + local udp = socket.udp() + local port = ngx.var.port + udp:settimeout(1000) -- 1 sec + + local ok, err = udp:setpeername("127.0.0.1", ngx.var.port) + if not ok then + ngx.say("failed to connect: ", err) return end - local sock = test.get_sock() - sock:close() - '; + + local function send(data) + local bytes, err = udp:send(data) + if not bytes then + ngx.say("failed to send: ", err) + return + end + ngx.say("sent ok") + end + + send(123456) + send(3.141926) + send(3.141592653579397238) + } } ---- user_files ->>> test.lua -module("test", package.seeall) +--- request +GET /t +--- response_body +sent ok +sent ok +sent ok +--- no_error_log +[error] +--- grep_error_log eval +qr/send: fd:\d+ \d+ of \d+/ +--- grep_error_log_out eval +qr/send: fd:\d+ 6 of 6 +send: fd:\d+ 8 of 8 +send: fd:\d+ 15 of 15/ +--- log_level: debug -local sock -function new_sock() - sock = ngx.socket.udp() - return sock -end -function get_sock() - return sock -end +=== TEST 22: send tables of string fragments (with numbers too) +the maximum number of significant digits is 14 in lua +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + local socket = ngx.socket + local udp = socket.udp() + local port = ngx.var.port + udp:settimeout(1000) -- 1 sec + + local ok, err = udp:setpeername("127.0.0.1", ngx.var.port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local function send(data) + local bytes, err = udp:send(data) + if not bytes then + ngx.say("failed to send: ", err) + return + end + ngx.say("sent ok") + end + + send({"integer: ", 1234567890123}) + send({"float: ", 3.1419265}) + send({"float: ", 3.141592653579397238}) + } + } --- request -GET /main ---- response_body_like eval -qr/^peer set - repeat_each() * (blocks() * 2 + 1); +plan tests => repeat_each() * (blocks() * 2 + 2) + 2; #no_diff(); #no_long_string(); @@ -176,3 +176,48 @@ GET /lua init_worker --- no_error_log [error] + + + +=== TEST 11: get_phase in exit_worker_by_lua +--- http_config + exit_worker_by_lua_block { + local phase = ngx.get_phase() + ngx.log(ngx.ERR, phase) + ngx.log(ngx.ERR, "exiting now") + } +--- config + location /lua { + content_by_lua_block { + ngx.say("ok") + } + } +--- request +GET /lua +--- response_body +ok +--- shutdown_error_log eval +[ +qr/exit_worker_by_lua\(nginx\.conf:\d+\):\d+: exit_worker/, +qr/exiting now$/, +] + + + +=== TEST 12: server_rewrite_by_lua_block in http +--- http_config + server_rewrite_by_lua_block { + ngx.ctx.phase = ngx.get_phase() + } +--- config + location /lua { + content_by_lua_block { + ngx.say(ngx.ctx.phase) + } + } +--- request +GET /lua +--- response_body +server_rewrite +--- no_error_log +[error] diff --git a/t/090-log-socket-errors.t b/t/090-log-socket-errors.t index 8215ec386f..f5a9f80b78 100644 --- a/t/090-log-socket-errors.t +++ b/t/090-log-socket-errors.t @@ -28,7 +28,7 @@ __DATA__ lua_socket_log_errors off; content_by_lua ' local sock = ngx.socket.tcp() - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) ngx.say(err) '; } @@ -50,7 +50,7 @@ timeout lua_socket_log_errors on; content_by_lua ' local sock = ngx.socket.tcp() - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) ngx.say(err) '; } @@ -59,7 +59,7 @@ GET /t --- response_body timeout --- error_log -lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 +lua tcp socket connect timed out, when connecting to 127.0.0.2:12345 @@ -72,7 +72,7 @@ lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 lua_socket_read_timeout 1ms; content_by_lua ' local sock = ngx.socket.udp() - local ok, err = sock:setpeername("agentzh.org", 12345) + local ok, err = sock:setpeername("127.0.0.2", 12345) ok, err = sock:receive() ngx.say(err) '; @@ -95,7 +95,7 @@ lua udp socket read timed out lua_socket_read_timeout 1ms; content_by_lua ' local sock = ngx.socket.udp() - local ok, err = sock:setpeername("agentzh.org", 12345) + local ok, err = sock:setpeername("127.0.0.2", 12345) ok, err = sock:receive() ngx.say(err) '; diff --git a/t/091-coroutine.t b/t/091-coroutine.t index bceb512460..16930ed3c0 100644 --- a/t/091-coroutine.t +++ b/t/091-coroutine.t @@ -4,7 +4,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); -plan tests => repeat_each() * (blocks() * 3 + 5); +plan tests => repeat_each() * (blocks() * 3 + 14); $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; @@ -85,7 +85,7 @@ __DATA__ content_by_lua ' local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield - function f() + local function f() local cnt = 0 for i = 1, 20 do ngx.say("Hello, ", cnt) @@ -120,7 +120,7 @@ Hello, 2 --- config location /lua { content_by_lua ' - function f(fid) + local function f(fid) local cnt = 0 while true do ngx.say("cc", fid, ": ", cnt) @@ -159,11 +159,13 @@ cc3: 2 === TEST 3: basic coroutine and cosocket +access the public network is unstable, need a bigger timeout +--- quic_max_idle_timeout: 4 --- config resolver $TEST_NGINX_RESOLVER ipv6=off; location /lua { content_by_lua ' - function worker(url) + local function worker(url) local sock = ngx.socket.tcp() local ok, err = sock:connect(url, 80) coroutine.yield() @@ -178,7 +180,7 @@ cc3: 2 local urls = { "agentzh.org", - "iscribblet.org", + "openresty.com", "openresty.org" } @@ -204,7 +206,7 @@ cc3: 2 GET /lua --- response_body successfully connected to: agentzh.org -successfully connected to: iscribblet.org +successfully connected to: openresty.com successfully connected to: openresty.org *** All Done *** --- no_error_log @@ -218,14 +220,14 @@ successfully connected to: openresty.org location /lua { content_by_lua ' -- generate all the numbers from 2 to n - function gen (n) + local function gen (n) return coroutine.wrap(function () for i=2,n do coroutine.yield(i) end end) end -- filter the numbers generated by g, removing multiples of p - function filter (p, g) + local function filter (p, g) return coroutine.wrap(function () while 1 do local n = g() @@ -235,8 +237,8 @@ successfully connected to: openresty.org end) end - N = 10 - x = gen(N) -- generate primes up to N + local N = 10 + local x = gen(N) -- generate primes up to N while 1 do local n = x() -- pick a number until done if n == nil then break end @@ -264,14 +266,14 @@ GET /lua coroutine.create = nil coroutine.resume = nil -- generate all the numbers from 2 to n - function gen (n) + local function gen (n) return coroutine.wrap(function () for i=2,n do coroutine.yield(i) end end) end -- filter the numbers generated by g, removing multiples of p - function filter (p, g) + local function filter (p, g) return coroutine.wrap(function () while 1 do local n = g() @@ -281,8 +283,8 @@ GET /lua end) end - N = 10 - x = gen(N) -- generate primes up to N + local N = 10 + local x = gen(N) -- generate primes up to N while 1 do local n = x() -- pick a number until done if n == nil then break end @@ -307,7 +309,7 @@ GET /lua --- config location /lua { content_by_lua ' - function generatefib (n) + local function generatefib (n) return coroutine.wrap(function () local a,b = 1, 1 while a <= n do @@ -362,7 +364,7 @@ GET /lua resolver $TEST_NGINX_RESOLVER ipv6=off; location /lua { content_by_lua ' - function worker(url) + local function worker(url) local sock = ngx.socket.tcp() local ok, err = sock:connect(url, 80) coroutine.yield() @@ -377,7 +379,7 @@ GET /lua local urls = { "agentzh.org", - "iscribblet.org", + "openresty.com", "openresty.org" } @@ -398,7 +400,7 @@ GET /lua GET /lua --- response_body successfully connected to: agentzh.org -successfully connected to: iscribblet.org +successfully connected to: openresty.com successfully connected to: openresty.org *** All Done *** --- no_error_log @@ -414,7 +416,7 @@ successfully connected to: openresty.org local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield local st, rn = coroutine.status, coroutine.running - function f(self) + local function f(self) local cnt = 0 if rn() ~= self then ngx.say("error"); return end ngx.say("running: ", st(self)) --running @@ -467,21 +469,25 @@ done === TEST 10: thread traceback (multi-thread) +Note: only coroutine.wrap propagates errors to the parent coroutine +(and thus produces a traceback) --- config location /lua { - content_by_lua ' + content_by_lua_block { local f = function(cr) coroutine.resume(cr) end -- emit a error local g = function() unknown.unknown = 1 end - local l1 = coroutine.create(f) - local l2 = coroutine.create(g) - coroutine.resume(l1, l2) + local l1 = coroutine.wrap(f) + local l2 = coroutine.wrap(g) + local l3 = coroutine.wrap(function() l1(l2) end) + l3() ngx.say("hello") - '; + } } --- request GET /lua ---- response_body +--- error_code: 500 +--- response_body_unlike hello --- error_log eval ["stack traceback:", "coroutine 0:", "coroutine 1:", "coroutine 2:"] @@ -509,7 +515,7 @@ GET /lua --- config location /lua { content_by_lua ' - function print(...) + local function print(...) local args = {...} local is_first = true for i,v in ipairs(args) do @@ -523,12 +529,12 @@ GET /lua ngx.print("\\\n") end - function foo (a) + local function foo (a) print("foo", a) return coroutine.yield(2*a) end - co = coroutine.create(function (a,b) + local co = coroutine.create(function (a,b) print("co-body", a, b) local r = foo(a+1) print("co-body", r) @@ -566,7 +572,8 @@ main false cannot resume dead coroutine local create = coroutine.create local resume = coroutine.resume local yield = coroutine.yield - function f() + local g + local function f() ngx.say("f begin") yield() local c2 = create(g) @@ -627,8 +634,9 @@ main done local yield = coroutine.yield local code = 400 + local g - function f() + local function f() local c2 = create(g) yield() code = code + 1 @@ -683,8 +691,9 @@ exit local yield = coroutine.yield local code = 0 + local g - function f() + local function f() local c2 = create(g) yield() code = code + 1 @@ -740,7 +749,7 @@ num: 3 location /lua { echo hello; header_filter_by_lua ' - function f() + local function f() yield() end @@ -753,6 +762,8 @@ GET /lua --- ignore_response --- error_log API disabled in the context of header_filter_by_lua* +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ @@ -764,13 +775,13 @@ API disabled in the context of header_filter_by_lua* local c1, c2 - function f() + local function f() print("f 1") print(coroutine.resume(c2)) print("f 2") end - function g() + local function g() print("g 1") -- print(coroutine.resume(c1)) print("g 2") @@ -803,13 +814,13 @@ f 2 local c1, c2 - function f() + local function f() print("f 1") print(coroutine.resume(c2)) print("f 2") end - function g() + local function g() print("g 1") print(coroutine.resume(c1)) print("g 2") @@ -859,7 +870,7 @@ falsecannot resume running coroutine location /t { content_by_lua ' local co - function f() + local function f() ngx.say("f: ", coroutine.status(co)) ngx.say("f: ", coroutine.resume(co)) end @@ -885,7 +896,7 @@ chunk: true location /t { content_by_lua ' local co - function f() + local function f() error("bad") end co = coroutine.create(f) @@ -901,8 +912,8 @@ qr/^child: resume: falsecontent_by_lua\(nginx\.conf:\d+\):4: bad child: status: dead parent: status: running $/s ---- error_log eval -qr/lua coroutine: runtime error: content_by_lua\(nginx\.conf:\d+\):4: bad/ +--- no_error_log +[error] @@ -990,7 +1001,7 @@ test10 resolver $TEST_NGINX_RESOLVER ipv6=off; location /lua { content_by_lua ' - function worker(url) + local function worker(url) local sock = ngx.socket.tcp() local ok, err = sock:connect(url, 80) coroutine.yield() @@ -1044,7 +1055,7 @@ successfully connected to: agentzh.org resolver $TEST_NGINX_RESOLVER ipv6=off; location /lua { content_by_lua ' - function worker(url) + local function worker(url) local sock = ngx.socket.tcp() local ok, err = sock:connect(url, 80) coroutine.yield() @@ -1104,7 +1115,7 @@ successfully connected to: agentzh.org --- config location /cotest { content_by_lua ' - function generator() + local function generator() return co_wrap(function() co_yield("data") end) @@ -1133,7 +1144,7 @@ data --- config location /cotest { content_by_lua ' - function generator() + local function generator() return co_wrap(function() co_yield("data") end) @@ -1166,7 +1177,7 @@ data content_by_lua ' local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield - function f() + local function f() return 3 end @@ -1199,7 +1210,7 @@ ok local coroutine = require "coroutine" local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield - function f() + local function f() local cnt = 0 for i = 1, 20 do ngx.say("Hello, ", cnt) @@ -1238,7 +1249,7 @@ Hello, 2 header_filter_by_lua ' local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield - function f() + local function f() local cnt = 0 for i = 1, 20 do print("co yield: ", cnt) @@ -1278,7 +1289,7 @@ co yield: 2 body_filter_by_lua ' local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield - function f() + local function f() local cnt = 0 for i = 1, 20 do print("co yield: ", cnt) @@ -1315,3 +1326,426 @@ co yield: 2 --- no_error_log [error] + + + +=== TEST 32: coroutine.wrap propagates errors to parent coroutine +--- config + location = /t { + content_by_lua_block { + local co = coroutine.wrap(function() + print("in wrapped coroutine") + error("something went wrong") + end) + + co() + + ngx.say("ok") + } + } +--- request +GET /t +--- error_code: 500 +--- response_body_unlike +ok +--- error_log eval +[ + qr/\[notice\] .*? in wrapped coroutine/, + qr/\[error\] .*? lua entry thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):\d+: something went wrong/, + "stack traceback:", + "coroutine 0:", + "coroutine 1:" +] + + + +=== TEST 33: coroutine.wrap propagates nested errors to parent coroutine +Note: in this case, both the error message and the traceback are constructed +from co1's stack level. +--- config + location = /t { + content_by_lua_block { + local co1 = coroutine.wrap(function() + error("something went wrong in co1") + end) + + local co2 = coroutine.wrap(function() + co1() + end) + + co2() + } + } +--- request +GET /t +--- error_code: 500 +--- ignore_response +--- error_log eval +[ + qr/\[error\] .*? lua entry thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):\d+: something went wrong in co1/, + "stack traceback:", + "coroutine 0:", + "coroutine 1:", + "coroutine 2:" +] + + + +=== TEST 34: coroutine.wrap propagates nested errors with stack level to parent coroutine +Note: in this case, the error message is constructed at the entry thread stack +level, and the traceback is constructed from co1's stack level. +--- config + location = /t { + content_by_lua_block { + local co1 = coroutine.wrap(function() + error("something went wrong in co1", 2) + end) + + local co2 = coroutine.wrap(function() + co1() + end) + + co2() + } + } +--- request +GET /t +--- error_code: 500 +--- error_log eval +[ + qr/\[error\] .*? lua entry thread aborted: runtime error: something went wrong in co1/, + "stack traceback:", + "coroutine 0:", + "coroutine 1:", + "coroutine 2:" +] + + + +=== TEST 35: coroutine.wrap runtime errors do not log errors +--- no_http2 +--- config + location = /t { + content_by_lua_block { + local co = coroutine.wrap(function() + print("in wrapped coroutine") + error("something went wrong") + end) + + co() + } + } +--- request +GET /t +--- error_code: 500 +--- ignore_response +--- no_error_log eval +[ + qr/\[error\] .*? lua coroutine: runtime error:/, + "[crit]", + "[emerg]", + "[warn]", +] + + + +=== TEST 36: coroutine.wrap does not return status boolean on yield +--- config + location = /t { + content_by_lua_block { + local co = coroutine.wrap(function() + coroutine.yield("ok", "err") + end) + + local ret1, ret2 = co() + + ngx.say(ret1, ", ", ret2) + } + } +--- request +GET /t +--- response_body +ok, err +--- no_error_log +[error] + + + +=== TEST 37: coroutine.wrap does not return status boolean on done +--- config + location = /t { + content_by_lua_block { + local co = coroutine.wrap(function() end) + + local ret1 = co() + + ngx.say(ret1) + } + } +--- request +GET /t +--- response_body +nil +--- no_error_log +[error]g + + + +=== TEST 38: coroutine.wrap does not return status boolean on error +--- SKIP: not supported +--- config + location = /t { + content_by_lua_block { + local co = coroutine.wrap(function() + error("something went wrong") + end) + + local ret1, ret2 = pcall(co) + + ngx.say(ret1, ", ", ret2) + } + } +--- request +GET /t +--- response_body +false, something went wrong +--- no_error_log +[error] + + + +=== TEST 39: coroutine.wrap creates different function refs +--- config + location = /t { + content_by_lua_block { + local f = function() end + local co = coroutine.wrap(f) + local co2 = coroutine.wrap(f) + + ngx.say("co == co2: ", co == co2) + } + } +--- request +GET /t +--- response_body +co == co2: false +--- no_error_log +[error] + + + +=== TEST 40: coroutine.wrap supports yielding and resuming +--- config + location = /t { + content_by_lua_block { + local function f() + local cnt = 0 + for i = 1, 20 do + ngx.say("co yield: ", cnt) + coroutine.yield() + cnt = cnt + 1 + end + end + + local f = coroutine.wrap(f) + for i = 1, 3 do + ngx.say("co resume") + f() + end + } + } +--- request +GET /t +--- response_body +co resume +co yield: 0 +co resume +co yield: 1 +co resume +co yield: 2 +--- no_error_log +[error] + + + +=== TEST 41: coroutine.wrap return values +--- config + location = /t { + content_by_lua_block { + local function f() + local cnt = 0 + for i = 1, 20 do + coroutine.yield(cnt, cnt + 1) + cnt = cnt + 1 + end + end + + local f = coroutine.wrap(f) + for i = 1, 3 do + ngx.say("co resume") + local ret1, ret2 = f() + ngx.say("co yield: ", ret1, ", ", ret2) + end + } + } +--- request +GET /t +--- response_body +co resume +co yield: 0, 1 +co resume +co yield: 1, 2 +co resume +co yield: 2, 3 +--- no_error_log +[error] + + + +=== TEST 42: coroutine.wrap arguments +--- config + location = /t { + content_by_lua_block { + local function f(step) + local cnt = 0 + for i = 1, 20 do + ngx.say("co yield: ", cnt) + coroutine.yield() + cnt = cnt + step + end + end + + local f = coroutine.wrap(f) + for i = 1, 3 do + ngx.say("co resume") + f(i) + end + } + } +--- request +GET /t +--- response_body +co resume +co yield: 0 +co resume +co yield: 1 +co resume +co yield: 2 +--- no_error_log +[error] + + + +=== TEST 43: coroutine.wrap in header_filter_by_lua (orig coroutine.wrap) +--- config + location = /t { + return 200; + + header_filter_by_lua_block { + local function f() + local cnt = 0 + for i = 1, 20 do + print("co yield: ", cnt) + coroutine.yield() + cnt = cnt + 1 + end + end + + local f = coroutine.wrap(f) + for i = 1, 3 do + print("co resume.") + f() + end + } + } +--- request +GET /t +--- ignore_response +--- grep_error_log eval: qr/co (?:yield: \d+|resume\.)/ +--- grep_error_log_out +co resume. +co yield: 0 +co resume. +co yield: 1 +co resume. +co yield: 2 +--- no_error_log +[error] + + + +=== TEST 44: coroutine.wrap in header_filter_by_lua propagates errors (orig coroutine.wrap) +--- config + location = /t { + return 200; + + header_filter_by_lua_block { + local co = coroutine.wrap(function() + print("in wrapped coroutine") + error("something went wrong") + end) + + local err = co() + + ngx.log(ngx.CRIT, "err: ", err) + } + } +--- request +GET /t +--- ignore_response +--- error_log eval +[ + qr/\[notice\] .*? in wrapped coroutine/, + qr/\[error\] .*? failed to run header_filter_by_lua\*: header_filter_by_lua\(nginx.conf:\d+\):\d+: header_filter_by_lua\(nginx.conf:\d+\):\d+: something went wrong/, + "stack traceback:", + "in function 'co'" +] +--- curl_error eval +qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/ + + + +=== TEST 45: coroutine.wrap in init_by_lua propagates errors (orig coroutine.wrap) +--- http_config + init_by_lua_block { + local co = coroutine.wrap(function() + print("in wrapped coroutine") + error("something went wrong") + end) + + local err = co() + + ngx.log(ngx.CRIT, "err: ", err) + } +--- config + +--- must_die +--- grep_error_log eval: qr/init_by_lua\(nginx.conf:25\).*? something went wrong/ +--- grep_error_log_out +init_by_lua(nginx.conf:25):7: init_by_lua(nginx.conf:25):4: something went wrong + + + +=== TEST 46: coroutine.resume runtime errors do not log errors +--- config + location = /t { + content_by_lua_block { + local function f() + error("something went wrong") + end + + local ret1, ret2 = coroutine.resume(coroutine.create(f)) + ngx.say(ret1) + ngx.say(ret2) + } + } +--- request +GET /t +--- response_body_like +false +content_by_lua\(nginx.conf:\d+\):\d+: something went wrong +--- no_error_log eval +[ + qr/\[error\] .*? lua coroutine: runtime error:", + "stack traceback:", +] diff --git a/t/093-uthread-spawn.t b/t/093-uthread-spawn.t index b622d30123..99750d3c0b 100644 --- a/t/093-uthread-spawn.t +++ b/t/093-uthread-spawn.t @@ -24,7 +24,7 @@ __DATA__ --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") end @@ -58,11 +58,11 @@ after --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("in thread 1") end - function g() + local function g() ngx.say("in thread 2") end @@ -107,7 +107,7 @@ after 2 --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("before sleep") ngx.sleep(0.1) ngx.say("after sleep") @@ -144,13 +144,13 @@ after sleep --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("1: before sleep") ngx.sleep(0.2) ngx.say("1: after sleep") end - function g() + local function g() ngx.say("2: before sleep") ngx.sleep(0.1) ngx.say("2: after sleep") @@ -200,7 +200,7 @@ delete thread 2 --- config location /lua { content_by_lua ' - function f() + local function f() ngx.blah() end @@ -231,9 +231,9 @@ qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):3: --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("before capture") - res = ngx.location.capture("/proxy") + local res = ngx.location.capture("/proxy") ngx.say("after capture: ", res.body) end @@ -277,7 +277,7 @@ after capture: hello world --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("before capture") local res = ngx.location.capture("/proxy?foo") ngx.say("after capture: ", res.body) @@ -330,7 +330,7 @@ after capture: hello foo --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("before capture") local res = ngx.location.capture("/proxy?foo") ngx.say("after capture: ", res.body) @@ -384,13 +384,13 @@ capture: hello bar --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("f: before capture") local res = ngx.location.capture("/proxy?foo") ngx.say("f: after capture: ", res.body) end - function g() + local function g() ngx.say("g: before capture") local res = ngx.location.capture("/proxy?bah") ngx.say("g: after capture: ", res.body) @@ -462,7 +462,8 @@ g: after capture: hello bah --- config location /lua { content_by_lua ' - function f() + local g + local function f() ngx.say("before g") ngx.thread.spawn(g) ngx.say("after g") @@ -508,7 +509,8 @@ after g --- config location /lua { content_by_lua ' - function f() + local g + local function f() ngx.say("before g") ngx.thread.spawn(g) ngx.say("after g") @@ -556,7 +558,7 @@ hello in g() location /lua { content_by_lua ' local co - function f() + local function f() co = coroutine.running() ngx.sleep(0.1) end @@ -589,7 +591,7 @@ status: running location /lua { content_by_lua ' local co - function f() + local function f() co = coroutine.running() end @@ -621,7 +623,8 @@ status: zombie location /lua { content_by_lua ' local co - function f() + local g + local function f() co = coroutine.running() local co2 = coroutine.create(g) coroutine.resume(co2) @@ -660,7 +663,8 @@ status: normal --- config location /lua { content_by_lua ' - function f() + local g + local function f() ngx.say("before g") ngx.thread.spawn(g) ngx.say("after g") @@ -707,7 +711,7 @@ after f content_by_lua ' local yield = coroutine.yield - function f() + local function f() local self = coroutine.running() ngx.say("f 1") yield(self) @@ -760,7 +764,7 @@ f 3 content_by_lua ' local yield = coroutine.yield - function f() + local function f() local self = coroutine.running() ngx.say("f 1") yield(self) @@ -769,7 +773,7 @@ f 3 ngx.say("f 3") end - function g() + local function g() local self = coroutine.running() ngx.say("g 1") yield(self) @@ -816,7 +820,7 @@ g 3 --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") coroutine.yield(coroutine.running) ngx.flush(true) @@ -853,12 +857,12 @@ after --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello from f") ngx.flush(true) end - function g() + local function g() ngx.say("hello from g") ngx.flush(true) end @@ -904,7 +908,7 @@ hello from g --- config location /lua { content_by_lua ' - function f() + local function f() local sock = ngx.socket.tcp() local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) if not ok then @@ -956,9 +960,9 @@ received: OK --- config location /lua { content_by_lua ' - function f() + local function f() local sock = ngx.socket.udp() - local ok, err = sock:setpeername("127.0.0.1", 12345) + local ok, err = sock:setpeername("127.0.0.1", $TEST_NGINX_RAND_PORT_1) local bytes, err = sock:send("blah") if not bytes then ngx.say("failed to send query: ", err) @@ -998,7 +1002,7 @@ delete thread 2 delete thread 1 )$ ---- udp_listen: 12345 +--- udp_listen: $TEST_NGINX_RAND_PORT_1 --- udp_query: blah --- udp_reply: hello udp --- response_body_like chop @@ -1018,7 +1022,7 @@ after)$ --- config location /lua { content_by_lua ' - function f() + local function f() ngx.req.read_body() local body = ngx.req.get_body_data() ngx.say("body: ", body) @@ -1060,10 +1064,12 @@ body: hello world)$ === TEST 23: simple user thread with ngx.req.socket() +ngx.req.socket() does not support in http3 +--- skip_eval: 4:$ENV{TEST_NGINX_USE_HTTP3} --- config location /lua { content_by_lua ' - function f() + local function f() local sock = ngx.req.socket() local body, err = sock:receive(11) if not body then @@ -1113,7 +1119,7 @@ body: hello world)$ --- config location /lua { content_by_lua ' - function f(a, b) + local function f(a, b) ngx.say("hello ", a, " and ", b) end @@ -1647,7 +1653,7 @@ ok --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.say("f") end diff --git a/t/094-uthread-exit.t b/t/094-uthread-exit.t index a62377392f..375d8d6023 100644 --- a/t/094-uthread-exit.t +++ b/t/094-uthread-exit.t @@ -8,7 +8,7 @@ our $StapScript = $t::StapThread::StapScript; repeat_each(2); -plan tests => repeat_each() * (blocks() * 4); +plan tests => repeat_each() * (blocks() * 4 - 2); $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; $ENV{TEST_NGINX_MEMCACHED_PORT} ||= '11211'; @@ -24,7 +24,7 @@ __DATA__ --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.exit(0) end @@ -87,7 +87,7 @@ hello in thread --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -171,13 +171,13 @@ after --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.say("f") ngx.exit(0) end - function g() + local function g() ngx.sleep(1) ngx.say("g") end @@ -261,7 +261,7 @@ f --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.say("exiting the user thread") ngx.exit(0) @@ -297,10 +297,10 @@ exiting the user thread === TEST 5: exit in user thread (entry thread is still pending on the DNS resolver for ngx.socket.tcp) --- config location /lua { - resolver agentzh.org; + resolver 127.0.0.2:12345; resolver_timeout 12s; content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.001) ngx.exit(0) @@ -393,10 +393,10 @@ after === TEST 6: exit in user thread (entry thread is still pending on the DNS resolver for ngx.socket.udp) --- config location /lua { - resolver agentzh.org; + resolver 127.0.0.2:12345; resolver_timeout 12s; content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.001) ngx.exit(0) @@ -490,7 +490,7 @@ after --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -501,7 +501,7 @@ after ngx.say("after") local sock = ngx.socket.tcp() sock:settimeout(12000) - local ok, err = sock:connect("106.184.1.99", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) if not ok then ngx.say("failed to connect: ", err) return @@ -580,7 +580,7 @@ after --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -680,7 +680,7 @@ after --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -786,7 +786,7 @@ after --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -881,7 +881,7 @@ after --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -966,6 +966,7 @@ hello in thread after --- no_error_log [error] +--- skip_eval: 4:$ENV{TEST_NGINX_USE_HTTP3} @@ -974,7 +975,7 @@ after location /lua { client_body_timeout 12000ms; content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -1051,6 +1052,7 @@ hello in thread after --- no_error_log [error] +--- skip_eval: 4:$ENV{TEST_NGINX_USE_HTTP3} @@ -1059,7 +1061,7 @@ after location /lua { client_body_timeout 12000ms; content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(0) @@ -1133,7 +1135,6 @@ free request attempt to abort with pending subrequests --- no_error_log [alert] -[warn] @@ -1142,7 +1143,7 @@ attempt to abort with pending subrequests location /lua { client_body_timeout 12000ms; content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exit(0) end @@ -1225,7 +1226,7 @@ attempt to abort with pending subrequests location /lua { client_body_timeout 12000ms; content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exit(0) end @@ -1311,11 +1312,12 @@ attempt to abort with pending subrequests === TEST 16: exit in entry thread (user thread is still pending on ngx.location.capture_multi), without pending output +--- no_http2 --- config location /lua { client_body_timeout 12000ms; content_by_lua ' - function f() + local function f() ngx.location.capture_multi{ {"/echo"}, {"/sleep"} @@ -1399,15 +1401,18 @@ attempt to abort with pending subrequests --- no_error_log [alert] [warn] +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server# === TEST 17: exit(444) in user thread (entry thread is still pending on ngx.location.capture), with pending output +--- no_http2 --- config location /lua { client_body_timeout 12000ms; content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(444) @@ -1482,15 +1487,18 @@ free request [alert] [error] [warn] +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server# === TEST 18: exit(408) in user thread (entry thread is still pending on ngx.location.capture), with pending output +--- no_http2 --- config location /lua { client_body_timeout 12000ms; content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(408) @@ -1565,6 +1573,8 @@ free request [alert] [error] [warn] +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server# @@ -1573,7 +1583,7 @@ free request location /lua { client_body_timeout 12000ms; content_by_lua ' - function f() + local function f() ngx.say("hello in thread") ngx.sleep(0.1) ngx.exit(499) @@ -1647,4 +1657,6 @@ free request --- no_error_log [alert] [error] -[warn] + +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server# diff --git a/t/095-uthread-exec.t b/t/095-uthread-exec.t index 56156c48a8..9ef6356090 100644 --- a/t/095-uthread-exec.t +++ b/t/095-uthread-exec.t @@ -23,7 +23,7 @@ __DATA__ --- config location /lua { content_by_lua ' - function f() + local function f() ngx.exec("/foo") end @@ -58,7 +58,7 @@ i am foo --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exec("/foo") end @@ -93,7 +93,7 @@ i am foo --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exec("/foo") end @@ -175,12 +175,12 @@ hello foo --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exec("/foo") end - function g() + local function g() ngx.sleep(1) end @@ -267,7 +267,7 @@ hello foo location /lua { client_body_timeout 12000ms; content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.exec("/foo") end @@ -344,11 +344,12 @@ attempt to abort with pending subrequests === TEST 6: exec in entry thread (user thread is still pending on ngx.location.capture), without pending output +--- no_http2 --- config location /lua { client_body_timeout 12000ms; content_by_lua ' - function f() + local function f() ngx.location.capture("/sleep") ngx.say("end") end @@ -423,3 +424,5 @@ attempt to abort with pending subrequests --- no_error_log [alert] [warn] +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server# diff --git a/t/096-uthread-redirect.t b/t/096-uthread-redirect.t index 003c6423de..5df5ecfb09 100644 --- a/t/096-uthread-redirect.t +++ b/t/096-uthread-redirect.t @@ -25,7 +25,7 @@ __DATA__ location /lua { client_body_timeout 12000ms; content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.redirect(301) end @@ -114,7 +114,7 @@ attempt to abort with pending subrequests --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.redirect(301) end @@ -190,11 +190,12 @@ free request === TEST 3: ngx.redirect() in entry thread (user thread is still pending on ngx.location.capture_multi), without pending output +--- no_http2 --- config location /lua { client_body_timeout 12000ms; content_by_lua ' - function f() + local function f() ngx.location.capture_multi{ {"/echo"}, {"/sleep"} @@ -277,3 +278,5 @@ attempt to abort with pending subrequests --- no_error_log [alert] [warn] +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server# diff --git a/t/097-uthread-rewrite.t b/t/097-uthread-rewrite.t index 2f0c06fb43..998e256221 100644 --- a/t/097-uthread-rewrite.t +++ b/t/097-uthread-rewrite.t @@ -23,7 +23,7 @@ __DATA__ --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.req.set_uri("/foo", true) end @@ -58,7 +58,7 @@ i am foo --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.req.set_uri("/foo", true) end @@ -93,7 +93,7 @@ i am foo --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.req.set_uri("/foo", true) end @@ -175,12 +175,12 @@ hello foo --- config location /lua { rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.req.set_uri("/foo", true) end - function g() + local function g() ngx.sleep(1) end @@ -267,7 +267,7 @@ hello foo location /lua { client_body_timeout 12000ms; rewrite_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.req.set_uri("/foo", true) end diff --git a/t/098-uthread-wait.t b/t/098-uthread-wait.t index 4948596f91..e2818e008e 100644 --- a/t/098-uthread-wait.t +++ b/t/098-uthread-wait.t @@ -23,7 +23,7 @@ __DATA__ --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") return "done" end @@ -72,7 +72,7 @@ done --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.say("hello in thread") return "done" @@ -120,13 +120,13 @@ done --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.say("f: hello") return "done" end - function g() + local function g() ngx.sleep(0.2) ngx.say("g: hello") return "done" @@ -200,13 +200,13 @@ f: done --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.say("f: hello") return "done" end - function g() + local function g() ngx.sleep(0.2) ngx.say("g: hello") return "done" @@ -228,7 +228,7 @@ f: done ngx.say("g thread created: ", coroutine.status(tg)) - ok, res = ngx.thread.wait(tf) + local ok, res = ngx.thread.wait(tf) if not ok then ngx.say("failed to wait f: ", res) return @@ -281,7 +281,7 @@ g: done --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") return "done", 3.14 end @@ -330,7 +330,7 @@ res: done 3.14 --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.say("hello in thread") return "done", 3.14 @@ -378,7 +378,7 @@ res: done 3.14 --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") error("bad bad!") end @@ -427,7 +427,7 @@ qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):4: --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.say("hello in thread") error("bad bad!") @@ -477,12 +477,12 @@ qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):5: --- config location /lua { content_by_lua ' - function g() + local function g() ngx.say("hello in thread") return "done" end - function f() + local function f() local t, err = ngx.thread.spawn(g) if not t then ngx.say("failed to spawn thread: ", err) @@ -533,13 +533,13 @@ done --- config location /lua { content_by_lua ' - function g() + local function g() ngx.sleep(0.1) ngx.say("hello in thread") return "done" end - function f() + local function f() local t, err = ngx.thread.spawn(g) if not t then ngx.say("failed to spawn thread: ", err) @@ -593,12 +593,12 @@ done -- local out = function (...) ngx.log(ngx.ERR, ...) end local out = ngx.say - function f() + local function f() out("f: hello") return "f done" end - function g() + local function g() out("g: hello") return "g done" end @@ -668,13 +668,13 @@ g status: zombie -- local out = function (...) ngx.log(ngx.ERR, ...) end local out = ngx.say - function f() + local function f() ngx.sleep(0.1) out("f: hello") return "f done" end - function g() + local function g() ngx.sleep(0.2) out("g: hello") return "g done" @@ -745,13 +745,13 @@ g: hello -- local out = function (...) ngx.log(ngx.ERR, ...) end local out = ngx.say - function f() + local function f() ngx.sleep(0.2) out("f: hello") return "f done" end - function g() + local function g() ngx.sleep(0.1) out("g: hello") return "g done" @@ -822,12 +822,12 @@ f: hello -- local out = function (...) ngx.log(ngx.ERR, ...) end local out = ngx.say - function f() + local function f() out("f: hello") error("f done") end - function g() + local function g() out("g: hello") error("g done") end @@ -896,13 +896,13 @@ qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):7: -- local out = function (...) ngx.log(ngx.ERR, ...) end local out = ngx.say - function f() + local function f() ngx.sleep(0.1) out("f: hello") error("f done") end - function g() + local function g() ngx.sleep(0.2) out("g: hello") error("g done") @@ -969,13 +969,13 @@ qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):8: --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) ngx.say("f: hello") return "done" end - function g() + local function g() ngx.sleep(0.2) ngx.say("g: hello") return "done" @@ -997,7 +997,7 @@ qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):8: ngx.say("g thread created: ", coroutine.status(tg)) - ok, res = ngx.thread.wait(tf, tg) + local ok, res = ngx.thread.wait(tf, tg) if not ok then ngx.say("failed to wait: ", res) return @@ -1038,13 +1038,13 @@ res: done --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.2) ngx.say("f: hello") return "f done" end - function g() + local function g() ngx.sleep(0.1) ngx.say("g: hello") return "g done" @@ -1066,7 +1066,7 @@ res: done ngx.say("g thread created: ", coroutine.status(tg)) - ok, res = ngx.thread.wait(tf, tg) + local ok, res = ngx.thread.wait(tf, tg) if not ok then ngx.say("failed to wait: ", res) return @@ -1109,12 +1109,12 @@ res: g done content_by_lua ' local t - function f() + local function f() ngx.sleep(0.1) return "done" end - function g() + local function g() t = ngx.thread.spawn(f) end @@ -1154,7 +1154,7 @@ only the parent coroutine can wait on the thread --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) coroutine.yield() return "done" @@ -1192,7 +1192,7 @@ qr/lua entry thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):11 --- config location /lua { content_by_lua ' - function f() + local function f() ngx.sleep(0.1) collectgarbage() error("f done") @@ -1228,7 +1228,7 @@ qr/lua user thread aborted: runtime error: content_by_lua\(nginx\.conf:\d+\):5: --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello in thread") return "done" end @@ -1283,7 +1283,7 @@ failed to run thread: already waited or killed --- config location /lua { content_by_lua ' - function f() + local function f() -- ngx.say("hello in thread") return "done" end @@ -1321,3 +1321,22 @@ $s; --- no_error_log [error] [alert] + + + +=== TEST 23: no parameters for ngx.thread.wait +--- config + location /lua { + content_by_lua_block { + ngx.thread.wait() + ngx.say("ok") + } + } +--- request +GET /lua +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +at least one coroutine should be specified +--- no_error_log +[crit] diff --git a/t/099-c-api.t b/t/099-c-api.t index a0b375c810..50334626bd 100644 --- a/t/099-c-api.t +++ b/t/099-c-api.t @@ -86,7 +86,7 @@ dogs zone: defined local buf = ffi.new("char[?]", 4) ffi.copy(buf, "dogs", 4) - zone = ffi.C.ngx_http_lua_find_zone(buf, 4) + local zone = ffi.C.ngx_http_lua_find_zone(buf, 4) local val = ffi.new("ngx_http_lua_value_t[?]", 1) @@ -149,7 +149,7 @@ bar: rc=0, type=3, val=3.14159 local buf = ffi.new("char[?]", 4) ffi.copy(buf, "dogs", 4) - zone = ffi.C.ngx_http_lua_find_zone(buf, 4) + local zone = ffi.C.ngx_http_lua_find_zone(buf, 4) local val = ffi.new("ngx_http_lua_value_t[?]", 1) @@ -212,7 +212,7 @@ bar: rc=0, type=1, val=0 local buf = ffi.new("char[?]", 4) ffi.copy(buf, "dogs", 4) - zone = ffi.C.ngx_http_lua_find_zone(buf, 4) + local zone = ffi.C.ngx_http_lua_find_zone(buf, 4) local val = ffi.new("ngx_http_lua_value_t[?]", 1) @@ -272,7 +272,7 @@ bar: rc=-5 local buf = ffi.new("char[?]", 4) ffi.copy(buf, "dogs", 4) - zone = ffi.C.ngx_http_lua_find_zone(buf, 4) + local zone = ffi.C.ngx_http_lua_find_zone(buf, 4) local s = ffi.new("char[?]", 20) @@ -344,7 +344,7 @@ bar: rc=0, type=4, val=, len=0 local buf = ffi.new("char[?]", 4) ffi.copy(buf, "dogs", 4) - zone = ffi.C.ngx_http_lua_find_zone(buf, 4) + local zone = ffi.C.ngx_http_lua_find_zone(buf, 4) local val = ffi.new("ngx_http_lua_value_t[?]", 1) diff --git a/t/100-client-abort.t b/t/100-client-abort.t index 89c1f6ac08..f1f6894beb 100644 --- a/t/100-client-abort.t +++ b/t/100-client-abort.t @@ -1,6 +1,14 @@ # vim:set ft= ts=4 sw=4 et fdm=marker: -use Test::Nginx::Socket::Lua; +BEGIN { + if ($ENV{TEST_NGINX_USE_HTTP3}) { + $SkipReason = "client abort detect does not support in http3"; + } elsif ($ENV{TEST_NGINX_USE_HTTP2}) { + $SkipReason = "client abort detect does not support in http2"; + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); use t::StapThread; our $GCScript = <<_EOC_; @@ -195,7 +203,7 @@ bad things happen location = /sub { proxy_ignore_client_abort on; - proxy_pass http://agentzh.org:12345/; + proxy_pass http://127.0.0.2:12345/; } location = /sleep { @@ -236,7 +244,7 @@ client prematurely closed connection location = /sub { proxy_ignore_client_abort off; - proxy_pass http://agentzh.org:12345/; + proxy_pass http://127.0.0.2:12345/; } --- request GET /t @@ -540,7 +548,7 @@ client prematurely closed connection return end - ok, err = sock:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_REDIS_PORT) if not ok then ngx.log(ngx.ERR, "failed to connect: ", err) return @@ -947,6 +955,7 @@ GET /t [alert] --- error_log say failed: nginx output filter error +--- skip_eval: 3:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) @@ -975,6 +984,7 @@ GET /t [alert] --- error_log print failed: nginx output filter error +--- skip_eval: 3:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) @@ -1034,6 +1044,7 @@ GET /t [alert] --- error_log flush succeeded +--- skip_eval: 3:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) @@ -1064,3 +1075,4 @@ GET /t eof succeeded --- error_log eof failed: nginx output filter error +--- skip_eval: 4:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/) diff --git a/t/101-on-abort.t b/t/101-on-abort.t index 182d12c546..784f244e04 100644 --- a/t/101-on-abort.t +++ b/t/101-on-abort.t @@ -1,6 +1,14 @@ # vim:set ft= ts=4 sw=4 et fdm=marker: -use Test::Nginx::Socket::Lua; +BEGIN { + if ($ENV{TEST_NGINX_USE_HTTP3}) { + $SkipReason = "client abort detect does not support in http3"; + } elsif ($ENV{TEST_NGINX_USE_HTTP2}) { + $SkipReason = "client abort detect does not support in http2"; + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); use t::StapThread; our $GCScript = <<_EOC_; @@ -408,7 +416,7 @@ main handler done -=== TEST 9: regsiter on_abort callback but no client abortion +=== TEST 9: register on_abort callback but no client abortion --- config location /t { lua_check_client_abort on; @@ -546,7 +554,7 @@ on abort called -=== TEST 12: regsiter on_abort callback but no client abortion (uthread) +=== TEST 12: register on_abort callback but no client abortion (uthread) --- config location /t { lua_check_client_abort on; @@ -591,7 +599,7 @@ main handler done -=== TEST 13: regsiter on_abort callback multiple times +=== TEST 13: register on_abort callback multiple times --- config location /t { lua_check_client_abort on; @@ -794,7 +802,7 @@ on abort called -=== TEST 18: regsiter on_abort callback but no client abortion (2 uthreads and 1 pending) +=== TEST 18: register on_abort callback but no client abortion (2 uthreads and 1 pending) --- config location /t { lua_check_client_abort on; diff --git a/t/102-req-start-time.t b/t/102-req-start-time.t index 3b041afda7..d54b19941d 100644 --- a/t/102-req-start-time.t +++ b/t/102-req-start-time.t @@ -59,7 +59,7 @@ GET /start --- request GET /req_time --- response_body_like chop -^(?:0\.[12]|0\.099)\d* +^(?:0\.[12]|0\.099|0\.098)\d* true$ --- no_error_log [error] @@ -88,7 +88,7 @@ true$ --- request GET /req_time --- response_body_like chomp -^(?:0\.[12]|0\.099)\d* +^(?:0\.[12]|0\.099|0\.098)\d* 0\.\d+ true$ --- no_error_log diff --git a/t/103-req-http-ver.t b/t/103-req-http-ver.t index e6de2388c5..73ecccae28 100644 --- a/t/103-req-http-ver.t +++ b/t/103-req-http-ver.t @@ -26,8 +26,17 @@ __DATA__ } --- request GET /t ---- response_body -1.1 +--- response_body eval +my $body; +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + $body="3\n"; +} elsif (defined $ENV{TEST_NGINX_USE_HTTP2}) { + $body="2\n"; +} else { + $body="1.1\n"; +} + +$body; --- no_error_log [error] diff --git a/t/104-req-raw-header.t b/t/104-req-raw-header.t index ac1f89e1b4..459d190ac7 100644 --- a/t/104-req-raw-header.t +++ b/t/104-req-raw-header.t @@ -1,5 +1,14 @@ # vim:set ft= ts=4 sw=4 et fdm=marker: -use Test::Nginx::Socket::Lua; + +our $SkipReason; + +BEGIN { + if ($ENV{TEST_NGINX_USE_HTTP3}) { + $SkipReason = "http3 does not support ngx.req.raw_header()"; + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); #worker_connections(1014); #master_on(); @@ -331,6 +340,8 @@ Foo: bar baz\r } --- no_error_log [error] +--- skip_nginx +3: >= 1.21.1 @@ -362,6 +373,8 @@ Connection: close\r --- no_error_log [error] --- timeout: 5 +--- skip_nginx +3: >= 1.21.1 @@ -988,3 +1001,61 @@ Connection: close --- no_error_log [error] --- timeout: 5 + + + +=== TEST 34: multi-line header is invalid (nginx >= 1.21.1) +--- config + location /t { + content_by_lua ' + ngx.print(ngx.req.raw_header()) + '; + } +--- raw_request eval +"GET /t HTTP/1.1\r +Host: localhost\r +Connection: close\r +Foo: bar baz\r + blah\r +\r +" +--- error_code: 400 +--- error_log +client sent invalid header line: "\x20..." while reading client request headers +--- no_error_log +[error] +--- skip_nginx +3: < 1.21.1 + + + +=== TEST 35: bugfix: invalid http request +--- log_level: error +--- http_config + lua_package_path "../lua-resty-core/lib/?.lua;;"; +--- config + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_PORT) + if not ok then + ngx.log(ngx.ERR, "failed to connect to memc: ", err) + return + end + sock:send("\n") + sock:close() + + ngx.say("OK") + } + } + + log_by_lua_block { + local h = ngx.req.raw_header() + } + +--- request +GET /t +--- response_body +OK +--- no_error_log +[error] diff --git a/t/105-pressure.t b/t/105-pressure.t index 9d1ba1ff1a..2fc130d09b 100644 --- a/t/105-pressure.t +++ b/t/105-pressure.t @@ -2,7 +2,7 @@ use Test::Nginx::Socket::Lua; -#worker_connections(1014); +worker_connections(1014); #master_on(); #log_level('debug'); @@ -28,6 +28,8 @@ run_tests(); __DATA__ === TEST 1: memory issue in the "args" string option for ngx.location.capture +the default worker_connections is 64, HTTP3 will keep the connection when curl +request finished. So need to change the worker_connection. --- config location /test1 { content_by_lua ' diff --git a/t/106-timer.t b/t/106-timer.t index 3e4741e50c..513e1e5b45 100644 --- a/t/106-timer.t +++ b/t/106-timer.t @@ -12,7 +12,7 @@ our $StapScript = $t::StapThread::StapScript; repeat_each(2); -plan tests => repeat_each() * (blocks() * 8 + 72); +plan tests => repeat_each() * (blocks() * 8 + 61); #no_diff(); no_long_string(); @@ -73,10 +73,22 @@ qr/\[lua\] content_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.0(?:4[4-9]|5[0-6]) "http lua close fake http connection", "timer prematurely expired: false", ] +--- grep_error_log eval: qr/lua caching unused lua thread|lua reusing cached lua thread/ +--- grep_error_log_out eval +[ + "lua caching unused lua thread +lua caching unused lua thread +", + "lua reusing cached lua thread +lua reusing cached lua thread +lua caching unused lua thread +lua caching unused lua thread +", +] -=== TEST 2: separated global env +=== TEST 2: globals are shared --- config location /t { content_by_lua ' @@ -104,9 +116,9 @@ F(ngx_http_lua_timer_handler) { --- response_body registered timer -foo = nil +foo = 3 ---- wait: 0.1 +--- wait: 0.2 --- no_error_log [error] [alert] @@ -216,10 +228,24 @@ qr/\[lua\] content_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.(?:1[4-9]|2[0-6]?) === TEST 5: tcp cosocket in timer handler (short connections) +--- no_http2 --- config server_tokens off; + + location = /gc { + content_by_lua_block { + local c = collectgarbage("count") + ngx.say("before: ", c) + collectgarbage("collect") + c = collectgarbage("count") + ngx.say("after: ", c) + } + } + location = /t { content_by_lua ' + collectgarbage() + -- ngx.say("gc size: ", collectgarbage("count")) local begin = ngx.now() local function fail(...) ngx.log(ngx.ERR, ...) @@ -269,6 +295,7 @@ qr/\[lua\] content_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.(?:1[4-9]|2[0-6]?) ngx.say("failed to set timer: ", err) return end + -- ngx.sleep(0.1) ngx.say("registered timer") '; } @@ -320,7 +347,7 @@ qr/received: Server: \S+/, === TEST 6: tcp cosocket in timer handler (keep-alive connections) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /t { @@ -577,7 +604,7 @@ delete thread 2 --- response_body hello world ---- wait: 0.12 +--- wait: 0.3 --- no_error_log [error] [alert] @@ -587,7 +614,7 @@ hello world [ "registered timer", qr/\[lua\] .*? my lua timer handler/, -qr/\[lua\] log_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.0(?:6[4-9]|7[0-6])/, +qr/\[lua\] log_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.0(?:6[4-9]|7[0-9]|8[1-3])/, "lua ngx.timer expired", "http lua close fake http connection" ] @@ -596,7 +623,7 @@ qr/\[lua\] log_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.0(?:6[4-9]|7[0-6])/, === TEST 10: tcp cosocket in timer handler (keep-alive connections) - log_by_lua --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /t { @@ -693,7 +720,7 @@ qr/go\(\): connected: 1, reused: \d+/, === TEST 11: tcp cosocket in timer handler (keep-alive connections) - header_filter_by_lua --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /t { @@ -799,7 +826,7 @@ qr/go\(\): connected: 1, reused: \d+/, === TEST 12: tcp cosocket in timer handler (keep-alive connections) - body_filter_by_lua --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /t { @@ -912,7 +939,7 @@ qr/go\(\): connected: 1, reused: \d+/, === TEST 13: tcp cosocket in timer handler (keep-alive connections) - set_by_lua --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /t { @@ -1023,7 +1050,7 @@ qr/go\(\): connected: 1, reused: \d+/, content_by_lua ' local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield local function f() - function f() + local function f() local cnt = 0 for i = 1, 20 do print("cnt = ", cnt) @@ -1090,7 +1117,7 @@ registered timer ngx.log(ngx.ERR, ...) end local function handle() - function f() + local function f() print("hello in thread") return "done" end @@ -1306,6 +1333,7 @@ API disabled === TEST 19: exit in user thread (entry thread is still pending on ngx.sleep) +--- quic_max_idle_timeout: 1.3 --- config location /t { content_by_lua ' @@ -1880,6 +1908,7 @@ trace: [m][f][g] --- config location /t { content_by_lua ' + collectgarbage() local s = "" local function fail(...) @@ -1932,7 +1961,7 @@ registered timer --- error_log eval [ -qr/\[alert\] .*? 1 lua_max_running_timers are not enough/, +qr/\[alert\] .*? lua failed to run timer with function defined at =content_by_lua\(nginx.conf:\d+\):11: 1 lua_max_running_timers are not enough/, "lua ngx.timer expired", "http lua close fake http connection", ] @@ -2181,8 +2210,8 @@ qr/\[lua\] content_by_lua\(nginx\.conf:\d+\):\d+: elapsed: .*?, context: ngx\.ti '; } --- log_level: error ---- error_log_file: syslog:server=127.0.0.1:12345 ---- udp_listen: 12345 +--- error_log_file: syslog:server=127.0.0.1:$TEST_NGINX_RAND_PORT_1 +--- udp_listen: $TEST_NGINX_RAND_PORT_1 --- udp_query eval: qr/Bad bad bad/ --- udp_reply: hello --- wait: 0.1 @@ -2193,3 +2222,178 @@ ok --- error_log Bad bad bad --- skip_nginx: 4: < 1.7.1 + + + +=== TEST 33: log function location when failed to run a timer +--- http_config + lua_max_running_timers 1; +--- config + location /t { + content_by_lua_block { + local function g() + ngx.sleep(0.01) + end + + local function f() + ngx.sleep(0.01) + end + + local ok, err = ngx.timer.at(0, f) + if not ok then + ngx.say("failed to create timer f: ", err) + return + end + + local ok, err = ngx.timer.at(0, g) + if not ok then + ngx.say("failed to create timer g: ", err) + return + end + + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +ok +--- wait: 0.1 +--- error_log eval +qr/\[alert\] .*? lua failed to run timer with function defined at =content_by_lua\(nginx.conf:\d+\):2: 1 lua_max_running_timers are not enough/ +--- no_error_log +[crit] +[error] + + + +=== TEST 34: log function location when failed to run a timer (anonymous function) +--- http_config + lua_max_running_timers 1; +--- config + location /t { + content_by_lua_block { + local function f() + ngx.sleep(0.01) + end + + local ok, err = ngx.timer.at(0, f) + if not ok then + ngx.say("failed to set timer f: ", err) + return + end + + local ok, err = ngx.timer.at(0, function() + ngx.sleep(0.01) + end) + + if not ok then + ngx.say("failed to set timer: ", err) + return + end + + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +ok +--- wait: 0.1 +--- error_log eval +qr/\[alert\] .*? lua failed to run timer with function defined at =content_by_lua\(nginx.conf:\d+\):12: 1 lua_max_running_timers are not enough/ +--- no_error_log +[crit] +[error] + + + +=== TEST 35: log function location when failed to run a timer (lua file) +--- user_files +>>> test.lua +local _M = {} + +function _M.run() + ngx.sleep(0.01) +end + +return _M +--- http_config + lua_package_path '$TEST_NGINX_HTML_DIR/?.lua;./?.lua;;'; + lua_max_running_timers 1; +--- config + location /t { + content_by_lua_block { + local test = require "test" + + local ok, err = ngx.timer.at(0, test.run) + if not ok then + ngx.say("failed to set timer: ", err) + return + end + + local ok, err = ngx.timer.at(0, test.run) + if not ok then + ngx.say("failed to set timer: ", err) + return + end + + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +ok +--- wait: 0.1 +--- no_error_log +[crit] +[error] +--- error_log eval +qr/\[alert\] .*? lua failed to run timer with function defined at @.+\/test.lua:3: 1 lua_max_running_timers are not enough/ + + + +=== TEST 36: log function location when failed to run a timer with args (lua file) +--- user_files +>>> test.lua +local _M = {} + +function _M.run(premature, arg) + ngx.sleep(0.01) +end + +return _M +--- http_config + lua_package_path '$TEST_NGINX_HTML_DIR/?.lua;./?.lua;;'; + lua_max_running_timers 1; +--- config + location /t { + content_by_lua_block { + local test = require "test" + + local ok, err = ngx.timer.at(0, test.run, "arg") + if not ok then + ngx.say("failed to set timer: ", err) + return + end + + local ok, err = ngx.timer.at(0, test.run, "arg") + if not ok then + ngx.say("failed to set timer: ", err) + return + end + + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +ok +--- wait: 0.1 +--- no_error_log +[crit] +[error] +--- error_log eval +qr/\[alert\] .*? lua failed to run timer with function defined at @.+\/test.lua:3: 1 lua_max_running_timers are not enough/ diff --git a/t/108-timer-safe.t b/t/108-timer-safe.t index 2795998a51..a23634cd3b 100644 --- a/t/108-timer-safe.t +++ b/t/108-timer-safe.t @@ -81,16 +81,16 @@ qr/\[lua\] content_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.0(?:4[4-9]|5[0-6]) local begin = ngx.now() local function f() print("my lua timer handler") - ngx.sleep(0.02) + ngx.sleep(0.2) print("elapsed: ", ngx.now() - begin) end - local ok, err = ngx.timer.at(0.05, f) + local ok, err = ngx.timer.at(0.5, f) if not ok then ngx.say("failed to set timer: ", err) return end ngx.say("registered timer") - ngx.sleep(0.05) + ngx.sleep(0.5) '; } --- request @@ -116,7 +116,7 @@ registered timer --- error_log eval [ qr/\[lua\] .*? my lua timer handler/, -qr/\[lua\] content_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.0(?:6[4-9]|7[0-6])/, +qr/\[lua\] content_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.(?:6[4-9]|7[0-6])/, "lua ngx.timer expired", "http lua close fake http connection" ] @@ -124,6 +124,7 @@ qr/\[lua\] content_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.0(?:6[4-9]|7[0-6]) === TEST 3: tcp cosocket in timer handler (short connections) +--- no_http2 --- config server_tokens off; location = /t { @@ -229,7 +230,7 @@ qr/received: Server: \S+/, === TEST 4: tcp cosocket in timer handler (keep-alive connections) --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /t { @@ -489,7 +490,7 @@ delete thread 2 --- response_body hello world ---- wait: 0.1 +--- wait: 0.15 --- no_error_log [error] [alert] @@ -499,7 +500,7 @@ hello world [ "registered timer", qr/\[lua\] .*? my lua timer handler/, -qr/\[lua\] log_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.0(?:6[4-9]|7[0-6])/, +qr/\[lua\] log_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.0(?:6[4-9]|7[0-9]|8[0-6])/, "lua ngx.timer expired", "http lua close fake http connection" ] @@ -508,7 +509,7 @@ qr/\[lua\] log_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.0(?:6[4-9]|7[0-6])/, === TEST 8: tcp cosocket in timer handler (keep-alive connections) - log_by_lua --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /t { @@ -606,7 +607,7 @@ qr/go\(\): connected: 1, reused: \d+/, === TEST 9: tcp cosocket in timer handler (keep-alive connections) - header_filter_by_lua --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /t { @@ -713,7 +714,7 @@ qr/go\(\): connected: 1, reused: \d+/, === TEST 10: tcp cosocket in timer handler (keep-alive connections) - body_filter_by_lua --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /t { @@ -827,7 +828,7 @@ qr/go\(\): connected: 1, reused: \d+/, === TEST 11: tcp cosocket in timer handler (keep-alive connections) - set_by_lua --- http_config eval - "lua_package_path '$::HtmlDir/?.lua;./?.lua';" + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" --- config location = /t { @@ -1007,7 +1008,7 @@ registered timer ngx.log(ngx.ERR, ...) end local function handle() - function f() + local function f() print("hello in thread") return "done" end diff --git a/t/109-timer-hup.t b/t/109-timer-hup.t index 0864046361..551dd486c7 100644 --- a/t/109-timer-hup.t +++ b/t/109-timer-hup.t @@ -6,6 +6,10 @@ BEGIN { if ($ENV{TEST_NGINX_CHECK_LEAK}) { $SkipReason = "unavailable for the hup tests"; + } elsif (defined $ENV{TEST_NGINX_USE_HTTP3}) { + #os.execute("kill -HUP " .. pid) + $SkipReason = "send HUP relaod signal by self make two workers with same id"; + } else { $ENV{TEST_NGINX_USE_HUP} = 1; undef $ENV{TEST_NGINX_USE_STAP}; @@ -46,7 +50,7 @@ __DATA__ --- config location /t { content_by_lua ' - local f, err = io.open("t/servroot/logs/nginx.pid", "r") + local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r") if not f then ngx.say("failed to open nginx.pid: ", err) return @@ -99,7 +103,7 @@ timer prematurely expired: true --- config location /t { content_by_lua ' - local f, err = io.open("t/servroot/logs/nginx.pid", "r") + local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r") if not f then ngx.say("failed to open nginx.pid: ", err) return @@ -163,7 +167,7 @@ timer prematurely expired: true --- config location /t { content_by_lua ' - local f, err = io.open("t/servroot/logs/nginx.pid", "r") + local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r") if not f then ngx.say("failed to open nginx.pid: ", err) return @@ -219,7 +223,7 @@ failed to register a new timer after reload: process exiting, context: ngx.timer --- config location /t { content_by_lua ' - local f, err = io.open("t/servroot/logs/nginx.pid", "r") + local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r") if not f then ngx.say("failed to open nginx.pid: ", err) return @@ -284,7 +288,7 @@ g: exiting=true --- config location /t { content_by_lua ' - local f, err = io.open("t/servroot/logs/nginx.pid", "r") + local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r") if not f then ngx.say("failed to open nginx.pid: ", err) return @@ -336,7 +340,7 @@ lua found 100 pending timers lua_shared_dict test_dict 1m; server { - listen 12355; + listen $TEST_NGINX_RAND_PORT_1; location = /foo { echo 'foo'; } @@ -350,7 +354,7 @@ lua found 100 pending timers -- Connect the socket local sock = ngx.socket.tcp() - local ok,err = sock:connect("127.0.0.1", 12355) + local ok,err = sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1) if not ok then ngx.log(ngx.ERR, err) end @@ -363,7 +367,7 @@ lua found 100 pending timers local line, err = sock:receive("*l") until not line or string.find(line, "^%s*$") - function foo() + local function foo() repeat -- Get and read chunk local line, err = sock:receive("*l") @@ -379,7 +383,7 @@ lua found 100 pending timers until len == 0 end - co = coroutine.create(foo) + local co = coroutine.create(foo) repeat local chunk = select(2,coroutine.resume(co)) until chunk == nil @@ -399,7 +403,7 @@ lua found 100 pending timers end local ok, err = ngx.timer.at(1, background_thread) - local f, err = io.open("t/servroot/logs/nginx.pid", "r") + local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r") if not f then ngx.say("failed to open nginx.pid: ", err) return @@ -453,7 +457,7 @@ lua found 1 pending timers end if kill then - local f, err = io.open("t/servroot/logs/nginx.pid", "r") + local f, err = io.open("$TEST_NGINX_SERVER_ROOT/logs/nginx.pid", "r") if not f then ngx.log(ngx.ERR, "failed to open nginx.pid: ", err) return diff --git a/t/113-req-header-cookie.t b/t/113-req-header-cookie.t index 4a9305342b..944549cd74 100644 --- a/t/113-req-header-cookie.t +++ b/t/113-req-header-cookie.t @@ -8,7 +8,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); -plan tests => repeat_each() * (4 * blocks()); +plan tests => repeat_each() * (3 * blocks() + 6); #no_diff(); no_long_string(); @@ -247,3 +247,26 @@ Cookie: boo=123; foo=bar --- no_error_log [error] + + + +=== TEST 7: set multiple custom cookies with unsafe values (with '\n' and 'r') +--- config + location /t { + rewrite_by_lua_block { + ngx.req.set_header("Cookie", {"boo=123\nfoo", "foo=bar\rbar"}) + } + echo "Cookie foo: $cookie_foo"; + echo "Cookie baz: $cookie_baz"; + echo "Cookie boo: $cookie_boo"; + echo "Cookie: $http_cookie"; + } +--- request +GET /t +--- response_body +Cookie foo: bar%0Dbar +Cookie baz: +Cookie boo: 123%0Afoo +Cookie: boo=123%0Afoo; foo=bar%0Dbar +--- no_error_log +[error] diff --git a/t/114-config.t b/t/114-config.t index 9d6680dda8..46d98de2fb 100644 --- a/t/114-config.t +++ b/t/114-config.t @@ -33,7 +33,7 @@ GET /t -=== TEST 2: ngx.config.subystem +=== TEST 2: ngx.config.subsystem --- config location /t { content_by_lua ' diff --git a/t/116-raw-req-socket.t b/t/116-raw-req-socket.t index ab06ee4a31..78a0e8d5ce 100644 --- a/t/116-raw-req-socket.t +++ b/t/116-raw-req-socket.t @@ -1,10 +1,18 @@ # vim:set ft= ts=4 sw=4 et fdm=marker: -use Test::Nginx::Socket::Lua; +our $SkipReason; + +BEGIN { + if ($ENV{TEST_NGINX_USE_HTTP3}) { + $SkipReason = "http3 does not support ngx.req.socket(true)"; + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); repeat_each(2); -plan tests => repeat_each() * 40; +plan tests => repeat_each() * 46; our $HtmlDir = html_dir; @@ -876,3 +884,183 @@ request body: hey, hello world --- no_error_log [error] [alert] + + + +=== TEST 16: receiveany +--- config + server_tokens off; + location = /t { + #set $port 5000; + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + local sock = ngx.socket.tcp() + local port = ngx.var.port + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local req = "GET /mysock HTTP/1.1\r\nUpgrade: mysock\r\nHost: localhost\r\nConnection: close\r\n\r\nhello" + -- req = "OK" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + -- Will return to I/O loop, causing receiveany() in /mysock location to be called + ngx.sleep(1) + + local bytes, err = sock:send(", world") + if not bytes then + ngx.say("failed to send packet 1: ", err) + return + end + + local reader = sock:receiveuntil("\r\n\r\n") + local data, err, partial = reader() + if not data then + ngx.say("no response header found") + return + end + + local msg, err = sock:receive() + if not msg then + ngx.say("failed to receive: ", err) + return + end + + ngx.say("msg: ", msg) + + ok, err = sock:close() + if not ok then + ngx.say("failed to close socket: ", err) + return + end + } + } + + location = /mysock { + content_by_lua_block { + ngx.status = 101 + ngx.send_headers() + ngx.flush(true) + ngx.req.read_body() + local sock, err = ngx.req.socket(true) + if not sock then + ngx.log(ngx.ERR, "server: failed to get raw req socket: ", err) + return + end + + local data, err = sock:receiveany(1024) + if not data then + ngx.log(ngx.ERR, "server: failed to receive: ", err) + return + end + + local bytes, err = sock:send("1: received: " .. data .. "\n") + if not bytes then + ngx.log(ngx.ERR, "server: failed to send: ", err) + return + end + } + more_clear_headers Date; + } + +--- request +GET /t +--- response_body +msg: 1: received: hello +--- no_error_log +[error] +--- skip_eval: 3:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} ne "") + + + +=== TEST 17: getfd() +--- config + server_tokens off; + location = /t { + #set $port 5000; + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + local sock = ngx.socket.tcp() + local port = ngx.var.port + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local req = "GET /mysock HTTP/1.1\r\nUpgrade: mysock\r\nHost: localhost\r\nConnection: close\r\n\r\nhello" + -- req = "OK" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + local reader = sock:receiveuntil("\r\n\r\n") + local data, err, partial = reader() + if not data then + ngx.say("no response header found") + return + end + + local msg, err = sock:receive() + if not msg then + ngx.say("failed to receive: ", err) + return + end + + ngx.say("msg: ", msg) + + ok, err = sock:close() + if not ok then + ngx.say("failed to close socket: ", err) + return + end + } + } + + location = /mysock { + content_by_lua_block { + ngx.status = 101 + ngx.send_headers() + ngx.flush(true) + ngx.req.read_body() + local sock, err = ngx.req.socket(true) + if not sock then + ngx.log(ngx.ERR, "server: failed to get raw req socket: ", err) + return + end + + local s = sock:getfd() + local data, err = sock:receive(5) + if not data then + ngx.log(ngx.ERR, "server: failed to receive: ", err) + return + end + + local bytes, err = sock:send("fd: " .. tostring(s) .. " 1: received: " .. data .. "\n") + if not bytes then + ngx.log(ngx.ERR, "server: failed to send: ", err) + return + end + } + more_clear_headers Date; + } + +--- request +GET /t +--- response_body eval +qr/\Amsg: fd: \d+ 1: received: hello +/ms +--- no_error_log +[error] diff --git a/t/120-re-find.t b/t/120-re-find.t index 73e6134fd7..43ccece1ab 100644 --- a/t/120-re-find.t +++ b/t/120-re-find.t @@ -354,8 +354,11 @@ matched: he } --- request GET /re ---- response_body -error: pcre_compile() failed: missing ) in "(abc" +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre2_compile() failed: missing closing parenthesis in \"(abc\"\n" +: +"error: pcre_compile() failed: missing ) in \"(abc\"\n" --- no_error_log [error] @@ -562,8 +565,11 @@ matched: hello, 1234 } --- request GET /t ---- response_body_like chop -^error: pcre_exec\(\) failed: -10$ +--- response_body eval +$Test::Nginx::Util::PcreVersion == 2 ? +"error: pcre_exec\(\) failed: -4\n" +: +"error: pcre_exec\(\) failed: -10\n" --- no_error_log [error] @@ -587,6 +593,7 @@ GET /t '; } --- stap +# TODO: PCRE2 use different option values from PCRE probe process("$LIBPCRE_PATH").function("pcre_compile") { printf("compile opts: %x\n", $options) } @@ -622,7 +629,7 @@ matched: 你 >>> a.lua local re = [==[(?i:([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:=|<=>|r?like|sounds\s+like|regexp)([\s'\"`´’‘\(\)]*)?\2|([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:!=|<=|>=|<>|<|>|\^|is\s+not|not\s+like|not\s+regexp)([\s'\"`´’‘\(\)]*)?(?!\6)([\d\w]+))]==] -s = string.rep([[ABCDEFG]], 10) +local s = string.rep([[ABCDEFG]], 10) local start = ngx.now() @@ -645,8 +652,14 @@ end --- request GET /re ---- response_body -error: pcre_exec() failed: -8 +--- response_body eval +# lua_regex_match_limit uses pcre_extra->match_limit in the PCRE, +# but PCRE2 replaces this with pcre2_set_match_limit interface, +# which has different effects. +$Test::Nginx::Util::PcreVersion == 2 ? +"failed to match.\n" +: +"error: pcre_exec() failed: -8\n" --- no_error_log [error] @@ -664,7 +677,7 @@ error: pcre_exec() failed: -8 >>> a.lua local re = [==[(?i:([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:=|<=>|r?like|sounds\s+like|regexp)([\s'\"`´’‘\(\)]*)?\2|([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:!=|<=|>=|<>|<|>|\^|is\s+not|not\s+like|not\s+regexp)([\s'\"`´’‘\(\)]*)?(?!\6)([\d\w]+))]==] -s = string.rep([[ABCDEFG]], 10) +local s = string.rep([[ABCDEFG]], 10) local start = ngx.now() @@ -917,3 +930,35 @@ to: 4 pos: 5 --- no_error_log [error] + + + +=== TEST 32: ignore match limit in DFA mode +--- http_config + lua_regex_match_limit 1; +--- config + location /re { + content_by_lua_block { + local s = "This is no more" + local from, to, err = ngx.re.find(s, "<.*>", "d") + if from then + ngx.say("from: ", from) + ngx.say("to: ", to) + ngx.say("matched: ", string.sub(s, from, to)) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + } + } +--- request + GET /re +--- response_body +from: 9 +to: 56 +matched: +--- no_error_log +[error] diff --git a/t/122-worker-2.t b/t/122-worker-2.t new file mode 100644 index 0000000000..c4ad2aea2a --- /dev/null +++ b/t/122-worker-2.t @@ -0,0 +1,49 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +#worker_connections(1014); +master_on(); +workers(4); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: get worker pids with multiple worker +--- config + location /lua { + content_by_lua_block { + local pids, err = ngx.worker.pids() + if err ~= nil then + return + end + local pid = ngx.worker.pid() + ngx.say("worker pid: ", pid) + local count = ngx.worker.count() + ngx.say("worker count: ", count) + ngx.say("worker pids count: ", #pids) + for i = 1, count do + if pids[i] == pid then + ngx.say("worker pid is correct.") + return + end + end + } + } +--- request +GET /lua +--- response_body_like +worker pid: \d+ +worker count: 4 +worker pids count: 4 +worker pid is correct\. +--- no_error_log +[error] diff --git a/t/122-worker-3.t b/t/122-worker-3.t new file mode 100644 index 0000000000..050486e532 --- /dev/null +++ b/t/122-worker-3.t @@ -0,0 +1,58 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +our $SkipReason; + +BEGIN { + if ($ENV{TEST_NGINX_CHECK_LEAK}) { + $SkipReason = "unavailable for the hup tests"; + + } else { + $ENV{TEST_NGINX_USE_HUP} = 1; + undef $ENV{TEST_NGINX_USE_STAP}; + } +} + +use Test::Nginx::Socket::Lua 'no_plan'; + +#worker_connections(1014); +master_on(); +workers(4); +#log_level('warn'); + +repeat_each(2); + +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: get worker pids with multiple worker +--- config + location /lua { + content_by_lua_block { + local pids, err = ngx.worker.pids() + if err ~= nil then + return + end + local pid = ngx.worker.pid() + ngx.say("worker pid: ", pid) + local count = ngx.worker.count() + ngx.say("worker count: ", count) + ngx.say("worker pids count: ", #pids) + for i = 1, count do + if pids[i] == pid then + ngx.say("worker pid is correct.") + return + end + end + } + } +--- request +GET /lua +--- response_body_like +worker pid: \d+ +worker count: 4 +worker pids count: 4 +worker pid is correct\. +--- no_error_log +[error] diff --git a/t/122-worker.t b/t/122-worker.t index b74c81fbcd..244c3142d3 100644 --- a/t/122-worker.t +++ b/t/122-worker.t @@ -9,7 +9,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); -plan tests => repeat_each() * (blocks() * 3); +plan tests => repeat_each() * (blocks() * 4 - 4); #no_diff(); no_long_string(); @@ -79,3 +79,33 @@ worker pid: \d+ worker pid is correct\. --- no_error_log [error] + + + +=== TEST 4: content_by_lua + ngx.worker.pids +--- config + location /lua { + content_by_lua ' + local pids = ngx.worker.pids() + local pid = ngx.worker.pid() + ngx.say("worker pid: ", pid) + local count = ngx.worker.count() + if count ~= #pids then + ngx.say("worker pids is wrong.") + end + for i = 1, count do + if pids[i] == pid then + ngx.say("worker pid is correct.") + return + end + end + ngx.say("worker pid is wrong.") + '; + } +--- request +GET /lua +--- response_body_like +worker pid: \d+ +worker pid is correct\. +--- no_error_log +[error] diff --git a/t/123-lua-path.t b/t/123-lua-path.t index 23681a9dbb..e30132f3c5 100644 --- a/t/123-lua-path.t +++ b/t/123-lua-path.t @@ -11,7 +11,7 @@ repeat_each(2); plan tests => repeat_each() * (blocks() * 3 + 1); -$ENV{LUA_PATH} = "/foo/bar/baz"; +$ENV{LUA_PATH} = "../lua-resty-core/lib/?.lua;../lua-resty-lrucache/lib/?.lua;/foo/bar/baz"; $ENV{LUA_CPATH} = "/baz/bar/foo"; #no_diff(); #no_long_string(); @@ -36,8 +36,8 @@ env LUA_CPATH; } --- request GET /lua ---- response_body -/foo/bar/baz +--- response_body_like +(?:\.\.\/lua-resty-core\/lib\/\?\.lua;\.\.\/lua-resty-lrucache\/lib\/\?\.lua;){1,2}\/foo\/bar\/baz /baz/bar/foo --- no_error_log @@ -60,8 +60,8 @@ env LUA_CPATH; } --- request GET /lua ---- response_body -/foo/bar/baz +--- response_body_like +(?:\.\.\/lua-resty-core\/lib\/\?\.lua;\.\.\/lua-resty-lrucache\/lib\/\?\.lua;){1,2}\/foo\/bar\/baz /baz/bar/foo --- no_error_log diff --git a/t/124-init-worker.t b/t/124-init-worker.t index 9b2a91fcfa..c68d74bb18 100644 --- a/t/124-init-worker.t +++ b/t/124-init-worker.t @@ -521,15 +521,15 @@ warn(): Thu, 01 Jan 1970 01:34:38 GMT } --- request GET /t ---- response_body -timer created +--- response_body_like connected: 1 request sent: 56 -first line received: HTTP/1.1 200 OK -second line received: Server: openresty +first line received: HTTP\/1\.1 200 OK +second line received: (?:Date|Server): .*? --- no_error_log [error] --- timeout: 10 +--- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3} @@ -550,6 +550,7 @@ second line received: Server: openresty else say("connect: ", ok, " ", err) end + done = true end local ok, err = ngx.timer.at(0, handler) @@ -600,6 +601,7 @@ qr/connect\(\) failed \(\d+: Connection refused\), context: ngx\.timer$/ else say("connect: ", ok, " ", err) end + done = true end local ok, err = ngx.timer.at(0, handler) @@ -650,6 +652,7 @@ qr/connect\(\) failed \(\d+: Connection refused\)/ else say("connect: ", ok, " ", err) end + done = true end local ok, err = ngx.timer.at(0, handler) @@ -727,8 +730,8 @@ ok '; } --- log_level: error ---- error_log_file: syslog:server=127.0.0.1:12345 ---- udp_listen: 12345 +--- error_log_file: syslog:server=127.0.0.1:$TEST_NGINX_RAND_PORT_1 +--- udp_listen: $TEST_NGINX_RAND_PORT_1 --- udp_query eval: qr/Bad bad bad/ --- udp_reply: hello --- wait: 0.1 @@ -789,6 +792,14 @@ lua close the global Lua VM \1 lua close the global Lua VM \2 in the cache helper process \d+ lua close the global Lua VM \2 lua close the global Lua VM \2 +|lua close the global Lua VM ([0-9A-F]+) +lua close the global Lua VM \3 in the cache helper process \d+ +lua close the global Lua VM \3 +lua close the global Lua VM \3 in the cache helper process \d+ +|lua close the global Lua VM ([0-9A-F]+) +lua close the global Lua VM \4 in the cache helper process \d+ +lua close the global Lua VM \4 in the cache helper process \d+ +lua close the global Lua VM \4 )(?:lua close the global Lua VM [0-9A-F]+ )*\z/ --- no_error_log @@ -827,7 +838,16 @@ lua close the global Lua VM \1 lua close the global Lua VM \2 in the cache helper process \d+ lua close the global Lua VM \2 lua close the global Lua VM \2 +|lua close the global Lua VM ([0-9A-F]+) +lua close the global Lua VM \3 in the cache helper process \d+ +lua close the global Lua VM \3 +lua close the global Lua VM \3 in the cache helper process \d+ )(?:lua close the global Lua VM [0-9A-F]+ +|lua close the global Lua VM ([0-9A-F]+) +lua close the global Lua VM \4 in the cache helper process \d+ +lua close the global Lua VM \4 in the cache helper process \d+ +lua close the global Lua VM \4 +lua close the global Lua VM \4 )*\z/ --- no_error_log [error] @@ -866,6 +886,14 @@ lua close the global Lua VM \1 lua close the global Lua VM \2 in the cache helper process \d+ lua close the global Lua VM \2 lua close the global Lua VM \2 +|lua close the global Lua VM ([0-9A-F]+) +lua close the global Lua VM \3 in the cache helper process \d+ +lua close the global Lua VM \3 +lua close the global Lua VM \3 in the cache helper process \d+ +|lua close the global Lua VM ([0-9A-F]+) +lua close the global Lua VM \4 in the cache helper process \d+ +lua close the global Lua VM \4 in the cache helper process \d+ +lua close the global Lua VM \4 )(?:lua close the global Lua VM [0-9A-F]+ )*\z/ --- error_log eval @@ -953,3 +981,53 @@ qr/lua close the global Lua VM ([0-9A-F]+)$/, --- no_error_log [error] start privileged agent process + + + +=== TEST 25: syntax error in init_worker_by_lua_block +--- http_config + init_worker_by_lua_block { + ngx.log(ngx.debug, "pass") + error("failed to init" + ngx.log(ngx.debug, "unreachable") + } +--- config + location /t { + content_by_lua_block { + ngx.say("hello world") + } + } +--- request + GET /t +--- response_body +hello world +--- error_log +init_worker_by_lua error: init_worker_by_lua(nginx.conf:25):4: ')' expected (to close '(' at line 3) near 'ngx' +--- no_error_log +no_such_error_log + + + +=== TEST 26: syntax error in init_worker_by_lua_file +--- http_config + init_worker_by_lua_file html/init.lua; +--- config + location /t { + content_by_lua_block { + ngx.say("hello world") + } + } +--- user_files +>>> init.lua + ngx.log(ngx.debug, "pass") + error("failed to init" + ngx.log(ngx.debug, "unreachable") + +--- request + GET /t +--- response_body +hello world +--- error_log eval +qr|init_worker_by_lua_file error: .*?t/servroot\w*/html/init.lua:3: '\)' expected \(to close '\(' at line 2\) near 'ngx'| +--- no_error_log +no_such_error_log diff --git a/t/126-shdict-frag.t b/t/126-shdict-frag.t index 5a2aff8202..33646d17c9 100644 --- a/t/126-shdict-frag.t +++ b/t/126-shdict-frag.t @@ -1240,8 +1240,8 @@ failed to safe set baz: no memory local key = "mylittlekey" .. rand(maxkeyidx) local ok, err = dogs:get(key) if not ok or rand() > 0.6 then - sz = rand(maxsz) - val = rep("a", sz) + local sz = rand(maxsz) + local val = rep("a", sz) local ok, err, forcible = dogs:set(key, val) if err then ngx.log(ngx.ERR, "failed to set key: ", err) diff --git a/t/127-uthread-kill.t b/t/127-uthread-kill.t index cc43c62764..11caee582f 100644 --- a/t/127-uthread-kill.t +++ b/t/127-uthread-kill.t @@ -8,7 +8,7 @@ our $StapScript = $t::StapThread::StapScript; repeat_each(2); -plan tests => repeat_each() * (blocks() * 5 + 2); +plan tests => repeat_each() * (blocks() * 5 + 1); $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; $ENV{TEST_NGINX_MEMCACHED_PORT} ||= '11211'; @@ -23,7 +23,7 @@ __DATA__ --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello from f()") ngx.sleep(1) end @@ -81,7 +81,7 @@ lua clean up the timer for pending ngx.sleep --- config location /lua { content_by_lua ' - function f() + local function f() ngx.say("hello from f()") ngx.sleep(0.001) return 32 @@ -138,12 +138,13 @@ lua clean up the timer for pending ngx.sleep === TEST 3: kill pending resolver --- config - resolver agentzh.org:12345; + resolver 127.0.0.2:12345; + resolver_timeout 5ms; location /lua { content_by_lua ' - function f() + local function f() local sock = ngx.socket.tcp() - sock:connect("some.agentzh.org", 12345) + sock:connect("some.127.0.0.2", 12345) end local t, err = ngx.thread.spawn(f) @@ -184,7 +185,6 @@ killed [error] --- error_log lua tcp socket abort resolver -resolve name done: -2 @@ -194,13 +194,13 @@ resolve name done: -2 location /lua { content_by_lua ' local ready = false - function f() + local function f() local sock = ngx.socket.tcp() sock:connect("agentzh.org", 80) sock:close() ready = true sock:settimeout(10000) - sock:connect("agentzh.org", 12345) + sock:connect("127.0.0.2", 12345) end local t, err = ngx.thread.spawn(f) @@ -262,7 +262,7 @@ lua finalize socket location = /t { content_by_lua ' - function f() + local function f() ngx.location.capture("/sub") end @@ -309,11 +309,11 @@ lua tcp socket abort resolver location = /t { content_by_lua ' - function f() + local function f() ngx.location.capture("/sub") end - function g() + local function g() ngx.sleep(0.3) end @@ -376,7 +376,7 @@ lua tcp socket abort resolver location = /t { content_by_lua ' local ready = false - function f() + local function f() ngx.location.capture("/sub") ready = true ngx.sleep(0.5) @@ -424,7 +424,7 @@ lua tcp socket abort resolver --- config location = /t { content_by_lua ' - function f() + local function f() return end @@ -473,7 +473,7 @@ lua tcp socket abort resolver ngx.say("killed main thread.") end - function f() + local function f() local ok, err = ngx.thread.kill(coroutine.running()) if not ok then ngx.say("failed to kill user thread: ", err) diff --git a/t/128-duplex-tcp-socket.t b/t/128-duplex-tcp-socket.t index e8434a3f84..511cc20bfc 100644 --- a/t/128-duplex-tcp-socket.t +++ b/t/128-duplex-tcp-socket.t @@ -193,7 +193,7 @@ close: 1 nil$ lua_socket_log_errors off; location /t { #set $port 5000; - set $port 7658; + set $port $TEST_NGINX_RAND_PORT_1; content_by_lua ' local sock = ngx.socket.tcp() @@ -258,7 +258,7 @@ received: OK! close: (?:nil socket busy writing|1 nil failed to send request: closed)$ ---- tcp_listen: 7658 +--- tcp_listen: $TEST_NGINX_RAND_PORT_1 --- tcp_shutdown: 0 --- tcp_reply: OK! --- tcp_no_close: 1 @@ -273,7 +273,7 @@ failed to send request: closed)$ lua_socket_log_errors off; location /t { #set $port 5000; - set $port 7658; + set $port $TEST_NGINX_RAND_PORT_1; content_by_lua ' local sock = ngx.socket.tcp() @@ -349,7 +349,7 @@ F(ngx_http_lua_socket_tcp_finalize_write_part) { print_ubacktrace() } --- stap_out2 ---- tcp_listen: 7658 +--- tcp_listen: $TEST_NGINX_RAND_PORT_1 --- tcp_shutdown: 1 --- tcp_query eval: "flush_all\r\n" --- tcp_query_len: 11 @@ -396,7 +396,7 @@ F(ngx_http_lua_socket_tcp_finalize_write_part) { end sock:settimeout(300) - local ok, err = sock:connect("106.184.1.99", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) ngx.say("connect: ", ok, " ", err) local ok, err = sock:close() @@ -425,7 +425,7 @@ close: nil closed --- config server_tokens off; lua_socket_log_errors off; - resolver agentzh.org:12345; + resolver 127.0.0.2:12345; resolver_timeout 300ms; location /t { content_by_lua ' @@ -459,7 +459,7 @@ close: nil closed end sock:settimeout(300) - local ok, err = sock:connect("some2.agentzh.org", 12345) + local ok, err = sock:connect("some2.agentzh.org", 80) ngx.say("connect: ", ok, " ", err) local ok, err = sock:close() diff --git a/t/129-ssl-socket.t b/t/129-ssl-socket.t index 7f36947d38..5651f3f817 100644 --- a/t/129-ssl-socket.t +++ b/t/129-ssl-socket.t @@ -1,16 +1,36 @@ # vim:set ft= ts=4 sw=4 et fdm=marker: -use Test::Nginx::Socket::Lua; +our $SkipReason; +BEGIN { + if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + # FIXME: we still need to enable this test file for HTTP3. + $SkipReason = "the test cases are very unstable, skip for now."; + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); + +use Cwd qw(abs_path realpath); +use File::Basename; repeat_each(2); -plan tests => repeat_each() * 218; +sub resolve($$); -$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); +plan tests => repeat_each() * (blocks() * 7 - 4); +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); $ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; $ENV{TEST_NGINX_SERVER_SSL_PORT} ||= 12345; +$ENV{TEST_NGINX_CERT_DIR} ||= dirname(realpath(abs_path(__FILE__))); +$ENV{TEST_NGINX_OPENRESTY_ORG_IP} ||= resolve("openresty.org", $ENV{TEST_NGINX_RESOLVER}); + +my $NginxBinary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; +my $openssl_version = eval { `$NginxBinary -V 2>&1` }; +if ($openssl_version =~ m/BoringSSL/) { + $ENV{TEST_NGINX_USE_BORINGSSL} = 1; +} #log_level 'warn'; log_level 'debug'; @@ -27,6 +47,19 @@ sub read_file { $cert; } +sub resolve ($$) { + my ($domain, $resolver) = @_; + my $ips = qx/dig \@$resolver +short $domain/; + + my $exit_code = $? >> 8; + if (!$ips || $exit_code != 0) { + die "failed to resolve '$domain' using '$resolver' as resolver"; + } + + my ($ip) = split /\n/, $ips; + return $ip; +} + our $DSTRootCertificate = read_file("t/cert/dst-ca.crt"); our $EquifaxRootCertificate = read_file("t/cert/equifax.crt"); our $TestCertificate = read_file("t/cert/test.crt"); @@ -38,6 +71,8 @@ run_tests(); __DATA__ === TEST 1: www.google.com +access the public network is unstable, need a bigger timeout value. +--- quic_max_idle_timeout: 3 --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -46,7 +81,7 @@ __DATA__ set $port $TEST_NGINX_MEMCACHED_PORT; content_by_lua ' - -- avoid flushing google in "check leak" testing mode: + -- avoid flushing bing in "check leak" testing mode: local counter = package.loaded.counter if not counter then counter = 1 @@ -104,7 +139,7 @@ __DATA__ GET /t --- response_body_like chop \Aconnected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 59 bytes. received: HTTP/1.1 (?:200 OK|302 Found) close: 1 nil @@ -188,7 +223,7 @@ SSL reused session GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 53 bytes. received: HTTP/1.1 201 Created close: 1 nil @@ -269,7 +304,7 @@ SSL reused session GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 58 bytes. received: HTTP/1.1 302 Moved Temporarily close: 1 nil @@ -291,6 +326,8 @@ SSL reused session === TEST 4: ssl session reuse +access the public network is unstable, need a bigger timeout value. +--- quic_max_idle_timeout: 3 --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -301,7 +338,7 @@ SSL reused session content_by_lua ' local sock = ngx.socket.tcp() - sock:settimeout(2000) + sock:settimeout(7000) do @@ -353,12 +390,12 @@ SSL reused session GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 200 OK close: 1 nil connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 200 OK close: 1 nil @@ -380,7 +417,7 @@ lua ssl free session --- no_error_log [error] [alert] ---- timeout: 5 +--- timeout: 10 @@ -573,7 +610,7 @@ SSL reused session ngx.say("ssl handshake: ", type(session)) - local req = "GET / HTTP/1.1\\r\\nHost: agentzh.org\\r\\nConnection: close\\r\\n\\r\\n" + local req = "GET /en/linux-packages.html HTTP/1.1\\r\\nHost: openresty.com\\r\\nConnection: close\\r\\n\\r\\n" local bytes, err = sock:send(req) if not bytes then ngx.say("failed to send http request: ", err) @@ -601,8 +638,8 @@ SSL reused session GET /t --- response_body connected: 1 -ssl handshake: userdata -sent http request: 56 bytes. +ssl handshake: cdata +sent http request: 80 bytes. received: HTTP/1.1 404 Not Found close: 1 nil @@ -686,7 +723,7 @@ $::DSTRootCertificate" GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 58 bytes. received: HTTP/1.1 302 Moved Temporarily close: 1 nil @@ -770,7 +807,7 @@ $::DSTRootCertificate" GET /t --- response_body eval qr{connected: 1 -failed to do SSL handshake: (22: certificate chain too long|20: unable to get local issuer certificate) +failed to do SSL handshake: (22: certificate chain too long|20: unable to get local issuer certificate|21: unable to verify the first certificate) failed to send http request: closed } @@ -779,7 +816,7 @@ failed to send http request: closed --- grep_error_log_out --- error_log eval ['lua ssl server name: "openresty.org"', -qr/lua ssl certificate verify error: \((22: certificate chain too long|20: unable to get local issuer certificate)\)/] +qr/lua ssl certificate verify error: \((22: certificate chain too long|20: unable to get local issuer certificate|21: unable to verify the first certificate)\)/] --- no_error_log SSL reused session [alert] @@ -850,7 +887,7 @@ $::DSTRootCertificate" GET /t --- response_body eval qr/connected: 1 -failed to do SSL handshake: (22: certificate chain too long|20: unable to get local issuer certificate) +failed to do SSL handshake: (22: certificate chain too long|20: unable to get local issuer certificate|21: unable to verify the first certificate) failed to send http request: closed / @@ -867,32 +904,21 @@ SSL reused session -=== TEST 11: www.google.com (SSL verify passes) +=== TEST 11: openresty.org: SSL verify enabled and no corresponding trusted certificates --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate ../html/trusted.crt; - lua_ssl_verify_depth 3; + lua_ssl_verify_depth 2; location /t { - #set $port 5000; set $port $TEST_NGINX_MEMCACHED_PORT; - content_by_lua ' - -- avoid flushing google in "check leak" testing mode: - local counter = package.loaded.counter - if not counter then - counter = 1 - elseif counter >= 2 then - return ngx.exit(503) - else - counter = counter + 1 - end - package.loaded.counter = counter + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(4000) do - local sock = ngx.socket.tcp() - sock:settimeout(2000) - local ok, err = sock:connect("www.google.com", 443) + local ok, err = sock:connect("openresty.org", 443) if not ok then ngx.say("failed to connect: ", err) return @@ -900,21 +926,20 @@ SSL reused session ngx.say("connected: ", ok) - local sess, err = sock:sslhandshake(nil, "www.google.com", true) - if not sess then + local session, err = sock:sslhandshake(nil, "openresty.org", true) + if not session then ngx.say("failed to do SSL handshake: ", err) return end - ngx.say("ssl handshake: ", type(sess)) + ngx.say("ssl handshake: ", type(session)) - local req = "GET / HTTP/1.1\\r\\nHost: www.google.com\\r\\nConnection: close\\r\\n\\r\\n" + local req = "GET / HTTP/1.1\r\nHost: openresty.org\r\nConnection: close\r\n\r\n" local bytes, err = sock:send(req) if not bytes then ngx.say("failed to send http request: ", err) return end - ngx.say("sent http request: ", bytes, " bytes.") local line, err = sock:receive() @@ -929,106 +954,13 @@ SSL reused session ngx.say("close: ", ok, " ", err) end -- do collectgarbage() - '; + } } --- user_files eval ">>> trusted.crt $::EquifaxRootCertificate" ---- request -GET /t ---- response_body_like chop -\Aconnected: 1 -ssl handshake: userdata -sent http request: 59 bytes. -received: HTTP/1.1 (?:200 OK|302 Found) -close: 1 nil -\z ---- grep_error_log eval: qr/lua ssl (?:set|save|free) session: [0-9A-F]+/ ---- grep_error_log_out eval -qr/^lua ssl save session: ([0-9A-F]+) -lua ssl free session: ([0-9A-F]+) -$/ ---- error_log -lua ssl server name: "www.google.com" ---- no_error_log -SSL reused session -[error] -[alert] ---- timeout: 5 - - - -=== TEST 12: www.google.com (SSL verify enabled and no corresponding trusted certificates) ---- config - server_tokens off; - resolver $TEST_NGINX_RESOLVER ipv6=off; - lua_ssl_trusted_certificate ../html/trusted.crt; - lua_ssl_verify_depth 3; - location /t { - #set $port 5000; - set $port $TEST_NGINX_MEMCACHED_PORT; - - content_by_lua ' - -- avoid flushing google in "check leak" testing mode: - local counter = package.loaded.counter - if not counter then - counter = 1 - elseif counter >= 2 then - return ngx.exit(503) - else - counter = counter + 1 - end - package.loaded.counter = counter - - do - local sock = ngx.socket.tcp() - sock:settimeout(2000) - local ok, err = sock:connect("www.google.com", 443) - if not ok then - ngx.say("failed to connect: ", err) - return - end - - ngx.say("connected: ", ok) - - local sess, err = sock:sslhandshake(nil, "www.google.com", true) - if not sess then - ngx.say("failed to do SSL handshake: ", err) - return - end - - ngx.say("ssl handshake: ", type(sess)) - - local req = "GET / HTTP/1.1\\r\\nHost: www.google.com\\r\\nConnection: close\\r\\n\\r\\n" - local bytes, err = sock:send(req) - if not bytes then - ngx.say("failed to send http request: ", err) - return - end - - ngx.say("sent http request: ", bytes, " bytes.") - - local line, err = sock:receive() - if not line then - ngx.say("failed to receive response status line: ", err) - return - end - - ngx.say("received: ", line) - - local ok, err = sock:close() - ngx.say("close: ", ok, " ", err) - end -- do - collectgarbage() - '; - } - ---- user_files eval -">>> trusted.crt -$::DSTRootCertificate" - --- request GET /t --- response_body @@ -1038,7 +970,7 @@ failed to do SSL handshake: 20: unable to get local issuer certificate --- grep_error_log eval: qr/lua ssl (?:set|save|free) session: [0-9A-F]+/ --- grep_error_log_out --- error_log -lua ssl server name: "www.google.com" +lua ssl server name: "openresty.org" lua ssl certificate verify error: (20: unable to get local issuer certificate) --- no_error_log SSL reused session @@ -1047,7 +979,7 @@ SSL reused session -=== TEST 13: openresty.org: passing SSL verify with multiple certificates +=== TEST 12: openresty.org: passing SSL verify with multiple certificates --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -1111,7 +1043,7 @@ $::DSTRootCertificate" GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 58 bytes. received: HTTP/1.1 302 Moved Temporarily close: 1 nil @@ -1133,10 +1065,12 @@ SSL reused session -=== TEST 14: default cipher +=== TEST 13: default cipher --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; + location /t { #set $port 5000; set $port $TEST_NGINX_MEMCACHED_PORT; @@ -1190,7 +1124,7 @@ SSL reused session GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 58 bytes. received: HTTP/1.1 302 Moved Temporarily close: 1 nil @@ -1201,9 +1135,11 @@ close: 1 nil qr/^lua ssl save session: ([0-9A-F]+) lua ssl free session: ([0-9A-F]+) $/ ---- error_log -lua ssl server name: "openresty.org" -SSL: TLSv1.2, cipher: "ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 +--- error_log eval +[ +'lua ssl server name: "openresty.org"', +qr/SSL: TLSv1\.2, cipher: "(?:ECDHE-RSA-AES(?:256|128)-GCM-SHA(?:384|256)|ECDHE-(?:RSA|ECDSA)-CHACHA20-POLY1305) TLSv1\.2/, +] --- no_error_log SSL reused session [error] @@ -1212,21 +1148,32 @@ SSL reused session -=== TEST 15: explicit cipher configuration +=== TEST 14: explicit cipher configuration +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_protocols TLSv1.2; + + location / { + content_by_lua_block { + ngx.exit(200) + } + } + } --- config server_tokens off; - resolver $TEST_NGINX_RESOLVER ipv6=off; - lua_ssl_ciphers ECDHE-RSA-AES256-SHA; - location /t { - #set $port 5000; - set $port $TEST_NGINX_MEMCACHED_PORT; + lua_ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384; + location /t { content_by_lua ' local sock = ngx.socket.tcp() sock:settimeout(2000) do - local ok, err = sock:connect("openresty.org", 443) + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") if not ok then ngx.say("failed to connect: ", err) return @@ -1234,7 +1181,7 @@ SSL reused session ngx.say("connected: ", ok) - local session, err = sock:sslhandshake(nil, "openresty.org") + local session, err = sock:sslhandshake(nil, "test.com") if not session then ngx.say("failed to do SSL handshake: ", err) return @@ -1242,7 +1189,7 @@ SSL reused session ngx.say("ssl handshake: ", type(session)) - local req = "GET / HTTP/1.1\\r\\nHost: openresty.org\\r\\nConnection: close\\r\\n\\r\\n" + local req = "GET / HTTP/1.1\\r\\nHost: test.com\\r\\nConnection: close\\r\\n\\r\\n" local bytes, err = sock:send(req) if not bytes then ngx.say("failed to send http request: ", err) @@ -1265,14 +1212,13 @@ SSL reused session collectgarbage() '; } - --- request GET /t --- response_body connected: 1 -ssl handshake: userdata -sent http request: 58 bytes. -received: HTTP/1.1 302 Moved Temporarily +ssl handshake: cdata +sent http request: 53 bytes. +received: HTTP/1.1 200 OK close: 1 nil --- log_level: debug @@ -1282,8 +1228,8 @@ qr/^lua ssl save session: ([0-9A-F]+) lua ssl free session: ([0-9A-F]+) $/ --- error_log eval -['lua ssl server name: "openresty.org"', -qr/SSL: TLSv1.2, cipher: "ECDHE-RSA-AES256-SHA (SSLv3|TLSv1)/] +['lua ssl server name: "test.com"', +qr/SSL: TLSv\d(?:\.\d)?, cipher: "ECDHE-RSA-AES256-GCM-SHA384 (SSLv3|TLSv1\.2)/] --- no_error_log SSL reused session [error] @@ -1292,21 +1238,32 @@ SSL reused session -=== TEST 16: explicit ssl protocol configuration +=== TEST 15: explicit ssl protocol configuration +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_protocols TLSv1.2; + + location / { + content_by_lua_block { + ngx.exit(200) + } + } + } --- config server_tokens off; - resolver $TEST_NGINX_RESOLVER ipv6=off; - lua_ssl_protocols TLSv1; - location /t { - #set $port 5000; - set $port $TEST_NGINX_MEMCACHED_PORT; + lua_ssl_protocols TLSv1.2; + location /t { content_by_lua ' local sock = ngx.socket.tcp() sock:settimeout(2000) do - local ok, err = sock:connect("openresty.org", 443) + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") if not ok then ngx.say("failed to connect: ", err) return @@ -1314,7 +1271,7 @@ SSL reused session ngx.say("connected: ", ok) - local session, err = sock:sslhandshake(nil, "openresty.org") + local session, err = sock:sslhandshake(nil, "test.com") if not session then ngx.say("failed to do SSL handshake: ", err) return @@ -1322,7 +1279,7 @@ SSL reused session ngx.say("ssl handshake: ", type(session)) - local req = "GET / HTTP/1.1\\r\\nHost: openresty.org\\r\\nConnection: close\\r\\n\\r\\n" + local req = "GET / HTTP/1.1\\r\\nHost: test.com\\r\\nConnection: close\\r\\n\\r\\n" local bytes, err = sock:send(req) if not bytes then ngx.say("failed to send http request: ", err) @@ -1345,14 +1302,13 @@ SSL reused session collectgarbage() '; } - --- request GET /t --- response_body connected: 1 -ssl handshake: userdata -sent http request: 58 bytes. -received: HTTP/1.1 302 Moved Temporarily +ssl handshake: cdata +sent http request: 53 bytes. +received: HTTP/1.1 200 OK close: 1 nil --- log_level: debug @@ -1362,17 +1318,16 @@ qr/^lua ssl save session: ([0-9A-F]+) lua ssl free session: ([0-9A-F]+) $/ --- error_log eval -['lua ssl server name: "openresty.org"', -qr/SSL: TLSv1, cipher: "ECDHE-RSA-AES128-SHA (SSLv3|TLSv1)/] +['lua ssl server name: "test.com"', +qr/SSL: TLSv1\.2, cipher: "ECDHE-RSA-AES256-GCM-SHA384 TLSv1\.2/] --- no_error_log SSL reused session [error] [alert] ---- timeout: 5 -=== TEST 17: unsupported ssl protocol +=== TEST 16: unsupported ssl protocol --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -1438,18 +1393,18 @@ failed to send http request: closed --- grep_error_log_out --- error_log eval [ -qr/\[crit\] .*?SSL_do_handshake\(\) failed .*?(unsupported protocol|no protocols available)/, +qr/\[(crit|error)\] .*?SSL_do_handshake\(\) failed .*?(unsupported protocol|no protocols available)/, 'lua ssl server name: "openresty.org"', ] --- no_error_log SSL reused session -[error] [alert] +[emerg] --- timeout: 5 -=== TEST 18: openresty.org: passing SSL verify: keepalive (reuse the ssl session) +=== TEST 17: openresty.org: passing SSL verify: keepalive (reuse the ssl session) --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -1458,6 +1413,7 @@ SSL reused session location /t { #set $port 5000; set $port $TEST_NGINX_MEMCACHED_PORT; + set $openresty_org_ip $TEST_NGINX_OPENRESTY_ORG_IP; content_by_lua ' local sock = ngx.socket.tcp() @@ -1467,7 +1423,8 @@ SSL reused session local session for i = 1, 3 do - local ok, err = sock:connect("openresty.org", 443) + -- Use the same IP to ensure that the connection can be reused + local ok, err = sock:connect(ngx.var.openresty_org_ip, 443) if not ok then ngx.say("failed to connect: ", err) return @@ -1500,13 +1457,13 @@ $::DSTRootCertificate" GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata set keepalive: 1 nil connected: 1 -ssl handshake: userdata +ssl handshake: cdata set keepalive: 1 nil connected: 1 -ssl handshake: userdata +ssl handshake: cdata set keepalive: 1 nil --- log_level: debug @@ -1526,7 +1483,73 @@ SSL reused session -=== TEST 19: openresty.org: passing SSL verify: keepalive (no reusing the ssl session) +=== TEST 18: openresty.org: passing SSL verify: keepalive (no reusing the ssl session) +The session returned by SSL_get1_session maybe different. +After function tls_process_new_session_ticket, the session saved in SSL->session +will be replace by a new one. + +ngx_ssl_session_t * +ngx_ssl_get_session(ngx_connection_t *c) +{ +#ifdef TLS1_3_VERSION + if (c->ssl->session) { + SSL_SESSION_up_ref(c->ssl->session); + return c->ssl->session; + } +#endif + + return SSL_get1_session(c->ssl->connection); +} + +SSL_SESSION *SSL_get1_session(SSL *ssl) +/* variant of SSL_get_session: caller really gets something */ +{ + SSL_SESSION *sess; + /* + * Need to lock this all up rather than just use CRYPTO_add so that + * somebody doesn't free ssl->session between when we check it's non-null + * and when we up the reference count. + */ + CRYPTO_THREAD_read_lock(ssl->lock); + sess = ssl->session; + if (sess) + SSL_SESSION_up_ref(sess); + CRYPTO_THREAD_unlock(ssl->lock); + return sess; +} + +#0 tls_process_new_session_ticket (s=0x7e6ea0, pkt=0x7fffffffc820) at ssl/statem/statem_clnt.c:2650 +#1 0x00007ffff7af50fd in read_state_machine (s=0x7e6ea0) at ssl/statem/statem.c:636 +#2 state_machine (s=0x7e6ea0, server=0) at ssl/statem/statem.c:434 +#3 0x00007ffff7aca6b3 in ssl3_read_bytes (s=, type=23, recvd_type=0x0, buf=0x7fffffffc9d7 "\027\320\355t", len=1, + peek=0, readbytes=0x7fffffffc978) at ssl/record/rec_layer_s3.c:1677 +#4 0x00007ffff7ad2250 in ssl3_read_internal (readbytes=0x7fffffffc978, peek=0, len=1, buf=0x7fffffffc9d7, s=0x7e6ea0) + at ssl/s3_lib.c:4477 +#5 ssl3_read (s=0x7e6ea0, buf=0x7fffffffc9d7, len=1, readbytes=0x7fffffffc978) at ssl/s3_lib.c:4500 +#6 0x00007ffff7ade695 in SSL_read (s=, buf=buf@entry=0x7fffffffc9d7, num=num@entry=1) at ssl/ssl_lib.c:1799 +#7 0x000000000045a965 in ngx_ssl_recv (c=0x72c3b0, buf=0x7fffffffc9d7 "\027\320\355t", size=1) + at src/event/ngx_event_openssl.c:2337 +#8 0x0000000000533b17 in ngx_http_lua_socket_keepalive_close_handler (ev=0x7e2f20) + at /var/code/openresty/lua-nginx-module/src/ngx_http_lua_socket_tcp.c:5753 +#9 0x000000000052cf40 in ngx_http_lua_socket_tcp_setkeepalive (L=0x74edd0) + at /var/code/openresty/lua-nginx-module/src/ngx_http_lua_socket_tcp.c:5602 +#10 0x00007ffff7f0fabe in lj_BC_FUNCC () + from /tmp/undodb.72729.1722915526.2470007.80d50d088e818fd4/debuggee-1-zwqz8svp/symbol-files/opt/luajit-sysm/lib/libluajit-5.1.so.2 +#11 0x000000000051f2b2 in ngx_http_lua_run_thread (L=L@entry=0x767670, r=r@entry=0x7edf80, ctx=ctx@entry=0x750e40, nrets=0) + at /var/code/openresty/lua-nginx-module/src/ngx_http_lua_util.c:1194 +#12 0x0000000000524347 in ngx_http_lua_content_by_chunk (L=0x767670, r=0x7edf80) + at /var/code/openresty/lua-nginx-module/src/ngx_http_lua_contentby.c:124 +#13 0x000000000047c663 in ngx_http_core_content_phase (r=0x7edf80, ph=0x7b4470) at src/http/ngx_http_core_module.c:1271 +#14 0x000000000047b80d in ngx_http_core_run_phases (r=0x7edf80) at src/http/ngx_http_core_module.c:885 +#15 ngx_http_handler (r=r@entry=0x7edf80) at src/http/ngx_http_core_module.c:868 +#16 0x00000000004854ad in ngx_http_process_request (r=r@entry=0x7edf80) at src/http/ngx_http_request.c:2140 +#17 0x00000000004868e8 in ngx_http_process_request_headers (rev=rev@entry=0x7e2f80) at src/http/ngx_http_request.c:1529 +#18 0x0000000000486468 in ngx_http_process_request_line (rev=0x7e2f80) at src/http/ngx_http_request.c:1196 +#19 0x000000000044b338 in ngx_event_process_posted (cycle=cycle@entry=0x721690, posted=0x62f250 ) + at src/event/ngx_event_posted.c:35 +#20 0x000000000044a522 in ngx_process_events_and_timers (cycle=cycle@entry=0x721690) at src/event/ngx_event.c:273 +#21 0x0000000000453819 in ngx_single_process_cycle (cycle=cycle@entry=0x721690) at src/os/unix/ngx_process_cycle.c:323 +#22 0x0000000000429dee in main (argc=argc@entry=5, argv=argv@entry=0x7fffffffd1a8) at src/core/nginx.c:384 --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -1534,6 +1557,7 @@ SSL reused session lua_ssl_verify_depth 2; location /t { #set $port 5000; + set $openresty_org_ip $TEST_NGINX_OPENRESTY_ORG_IP; set $port $TEST_NGINX_MEMCACHED_PORT; content_by_lua ' @@ -1543,7 +1567,8 @@ SSL reused session do for i = 1, 3 do - local ok, err = sock:connect("openresty.org", 443) + -- Use the same IP to ensure that the connection can be reused + local ok, err = sock:connect(ngx.var.openresty_org_ip, 443) if not ok then ngx.say("failed to connect: ", err) return @@ -1576,24 +1601,24 @@ $::DSTRootCertificate" GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata set keepalive: 1 nil connected: 1 -ssl handshake: userdata +ssl handshake: cdata set keepalive: 1 nil connected: 1 -ssl handshake: userdata +ssl handshake: cdata set keepalive: 1 nil --- log_level: debug --- grep_error_log eval: qr/lua ssl (?:set|save|free) session: [0-9A-F]+/ --- grep_error_log_out eval qr/^lua ssl save session: ([0-9A-F]+) -lua ssl save session: \1 -lua ssl save session: \1 -lua ssl free session: \1 -lua ssl free session: \1 -lua ssl free session: \1 +lua ssl save session: ([0-9A-F]+) +lua ssl save session: ([0-9A-F]+) +lua ssl free session: ([0-9A-F]+) +lua ssl free session: ([0-9A-F]+) +lua ssl free session: ([0-9A-F]+) $/ --- error_log @@ -1606,7 +1631,7 @@ SSL reused session -=== TEST 20: downstream cosockets do not support ssl handshake +=== TEST 19: downstream cosockets do not support ssl handshake --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -1644,10 +1669,11 @@ attempt to call method 'sslhandshake' (a nil value) --- no_error_log [alert] --- timeout: 3 +--- skip_eval: 5:$ENV{TEST_NGINX_USE_HTTP3} -=== TEST 21: unix domain ssl cosocket (no verify) +=== TEST 20: unix domain ssl cosocket (no verify) --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -1719,7 +1745,7 @@ attempt to call method 'sslhandshake' (a nil value) GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -1750,7 +1776,7 @@ SSL reused session -=== TEST 22: unix domain ssl cosocket (verify) +=== TEST 21: unix domain ssl cosocket (verify) --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -1824,7 +1850,7 @@ SSL reused session GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -1856,7 +1882,7 @@ SSL reused session -=== TEST 23: unix domain ssl cosocket (no ssl on server) +=== TEST 22: unix domain ssl cosocket (no ssl on server) --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; @@ -1937,7 +1963,7 @@ $::TestCertificate" --- grep_error_log eval: qr/lua ssl (?:set|save|free) session: [0-9A-F]+/ --- grep_error_log_out --- error_log eval -qr/SSL_do_handshake\(\) failed .*?(unknown protocol|wrong version number)/ +qr/SSL_do_handshake\(\) failed .*?(unknown protocol|wrong version number|routines::record layer failure)/ --- no_error_log lua ssl server name: SSL reused session @@ -1946,7 +1972,7 @@ SSL reused session -=== TEST 24: lua_ssl_crl +=== TEST 23: lua_ssl_crl --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -2020,10 +2046,18 @@ SSL reused session --- request GET /t ---- response_body -connected: 1 +--- response_body eval +# Since nginx version 1.19.1, invalidity date is considered a non-critical CRL +# entry extension, in other words, revoke still works even if CRL has expired. +$Test::Nginx::Util::NginxVersion >= 1.019001 ? + +"connected: 1 +failed to do SSL handshake: 23: certificate revoked +failed to send http request: closed\n" : + +"connected: 1 failed to do SSL handshake: 12: CRL has expired -failed to send http request: closed +failed to send http request: closed\n"; --- user_files eval ">>> test.key @@ -2045,7 +2079,7 @@ SSL reused session -=== TEST 25: multiple handshake calls +=== TEST 24: multiple handshake calls --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -2105,8 +2139,8 @@ SSL reused session GET /t --- response_body connected: 1 -ssl handshake: userdata -ssl handshake: userdata +ssl handshake: cdata +ssl handshake: cdata sent http request: 58 bytes. received: HTTP/1.1 302 Moved Temporarily close: 1 nil @@ -2129,7 +2163,7 @@ SSL reused session -=== TEST 26: handshake timed out +=== TEST 25: handshake timed out --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -2173,8 +2207,6 @@ failed to do SSL handshake: timeout --- log_level: debug --- grep_error_log eval: qr/lua ssl (?:set|save|free) session: [0-9A-F]+/ --- grep_error_log_out ---- error_log -lua ssl server name: "openresty.org" --- no_error_log SSL reused session [error] @@ -2183,7 +2215,7 @@ SSL reused session -=== TEST 27: unix domain ssl cosocket (no gen session) +=== TEST 26: unix domain ssl cosocket (no gen session) --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -2254,7 +2286,7 @@ SSL reused session -=== TEST 28: unix domain ssl cosocket (gen session, true) +=== TEST 27: unix domain ssl cosocket (gen session, true) --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -2306,7 +2338,7 @@ SSL reused session GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- user_files eval ">>> test.key @@ -2328,7 +2360,7 @@ SSL reused session -=== TEST 29: unix domain ssl cosocket (keepalive) +=== TEST 28: unix domain ssl cosocket (keepalive) --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -2405,7 +2437,7 @@ SSL reused session -=== TEST 30: unix domain ssl cosocket (verify cert but no host name check, passed) +=== TEST 29: unix domain ssl cosocket (verify cert but no host name check, passed) --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -2479,7 +2511,7 @@ SSL reused session GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -2510,7 +2542,7 @@ SSL reused session -=== TEST 31: unix domain ssl cosocket (verify cert but no host name check, NOT passed) +=== TEST 30: unix domain ssl cosocket (verify cert but no host name check, NOT passed) --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -2582,10 +2614,10 @@ SSL reused session --- request GET /t ---- response_body -connected: 1 -failed to do SSL handshake: 18: self signed certificate - +--- response_body eval +qr/connected: 1 +failed to do SSL handshake: 18: self[- ]signed certificate +/ms --- user_files eval ">>> test.key $::TestCertificateKey @@ -2594,8 +2626,8 @@ $::TestCertificate" --- grep_error_log eval: qr/lua ssl (?:set|save|free) session: [0-9A-F]+/ --- grep_error_log_out ---- error_log -lua ssl certificate verify error: (18: self signed certificate) +--- error_log eval +qr/lua ssl certificate verify error: \(18: self[- ]signed certificate\)/ --- no_error_log SSL reused session [alert] @@ -2603,7 +2635,7 @@ SSL reused session -=== TEST 32: handshake, too many arguments +=== TEST 31: handshake, too few arguments --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; @@ -2613,7 +2645,7 @@ SSL reused session content_by_lua_block { local sock = ngx.socket.tcp() - sock:settimeout(2000) + sock:settimeout(7000) local ok, err = sock:connect("openresty.org", 443) if not ok then @@ -2634,4 +2666,383 @@ GET /t qr/\[error\] .* ngx.socket sslhandshake: expecting 1 ~ 5 arguments \(including the object\), but seen 0/ --- no_error_log [alert] ---- timeout: 5 +--- timeout: 10 +--- curl_error eval +qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server# + + + +=== TEST 32: default cipher -TLSv1.3 +--- skip_openssl: 8: < 1.1.1 +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_protocols TLSv1.3; + + location / { + content_by_lua_block { + ngx.exit(200) + } + } + } +--- config + server_tokens off; + lua_ssl_protocols TLSv1.3; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + do + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local session, err = sock:sslhandshake(nil, "test.com") + if not session then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(session)) + + local req = "GET / HTTP/1.1\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + local line, err = sock:receive() + if not line then + ngx.say("failed to receive response status line: ", err) + return + end + + ngx.say("received: ", line) + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + collectgarbage() + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 53 bytes. +received: HTTP/1.1 200 OK +close: 1 nil + +--- log_level: debug +--- grep_error_log eval: qr/lua ssl (?:set|save|free) session: [0-9A-F]+/ +--- grep_error_log_out eval +qr/^lua ssl save session: ([0-9A-F]+) +lua ssl free session: ([0-9A-F]+) +$/ +--- error_log eval +['lua ssl server name: "test.com"', +qr/SSL: TLSv1.3, cipher: "TLS_AES_256_GCM_SHA384 TLSv1.3/] +--- no_error_log +SSL reused session +[error] +[alert] +--- timeout: 10 + + + +=== TEST 33: explicit cipher configuration - TLSv1.3 +--- skip_openssl: 8: < 1.1.1 +--- skip_nginx: 8: < 1.19.4 +--- skip_eval: 8:$ENV{TEST_NGINX_USE_BORINGSSL} +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_protocols TLSv1.3; + + location / { + content_by_lua_block { + ngx.exit(200) + } + } + } +--- config + server_tokens off; + lua_ssl_protocols TLSv1.3; + lua_ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + do + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local session, err = sock:sslhandshake(nil, "test.com") + if not session then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(session)) + + local req = "GET / HTTP/1.1\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + local line, err = sock:receive() + if not line then + ngx.say("failed to receive response status line: ", err) + return + end + + ngx.say("received: ", line) + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + collectgarbage() + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 53 bytes. +received: HTTP/1.1 200 OK +close: 1 nil + +--- log_level: debug +--- grep_error_log eval: qr/lua ssl (?:set|save|free) session: [0-9A-F]+/ +--- grep_error_log_out eval +qr/^lua ssl save session: ([0-9A-F]+) +lua ssl free session: ([0-9A-F]+) +$/ +--- error_log eval +['lua ssl server name: "test.com"', +qr/SSL: TLSv1.3, cipher: "TLS_AES_128_GCM_SHA256 TLSv1.3/] +--- no_error_log +SSL reused session +[error] +[alert] +--- timeout: 10 + + + +=== TEST 34: explicit cipher configuration not in the default list - TLSv1.3 +--- skip_openssl: 8: < 1.1.1 +--- skip_nginx: 8: < 1.19.4 +--- skip_eval: 8:$ENV{TEST_NGINX_USE_BORINGSSL} +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_protocols TLSv1.3; + + location / { + content_by_lua_block { + ngx.exit(200) + } + } + } +--- config + server_tokens off; + lua_ssl_protocols TLSv1.3; + lua_ssl_conf_command Ciphersuites TLS_AES_128_CCM_SHA256; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + do + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local session, err = sock:sslhandshake(nil, "test.com") + if not session then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(session)) + + local req = "GET / HTTP/1.1\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + local line, err = sock:receive() + if not line then + ngx.say("failed to receive response status line: ", err) + return + end + + ngx.say("received: ", line) + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + collectgarbage() + } + } +--- request +GET /t +--- response_body +connected: 1 +failed to do SSL handshake: handshake failed + +--- log_level: debug +--- grep_error_log eval: qr/lua ssl (?:set|save|free) session: [0-9A-F]+/ +--- grep_error_log_out +--- error_log eval +[ +qr/\[info\] .*?SSL_do_handshake\(\) failed .*?no shared cipher/, +'lua ssl server name: "test.com"', +] +--- no_error_log +SSL reused session +[alert] +[emerg] +--- timeout: 10 + + + +=== TEST 35: lua_ssl_key_log directive +--- skip_openssl: 8: < 1.1.1 +--- http_config + server { + listen $TEST_NGINX_SERVER_SSL_PORT ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_protocols TLSv1.3; + + location / { + content_by_lua_block { + ngx.exit(200) + } + } + } +--- config + server_tokens off; + lua_ssl_protocols TLSv1.3; + lua_ssl_key_log sslkey.log; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + do + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_SSL_PORT) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local session, err = sock:sslhandshake(nil, "test.com") + if not session then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(session)) + + local req = "GET / HTTP/1.1\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + local line, err = sock:receive() + if not line then + ngx.say("failed to receive response status line: ", err) + return + end + + ngx.say("received: ", line) + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + + local f, err = io.open("$TEST_NGINX_SERVER_ROOT/conf/sslkey.log", "r") + if not f then + ngx.log(ngx.ERR, "failed to open sslkey.log: ", err) + return + end + + local key_log = f:read("*a") + ngx.say(key_log) + f:close() + end -- do + collectgarbage() + } + } +--- request +GET /t +--- response_body_like +connected: 1 +ssl handshake: cdata +sent http request: 53 bytes. +received: HTTP/1.1 200 OK +close: 1 nil +SERVER_HANDSHAKE_TRAFFIC_SECRET [0-9a-z\s]+ +EXPORTER_SECRET [0-9a-z\s]+ +SERVER_TRAFFIC_SECRET_0 [0-9a-z\s]+ +CLIENT_HANDSHAKE_TRAFFIC_SECRET [0-9a-z\s]+ +CLIENT_TRAFFIC_SECRET_0 [0-9a-z\s]+ + +--- log_level: debug +--- error_log eval +['lua ssl server name: "test.com"', +qr/SSL: TLSv1.3, cipher: "TLS_AES_256_GCM_SHA384 TLSv1.3/] +--- no_error_log +SSL reused session +[error] +[alert] +--- timeout: 10 diff --git a/t/130-internal-api.t b/t/130-internal-api.t index eba0980f8f..2158e878be 100644 --- a/t/130-internal-api.t +++ b/t/130-internal-api.t @@ -8,7 +8,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); -plan tests => repeat_each() * 3; +plan tests => repeat_each() * blocks() * 3; #no_diff(); no_long_string(); @@ -19,12 +19,7 @@ run_tests(); __DATA__ -=== TEST 1: __ngx_req and __ngx_cycle ---- http_config - init_by_lua ' - my_cycle = __ngx_cycle - '; - +=== TEST 1: req --- config location = /t { content_by_lua ' @@ -32,18 +27,14 @@ __DATA__ local function tonum(ud) return tonumber(ffi.cast("uintptr_t", ud)) end - ngx.say(string.format("init: cycle=%#x", tonum(my_cycle))) - ngx.say(string.format("content cycle=%#x", tonum(__ngx_cycle))) - ngx.say(string.format("content req=%#x", tonum(__ngx_req))) + ngx.say(string.format("content req=%#x", tonum(exdata()))) '; } --- request GET /t --- response_body_like chop -^init: cycle=(0x[a-f0-9]{4,}) -content cycle=\1 -content req=0x[a-f0-9]{4,} +^content req=0x[a-f0-9]{4,} $ --- no_error_log [error] diff --git a/t/131-duplex-req-socket.t b/t/131-duplex-req-socket.t index 5d698b9344..f88649b8d8 100644 --- a/t/131-duplex-req-socket.t +++ b/t/131-duplex-req-socket.t @@ -1,5 +1,7 @@ # vim:set ft= ts=4 sw=4 et fdm=marker: +our $SkipReason; + BEGIN { if (!defined $ENV{LD_PRELOAD}) { $ENV{LD_PRELOAD} = ''; @@ -18,9 +20,13 @@ BEGIN { $ENV{TEST_NGINX_EVENT_TYPE} = 'poll'; $ENV{MOCKEAGAIN_WRITE_TIMEOUT_PATTERN} = 'slow'; + + if ($ENV{TEST_NGINX_USE_HTTP3}) { + $SkipReason = "http3 does not support ngx.req.socket()"; + } } -use Test::Nginx::Socket::Lua; +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); log_level('debug'); diff --git a/t/132-lua-blocks.t b/t/132-lua-blocks.t index a57d701735..35638a8489 100644 --- a/t/132-lua-blocks.t +++ b/t/132-lua-blocks.t @@ -12,6 +12,8 @@ repeat_each(2); plan tests => repeat_each() * (blocks() * 3 + 3); +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + #no_diff(); no_long_string(); run_tests(); @@ -164,6 +166,7 @@ ok === TEST 8: content_by_lua_block (cosockets) +--- no_http2 --- config server_tokens off; location = /t { @@ -310,7 +313,7 @@ hello, world [error] --- must_die --- error_log eval -qr/\[emerg\] .*? Lua code block missing the closing long bracket "]]" in .*?\bnginx\.conf:41/ +qr/\[emerg\] .*? Lua code block missing the closing long bracket "]]", the inlined Lua code may be too long in .*?\bnginx\.conf:\d+/ @@ -329,7 +332,7 @@ hello, world [error] --- must_die --- error_log eval -qr/\[emerg\] .*? Lua code block missing the closing long bracket "]==]" in .*?\bnginx\.conf:41/ +qr/\[emerg\] .*? Lua code block missing the closing long bracket "]==]", the inlined Lua code may be too long in .*?\bnginx.conf:\d+/ @@ -348,7 +351,7 @@ hello, world [error] --- must_die --- error_log eval -qr/\[emerg\] .*? Lua code block missing the closing long bracket "]]" in .*?\bnginx\.conf:41/ +qr/\[emerg\] .*? Lua code block missing the closing long bracket "]]", the inlined Lua code may be too long in .*?\bnginx\.conf:\d+/ @@ -367,7 +370,7 @@ hello, world [error] --- must_die --- error_log eval -qr/\[emerg\] .*? Lua code block missing the closing long bracket "]=]" in .*?\bnginx\.conf:41/ +qr/\[emerg\] .*? Lua code block missing the closing long bracket "]=]", the inlined Lua code may be too long in .*?\bnginx\.conf:\d+/ @@ -604,3 +607,38 @@ GET /t --- response_body_like: } --- no_error_log [error] + + + +=== TEST 24: too long bracket +--- config + location = /t { + content_by_lua_block { + local foo = [[ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ]] + ngx.say(foo) + } + } +--- request +GET /t +--- response_body +hello, world +--- no_error_log +[error] +--- must_die +--- error_log eval +qr/\[emerg\] .*? Lua code block missing the closing long bracket "]]", the inlined Lua code may be too long in .*?\bnginx\.conf:\d+/ diff --git a/t/134-worker-count-5.t b/t/134-worker-count-5.t index b257c12e61..ec6d0e7e35 100644 --- a/t/134-worker-count-5.t +++ b/t/134-worker-count-5.t @@ -3,7 +3,7 @@ use Test::Nginx::Socket::Lua; #worker_connections(1014); -#master_on(); +master_on(); workers(5); #log_level('warn'); @@ -55,7 +55,7 @@ workers: 5 === TEST 3: init_by_lua + module (github #681) --- http_config - lua_package_path "t/servroot/html/?.lua;;"; + lua_package_path "$TEST_NGINX_SERVER_ROOT/html/?.lua;;"; init_by_lua_block { local blah = require "file" diff --git a/t/138-balancer-upstream-bind.t b/t/138-balancer-upstream-bind.t new file mode 100644 index 0000000000..442f37c8f3 --- /dev/null +++ b/t/138-balancer-upstream-bind.t @@ -0,0 +1,142 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +#worker_connections(1014); +#master_on(); +#workers(2); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: bind to empty +--- no_http2 +--- http_config + lua_package_path "$TEST_NGINX_SERVER_ROOT/html/?.lua;;"; + + upstream backend { + server 127.0.0.1:$TEST_NGINX_SERVER_PORT; + } +--- config + set $proxy_local_addr ""; + proxy_bind $proxy_local_addr; + + location = /t { + proxy_pass http://backend/back; + } + + location = /back { + echo ok; + } + +--- request + GET /t +--- response_body +ok +--- no_error_log +[cirt] + + + +=== TEST 2: bind to 127.0.0.1 +--- no_http2 +--- http_config + lua_package_path "$TEST_NGINX_SERVER_ROOT/html/?.lua;;"; + + upstream backend { + server 127.0.0.1:$TEST_NGINX_SERVER_PORT; + } +--- config + set $proxy_local_addr ""; + proxy_bind $proxy_local_addr; + + location = /t { + access_by_lua_block { + ngx.var.proxy_local_addr="127.0.0.1" + } + proxy_pass http://backend/back; + } + + location = /back { + echo ok; + } + +--- request + GET /t +--- response_body +ok +--- no_error_log +[cirt] + + + +=== TEST 3: bind to 127.0.0.10 +--- no_http2 +--- http_config + lua_package_path "$TEST_NGINX_SERVER_ROOT/html/?.lua;;"; + + upstream backend { + server 127.0.0.1:$TEST_NGINX_SERVER_PORT; + } +--- config + set $proxy_local_addr ""; + proxy_bind $proxy_local_addr; + + location = /t { + access_by_lua_block { + ngx.var.proxy_local_addr="127.0.0.10" + } + proxy_pass http://backend/back; + } + + location = /back { + echo ok; + } + +--- request + GET /t +--- response_body +ok +--- no_error_log +[cirt] + + + +=== TEST 4: bind to not exist addr 100.100.100.100 +--- no_http2 +--- http_config + lua_package_path "$TEST_NGINX_SERVER_ROOT/html/?.lua;;"; + + upstream backend { + server 127.0.0.1:$TEST_NGINX_SERVER_PORT; + } +--- config + set $proxy_local_addr ""; + proxy_bind $proxy_local_addr; + + location = /t { + access_by_lua_block { + ngx.var.proxy_local_addr="100.100.100.100" + } + proxy_pass http://backend/back; + } + + location = /back { + echo ok; + } + +--- request + GET /t +--- response_body_like chomp +500 Internal Server Error +--- error_code: 500 +--- error_log +bind(100.100.100.100) failed (99: Cannot assign requested address) diff --git a/t/138-balancer.t b/t/138-balancer.t index ddd0da577a..f48c7fa41d 100644 --- a/t/138-balancer.t +++ b/t/138-balancer.t @@ -7,9 +7,12 @@ use Test::Nginx::Socket::Lua; #workers(2); #log_level('warn'); +#connect 0.0.0.1 on newer kernel won't return EINVAL +#so add an route with cmd: sudo ip route add prohibit 0.0.0.1/32 + repeat_each(2); -plan tests => repeat_each() * (blocks() * 4 + 9); +plan tests => repeat_each() * (blocks() * 4 - 3); #no_diff(); no_long_string(); @@ -35,11 +38,9 @@ __DATA__ --- error_code: 502 --- error_log eval [ -'[lua] balancer_by_lua:2: hello from balancer by lua! while connecting to upstream,', +'[lua] balancer_by_lua(nginx.conf:27):2: hello from balancer by lua! while connecting to upstream,', qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\.0\.1:80/t"}, ] ---- no_error_log -[warn] @@ -61,10 +62,9 @@ qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\ --- response_body_like: 403 Forbidden --- error_code: 403 --- error_log -[lua] balancer_by_lua:2: hello from balancer by lua! while connecting to upstream, +[lua] balancer_by_lua(nginx.conf:27):2: hello from balancer by lua! while connecting to upstream, --- no_error_log eval [ -'[warn]', qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\.0\.1:80/t"}, ] @@ -89,11 +89,9 @@ qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\ --- error_code: 502 --- error_log eval [ -'[lua] balancer_by_lua:2: hello from balancer by lua! while connecting to upstream,', +'[lua] balancer_by_lua(nginx.conf:27):2: hello from balancer by lua! while connecting to upstream,', qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\.0\.1:80/t"}, ] ---- no_error_log -[warn] @@ -122,8 +120,6 @@ qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\ "2: variable foo = 33", qr/\[crit\] .* connect\(\) .*? failed/, ] ---- no_error_log -[warn] @@ -150,8 +146,6 @@ Foo: bar "header foo: bar", qr/\[crit\] .* connect\(\) .*? failed/, ] ---- no_error_log -[warn] @@ -160,7 +154,7 @@ qr/\[crit\] .* connect\(\) .*? failed/, upstream backend { server 0.0.0.1; balancer_by_lua_block { - print("arg foo: ", ngx.req.get_uri_args()["foo"]) + print("arg foo: ", (ngx.req.get_uri_args())["foo"]) } } --- config @@ -177,12 +171,11 @@ Foo: bar ["arg foo: bar", qr/\[crit\] .* connect\(\) .*? failed/, ] ---- no_error_log -[warn] === TEST 7: ngx.req.get_method() works +--- no_http2 --- http_config upstream backend { server 0.0.0.1; @@ -205,8 +198,6 @@ Foo: bar "method: GET", qr/\[crit\] .* connect\(\) .*? failed/, ] ---- no_error_log -[warn] @@ -232,8 +223,6 @@ print("hello from balancer by lua!") '[lua] a.lua:1: hello from balancer by lua! while connecting to upstream,', qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\.0\.1:80/t"}, ] ---- no_error_log -[warn] @@ -254,7 +243,7 @@ qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\ --- response_body_like: 500 Internal Server Error --- error_code: 500 --- error_log eval -qr/\[error\] .*? failed to run balancer_by_lua\*: balancer_by_lua:2: API disabled in the context of balancer_by_lua\*/ +qr/\[error\] .*? failed to run balancer_by_lua\*: balancer_by_lua\(nginx\.conf:27\):2: API disabled in the context of balancer_by_lua\*/ @@ -275,7 +264,7 @@ qr/\[error\] .*? failed to run balancer_by_lua\*: balancer_by_lua:2: API disable --- response_body_like: 500 Internal Server Error --- error_code: 500 --- error_log eval -qr/\[error\] .*? failed to run balancer_by_lua\*: balancer_by_lua:2: API disabled in the context of balancer_by_lua\*/ +qr/\[error\] .*? failed to run balancer_by_lua\*: balancer_by_lua\(nginx\.conf:27\):2: API disabled in the context of balancer_by_lua\*/ @@ -306,8 +295,9 @@ qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\ === TEST 12: code cache off +--- no_http2 --- http_config - lua_package_path "t/servroot/html/?.lua;;"; + lua_package_path "$TEST_NGINX_SERVER_ROOT/html/?.lua;;"; lua_code_cache off; @@ -327,7 +317,7 @@ qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\ location = /update { content_by_lua_block { -- os.execute("(echo HERE; pwd) > /dev/stderr") - local f = assert(io.open("t/servroot/html/test.lua", "w")) + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/test.lua", "w")) f:write("print('me: ', 101)") f:close() ngx.say("updated") @@ -362,8 +352,6 @@ me: 101 === TEST 13: lua subrequests --- http_config - lua_package_path "t/servroot/html/?.lua;;"; - lua_code_cache off; upstream backend { @@ -426,15 +414,13 @@ ctx counter: nil --- error_code: 502 --- error_log eval [ -'[lua] balancer_by_lua:2: hello from balancer by lua! while connecting to upstream,', +'[lua] balancer_by_lua(nginx.conf:27):2: hello from balancer by lua! while connecting to upstream,', qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\.0\.1:80/t"}, ] ---- no_error_log -[warn] -=== TEST 15: test if execeed proxy_next_upstream_limit +=== TEST 15: test if exceed proxy_next_upstream_limit --- http_config lua_package_path "../lua-resty-core/lib/?.lua;;"; @@ -526,3 +512,151 @@ http next upstream, 2 --- no_error_log failed to set more tries: reduced tries due to limit [alert] + + + +=== TEST 17: recreate_request buffer bugfix +--- http_config + lua_package_path "../lua-resty-core/lib/?.lua;;"; + + server { + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1; + + location / { + return 200 "it works"; + } + } + + upstream foo { + server 127.0.0.1:$TEST_NGINX_RAND_PORT_1 max_fails=0; + server 127.0.0.1:$TEST_NGINX_RAND_PORT_2 max_fails=0 weight=9999; + + balancer_by_lua_block { + local bal = require "ngx.balancer" + + assert(bal.recreate_request()) + } + } + +--- config + location = /t { + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_pass http://foo; + } +--- request +GET /t +--- error_code: 200 +--- error_log +connect() failed (111: Connection refused) while connecting to upstream +--- no_error_log +upstream sent more data than specified in "Content-Length" header while reading upstream +[alert] + + + +=== TEST 18: error in balancer_by_lua_block +--- http_config + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + ngx.say("hello" + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- request + GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log eval + "failed to load inlined Lua code: balancer_by_lua(nginx.conf:27):3: ')' expected (to close '(' at line 2) near ''", + + + +=== TEST 19: disable ssl +--- http_config + lua_package_path "$TEST_NGINX_SERVER_ROOT/html/?.lua;;"; + + upstream backend { + server 127.0.0.1:$TEST_NGINX_SERVER_PORT; + balancer_by_lua_block { + local ffi = require "ffi" + local C = ffi.C +ffi.cdef[[ +int +ngx_http_lua_ffi_balancer_set_upstream_tls(ngx_http_request_t *r, int on, char **err); +]] + local errmsg = ffi.new("char *[1]") + local r = require "resty.core.base" .get_request() + if r == nil then + ngx.log(ngx.ERR, "no request found") + return + end + + local rc = C.ngx_http_lua_ffi_balancer_set_upstream_tls(r, 0, errmsg) + if rc < 0 then + ngx.log(ngx.ERR, "failed to disable ssl: ", ffi.string(errmsg[0])) + return + end + } + } +--- config + location = /t { + proxy_pass https://backend/back; + } + + location = /back { + echo ok; + } + +--- request + GET /t +--- response_body +ok +--- no_error_log +[error] +[cirt] + + + +=== TEST 20: recreate_request refresh body buffer when ngx.req.set_body_data is used in balancer phase +--- http_config + lua_package_path "../lua-resty-core/lib/?.lua;;"; + + server { + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1; + + location / { + content_by_lua_block { + ngx.req.read_body() + local body = ngx.req.get_body_data() + ngx.log(ngx.ERR, "body: ", body) + ngx.say(body) + } + } + } + + upstream foo { + server 127.0.0.1:$TEST_NGINX_RAND_PORT_1 max_fails=0; + + balancer_by_lua_block { + local bal = require "ngx.balancer" + ngx.req.set_body_data("hello world") + assert(bal.recreate_request()) + } + } + +--- config + location = /t { + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_pass http://foo; + } +--- request +GET /t +--- error_code: 200 +--- response_body +hello world diff --git a/t/139-ssl-cert-by.t b/t/139-ssl-cert-by.t index a65d7038e3..d905cb3f16 100644 --- a/t/139-ssl-cert-by.t +++ b/t/139-ssl-cert-by.t @@ -7,15 +7,19 @@ repeat_each(3); # All these tests need to have new openssl my $NginxBinary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; my $openssl_version = eval { `$NginxBinary -V 2>&1` }; +if ($openssl_version =~ m/BoringSSL/) { + $ENV{TEST_NGINX_USE_BORINGSSL} = 1; +} if ($openssl_version =~ m/built with OpenSSL (0|1\.0\.(?:0|1[^\d]|2[a-d]).*)/) { - plan(skip_all => "too old OpenSSL, need 1.0.2e, was $1"); + plan(skip_all => "too old OpenSSL, need >= 1.0.2e, was $1"); } else { plan tests => repeat_each() * (blocks() * 6 + 4); } $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); $ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; +$ENV{TEST_NGINX_QUIC_IDLE_TIMEOUT} ||= 0.6; #log_level 'warn'; log_level 'debug'; @@ -100,7 +104,7 @@ __DATA__ GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -117,13 +121,18 @@ lua ssl server name: "test.com" --- no_error_log [error] [alert] ---- grep_error_log eval: qr/ssl_certificate_by_lua:.*?,|\bssl cert: connection reusable: \d+|\breusable connection: \d+/ +--- grep_error_log eval: qr/ssl_certificate_by_lua\(nginx.conf:\d+\):.*?,|\bssl cert: connection reusable: \d+|\breusable connection: \d+/ --- grep_error_log_out eval -qr/reusable connection: 1 +# Since nginx version 1.17.9, nginx call ngx_reusable_connection(c, 0) +# before call ssl callback function +$Test::Nginx::Util::NginxVersion >= 1.017009 ? +qr/reusable connection: 0 +ssl cert: connection reusable: 0 +ssl_certificate_by_lua\(nginx.conf:28\):1: ssl cert by lua is running!,/ +: qr /reusable connection: 1 ssl cert: connection reusable: 1 reusable connection: 0 -ssl_certificate_by_lua:1: ssl cert by lua is running!, -/ +ssl_certificate_by_lua\(nginx.conf:28\):1: ssl cert by lua is running!,/ @@ -204,7 +213,7 @@ ssl_certificate_by_lua:1: ssl cert by lua is running!, GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -218,7 +227,7 @@ close: 1 nil --- error_log eval [ 'lua ssl server name: "test.com"', -qr/elapsed in ssl cert by lua: 0.(?:09|1[01])\d+,/, +qr/elapsed in ssl cert by lua: 0.(?:09|1\d)\d+,/, ] --- no_error_log @@ -309,7 +318,7 @@ qr/elapsed in ssl cert by lua: 0.(?:09|1[01])\d+,/, GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -427,7 +436,7 @@ my timer run! GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -451,7 +460,7 @@ received memc reply: OK === TEST 5: ngx.exit(0) - no yield --- http_config server { - listen 127.0.0.2:8080 ssl; + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; server_name test.com; ssl_certificate_by_lua_block { ngx.exit(0) @@ -479,7 +488,7 @@ received memc reply: OK sock:settimeout(2000) - local ok, err = sock:connect("127.0.0.2", 8080) + local ok, err = sock:connect("127.0.0.2", $TEST_NGINX_RAND_PORT_1) if not ok then ngx.say("failed to connect: ", err) return @@ -518,7 +527,7 @@ should never reached here === TEST 6: ngx.exit(ngx.ERROR) - no yield --- http_config server { - listen 127.0.0.2:8080 ssl; + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; server_name test.com; ssl_certificate_by_lua_block { ngx.exit(ngx.ERROR) @@ -546,7 +555,7 @@ should never reached here sock:settimeout(2000) - local ok, err = sock:connect("127.0.0.2", 8080) + local ok, err = sock:connect("127.0.0.2", $TEST_NGINX_RAND_PORT_1) if not ok then ngx.say("failed to connect: ", err) return @@ -573,8 +582,8 @@ failed to do SSL handshake: handshake failed --- error_log eval [ -'lua_certificate_by_lua: handler return value: -1, cert cb exit code: 0', -qr/\[crit\] .*? SSL_do_handshake\(\) failed .*?cert cb error/, +'ssl_certificate_by_lua: handler return value: -1, cert cb exit code: 0', +qr/(\[info\] .*? SSL_do_handshake\(\) failed .*?cert cb error|routines:OPENSSL_internal:CERT_CB_ERROR)/, 'lua exit with code -1', ] @@ -588,7 +597,7 @@ should never reached here === TEST 7: ngx.exit(0) - yield --- http_config server { - listen 127.0.0.2:8080 ssl; + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; server_name test.com; ssl_certificate_by_lua_block { ngx.sleep(0.001) @@ -618,7 +627,7 @@ should never reached here sock:settimeout(2000) - local ok, err = sock:connect("127.0.0.2", 8080) + local ok, err = sock:connect("127.0.0.2", $TEST_NGINX_RAND_PORT_1) if not ok then ngx.say("failed to connect: ", err) return @@ -657,7 +666,7 @@ should never reached here === TEST 8: ngx.exit(ngx.ERROR) - yield --- http_config server { - listen 127.0.0.2:8080 ssl; + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; server_name test.com; ssl_certificate_by_lua_block { ngx.sleep(0.001) @@ -687,7 +696,7 @@ should never reached here sock:settimeout(2000) - local ok, err = sock:connect("127.0.0.2", 8080) + local ok, err = sock:connect("127.0.0.2", $TEST_NGINX_RAND_PORT_1) if not ok then ngx.say("failed to connect: ", err) return @@ -714,8 +723,8 @@ failed to do SSL handshake: handshake failed --- error_log eval [ -'lua_certificate_by_lua: cert cb exit code: 0', -qr/\[crit\] .*? SSL_do_handshake\(\) failed .*?cert cb error/, +'ssl_certificate_by_lua: cert cb exit code: 0', +qr/(\[info\] .*? SSL_do_handshake\(\) failed .*?cert cb error|routines:OPENSSL_internal:CERT_CB_ERROR)/, 'lua exit with code -1', ] @@ -729,7 +738,7 @@ should never reached here === TEST 9: lua exception - no yield --- http_config server { - listen 127.0.0.2:8080 ssl; + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; server_name test.com; ssl_certificate_by_lua_block { error("bad bad bad") @@ -757,7 +766,7 @@ should never reached here sock:settimeout(2000) - local ok, err = sock:connect("127.0.0.2", 8080) + local ok, err = sock:connect("127.0.0.2", $TEST_NGINX_RAND_PORT_1) if not ok then ngx.say("failed to connect: ", err) return @@ -784,9 +793,9 @@ failed to do SSL handshake: handshake failed --- error_log eval [ -'runtime error: ssl_certificate_by_lua:2: bad bad bad', -'lua_certificate_by_lua: handler return value: 500, cert cb exit code: 0', -qr/\[crit\] .*? SSL_do_handshake\(\) failed .*?cert cb error/, +'runtime error: ssl_certificate_by_lua(nginx.conf:28):2: bad bad bad', +'ssl_certificate_by_lua: handler return value: 500, cert cb exit code: 0', +qr/(\[info\] .*? SSL_do_handshake\(\) failed .*?cert cb error|routines:OPENSSL_internal:CERT_CB_ERROR)/, qr/context: ssl_certificate_by_lua\*, client: \d+\.\d+\.\d+\.\d+, server: \d+\.\d+\.\d+\.\d+:\d+/, ] @@ -800,7 +809,7 @@ should never reached here === TEST 10: lua exception - yield --- http_config server { - listen 127.0.0.2:8080 ssl; + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; server_name test.com; ssl_certificate_by_lua_block { ngx.sleep(0.001) @@ -829,7 +838,7 @@ should never reached here sock:settimeout(2000) - local ok, err = sock:connect("127.0.0.2", 8080) + local ok, err = sock:connect("127.0.0.2", $TEST_NGINX_RAND_PORT_1) if not ok then ngx.say("failed to connect: ", err) return @@ -856,9 +865,9 @@ failed to do SSL handshake: handshake failed --- error_log eval [ -'runtime error: ssl_certificate_by_lua:3: bad bad bad', -'lua_certificate_by_lua: cert cb exit code: 0', -qr/\[crit\] .*? SSL_do_handshake\(\) failed .*?cert cb error/, +'runtime error: ssl_certificate_by_lua(nginx.conf:28):3: bad bad bad', +'ssl_certificate_by_lua: cert cb exit code: 0', +qr/(\[info\] .*? SSL_do_handshake\(\) failed .*?cert cb error|routines:OPENSSL_internal:CERT_CB_ERROR)/, ] --- no_error_log @@ -919,7 +928,7 @@ should never reached here GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -1045,8 +1054,8 @@ failed to do SSL handshake: handshake failed --- error_log eval [ 'lua ssl server name: "test.com"', -'ssl_certificate_by_lua:1: API disabled in the context of ssl_certificate_by_lua*', -qr/\[crit\] .*?cert cb error/, +'ssl_certificate_by_lua(nginx.conf:28):1: API disabled in the context of ssl_certificate_by_lua*', +qr/(\[info\] .*? SSL_do_handshake\(\) failed .*?cert cb error|routines:OPENSSL_internal:CERT_CB_ERROR)/, ] --- no_error_log @@ -1132,7 +1141,7 @@ print("ssl cert by lua is running!") GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -1243,7 +1252,7 @@ a.lua:1: ssl cert by lua is running! GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -1278,7 +1287,7 @@ lua ssl server name: "test.com" listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; server_name test.com; ssl_certificate_by_lua_block { - function f() + local function f() ngx.sleep(0.01) print("uthread: hello in thread") return "done" @@ -1367,7 +1376,7 @@ lua ssl server name: "test.com" GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -1463,7 +1472,7 @@ GitHub openresty/lua-resty-core#42 GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -1476,7 +1485,7 @@ close: 1 nil --- error_log lua ssl server name: "test.com" -ssl_certificate_by_lua:1: ssl cert by lua is running! +ssl_certificate_by_lua(nginx.conf:25):1: ssl cert by lua is running! --- no_error_log [error] @@ -1561,7 +1570,7 @@ github issue #723 GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -1663,7 +1672,7 @@ ssl_certificate_by_lua:1: ssl cert by lua is running! GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 59 bytes. received: HTTP/1.1 200 OK received: Server: nginx @@ -1676,7 +1685,7 @@ close: 1 nil --- error_log eval [ -'ssl_certificate_by_lua:1: ssl cert by lua is running!', +'ssl_certificate_by_lua(nginx.conf:29):1: ssl cert by lua is running!', 'lua ssl server name: "test.com"', ] --- no_error_log @@ -1775,7 +1784,7 @@ failed to do SSL handshake: handshake failed --- error_log eval [ qr/\[alert\] .*? no ssl_certificate_by_lua\* defined in server test2\.com\b/, -qr/\[crit\] .*? SSL_do_handshake\(\) failed\b/, +qr/\[info\] .*? SSL_do_handshake\(\) failed\b/, ] @@ -1870,7 +1879,7 @@ failed to do SSL handshake: handshake failed --- error_log eval [ qr/\[alert\] .*? no ssl_certificate_by_lua\* defined in server ~test2\\\.com\b/, -qr/\[crit\] .*? SSL_do_handshake\(\) failed\b/, +qr/\[info\] .*? SSL_do_handshake\(\) failed\b/, ] @@ -1880,7 +1889,7 @@ qr/\[crit\] .*? SSL_do_handshake\(\) failed\b/, lua_package_path "../lua-resty-core/lib/?.lua;;"; server { - listen 127.0.0.1:12345 ssl; + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1 ssl; server_name test.com; ssl_certificate_by_lua_block { @@ -1912,7 +1921,7 @@ qr/\[crit\] .*? SSL_do_handshake\(\) failed\b/, sock:settimeout(2000) - local ok, err = sock:connect("127.0.0.1", 12345) + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1) if not ok then ngx.say("failed to connect: ", err) return @@ -1958,7 +1967,7 @@ qr/\[crit\] .*? SSL_do_handshake\(\) failed\b/, GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -2058,7 +2067,7 @@ client ip: 127.0.0.1 GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -2070,8 +2079,691 @@ received: foo close: 1 nil --- error_log -client socket file: +client socket file: + +--- no_error_log +[error] +[alert] + + + +=== TEST 24: ssl_certificate_by_lua* can yield when reading early data +--- skip_openssl: 6: < 1.1.1 +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_early_data on; + server_tokens off; + + ssl_certificate_by_lua_block { + local begin = ngx.now() + ngx.sleep(0.1) + print("elapsed in ssl_certificate_by_lua*: ", ngx.now() - begin) + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: boolean +--- grep_error_log eval +qr/elapsed in ssl_certificate_by_lua\*: 0\.(?:09|1\d)\d+,/, +--- grep_error_log_out eval +[ +qr/elapsed in ssl_certificate_by_lua\*: 0\.(?:09|1\d)\d+,/, +qr/elapsed in ssl_certificate_by_lua\*: 0\.(?:09|1\d)\d+,/, +qr/elapsed in ssl_certificate_by_lua\*: 0\.(?:09|1\d)\d+,/, +] +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 25: cosocket (UDP) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + server_tokens off; + + ssl_certificate_by_lua_block { + local sock = ngx.socket.udp() + + sock:settimeout(1000) + + local ok, err = sock:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if not ok then + ngx.log(ngx.ERR, "failed to connect to memc: ", err) + return + end + + local req = "\0\1\0\0\0\1\0\0flush_all\r\n" + local ok, err = sock:send(req) + if not ok then + ngx.log(ngx.ERR, "failed to send flush_all to memc: ", err) + return + end + + local res, err = sock:receive() + if not res then + ngx.log(ngx.ERR, "failed to receive memc reply: ", err) + return + end + + ngx.log(ngx.INFO, "received memc reply of ", #res, " bytes") + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + -- collectgarbage() + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +--- no_error_log +[error] +[alert] +[emerg] +--- grep_error_log eval: qr/received memc reply of \d+ bytes/ +--- grep_error_log_out eval +[ +'received memc reply of 12 bytes +', +'received memc reply of 12 bytes +', +'received memc reply of 12 bytes +', +'received memc reply of 12 bytes +', +] + + + +=== TEST 26: uthread (kill) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + server_tokens off; + + ssl_certificate_by_lua_block { + local function f() + ngx.log(ngx.INFO, "uthread: hello from f()") + ngx.sleep(1) + end + + local t, err = ngx.thread.spawn(f) + if not t then + ngx.log(ngx.ERR, "failed to spawn thread: ", err) + return ngx.exit(ngx.ERROR) + end + + local ok, res = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.ERR, "failed to kill thread: ", res) + return + end + + ngx.log(ngx.INFO, "uthread: killed") + + local ok, err = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.INFO, "uthread: failed to kill: ", err) + end + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + -- collectgarbage() + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +--- no_error_log +[error] +[alert] +[emerg] +--- grep_error_log eval: qr/uthread: [^.,]+/ +--- grep_error_log_out +uthread: hello from f() +uthread: killed +uthread: failed to kill: already waited or killed + + + +=== TEST 27: ssl_certificate_by_lua* with TCP cosocket (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} || $ENV{TEST_NGINX_USE_BORINGSSL} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_certificate_by_lua_block { + print("ssl certificate: TCP cosocket test start") + + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if not ok then + ngx.log(ngx.ERR, "failed to connect to memc: ", err) + return + end + + local bytes, err = sock:send("flush_all\r\n") + if not bytes then + ngx.log(ngx.ERR, "failed to send flush_all command: ", err) + return + end + + local res, err = sock:receive() + if not res then + ngx.log(ngx.ERR, "failed to receive memc reply: ", err) + return + end + + print("ssl certificate: received TCP memc reply: ", res) + sock:close() + print("ssl certificate: TCP cosocket test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl certificate: TCP cosocket test start|ssl certificate: received TCP memc reply: OK|ssl certificate: TCP cosocket test done|test completed)/ +--- grep_error_log_out +ssl certificate: TCP cosocket test start +ssl certificate: received TCP memc reply: OK +ssl certificate: TCP cosocket test done +test completed + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 28: ssl_certificate_by_lua* with UDP cosocket (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} || $ENV{TEST_NGINX_USE_BORINGSSL} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_certificate_by_lua_block { + print("ssl certificate: UDP cosocket test start") + + local sock = ngx.socket.udp() + sock:settimeout(1000) + + local ok, err = sock:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if not ok then + ngx.log(ngx.ERR, "failed to connect to memc: ", err) + return + end + + local req = "\0\1\0\0\0\1\0\0flush_all\r\n" + local ok, err = sock:send(req) + if not ok then + ngx.log(ngx.ERR, "failed to send flush_all to memc: ", err) + return + end + + local res, err = sock:receive() + if not res then + ngx.log(ngx.ERR, "failed to receive memc reply: ", err) + return + end + + print("ssl certificate: received UDP memc reply of ", #res, " bytes") + sock:close() + print("ssl certificate: UDP cosocket test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl certificate: UDP cosocket test start|ssl certificate: received UDP memc reply of \d+ bytes|ssl certificate: UDP cosocket test done|test completed)/ +--- grep_error_log_out eval +[ +qr/ssl certificate: UDP cosocket test start/, +qr/ssl certificate: received UDP memc reply of 12 bytes/, +qr/ssl certificate: UDP cosocket test done/, +qr/test completed/, +] + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 29: ssl_certificate_by_lua* with ngx.timer (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} || $ENV{TEST_NGINX_USE_BORINGSSL} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_certificate_by_lua_block { + print("ssl certificate: timer test start") + + local function timer_handler() + print("ssl certificate: timer executed") + end + + local ok, err = ngx.timer.at(0, timer_handler) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + + print("ssl certificate: timer created") + print("ssl certificate: timer test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl certificate: timer test start|ssl certificate: timer created|ssl certificate: timer test done|ssl certificate: timer executed|test completed)/ +--- grep_error_log_out +ssl certificate: timer test start +ssl certificate: timer created +ssl certificate: timer test done +ssl certificate: timer executed +test completed + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 30: ssl_certificate_by_lua* with user threads (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} || $ENV{TEST_NGINX_USE_BORINGSSL} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_certificate_by_lua_block { + print("ssl certificate: uthread test start") + + local function worker() + ngx.sleep(0.01) + print("ssl certificate: uthread worker executed") + return "worker_result" + end + + local t, err = ngx.thread.spawn(worker) + if not t then + ngx.log(ngx.ERR, "failed to spawn thread: ", err) + return + end + + print("ssl certificate: uthread spawned") + + local ok, res = ngx.thread.wait(t) + if not ok then + ngx.log(ngx.ERR, "failed to wait thread: ", res) + return + end + + print("ssl certificate: uthread result: ", res) + print("ssl certificate: uthread test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl certificate: uthread test start|ssl certificate: uthread spawned|ssl certificate: uthread worker executed|ssl certificate: uthread result: worker_result|ssl certificate: uthread test done|test completed)/ +--- grep_error_log_out +ssl certificate: uthread test start +ssl certificate: uthread spawned +ssl certificate: uthread worker executed +ssl certificate: uthread result: worker_result +ssl certificate: uthread test done +test completed --- no_error_log [error] [alert] +[emerg] + + + +=== TEST 31: ssl_certificate_by_lua* with coroutines (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} || $ENV{TEST_NGINX_USE_BORINGSSL} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_certificate_by_lua_block { + print("ssl certificate: coroutine test start") + + local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield + + local function coro_func() + local cnt = 0 + for i = 1, 3 do + print("ssl certificate: coro yield: ", cnt) + cy() + cnt = cnt + 1 + end + return "coro_done" + end + + local c = cc(coro_func) + for i = 1, 4 do + print("ssl certificate: coro resume, status: ", coroutine.status(c)) + local ok, res = cr(c) + if not ok then + print("ssl certificate: coro error: ", res) + break + end + if coroutine.status(c) == "dead" then + print("ssl certificate: coro result: ", res) + break + end + end + + print("ssl certificate: coroutine test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl certificate: coroutine test start|ssl certificate: coro resume, status: \w+|ssl certificate: coro yield: \d+|ssl certificate: coro result: coro_done|ssl certificate: coroutine test done|test completed)/ +--- grep_error_log_out +ssl certificate: coroutine test start +ssl certificate: coro resume, status: suspended +ssl certificate: coro yield: 0 +ssl certificate: coro resume, status: suspended +ssl certificate: coro yield: 1 +ssl certificate: coro resume, status: suspended +ssl certificate: coro yield: 2 +ssl certificate: coro resume, status: suspended +ssl certificate: coro result: coro_done +ssl certificate: coroutine test done +test completed + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 32: ssl_certificate_by_lua* without yield API (simple logic) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} || $ENV{TEST_NGINX_USE_BORINGSSL} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_certificate_by_lua_block { + print("ssl certificate: simple test start") + + -- Simple calculations without yield + local sum = 0 + for i = 1, 10 do + sum = sum + i + end + + print("ssl certificate: calculated sum: ", sum) + + -- String operations + local str = "hello" + str = str .. " world" + print("ssl certificate: concatenated string: ", str) + + -- Table operations + local t = {a = 1, b = 2, c = 3} + local count = 0 + for k, v in pairs(t) do + count = count + v + end + print("ssl certificate: table sum: ", count) + + print("ssl certificate: simple test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl certificate: simple test start|ssl certificate: calculated sum: 55|ssl certificate: concatenated string: hello world|ssl certificate: table sum: 6|ssl certificate: simple test done|test completed)/ +--- grep_error_log_out +ssl certificate: simple test start +ssl certificate: calculated sum: 55 +ssl certificate: concatenated string: hello world +ssl certificate: table sum: 6 +ssl certificate: simple test done +test completed + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 33: ssl_certificate_by_lua* with multiple network operations (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} || $ENV{TEST_NGINX_USE_BORINGSSL} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_certificate_by_lua_block { + print("ssl certificate: multiple network test start") + + -- First TCP operation + local sock1 = ngx.socket.tcp() + sock1:settimeout(2000) + local ok, err = sock1:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if ok then + local bytes, err = sock1:send("version\r\n") + if bytes then + local res, err = sock1:receive() + if res then + print("ssl certificate: TCP1 version: ", res) + end + end + sock1:close() + end + + ngx.sleep(0.01) -- Small delay + + -- Second UDP operation + local sock2 = ngx.socket.udp() + sock2:settimeout(1000) + local ok, err = sock2:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if ok then + local req = "\0\1\0\0\0\1\0\0version\r\n" + local ok, err = sock2:send(req) + if ok then + local res, err = sock2:receive() + if res then + print("ssl certificate: UDP version reply length: ", #res) + end + end + sock2:close() + end + + print("ssl certificate: multiple network test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl certificate: multiple network test start|ssl certificate: TCP1 version: VERSION|ssl certificate: UDP version reply length: \d+|ssl certificate: multiple network test done|test completed)/ +--- grep_error_log_out eval +[ +qr/ssl certificate: multiple network test start/, +qr/ssl certificate: TCP1 version: VERSION/, +qr/ssl certificate: UDP version reply length: \d+/, +qr/ssl certificate: multiple network test done/, +qr/test completed/, +] + +--- no_error_log +[error] +[alert] +[emerg] diff --git a/t/140-ssl-c-api.t b/t/140-ssl-c-api.t index 8734d1477d..9f647c41bc 100644 --- a/t/140-ssl-c-api.t +++ b/t/140-ssl-c-api.t @@ -9,9 +9,13 @@ my $NginxBinary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; my $openssl_version = eval { `$NginxBinary -V 2>&1` }; if ($openssl_version =~ m/built with OpenSSL (0|1\.0\.(?:0|1[^\d]|2[a-d]).*)/) { - plan(skip_all => "too old OpenSSL, need 1.0.2e, was $1"); + plan(skip_all => "too old OpenSSL, need >= 1.0.2e, was $1"); +} elsif ($openssl_version =~ m/BoringSSL/) { + $ENV{TEST_NGINX_USE_BORINGSSL} = 1; + plan tests => repeat_each() * (blocks() * 6 - 6); } else { - plan tests => repeat_each() * (blocks() * 5 + 1); + plan tests => repeat_each() * (blocks() * 5 - 5); + $ENV{TEST_NGINX_USE_OPENSSL} = 1; } $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); @@ -35,7 +39,8 @@ ffi.cdef[[ size_t pem_len, unsigned char *der, char **err); int ngx_http_lua_ffi_priv_key_pem_to_der(const unsigned char *pem, - size_t pem_len, unsigned char *der, char **err); + size_t pem_len, const unsigned char *passphrase, + unsigned char *der, char **err); int ngx_http_lua_ffi_ssl_set_der_certificate(void *r, const char *data, size_t len, char **err); @@ -51,24 +56,39 @@ ffi.cdef[[ void *ngx_http_lua_ffi_parse_pem_priv_key(const unsigned char *pem, size_t pem_len, char **err); + void *ngx_http_lua_ffi_parse_der_cert(const char *data, size_t len, + char **err); + + void *ngx_http_lua_ffi_parse_der_priv_key(const char *data, size_t len, + char **err); + int ngx_http_lua_ffi_set_cert(void *r, void *cdata, char **err); int ngx_http_lua_ffi_set_priv_key(void *r, void *cdata, char **err); + void *ngx_http_lua_ffi_get_req_ssl_pointer(void *r); + void ngx_http_lua_ffi_free_cert(void *cdata); void ngx_http_lua_ffi_free_priv_key(void *cdata); - int ngx_http_lua_ffi_ssl_clear_certs(void *r, char **err); + int ngx_http_lua_ffi_ssl_verify_client(void *r, void *cdata, + void *cdata, int depth, char **err); + + int ngx_http_lua_ffi_ssl_client_random(ngx_http_request_t *r, + unsigned char *out, size_t *outlen, char **err); + + int ngx_http_lua_ffi_req_shared_ssl_ciphers(void *r, uint16_t *ciphers, + uint16_t *nciphers, int filter_grease, char **err); ]] _EOC_ } my $http_config = $block->http_config || ''; $http_config .= <<'_EOC_'; -lua_package_path "$prefix/html/?.lua;;"; +lua_package_path "$prefix/html/?.lua;../lua-resty-core/lib/?.lua;;"; _EOC_ $block->set_value("http_config", $http_config); }); @@ -91,8 +111,8 @@ __DATA__ local errmsg = ffi.new("char *[1]") - local r = getfenv(0).__ngx_req - if not r then + local r = require "resty.core.base" .get_request() + if r == nil then ngx.log(ngx.ERR, "no request found") return end @@ -127,7 +147,7 @@ __DATA__ out = ffi.new("char [?]", #pkey) - local rc = ffi.C.ngx_http_lua_ffi_priv_key_pem_to_der(pkey, #pkey, out, errmsg) + local rc = ffi.C.ngx_http_lua_ffi_priv_key_pem_to_der(pkey, #pkey, nil, out, errmsg) if rc < 1 then ngx.log(ngx.ERR, "failed to parse PEM priv key: ", ffi.string(errmsg[0])) @@ -211,7 +231,7 @@ __DATA__ GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -245,8 +265,8 @@ lua ssl server name: "test.com" local errmsg = ffi.new("char *[1]") - local r = getfenv(0).__ngx_req - if not r then + local r = require "resty.core.base" .get_request() + if r == nil then ngx.log(ngx.ERR, "no request found") return end @@ -281,7 +301,7 @@ lua ssl server name: "test.com" out = ffi.new("char [?]", #pkey) - local rc = ffi.C.ngx_http_lua_ffi_priv_key_pem_to_der(pkey, #pkey, out, errmsg) + local rc = ffi.C.ngx_http_lua_ffi_priv_key_pem_to_der(pkey, #pkey, nil, out, errmsg) if rc < 1 then ngx.log(ngx.ERR, "failed to parse PEM priv key: ", ffi.string(errmsg[0])) @@ -365,7 +385,7 @@ lua ssl server name: "test.com" GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -399,8 +419,8 @@ lua ssl server name: "test.com" local errmsg = ffi.new("char *[1]") - local r = getfenv(0).__ngx_req - if not r then + local r = require "resty.core.base" .get_request() + if r == nil then ngx.log(ngx.ERR, "no request found") return end @@ -419,7 +439,7 @@ lua ssl server name: "test.com" out = ffi.new("char [?]", #pkey) - local rc = ffi.C.ngx_http_lua_ffi_priv_key_pem_to_der(pkey, #pkey, out, errmsg) + local rc = ffi.C.ngx_http_lua_ffi_priv_key_pem_to_der(pkey, #pkey, nil, out, errmsg) if rc < 1 then ngx.log(ngx.ERR, "failed to parse PEM priv key: ", ffi.string(errmsg[0])) @@ -493,7 +513,7 @@ lua ssl server name: "test.com" GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -528,8 +548,8 @@ failed to parse PEM priv key: PEM_read_bio_PrivateKey() failed local errmsg = ffi.new("char *[1]") - local r = getfenv(0).__ngx_req - if not r then + local r = require "resty.core.base" .get_request() + if r == nil then ngx.log(ngx.ERR, "no request found") return end @@ -644,7 +664,7 @@ failed to parse PEM priv key: PEM_read_bio_PrivateKey() failed GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -678,8 +698,8 @@ lua ssl server name: "test.com" local errmsg = ffi.new("char *[1]") - local r = getfenv(0).__ngx_req - if not r then + local r = require "resty.core.base" .get_request() + if r == nil then ngx.log(ngx.ERR, "no request found") return end @@ -794,7 +814,7 @@ lua ssl server name: "test.com" GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -811,3 +831,1246 @@ lua ssl server name: "test.com" --- no_error_log [error] [alert] + + + +=== TEST 6: verify client with CA certificates +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_certificate_by_lua_block { + collectgarbage() + + require "defines" + local ffi = require "ffi" + + local errmsg = ffi.new("char *[1]") + + local r = require "resty.core.base" .get_request() + if r == nil then + ngx.log(ngx.ERR, "no request found") + return + end + + local f = assert(io.open("t/cert/test.crt", "rb")) + local cert_data = f:read("*all") + f:close() + + local client_cert = ffi.C.ngx_http_lua_ffi_parse_pem_cert(cert_data, #cert_data, errmsg) + if not client_cert then + ngx.log(ngx.ERR, "failed to parse PEM client cert: ", + ffi.string(errmsg[0])) + return + end + + local rc = ffi.C.ngx_http_lua_ffi_ssl_verify_client(r, client_cert, nil, 1, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to verify client: ", + ffi.string(errmsg[0])) + return + end + + ffi.C.ngx_http_lua_ffi_free_cert(client_cert) + } + + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + location / { + default_type 'text/plain'; + content_by_lua_block { + print('client certificate subject: ', ngx.var.ssl_client_s_dn) + ngx.say(ngx.var.ssl_client_verify) + } + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_certificate ../../cert/test.crt; + proxy_ssl_certificate_key ../../cert/test.key; + proxy_ssl_session_reuse off; + } + +--- request +GET /t +--- response_body +SUCCESS + +--- error_log +client certificate subject: emailAddress=agentzh@gmail.com,CN=test.com + +--- no_error_log +[error] +[alert] + + + +=== TEST 7: verify client without CA certificates +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_certificate_by_lua_block { + collectgarbage() + + require "defines" + local ffi = require "ffi" + + local errmsg = ffi.new("char *[1]") + + local r = require "resty.core.base" .get_request() + if r == nil then + ngx.log(ngx.ERR, "no request found") + return + end + + local rc = ffi.C.ngx_http_lua_ffi_ssl_verify_client(r, nil, nil, -1, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to verify client: ", + ffi.string(errmsg[0])) + return + end + } + + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + location / { + default_type 'text/plain'; + content_by_lua_block { + print('client certificate subject: ', ngx.var.ssl_client_s_dn) + ngx.say(ngx.var.ssl_client_verify) + } + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_certificate ../../cert/test.crt; + proxy_ssl_certificate_key ../../cert/test.key; + proxy_ssl_session_reuse off; + } + +--- request +GET /t +--- response_body eval +qr/FAILED:self[- ]signed certificate/ + +--- error_log +client certificate subject: emailAddress=agentzh@gmail.com,CN=test.com + +--- no_error_log +[error] +[alert] + + + +=== TEST 8: verify client but client provides no certificate +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_certificate_by_lua_block { + collectgarbage() + + require "defines" + local ffi = require "ffi" + + local errmsg = ffi.new("char *[1]") + + local r = require "resty.core.base" .get_request() + if r == nil then + ngx.log(ngx.ERR, "no request found") + return + end + + local f = assert(io.open("t/cert/test.crt", "rb")) + local cert_data = f:read("*all") + f:close() + + local client_cert = ffi.C.ngx_http_lua_ffi_parse_pem_cert(cert_data, #cert_data, errmsg) + if not client_cert then + ngx.log(ngx.ERR, "failed to parse PEM client cert: ", + ffi.string(errmsg[0])) + return + end + + local rc = ffi.C.ngx_http_lua_ffi_ssl_verify_client(r, client_cert, nil, 1, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to verify client: ", + ffi.string(errmsg[0])) + return + end + + ffi.C.ngx_http_lua_ffi_free_cert(client_cert) + } + + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + location / { + default_type 'text/plain'; + content_by_lua_block { + print('client certificate subject: ', ngx.var.ssl_client_s_dn) + ngx.say(ngx.var.ssl_client_verify) + } + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_session_reuse off; + } + +--- request +GET /t +--- response_body +NONE + +--- error_log +client certificate subject: nil + +--- no_error_log +[error] +[alert] + + + +=== TEST 9: simple cert + private key with passphrase +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_certificate_by_lua_block { + collectgarbage() + + local ffi = require "ffi" + + ffi.cdef[[ + int ngx_http_lua_ffi_cert_pem_to_der(const unsigned char *pem, + size_t pem_len, unsigned char *der, char **err); + + int ngx_http_lua_ffi_priv_key_pem_to_der(const unsigned char *pem, + size_t pem_len, const unsigned char *passphrase, + unsigned char *der, char **err); + + int ngx_http_lua_ffi_ssl_set_der_certificate(void *r, + const char *data, size_t len, char **err); + + int ngx_http_lua_ffi_ssl_set_der_private_key(void *r, + const char *data, size_t len, char **err); + + int ngx_http_lua_ffi_ssl_clear_certs(void *r, char **err); + ]] + + local errmsg = ffi.new("char *[1]") + + local r = require "resty.core.base" .get_request() + if not r then + ngx.log(ngx.ERR, "no request found") + return + end + + ffi.C.ngx_http_lua_ffi_ssl_clear_certs(r, errmsg) + + local f = assert(io.open("t/cert/test_passphrase.crt", "rb")) + local cert = f:read("*all") + f:close() + + local out = ffi.new("char [?]", #cert) + + local rc = ffi.C.ngx_http_lua_ffi_cert_pem_to_der(cert, #cert, out, errmsg) + if rc < 1 then + ngx.log(ngx.ERR, "failed to parse PEM cert: ", + ffi.string(errmsg[0])) + return + end + + local cert_der = ffi.string(out, rc) + + local rc = ffi.C.ngx_http_lua_ffi_ssl_set_der_certificate(r, cert_der, #cert_der, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to set DER cert: ", + ffi.string(errmsg[0])) + return + end + + f = assert(io.open("t/cert/test_passphrase.key", "rb")) + local pkey = f:read("*all") + f:close() + + local passphrase = "123456" + + out = ffi.new("char [?]", #pkey) + + local rc = ffi.C.ngx_http_lua_ffi_priv_key_pem_to_der(pkey, #pkey, passphrase, out, errmsg) + if rc < 1 then + ngx.log(ngx.ERR, "failed to parse PEM priv key: ", + ffi.string(errmsg[0])) + return + end + + local pkey_der = ffi.string(out, rc) + + local rc = ffi.C.ngx_http_lua_ffi_ssl_set_der_private_key(r, pkey_der, #pkey_der, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to set DER priv key: ", + ffi.string(errmsg[0])) + return + end + } + + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test_passphrase.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to recieve response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] + + + +=== TEST 10: Raw SSL pointer +--- skip_eval: 8:$ENV{TEST_NGINX_USE_BORINGSSL} +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_certificate_by_lua_block { + collectgarbage() + + local ffi = require "ffi" + require "defines" + + local r = require "resty.core.base" .get_request() + if not r then + ngx.log(ngx.ERR, "no request found") + return + end + + local ssl = ffi.C.ngx_http_lua_ffi_get_req_ssl_pointer(r); + if ssl == nil then + ngx.log(ngx.ERR, "failed to retrieve SSL*") + return + end + + ffi.cdef[[ + const char *SSL_get_servername(const void *, const int); + ]] + local TLSEXT_NAMETYPE_host_name = 0 + local sni = ffi.C.SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name) + if sni == nil then + ngx.log(ngx.ERR, "failed to get sni") + return + end + + ngx.log(ngx.INFO, "SNI is ", ffi.string(sni)) + } + + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +SNI is test.com + +--- no_error_log +[error] +[alert] + + + +=== TEST 11: DER cert + private key cdata +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_certificate_by_lua_block { + collectgarbage() + + local ffi = require "ffi" + require "defines" + + local errmsg = ffi.new("char *[1]") + + local r = require "resty.core.base" .get_request() + if r == nil then + ngx.log(ngx.ERR, "no request found") + return + end + + ffi.C.ngx_http_lua_ffi_ssl_clear_certs(r, errmsg) + + local f = assert(io.open("t/cert/test_der.crt", "rb")) + local cert_data = f:read("*all") + f:close() + + local cert = ffi.C.ngx_http_lua_ffi_parse_der_cert(cert_data, #cert_data, errmsg) + if not cert then + ngx.log(ngx.ERR, "failed to parse DER cert: ", + ffi.string(errmsg[0])) + return + end + + local rc = ffi.C.ngx_http_lua_ffi_set_cert(r, cert, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to set cdata cert: ", + ffi.string(errmsg[0])) + return + end + + ffi.C.ngx_http_lua_ffi_free_cert(cert) + + f = assert(io.open("t/cert/test_der.key", "rb")) + local pkey_data = f:read("*all") + f:close() + + local pkey = ffi.C.ngx_http_lua_ffi_parse_der_priv_key(pkey_data, #pkey_data, errmsg) + if pkey == nil then + ngx.log(ngx.ERR, "failed to parse DER priv key: ", + ffi.string(errmsg[0])) + return + end + + local rc = ffi.C.ngx_http_lua_ffi_set_priv_key(r, pkey, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to set cdata priv key: ", + ffi.string(errmsg[0])) + return + end + + ffi.C.ngx_http_lua_ffi_free_priv_key(pkey) + } + + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] + + + +=== TEST 12: client random +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_certificate_by_lua_block { + collectgarbage() + + local ffi = require "ffi" + require "defines" + + local errmsg = ffi.new("char *[1]") + + local r = require "resty.core.base" .get_request() + if r == nil then + ngx.log(ngx.ERR, "no request found") + return + end + + -- test client random length + local out = ffi.new("unsigned char[?]", 0) + local sizep = ffi.new("size_t[1]", 0) + + local rc = ffi.C.ngx_http_lua_ffi_ssl_client_random(r, out, sizep, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to get client random length: ", + ffi.string(errmsg[0])) + return + end + + if tonumber(sizep[0]) ~= 32 then + ngx.log(ngx.ERR, "client random length does not equal 32") + return + end + + -- test client random value + out = ffi.new("unsigned char[?]", 50) + sizep = ffi.new("size_t[1]", 50) + + rc = ffi.C.ngx_http_lua_ffi_ssl_client_random(r, out, sizep, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to get client random: ", + ffi.string(errmsg[0])) + return + end + + local init_v = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + if ffi.string(out, sizep[0]) == init_v then + ngx.log(ngx.ERR, "maybe the client random value is incorrect") + return + end + } + + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] + + + +=== TEST 13: verify client, but server don't trust root ca +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name example.com; + + ssl_certificate_by_lua_block { + collectgarbage() + + require "defines" + local ffi = require "ffi" + + local errmsg = ffi.new("char *[1]") + + local r = require "resty.core.base" .get_request() + if r == nil then + ngx.log(ngx.ERR, "no request found") + return + end + + local f = assert(io.open("t/cert/mtls_server.crt", "rb")) + local cert_data = f:read("*all") + f:close() + + local client_cert = ffi.C.ngx_http_lua_ffi_parse_pem_cert(cert_data, #cert_data, errmsg) + if not client_cert then + ngx.log(ngx.ERR, "failed to parse PEM client cert: ", + ffi.string(errmsg[0])) + return + end + + local rc = ffi.C.ngx_http_lua_ffi_ssl_verify_client(r, client_cert, nil, 2, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to verify client: ", + ffi.string(errmsg[0])) + return + end + + ffi.C.ngx_http_lua_ffi_free_cert(client_cert) + } + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + content_by_lua_block { + ngx.say(ngx.var.ssl_client_verify) + } + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_session_reuse off; + } + +--- request +GET /t +--- response_body +FAILED:unable to verify the first certificate + +--- no_error_log +[error] +[alert] + + + +=== TEST 14: verify client and server trust root ca +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name example.com; + + ssl_certificate_by_lua_block { + collectgarbage() + + require "defines" + local ffi = require "ffi" + + local errmsg = ffi.new("char *[1]") + + local r = require "resty.core.base" .get_request() + if r == nil then + ngx.log(ngx.ERR, "no request found") + return + end + + local f = assert(io.open("t/cert/mtls_server.crt", "rb")) + local cert_data = f:read("*all") + f:close() + + local client_cert = ffi.C.ngx_http_lua_ffi_parse_pem_cert(cert_data, #cert_data, errmsg) + if not client_cert then + ngx.log(ngx.ERR, "failed to parse PEM client cert: ", + ffi.string(errmsg[0])) + return + end + + local f = assert(io.open("t/cert/mtls_ca.crt", "rb")) + local cert_data = f:read("*all") + f:close() + + local trusted_cert = ffi.C.ngx_http_lua_ffi_parse_pem_cert(cert_data, #cert_data, errmsg) + if not trusted_cert then + ngx.log(ngx.ERR, "failed to parse PEM trusted cert: ", + ffi.string(errmsg[0])) + return + end + + local rc = ffi.C.ngx_http_lua_ffi_ssl_verify_client(r, cert, trusted_cert, 2, errmsg) + if rc ~= 0 then + ngx.log(ngx.ERR, "failed to verify client: ", + ffi.string(errmsg[0])) + return + end + + ffi.C.ngx_http_lua_ffi_free_cert(client_cert) + ffi.C.ngx_http_lua_ffi_free_cert(trusted_cert) + } + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + content_by_lua_block { + ngx.say(ngx.var.ssl_client_verify) + } + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_session_reuse off; + } + +--- request +GET /t +--- response_body +SUCCESS + +--- no_error_log +[error] +[alert] + + + +=== TEST 15: Get supported ciphers +--- skip_eval: 8:$ENV{TEST_NGINX_USE_BORINGSSL} +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.2; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384; + + server_tokens off; + + location /ciphers { + content_by_lua_block { + require "defines" + local ffi = require "ffi" + local cjson = require "cjson.safe" + local base = require "resty.core.base" + local get_request = base.get_request + + local MAX_CIPHERS = 64 + local ciphers = ffi.new("uint16_t[?]", MAX_CIPHERS) + local nciphers = ffi.new("uint16_t[1]", MAX_CIPHERS) + local err = ffi.new("char*[1]") + + local r = get_request() + local ret = ffi.C.ngx_http_lua_ffi_req_shared_ssl_ciphers(r, ciphers, nciphers, 0, err) + + if ret ~= 0 then + ngx.log(ngx.ERR, "error: ", ffi.string(err[0])) + return + end + + local res = {} + for i = 0, nciphers[0] - 1 do + local cipher_id = string.format("%04x", ciphers[i]) + table.insert(res, cipher_id) + end + + ngx.say(cjson.encode(res)) + } + } + } +--- config + server_tokens off; + location /t { + proxy_ssl_protocols TLSv1.2; + proxy_ssl_session_reuse off; + proxy_ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256; + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock:/ciphers; + } +--- request +GET /t +--- response_body_like +\["c02f","c02b"\] +--- error_log chomp +TLSv1.2, cipher: "ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD" + + + +=== TEST 16: SSL cipher API error handling (no SSL) +--- skip_eval: 8:$ENV{TEST_NGINX_USE_BORINGSSL} +--- config + location /t { + content_by_lua_block { + require "defines" + local ffi = require "ffi" + + local ciphers = ffi.new("uint16_t[64]") + local nciphers = ffi.new("uint16_t[1]", 64) + local err = ffi.new("char*[1]") + + -- use nil request to trigger error + local ret = ffi.C.ngx_http_lua_ffi_req_shared_ssl_ciphers(nil, ciphers, nciphers, 0, err) + + ngx.say("ret: ", ret) + if err[0] ~= nil then + ngx.say("err: ", ffi.string(err[0])) + end + } + } +--- request +GET /t +--- response_body +ret: -1 +err: bad request + +--- no_error_log +[error] +[alert] + + + +=== TEST 17: Buffer overflow handling +--- skip_eval: 8:$ENV{TEST_NGINX_USE_BORINGSSL} +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.2; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384; + + server_tokens off; + + + location /ciphers { + content_by_lua_block { + require "defines" + local ffi = require "ffi" + local base = require "resty.core.base" + local get_request = base.get_request + local cjson = require "cjson.safe" + + local MAX_CIPHERS = 64 + local ciphers = ffi.new("uint16_t[?]", MAX_CIPHERS) + local nciphers = ffi.new("uint16_t[1]", MAX_CIPHERS) + local err = ffi.new("char*[1]") + + local r = get_request() + local ret = ffi.C.ngx_http_lua_ffi_req_shared_ssl_ciphers(r, ciphers, nciphers, 0, err) + + if ret ~= 0 then + ngx.log(ngx.ERR, "error: ", ffi.string(err[0])) + return + end + local res = {} + for i = 0, nciphers[0] - 1 do + local cipher_id = string.format("%04x", ciphers[i]) + + table.insert(res, cipher_id) + end + ngx.say(cjson.encode(res)) + } + } + } +--- config + server_tokens off; + location /t { + proxy_ssl_protocols TLSv1.2; + proxy_ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256; + proxy_ssl_session_reuse off; + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock:/ciphers; + } +--- request +GET /t +--- response_body_like +\["c02f"\] +--- error_code: 200 +--- error_log chomp +TLSv1.2, cipher: "ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD" + + + +=== TEST 18: BORINGSSL error handling +--- skip_eval: 8:$ENV{TEST_NGINX_USE_OPENSSL} +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384; + + server_tokens off; + + + location /ciphers { + content_by_lua_block { + require "defines" + local ffi = require "ffi" + local base = require "resty.core.base" + local get_request = base.get_request + + local MAX_CIPHERS = 64 + local ciphers = ffi.new("uint16_t[?]", MAX_CIPHERS) + local nciphers = ffi.new("uint16_t[1]", MAX_CIPHERS) + local err = ffi.new("char*[1]") + + local r = get_request() + local ret = ffi.C.ngx_http_lua_ffi_req_shared_ssl_ciphers(r, ciphers, nciphers, 0, err) + + if ret ~= 0 then + ngx.say("Error: ", ffi.string(err[0])) + return + end + + } + } + } +--- config + server_tokens off; + location /t { + proxy_ssl_protocols TLSv1.2 TLSv1.3; + proxy_ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256; + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock:/ciphers; + } +--- request +GET /t +--- response_body_like chomp +Error: BoringSSL is not supported for SSL cipher operations +--- error_code: 200 + +--- no_error_log +[error] +[alert] + + + +=== TEST 19: Get supported ciphers with GREASE filtering +--- skip_eval: 8:$ENV{TEST_NGINX_USE_BORINGSSL} +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.2; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384; + + server_tokens off; + + location /ciphers { + content_by_lua_block { + require "defines" + local ffi = require "ffi" + local cjson = require "cjson.safe" + local base = require "resty.core.base" + local get_request = base.get_request + + local MAX_CIPHERS = 64 + local ciphers = ffi.new("uint16_t[?]", MAX_CIPHERS) + local nciphers = ffi.new("uint16_t[1]", MAX_CIPHERS) + local err = ffi.new("char*[1]") + + local r = get_request() + -- Test without GREASE filtering + local ret = ffi.C.ngx_http_lua_ffi_req_shared_ssl_ciphers(r, ciphers, nciphers, 0, err) + if ret ~= 0 then + ngx.log(ngx.ERR, "error without filtering: ", ffi.string(err[0])) + return + end + + local res_no_filter = {} + for i = 0, nciphers[0] - 1 do + local cipher_id = string.format("%04x", ciphers[i]) + table.insert(res_no_filter, cipher_id) + end + + -- Reset buffers + nciphers[0] = MAX_CIPHERS + + -- Test with GREASE filtering + local ret = ffi.C.ngx_http_lua_ffi_req_shared_ssl_ciphers(r, ciphers, nciphers, 1, err) + if ret ~= 0 then + ngx.log(ngx.ERR, "error with filtering: ", ffi.string(err[0])) + return + end + + local res_with_filter = {} + for i = 0, nciphers[0] - 1 do + local cipher_id = string.format("%04x", ciphers[i]) + table.insert(res_with_filter, cipher_id) + end + + ngx.say("without_filter:", cjson.encode(res_no_filter)) + ngx.say("with_filter:", cjson.encode(res_with_filter)) + } + } + } +--- config + server_tokens off; + location /t { + proxy_ssl_protocols TLSv1.2; + proxy_ssl_session_reuse off; + proxy_ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256; + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock:/ciphers; + } +--- request +GET /t +--- response_body_like +without_filter:\[.*\] +with_filter:\[.*\] +--- error_log chomp +TLSv1.2, cipher: "ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD" diff --git a/t/142-ssl-session-store.t b/t/142-ssl-session-store.t index cc3a664cfb..11deb83207 100644 --- a/t/142-ssl-session-store.t +++ b/t/142-ssl-session-store.t @@ -40,6 +40,7 @@ __DATA__ server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -77,7 +78,7 @@ __DATA__ GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log @@ -86,11 +87,11 @@ lua ssl server name: "test.com" --- no_error_log [error] [alert] ---- grep_error_log eval: qr/ssl_session_store_by_lua_block:.*?,|\bssl session store: connection reusable: \d+|\breusable connection: \d+/ +--- grep_error_log eval: qr/ssl_session_store_by_lua\(nginx.conf:\d+\):.*?,|\bssl session store: connection reusable: \d+|\breusable connection: \d+/ --- grep_error_log_out eval qr/^reusable connection: 0 ssl session store: connection reusable: 0 -ssl_session_store_by_lua_block:1: ssl session store by lua is running!, +ssl_session_store_by_lua\(nginx\.conf:25\):1: ssl session store by lua is running!, /m, @@ -115,6 +116,7 @@ ssl_session_store_by_lua_block:1: ssl session store by lua is running!, server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -152,7 +154,7 @@ ssl_session_store_by_lua_block:1: ssl session store by lua is running!, GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log @@ -190,6 +192,7 @@ API disabled in the context of ssl_session_store_by_lua* server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -227,7 +230,7 @@ API disabled in the context of ssl_session_store_by_lua* GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log @@ -282,6 +285,7 @@ my timer run! server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -319,7 +323,7 @@ my timer run! GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log @@ -351,6 +355,7 @@ API disabled in the context of ssl_session_store_by_lua* resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -390,7 +395,7 @@ API disabled in the context of ssl_session_store_by_lua* GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log @@ -423,6 +428,7 @@ ngx.exit does not yield and the error code is eaten. resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -462,7 +468,7 @@ ngx.exit does not yield and the error code is eaten. GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log @@ -496,6 +502,7 @@ ssl_session_store_by_lua*: handler return value: 0, sess new cb exit code: 0 resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -532,11 +539,11 @@ ssl_session_store_by_lua*: handler return value: 0, sess new cb exit code: 0 GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log -failed to run session_store_by_lua*: ssl_session_store_by_lua_block:2: bad bad bad +failed to run session_store_by_lua*: ssl_session_store_by_lua(nginx.conf:25):2: bad bad bad --- no_error_log should never reached here @@ -564,6 +571,7 @@ should never reached here resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -600,7 +608,7 @@ should never reached here GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log @@ -634,6 +642,7 @@ get_phase: ssl_session_store server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -671,14 +680,14 @@ get_phase: ssl_session_store GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log eval [ 'lua ssl server name: "test.com"', qr/elapsed in ssl cert by lua: 0.(?:09|1[01])\d+,/, -'ssl_session_store_by_lua_block:1: ssl store session by lua is running!', +'ssl_session_store_by_lua(nginx.conf:25):1: ssl store session by lua is running!', ] --- no_error_log @@ -703,6 +712,7 @@ qr/elapsed in ssl cert by lua: 0.(?:09|1[01])\d+,/, server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -744,7 +754,7 @@ print("ssl store session by lua is running!") GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log @@ -784,6 +794,7 @@ a.lua:1: ssl store session by lua is running! resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -820,7 +831,7 @@ a.lua:1: ssl store session by lua is running! GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- no_error_log @@ -854,6 +865,7 @@ qr/\[emerg\] .*? "ssl_session_store_by_lua_block" directive is not allowed here server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -891,13 +903,76 @@ qr/\[emerg\] .*? "ssl_session_store_by_lua_block" directive is not allowed here GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log lua ssl server name: "test.com" -ssl_session_store_by_lua_block:1: ssl session store by lua is running! +ssl_session_store_by_lua(nginx.conf:25):1: ssl session store by lua is running! --- no_error_log [error] [alert] + + + +=== TEST 13: ssl_session_store_by_lua* is skipped when using TLSv1.3 +--- skip_openssl: 6: < 1.1.1 +--- http_config + ssl_session_store_by_lua_block { ngx.log(ngx.ERR, "ssl_session_store_by_lua* is running!") } + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_session_tickets off; + ssl_protocols TLSv1.3; + server_tokens off; + } +--- config + server_tokens off; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1.3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(5000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil +--- error_log eval +qr/ssl_session_store_by_lua\*: skipped since TLS version >= 1\.3 \(\d+\)/ +--- no_error_log +[error] +[alert] +[emerg] +--- skip_eval: 6:$ENV{TEST_NGINX_USE_HTTP3} diff --git a/t/143-ssl-session-fetch.t b/t/143-ssl-session-fetch.t index 4dc992d370..9cc1870a87 100644 --- a/t/143-ssl-session-fetch.t +++ b/t/143-ssl-session-fetch.t @@ -7,7 +7,13 @@ use File::Basename; repeat_each(3); -plan tests => repeat_each() * (blocks() * 6); +plan tests => repeat_each() * (blocks() * 6) - 3; + +my $NginxBinary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; +my $openssl_version = eval { `$NginxBinary -V 2>&1` }; +if ($openssl_version =~ m/BoringSSL/) { + $ENV{TEST_NGINX_USE_BORINGSSL} = 1; +} $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); @@ -41,6 +47,7 @@ __DATA__ server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -80,26 +87,40 @@ __DATA__ GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil ---- grep_error_log eval: qr/ssl_session_fetch_by_lua_block:.*?,|\bssl session fetch: connection reusable: \d+|\breusable connection: \d+/ +--- grep_error_log eval: qr/ssl_session_fetch_by_lua\(nginx\.conf:\d+\):.*?,|\bssl session fetch: connection reusable: \d+|\breusable connection: \d+/ --- grep_error_log_out eval +# Since nginx version 1.17.9, nginx call ngx_reusable_connection(c, 0) +# before call ssl callback function +$Test::Nginx::Util::NginxVersion >= 1.017009 ? +[ +qr/\A(?:reusable connection: [01]\n)+\z/s, +qr/^reusable connection: 0 +ssl session fetch: connection reusable: 0 +ssl_session_fetch_by_lua\(nginx\.conf:25\):1: ssl fetch sess by lua is running!, +/m, +qr/^reusable connection: 0 +ssl session fetch: connection reusable: 0 +ssl_session_fetch_by_lua\(nginx\.conf:25\):1: ssl fetch sess by lua is running!, +/m, +] +: [ qr/\A(?:reusable connection: [01]\n)+\z/s, qr/^reusable connection: 1 ssl session fetch: connection reusable: 1 reusable connection: 0 -ssl_session_fetch_by_lua_block:1: ssl fetch sess by lua is running!, +ssl_session_fetch_by_lua\(nginx\.conf:25\):1: ssl fetch sess by lua is running!, /m, qr/^reusable connection: 1 ssl session fetch: connection reusable: 1 reusable connection: 0 -ssl_session_fetch_by_lua_block:1: ssl fetch sess by lua is running!, +ssl_session_fetch_by_lua\(nginx\.conf:25\):1: ssl fetch sess by lua is running!, /m, ] - --- no_error_log [error] [alert] @@ -123,10 +144,12 @@ ssl_session_fetch_by_lua_block:1: ssl fetch sess by lua is running!, server_tokens off; } +--- skip_eval: 6:$ENV{TEST_NGINX_USE_BORINGSSL} --- config server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2 TLSv1.3; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -166,7 +189,7 @@ ssl_session_fetch_by_lua_block:1: ssl fetch sess by lua is running!, GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval @@ -183,6 +206,7 @@ qr/elapsed in ssl fetch session by lua: 0.(?:09|1[01])\d+,/, [error] [alert] [emerg] +--- skip_openssl: 6: > 1.1.1w @@ -211,6 +235,7 @@ qr/elapsed in ssl fetch session by lua: 0.(?:09|1[01])\d+,/, server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -250,7 +275,7 @@ qr/elapsed in ssl fetch session by lua: 0.(?:09|1[01])\d+,/, GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval @@ -312,6 +337,7 @@ qr/my timer run!/s server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -351,7 +377,7 @@ qr/my timer run!/s GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval @@ -370,6 +396,7 @@ qr/received memc reply: OK/s [alert] [error] [emerg] +--- skip_openssl: 6: > 1.1.1w @@ -393,6 +420,7 @@ qr/received memc reply: OK/s resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -432,7 +460,7 @@ qr/received memc reply: OK/s GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval @@ -474,6 +502,7 @@ should never reached here resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -513,7 +542,7 @@ should never reached here GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval @@ -556,6 +585,7 @@ should never reached here resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -595,7 +625,7 @@ should never reached here GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval @@ -614,6 +644,7 @@ qr/ssl_session_fetch_by_lua\*: sess get cb exit code: 0/s should never reached here [alert] [emerg] +--- skip_openssl: 6: > 1.1.1w @@ -636,6 +667,7 @@ should never reached here server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; lua_ssl_verify_depth 3; location /t { @@ -676,18 +708,18 @@ should never reached here GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval -qr/ssl_session_fetch_by_lua_block:2: bad bad bad/s +qr/ssl_session_fetch_by_lua\(nginx.conf:\d+\):2: bad bad bad/s --- grep_error_log_out eval [ '', -'ssl_session_fetch_by_lua_block:2: bad bad bad +'ssl_session_fetch_by_lua(nginx.conf:25):2: bad bad bad ', -'ssl_session_fetch_by_lua_block:2: bad bad bad +'ssl_session_fetch_by_lua(nginx.conf:25):2: bad bad bad ', ] @@ -719,6 +751,7 @@ should never reached here server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; lua_ssl_verify_depth 3; location /t { @@ -759,19 +792,19 @@ should never reached here GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval -qr/ssl_session_fetch_by_lua_block:3: bad bad bad|ssl_session_fetch_by_lua\*: sess get cb exit code: 0/s +qr/ssl_session_fetch_by_lua\(nginx.conf:\d+\):3: bad bad bad|ssl_session_fetch_by_lua\*: sess get cb exit code: 0/s --- grep_error_log_out eval [ '', -'ssl_session_fetch_by_lua_block:3: bad bad bad +'ssl_session_fetch_by_lua(nginx.conf:25):3: bad bad bad ssl_session_fetch_by_lua*: sess get cb exit code: 0 ', -'ssl_session_fetch_by_lua_block:3: bad bad bad +'ssl_session_fetch_by_lua(nginx.conf:25):3: bad bad bad ssl_session_fetch_by_lua*: sess get cb exit code: 0 ', @@ -781,6 +814,7 @@ ssl_session_fetch_by_lua*: sess get cb exit code: 0 should never reached here [alert] [emerg] +--- skip_openssl: 6: > 1.1.1w @@ -800,6 +834,7 @@ should never reached here server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -839,7 +874,7 @@ should never reached here GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval @@ -886,6 +921,7 @@ qr/get_phase: ssl_session_fetch/s server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -925,13 +961,28 @@ qr/get_phase: ssl_session_fetch/s GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval qr/ssl ((fetch|store) session|cert) by lua is running!/s --- grep_error_log_out eval +if ($ENV{TEST_NGINX_USE_HTTP3}) { +[ +'ssl cert by lua is running! +ssl store session by lua is running! +', +'ssl cert by lua is running! +ssl fetch session by lua is running! +ssl store session by lua is running! +', +'ssl cert by lua is running! +ssl fetch session by lua is running! +ssl store session by lua is running! +', +] +} else { [ 'ssl cert by lua is running! ssl store session by lua is running! @@ -945,11 +996,13 @@ ssl cert by lua is running! ssl store session by lua is running! ', ] +} --- no_error_log [error] [alert] [emerg] +--- skip_openssl: 6: > 1.1.1w @@ -968,6 +1021,7 @@ ssl store session by lua is running! --- config server_tokens off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -1011,7 +1065,7 @@ print("ssl fetch sess by lua is running!") GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval @@ -1056,6 +1110,7 @@ qr/\S+:\d+: ssl fetch sess by lua is running!/s server_tokens off; resolver $TEST_NGINX_RESOLVER ipv6=off; lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; location /t { set $port $TEST_NGINX_MEMCACHED_PORT; @@ -1095,18 +1150,18 @@ qr/\S+:\d+: ssl fetch sess by lua is running!/s GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval -qr/ssl_session_fetch_by_lua_block:1: ssl fetch sess by lua is running!/s +qr/ssl_session_fetch_by_lua\(nginx.conf:\d+\):1: ssl fetch sess by lua is running!/s --- grep_error_log_out eval [ '', -'ssl_session_fetch_by_lua_block:1: ssl fetch sess by lua is running! +'ssl_session_fetch_by_lua(nginx.conf:25):1: ssl fetch sess by lua is running! ', -'ssl_session_fetch_by_lua_block:1: ssl fetch sess by lua is running! +'ssl_session_fetch_by_lua(nginx.conf:25):1: ssl fetch sess by lua is running! ', ] @@ -1114,3 +1169,544 @@ qr/ssl_session_fetch_by_lua_block:1: ssl fetch sess by lua is running!/s [error] [alert] [emerg] + + + +=== TEST 14: keep global variable in ssl_session_(store|fetch)_by_lua when OpenResty LuaJIT is used +--- http_config + ssl_session_store_by_lua_block { + ngx.log(ngx.WARN, "new foo: ", foo) + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + } + ssl_session_fetch_by_lua_block { + ngx.log(ngx.WARN, "new bar: ", foo) + if not bar then + bar = 1 + else + ngx.log(ngx.WARN, "old bar: ", bar) + bar = bar + 1 + end + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_session_tickets off; + + server_tokens off; + location /foo { + content_by_lua_block { + ngx.say("foo: ", foo) + ngx.say("bar: ", bar) + } + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local sess, err = sock:sslhandshake(package.loaded.session, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + package.loaded.session = sess + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + local m, err = ngx.re.match(line, "^foo: (.*)$", "jo") + if err then + ngx.say("failed to match line: ", err) + end + + if m and m[1] then + ngx.print(m[1]) + end + end + + local ok, err = sock:close() + ngx.say("done") + end -- do + } + } + +--- request +GET /t +--- response_body_like chomp +\A[123]done\n\z +--- grep_error_log eval: qr/old (foo|bar): \d+/ +--- grep_error_log_out eval +["", "old foo: 1\n", "old bar: 1\nold foo: 2\n"] +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 15: ssl_session_fetch_by_lua* is skipped when session ticket is provided +--- http_config + ssl_session_fetch_by_lua_block { ngx.log(ngx.ERR, "ssl_session_fetch_by_lua* is running!") } + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + server_tokens off; + } +--- config + server_tokens off; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(5000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(package.loaded.session, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + package.loaded.session = sess + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 16: ssl_session_fetch_by_lua* can yield when reading early data +--- skip_openssl: 6: < 1.1.1 +--- http_config + ssl_session_fetch_by_lua_block { + local begin = ngx.now() + ngx.sleep(0.1) + print("elapsed in ssl_session_fetch_by_lua*: ", ngx.now() - begin) + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_session_tickets off; + ssl_early_data on; + server_tokens off; + } +--- config + server_tokens off; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; + + location /t { + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(5000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(package.loaded.session, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + package.loaded.session = sess + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil +--- grep_error_log eval +qr/elapsed in ssl_session_fetch_by_lua\*: 0\.(?:09|1[01])\d+,/, +--- grep_error_log_out eval +[ +'', +qr/elapsed in ssl_session_fetch_by_lua\*: 0\.(?:09|1[01])\d+,/, +qr/elapsed in ssl_session_fetch_by_lua\*: 0\.(?:09|1[01])\d+,/, +] +--- no_error_log +[error] +[alert] +[emerg] +--- skip_openssl: 6: > 1.1.1w + + + +=== TEST 17: cosocket (UDP) +--- http_config + ssl_session_fetch_by_lua_block { + local sock = ngx.socket.udp() + + sock:settimeout(1000) + + local ok, err = sock:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if not ok then + ngx.log(ngx.ERR, "failed to connect to memc: ", err) + return + end + + local req = "\0\1\0\0\0\1\0\0flush_all\r\n" + local ok, err = sock:send(req) + if not ok then + ngx.log(ngx.ERR, "failed to send flush_all to memc: ", err) + return + end + + local res, err = sock:receive() + if not res then + ngx.log(ngx.ERR, "failed to receive memc reply: ", err) + return + end + + ngx.log(ngx.INFO, "received memc reply of ", #res, " bytes") + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_session_tickets off; + server_tokens off; + } +--- config + server_tokens off; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(5000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(package.loaded.session, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + package.loaded.session = sess + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil +--- grep_error_log eval: qr/received memc reply of \d+ bytes/ +--- grep_error_log_out eval +[ +'', +'received memc reply of 12 bytes +', +'received memc reply of 12 bytes +', +] +--- no_error_log +[alert] +[error] +[emerg] +--- skip_openssl: 6: > 1.1.1w + + + +=== TEST 18: uthread (kill) +--- http_config + ssl_session_fetch_by_lua_block { + local function f() + ngx.log(ngx.INFO, "uthread: hello from f()") + ngx.sleep(1) + end + + local t, err = ngx.thread.spawn(f) + if not t then + ngx.log(ngx.ERR, "failed to spawn thread: ", err) + return + end + + collectgarbage() + + local ok, err = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.ERR, "failed to kill thread: ", err) + return + end + + ngx.log(ngx.INFO, "uthread: killed") + + local ok, err = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.INFO, "uthread: failed to kill: ", err) + end + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_session_tickets off; + server_tokens off; + } +--- config + server_tokens off; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(5000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(package.loaded.session, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + package.loaded.session = sess + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil +--- grep_error_log eval: qr/uthread: [^.,]+/ +--- grep_error_log_out eval +[ +'', +'uthread: hello from f() +uthread: killed +uthread: failed to kill: already waited or killed +', +'uthread: hello from f() +uthread: killed +uthread: failed to kill: already waited or killed +' +] +--- no_error_log +[alert] +[error] +[emerg] + + + +=== TEST 19: uthread (wait) +--- http_config + ssl_session_fetch_by_lua_block { + local function f() + ngx.log(ngx.INFO, "uthread: hello from f()") + ngx.sleep(0.001) + return 32 + end + + local t, err = ngx.thread.spawn(f) + if not t then + ngx.log(ngx.ERR, "failed to spawn thread: ", err) + return + end + + collectgarbage() + + local ok, res = ngx.thread.wait(t) + if not ok then + ngx.log(ngx.ERR, "failed to wait on thread: ", res) + return + end + + ngx.log(ngx.INFO, "uthread: ", res) + + local ok, err = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.INFO, "uthread: failed to kill: ", err) + end + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_session_tickets off; + server_tokens off; + } +--- config + server_tokens off; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_protocols TLSv1 TLSv1.1 TLSV1.2; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(5000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(package.loaded.session, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + package.loaded.session = sess + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil +--- grep_error_log eval: qr/uthread: [^.,]+/ +--- grep_error_log_out eval +[ +'', +'uthread: hello from f() +uthread: 32 +uthread: failed to kill: already waited or killed +', +'uthread: hello from f() +uthread: 32 +uthread: failed to kill: already waited or killed +' +] +--- no_error_log +[alert] +[error] +[emerg] +--- skip_openssl: 6: > 1.1.1w diff --git a/t/145-shdict-list.t b/t/145-shdict-list.t index 9bb1592606..5672adbc55 100644 --- a/t/145-shdict-list.t +++ b/t/145-shdict-list.t @@ -743,3 +743,111 @@ GET /test done --- no_error_log [error] + + + +=== TEST 20: push to an expired list +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /test { + content_by_lua_block { + local dogs = ngx.shared.dogs + local len, err = dogs:lpush("cc", "1") --add another list to avoid key"aa" be cleaned (run ‘ngx_http_lua_shdict_expire(ctx, 1)’ may clean key ,ensure key'aa' not clean ,just expired)) + if not len then + ngx.say("push cc err: ", err) + end + local len, err = dogs:lpush("aa", "1") + if not len then + ngx.say("push1 err: ", err) + end + local succ, err = dogs:expire("aa", 0.2) + if not succ then + ngx.say("expire err: ",err) + end + ngx.sleep(0.3) -- list aa expired + local len, err = dogs:lpush("aa", "2") --push to an expired list may set as a new list + if not len then + ngx.say("push2 err: ", err) + end + local len, err = dogs:llen("aa") -- new list len is 1 + if not len then + ngx.say("llen err: ", err) + else + ngx.say("aa:len :", dogs:llen("aa")) + end + } + } + +--- request +GET /test +--- response_body +aa:len :1 +--- no_error_log +[error] + + + +=== TEST 21: push to an expired list then pop many time (more then list len ) +--- http_config + lua_shared_dict dogs 1m; +--- config + location = /test { + content_by_lua_block { + local dogs = ngx.shared.dogs + local len, err = dogs:lpush("cc", "1") --add another list to avoid key"aa" be cleaned (run ‘ngx_http_lua_shdict_expire(ctx, 1)’ may clean key ,ensure key'aa' not clean ,just expired)) + if not len then + ngx.say("push cc err: ", err) + end + local len, err = dogs:lpush("aa", "1") + if not len then + ngx.say("push1 err: ", err) + end + local succ, err = dogs:expire("aa", 0.2) + if not succ then + ngx.say("expire err: ",err) + end + ngx.sleep(0.3) -- list aa expired + local len, err = dogs:lpush("aa", "2") --push to an expired list may set as a new list + if not len then + ngx.say("push2 err: ", err) + end + local val, err = dogs:lpop("aa") + if not val then + ngx.say("llen err: ", err) + end + local val, err = dogs:lpop("aa") -- val == nil + ngx.say("aa list value: ", val) + } + } + +--- request +GET /test +--- response_body +aa list value: nil +--- no_error_log +[error] + + + +=== TEST 22: lpush return nil +--- http_config + lua_shared_dict dogs 100k; +--- config + location = /test { + content_by_lua_block { + local dogs = ngx.shared.dogs + for i = 1, 2920 + do + local len, err = dogs:lpush("foo", "bar") + end + local len, err = dogs:lpush("foo", "bar") + ngx.say(len) + } + } +--- request +GET /test +--- response_body +nil +--- no_error_log +[error] diff --git a/t/147-tcp-socket-timeouts.t b/t/147-tcp-socket-timeouts.t index 1872d29fb5..ceddb61dcc 100644 --- a/t/147-tcp-socket-timeouts.t +++ b/t/147-tcp-socket-timeouts.t @@ -17,6 +17,7 @@ BEGIN { } $ENV{TEST_NGINX_EVENT_TYPE} = 'poll'; + delete($ENV{TEST_NGINX_USE_HTTP2}); $ENV{MOCKEAGAIN_WRITE_TIMEOUT_PATTERN} = 'slowdata'; } @@ -323,7 +324,7 @@ lua tcp socket write timed out sock:settimeouts(100, 100, 100) - local ok, err = sock:connect("agentzh.org", 12345) + local ok, err = sock:connect("127.0.0.2", 12345) ngx.say("connect: ", ok, " ", err) local bytes @@ -346,7 +347,7 @@ send: nil closed receive: nil closed close: nil closed --- error_log -lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 +lua tcp socket connect timed out, when connecting to 127.0.0.2:12345 --- timeout: 10 @@ -411,7 +412,7 @@ lua tcp socket connect timed out, when connecting to 106.184.1.99:12345 local chunk = 4 - function read() + local function read() sock:settimeout(200) -- read: 200 ms local data, err = sock:receive(content_length) @@ -506,7 +507,7 @@ failed to receive data: timeout local chunk = 4 - function read() + local function read() local data, err = sock:receive(content_length) if not data then ngx.log(ngx.ERR, "failed to receive data: ", err) diff --git a/t/151-initby-hup.t b/t/151-initby-hup.t index f2788670db..a37db9fe43 100644 --- a/t/151-initby-hup.t +++ b/t/151-initby-hup.t @@ -12,7 +12,7 @@ BEGIN { } } -use Test::Nginx::Socket::Lua 'no_plan'; +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); #worker_connections(1014); #master_on(); @@ -21,7 +21,7 @@ use Test::Nginx::Socket::Lua 'no_plan'; repeat_each(1); -#plan tests => repeat_each() * (blocks() * 3 + 3); +plan tests => repeat_each() * (blocks() * 3); #no_diff(); #no_long_string(); diff --git a/t/152-timer-every.t b/t/152-timer-every.t index c8d62e704d..374e80e04b 100644 --- a/t/152-timer-every.t +++ b/t/152-timer-every.t @@ -63,7 +63,7 @@ qr/\[lua\] content_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.(?:09|10)\d*, cont -=== TEST 2: separated global env +=== TEST 2: shared global env --- config location /t { content_by_lua_block { @@ -84,7 +84,7 @@ qr/\[lua\] content_by_lua\(nginx\.conf:\d+\):\d+: elapsed: 0\.(?:09|10)\d*, cont --- request GET /t --- response_body -foo = nil +foo = 3 --- wait: 0.12 --- no_error_log [error] @@ -224,6 +224,7 @@ registered timer === TEST 6: memory leak check +--- quic_max_idle_timeout: 8 --- config location /t { content_by_lua_block { diff --git a/t/153-semaphore-hup.t b/t/153-semaphore-hup.t index c85a21dae4..496db6862e 100644 --- a/t/153-semaphore-hup.t +++ b/t/153-semaphore-hup.t @@ -67,7 +67,7 @@ add_block_preprocessor(sub { return end - shdict = ngx.shared.shdict + local shdict = ngx.shared.shdict local success = shdict:add("reloaded", 1) if not success then return @@ -104,6 +104,7 @@ run_tests(); __DATA__ === TEST 1: timer + reload +--- quic_max_idle_timeout: 1.1 --- config location /test { content_by_lua_block { @@ -129,6 +130,7 @@ created semaphore object === TEST 2: timer + reload (lua code cache off) +--- quic_max_idle_timeout: 1.1 --- http_config lua_code_cache off; --- config diff --git a/t/154-semaphore.t b/t/154-semaphore.t index 875f181ce7..5bb2b937af 100644 --- a/t/154-semaphore.t +++ b/t/154-semaphore.t @@ -8,7 +8,7 @@ use Test::Nginx::Socket::Lua; repeat_each(2); -plan tests => repeat_each() * (blocks() * 3) + blocks(); +plan tests => repeat_each() * (blocks() * 3) + 1; no_long_string(); #no_diff(); @@ -118,3 +118,53 @@ hello, world --- no_shutdown_error_log semaphore gc wait queue is not empty --- SKIP + + + +=== TEST 3: exit before post_handler was called +If gc is called before the ngx_http_lua_sema_handler and free the sema memory +ngx_http_lua_sema_handler would use the freed memory. +--- config + location /up { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + + local function sem_wait() + ngx.log(ngx.ERR, "ngx.sem wait start") + local ok, err = sem:wait(10) + if not ok then + ngx.log(ngx.ERR, "ngx.sem wait err: ", err) + else + ngx.log(ngx.ERR, "ngx.sem wait success") + end + end + local co = ngx.thread.spawn(sem_wait) + ngx.log(ngx.ERR, "ngx.sem post start") + sem:post() + ngx.log(ngx.ERR, "ngx.sem post end") + ngx.say("hello") + ngx.exit(200) + ngx.say("not reach here") + } + } + + location /t { + content_by_lua_block { + local res = ngx.location.capture("/up") + collectgarbage() + ngx.print(res.body) + } + } + +--- request +GET /t +--- response_body +hello +--- grep_error_log eval: qr/(ngx.sem .*?,|http close request|semaphore handler: wait queue: empty, resource count: 1|in lua gc, semaphore)/ +--- grep_error_log_out +ngx.sem wait start, +ngx.sem post start, +ngx.sem post end, +in lua gc, semaphore +http close request diff --git a/t/155-tls13.t b/t/155-tls13.t new file mode 100644 index 0000000000..54379e4412 --- /dev/null +++ b/t/155-tls13.t @@ -0,0 +1,106 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +repeat_each(3); + +# All these tests need to have new openssl +my $NginxBinary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; +my $openssl_version = eval { `$NginxBinary -V 2>&1` }; + +if ($openssl_version =~ m/built with OpenSSL (0\S*|1\.0\S*|1\.1\.0\S*)/) { + plan(skip_all => "too old OpenSSL, need >= 1.1.1, was $1"); +} else { + plan tests => repeat_each() * (blocks() * 5); +} + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; + +sub read_file { + my $infile = shift; + open my $in, $infile + or die "cannot open $infile for reading: $!"; + my $cert = do { local $/; <$in> }; + close $in; + $cert; +} + +our $TestCertificate = read_file("t/cert/test.crt"); +our $TestCertificateKey = read_file("t/cert/test.key"); + +#log_level 'warn'; +log_level 'debug'; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: handshake, TLSv1.3 +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate ../html/test.crt; + ssl_certificate_key ../html/test.key; + ssl_protocols TLSv1.2 TLSv1.3; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../html/test.crt; + lua_ssl_protocols TLSv1.2 TLSv1.3; + location /t { + #set $port 5000; + set $port $TEST_NGINX_MEMCACHED_PORT; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + else + ngx.say("ssl handshake: ", type(sess)) + end + end -- do + collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- user_files eval +">>> test.key +$::TestCertificateKey +>>> test.crt +$::TestCertificate" +--- error_log +SSL: TLSv1.3, +--- no_error_log +[error] +[alert] +--- timeout: 5 diff --git a/t/156-slow-network.t b/t/156-slow-network.t new file mode 100644 index 0000000000..d5897fb33e --- /dev/null +++ b/t/156-slow-network.t @@ -0,0 +1,139 @@ +BEGIN { + if (!defined $ENV{LD_PRELOAD}) { + $ENV{LD_PRELOAD} = ''; + } + + if ($ENV{LD_PRELOAD} !~ /\bmockeagain\.so\b/) { + $ENV{LD_PRELOAD} = "mockeagain.so $ENV{LD_PRELOAD}"; + } + + if ($ENV{MOCKEAGAIN} eq 'r') { + $ENV{MOCKEAGAIN} = 'rw'; + + } else { + $ENV{MOCKEAGAIN} = 'w'; + } + + $ENV{TEST_NGINX_EVENT_TYPE} = 'poll'; + delete($ENV{TEST_NGINX_USE_HTTP2}); +} + +use Test::Nginx::Socket::Lua; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4); + +add_block_preprocessor(sub { + my $block = shift; + + if (!defined $block->error_log) { + $block->set_value("no_error_log", "[error]"); + } + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } + +}); + + +log_level("debug"); +no_long_string(); +#no_diff(); +run_tests(); + +__DATA__ + +=== TEST 1: receiveany returns anything once socket receives +--- config + server_tokens off; + location = /t { + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(500) + assert(sock:connect("127.0.0.1", ngx.var.port)) + local req = { + 'GET /foo HTTP/1.0\r\n', + 'Host: localhost\r\n', + 'Connection: close\r\n\r\n', + } + local ok, err = sock:send(req) + if not ok then + ngx.say("send request failed: ", err) + return + end + + + -- skip http header + while true do + local data, err, _ = sock:receive('*l') + if err then + ngx.say('unexpected error occurs when receiving http head: ' .. err) + return + end + if #data == 0 then -- read last line of head + break + end + end + + -- receive http body + while true do + local data, err = sock:receiveany(1024) + if err then + if err ~= 'closed' then + ngx.say('unexpected err: ', err) + end + break + end + ngx.say(data) + end + + sock:close() + } + } + + location = /foo { + content_by_lua_block { + local resp = { + '1', + 'hello', + } + + local length = 0 + for _, v in ipairs(resp) do + length = length + #v + end + + -- flush http header + ngx.header['Content-Length'] = length + ngx.flush(true) + ngx.sleep(0.01) + + -- send http body bytes by bytes + for _, v in ipairs(resp) do + ngx.print(v) + ngx.flush(true) + ngx.sleep(0.01) + end + } + } + +--- response_body +1 +h +e +l +l +o +--- grep_error_log eval +qr/lua tcp socket read any/ +--- grep_error_log_out +lua tcp socket read any +lua tcp socket read any +lua tcp socket read any +lua tcp socket read any +lua tcp socket read any +lua tcp socket read any +lua tcp socket read any diff --git a/t/157-socket-keepalive-hup.t b/t/157-socket-keepalive-hup.t new file mode 100644 index 0000000000..4ebf637a33 --- /dev/null +++ b/t/157-socket-keepalive-hup.t @@ -0,0 +1,96 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +our $SkipReason; + +BEGIN { + if ($ENV{TEST_NGINX_CHECK_LEAK}) { + $SkipReason = "unavailable for the hup tests"; + + } elsif (defined $ENV{TEST_NGINX_USE_HTTP3}) { + #os.execute("kill -HUP " .. pid) + $SkipReason = "send HUP relaod signal by self make two workers with same id"; + + } else { + $ENV{TEST_NGINX_USE_HUP} = 1; + undef $ENV{TEST_NGINX_USE_STAP}; + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 8); + +#no_diff(); +no_long_string(); + +worker_connections(1024); +run_tests(); + +__DATA__ + +=== TEST 1: exiting +--- config + location /t { + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + local pidfile = ngx.config.prefix() .. "/logs/nginx.pid" + local f, err = io.open(pidfile, "r") + if not f then + ngx.say("failed to open nginx.pid: ", err) + return + end + + local pid = f:read() + -- ngx.say("master pid: [", pid, "]") + + f:close() + + local i = 0 + local port = ngx.var.port + + local function f(premature) + print("timer prematurely expired: ", premature) + + local sock = ngx.socket.tcp() + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + print("failed to connect: ", err) + return + end + + local ok, err = sock:setkeepalive() + if not ok then + print("failed to setkeepalive: ", err) + return + end + + print("setkeepalive successfully") + end + local ok, err = ngx.timer.at(3, f) + if not ok then + ngx.say("failed to set timer: ", err) + return + end + ngx.say("registered timer") + os.execute("kill -HUP " .. pid) + } + } +--- request +GET /t + +--- response_body +registered timer + +--- wait: 0.3 +--- no_error_log +[error] +[alert] +[crit] +--- error_log +timer prematurely expired: true +setkeepalive successfully +lua tcp socket set keepalive while process exiting, closing connection diff --git a/t/158-global-var.t b/t/158-global-var.t new file mode 100644 index 0000000000..3f5db60fa2 --- /dev/null +++ b/t/158-global-var.t @@ -0,0 +1,508 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +log_level('debug'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 14); + +our $HtmlDir = html_dir; + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +no_long_string(); + +sub read_file { + my $infile = shift; + open my $in, $infile + or die "cannot open $infile for reading: $!"; + my $cert = do { local $/; <$in> }; + close $in; + $cert; +} + +our $TestCertificate = read_file("t/cert/test.crt"); +our $TestCertificateKey = read_file("t/cert/test.key"); + +add_block_preprocessor(sub { + my $block = shift; + + if (!defined $block->error_log) { + $block->set_value("no_error_log", "[error]"); + } + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } + +}); + +run_tests(); + +__DATA__ + +=== TEST 1: set_by_lua +--- config + location /t { + set_by_lua_block $res { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + return foo + } + echo $res; + } +--- response_body_like chomp +\A[12]\n\z +--- grep_error_log eval +qr/(old foo: \d+|\[\w+\].*?writing a global Lua variable \('[^'\s]+'\)|set_by_lua\(nginx.conf:\d+\):\d+: in main chunk, )/ +--- grep_error_log_out eval +[qr/\A\[warn\] .*?writing a global Lua variable \('foo'\) +set_by_lua\(nginx.conf:40\):3: in main chunk, \n\z/, "old foo: 1\n"] + + + +=== TEST 2: rewrite_by_lua +--- config + location /t { + rewrite_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + ngx.say(foo) + } + } +--- response_body_like chomp +\A[12]\n\z +--- grep_error_log eval +qr/(old foo: \d+|\[\w+\].*?writing a global Lua variable \('[^'\s]+'\)|\w+_by_lua\(.*?\):\d+: in main chunk, )/ +--- grep_error_log_out eval +[qr/\A\[warn\] .*?writing a global Lua variable \('foo'\) +rewrite_by_lua\(nginx\.conf:\d+\):\d+: in main chunk, \n\z/, "old foo: 1\n"] + + + +=== TEST 3: access_by_lua +--- config + location /t { + access_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + ngx.say(foo) + } + } +--- response_body_like chomp +\A[12]\n\z +--- grep_error_log eval +qr/(old foo: \d+|\[\w+\].*?writing a global Lua variable \('[^'\s]+'\)|\w+_by_lua\(.*?\):\d+: in main chunk, )/ +--- grep_error_log_out eval +[qr/\A\[warn\] .*?writing a global Lua variable \('foo'\) +access_by_lua\(nginx\.conf:\d+\):\d+: in main chunk, \n\z/, "old foo: 1\n"] + + + +=== TEST 4: content_by_lua +--- config + location /t { + content_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + ngx.say(foo) + } + } +--- response_body_like chomp +\A[12]\n\z +--- grep_error_log eval +qr/(old foo: \d+|\[\w+\].*?writing a global Lua variable \('[^'\s]+'\)|\w+_by_lua\(.*?\):\d+: in main chunk, )/ +--- grep_error_log_out eval +[qr/\A\[warn\] .*?writing a global Lua variable \('foo'\) +content_by_lua\(nginx\.conf:\d+\):\d+: in main chunk, \n\z/, "old foo: 1\n"] + + + +=== TEST 5: header_filter_by_lua +--- config + location /t { + content_by_lua_block { + ngx.say(foo) + } + header_filter_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + } + } +--- response_body_like chomp +\A(?:nil|1)\n\z +--- grep_error_log eval +qr/(old foo: \d+|\[\w+\].*?writing a global Lua variable \('[^'\s]+'\)|\w+_by_lua\(nginx\.conf:\d+\):\d+: in main chunk, )/ +--- grep_error_log_out eval +[qr/\A\[warn\] .*?writing a global Lua variable \('foo'\) +header_filter_by_lua\(nginx.conf:43\):3: in main chunk, \n\z/, "old foo: 1\n"] + + + +=== TEST 6: body_filter_by_lua +--- config + location /t { + content_by_lua_block { + ngx.say(foo) + } + body_filter_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + } + } +--- response_body_like chomp +\A(?:nil|2)\n\z +--- grep_error_log eval +qr/(old foo: \d+|\[\w+\].*?writing a global Lua variable \('[^'\s]+'\)|\w+_by_lua\(nginx\.conf:\d+\):\d+: in main chunk,)/ +--- grep_error_log_out eval +[qr/\[warn\] .*?writing a global Lua variable \('foo'\) +body_filter_by_lua\(nginx.conf:43\):3: in main chunk, +old foo: 1\n\z/, "old foo: 2\nold foo: 3\n"] + + + +=== TEST 7: log_by_lua +--- config + location /t { + content_by_lua_block { + ngx.say(foo) + } + log_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + } + } +--- response_body_like chomp +\A(?:nil|1)\n\z +--- grep_error_log eval +qr/(old foo: \d+|\[\w+\].*?writing a global Lua variable \('[^'\s]+'\)|\w+_by_lua\(.*?\):\d+: in main chunk)/ +--- grep_error_log_out eval +[qr/\A\[warn\] .*?writing a global Lua variable \('foo'\) +log_by_lua\(nginx\.conf:\d+\):\d+: in main chunk\n\z/, "old foo: 1\n"] + + + +=== TEST 8: ssl_certificate_by_lua +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + content_by_lua_block { + ngx.say("foo: ", foo) + } + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + -- ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + -- ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + -- ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + local m, err = ngx.re.match(line, "^foo: (.*)$", "jo") + if err then + ngx.say("failed to match line: ", err) + end + + if m and m[1] then + ngx.print(m[1]) + end + end + + local ok, err = sock:close() + ngx.say("done") + end -- do + } + } + +--- response_body_like chomp +\A[12]done\n\z +--- grep_error_log eval +qr/(old foo: \d+|\[\w+\].*?writing a global Lua variable \('[^'\s]+'\)|\w+_by_lua\(nginx.conf:\d+\):\d+: in main chunk)/ +--- grep_error_log_out eval +[qr/\A\[warn\] .*?writing a global Lua variable \('foo'\) +ssl_certificate_by_lua\(nginx.conf:28\):3: in main chunk\n\z/, "old foo: 1\n"] + + + +=== TEST 9: timer +--- config + location /t { + content_by_lua_block { + local function f() + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + end + local ok, err = ngx.timer.at(0, f) + if not ok then + ngx.say("failed to set timer: ", err) + return + end + ngx.sleep(0.01) + ngx.say(foo) + } + } +--- response_body_like chomp +\A[12]\n\z +--- grep_error_log eval +qr/(old foo: \d+|\[\w+\].*?writing a global Lua variable \('[^'\s]+'\)|\w+_by_lua\(.*?\):\d+: in\b)/ +--- grep_error_log_out eval +[qr/\A\[warn\] .*?writing a global Lua variable \('foo'\) +content_by_lua\(nginx\.conf:\d+\):\d+: in\n\z/, "old foo: 1\n"] + + + +=== TEST 10: init_by_lua +--- http_config + init_by_lua_block { + foo = 1 + } +--- config + location /t { + content_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + ngx.say(foo) + } + } +--- response_body_like chomp +\A[23]\n\z +--- grep_error_log eval: qr/old foo: \d+/ +--- grep_error_log_out eval +["old foo: 1\n", "old foo: 2\n"] + + + +=== TEST 11: init_worker_by_lua +--- http_config + init_worker_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + } +--- config + location /t { + content_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + ngx.say(foo) + } + } +--- response_body_like chomp +\A[23]\n\z +--- grep_error_log eval: qr/old foo: \d+/ +--- grep_error_log_out eval +["old foo: 1\n", "old foo: 2\n"] + + + +=== TEST 12: init_by_lua + init_worker_by_lua +--- http_config + init_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + } + init_worker_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + } +--- config + location /t { + content_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + ngx.say(foo) + } + } +--- response_body_like chomp +\A[34]\n\z +--- grep_error_log eval: qr/old foo: \d+/ +--- grep_error_log_out eval +["old foo: 1\nold foo: 2\n", "old foo: 3\n"] + + + +=== TEST 13: don't show warn messages in init/init_worker +--- http_config + init_by_lua_block { + foo = 1 + } + + init_worker_by_lua_block { + bar = 2 + } +--- config + location /t { + content_by_lua_block { + ngx.say(foo) + ngx.say(bar) + } + } +--- response_body +1 +2 +--- no_error_log +setting global variable + + + +=== TEST 14: uthread +--- config + location /t { + content_by_lua_block { + local function f() + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + end + local ok, err = ngx.thread.spawn(f) + if not ok then + ngx.say("failed to set timer: ", err) + return + end + ngx.sleep(0.01) + ngx.say(foo) + } + } +--- response_body_like chomp +\A[12]\n\z +--- grep_error_log eval +qr/(old foo: \d+|writing a global Lua variable \('\w+'\))/ +--- grep_error_log_out eval +["writing a global Lua variable \('foo'\)\n", "old foo: 1\n"] + + + +=== TEST 15: balancer_by_lua +--- http_config + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + if not foo then + foo = 1 + else + ngx.log(ngx.WARN, "old foo: ", foo) + foo = foo + 1 + end + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- error_log eval +qr/\[crit\].*?\Qconnect() to 0.0.0.1:80 failed\E/ +--- grep_error_log eval: qr/(old foo: \d+|writing a global Lua variable \('\w+'\))/ +--- grep_error_log_out eval +["writing a global Lua variable \('foo'\)\n", "old foo: 1\n"] diff --git a/t/159-sa-restart.t b/t/159-sa-restart.t new file mode 100644 index 0000000000..a0f0c0eeb1 --- /dev/null +++ b/t/159-sa-restart.t @@ -0,0 +1,180 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + + $http_config .= <<_EOC_; + init_by_lua_block { + function test_sa_restart() + local signals = { + --"HUP", + --"INFO", + --"XCPU", + --"USR1", + --"USR2", + "ALRM", + --"INT", + "IO", + "CHLD", + --"WINCH", + } + + for _, signame in ipairs(signals) do + local cmd = string.format("kill -s %s %d && sleep 0.01", + signame, ngx.worker.pid()) + local err = select(2, io.popen(cmd):read("*a")) + if err then + error("SIG" .. signame .. " caused: " .. err) + end + end + end + } +_EOC_ + + $block->set_value("http_config", $http_config); + + if (!defined $block->config) { + my $config = <<_EOC_; + location /t { + echo ok; + } +_EOC_ + + $block->set_value("config", $config); + } + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } + + if (!defined $block->response_body) { + $block->set_value("ignore_response_body"); + } + + if (!defined $block->no_error_log) { + $block->set_value("no_error_log", "[error]"); + } +}); + +plan tests => repeat_each() * (blocks() * 2 + 1); + +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: lua_sa_restart default - sets SA_RESTART in init_worker_by_lua* +--- http_config + init_worker_by_lua_block { + test_sa_restart() + } + + + +=== TEST 2: lua_sa_restart off - does not set SA_RESTART +--- http_config + lua_sa_restart off; + + init_worker_by_lua_block { + test_sa_restart() + } +--- no_error_log +[crit] +--- error_log +Interrupted system call + + + +=== TEST 3: lua_sa_restart on (default) - sets SA_RESTART if no init_worker_by_lua* phase is defined +--- config + location /t { + content_by_lua_block { + test_sa_restart() + } + } + + + +=== TEST 4: lua_sa_restart on (default) - SA_RESTART is effective in rewrite_by_lua* +--- config + location /t { + rewrite_by_lua_block { + test_sa_restart() + } + + echo ok; + } + + + +=== TEST 5: lua_sa_restart on (default) - SA_RESTART is effective in access_by_lua* +--- config + location /t { + access_by_lua_block { + test_sa_restart() + } + + echo ok; + } + + + +=== TEST 6: lua_sa_restart on (default) - SA_RESTART is effective in content_by_lua* +--- config + location /t { + content_by_lua_block { + test_sa_restart() + } + } + + + +=== TEST 7: lua_sa_restart on (default) - SA_RESTART is effective in header_filter_by_lua* +--- config + location /t { + echo ok; + + header_filter_by_lua_block { + test_sa_restart() + } + } + + + +=== TEST 8: lua_sa_restart on (default) - SA_RESTART is effective in body_filter_by_lua* +--- config + location /t { + echo ok; + + body_filter_by_lua_block { + test_sa_restart() + } + } + + + +=== TEST 9: lua_sa_restart on (default) - SA_RESTART is effective in log_by_lua* +--- config + location /t { + echo ok; + + log_by_lua_block { + test_sa_restart() + } + } + + + +=== TEST 10: lua_sa_restart on (default) - SA_RESTART is effective in timer phase +--- config + location /t { + echo ok; + + log_by_lua_block { + ngx.timer.at(0, test_sa_restart) + } + } diff --git a/t/160-disable-init-by-lua.t b/t/160-disable-init-by-lua.t new file mode 100644 index 0000000000..e98c136dcf --- /dev/null +++ b/t/160-disable-init-by-lua.t @@ -0,0 +1,207 @@ +use Test::Nginx::Socket::Lua; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +my $html_dir = $ENV{TEST_NGINX_HTML_DIR}; +my $http_config = <<_EOC_; + init_by_lua_block { + function set_up_ngx_tmp_conf(conf) + if conf == nil then + conf = [[ + # to prevent the test process from overwriting the + # original pid file + pid logs/test_nginx.pid; + events { + worker_connections 64; + } + http { + init_by_lua_block { + ngx.log(ngx.ERR, "run init_by_lua") + } + } + ]] + end + + assert(os.execute("mkdir -p $html_dir/logs")) + + local conf_file = "$html_dir/nginx.conf" + local f, err = io.open(conf_file, "w") + if not f then + ngx.log(ngx.ERR, err) + return + end + + assert(f:write(conf)) + f:close() + + return conf_file + end + + function get_ngx_bin_path() + local ffi = require "ffi" + ffi.cdef[[char **ngx_argv;]] + return ffi.string(ffi.C.ngx_argv[0]) + end + } +_EOC_ + +add_block_preprocessor(sub { + my $block = shift; + + if (!defined $block->http_config) { + $block->set_value("http_config", $http_config); + } + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } +}); + +env_to_nginx("PATH"); +log_level("warn"); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: ensure init_by_lua* is not run in signaller process +--- config + location = /t { + content_by_lua_block { + local conf_file = set_up_ngx_tmp_conf() + local nginx = get_ngx_bin_path() + + local cmd = nginx .. " -p $TEST_NGINX_HTML_DIR -c " .. conf_file .. " -s reopen" + local p, err = io.popen(cmd) + if not p then + ngx.log(ngx.ERR, err) + return + end + + local out, err = p:read("*a") + if not out then + ngx.log(ngx.ERR, err) + + else + ngx.log(ngx.WARN, out) + end + p:close() + collectgarbage("collect") + } + } +--- error_log +failed (2: No such file or directory) +--- no_error_log eval +qr/\[error\] .*? init_by_lua:\d+: run init_by_lua/ + + + +=== TEST 2: init_by_lua* does not run when testing Nginx configuration +--- config + location = /t { + content_by_lua_block { + local conf_file = set_up_ngx_tmp_conf() + local nginx = get_ngx_bin_path() + + local cmd = nginx .. " -p $TEST_NGINX_HTML_DIR -c " .. conf_file .. " -t" + local p, err = io.popen(cmd) + if not p then + ngx.log(ngx.ERR, err) + return + end + + local out, err = p:read("*a") + if not out then + ngx.log(ngx.ERR, err) + + else + ngx.log(ngx.WARN, out) + end + p:close() + + local cmd = nginx .. " -p $TEST_NGINX_HTML_DIR -c " .. conf_file .. " -T" + local p, err = io.popen(cmd) + if not p then + ngx.log(ngx.ERR, err) + return + end + + local out, err = p:read("*a") + if not out then + ngx.log(ngx.ERR, err) + + else + ngx.log(ngx.WARN, out) + end + p:close() + collectgarbage("collect") + } + } +--- error_log +test is successful +--- no_error_log eval +qr/\[error\] .*? init_by_lua:\d+: run init_by_lua/ + + + +=== TEST 3: init_by_lua* does not run when testing Nginx configuration which contains 'lua_shared_dict' (GitHub #1462) +--- config + location = /t { + content_by_lua_block { + local conf = [[ + pid logs/test_nginx.pid; + events { + worker_connections 64; + } + http { + lua_shared_dict test 64k; + init_by_lua_block { + ngx.log(ngx.ERR, "run init_by_lua with lua_shared_dict") + } + } + ]] + local conf_file = set_up_ngx_tmp_conf(conf) + local nginx = get_ngx_bin_path() + + local cmd = nginx .. " -p $TEST_NGINX_HTML_DIR -c " .. conf_file .. " -t" + local p, err = io.popen(cmd) + if not p then + ngx.log(ngx.ERR, err) + return + end + + local out, err = p:read("*a") + if not out then + ngx.log(ngx.ERR, err) + + else + ngx.log(ngx.WARN, out) + end + p:close() + + local cmd = nginx .. " -p $TEST_NGINX_HTML_DIR -c " .. conf_file .. " -T" + local p, err = io.popen(cmd) + if not p then + ngx.log(ngx.ERR, err) + return + end + + local out, err = p:read("*a") + if not out then + ngx.log(ngx.ERR, err) + + else + ngx.log(ngx.WARN, out) + end + p:close() + collectgarbage("collect") + } + } +--- error_log +test is successful +--- no_error_log eval +qr/\[error\] .*? init_by_lua:\d+: run init_by_lua with lua_shared_dict/ diff --git a/t/161-load-resty-core.t b/t/161-load-resty-core.t new file mode 100644 index 0000000000..2c2643068d --- /dev/null +++ b/t/161-load-resty-core.t @@ -0,0 +1,166 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +our $HtmlDir = html_dir; + +add_block_preprocessor(sub { + my $block = shift; + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } + + if (!defined $block->no_error_log) { + $block->set_value("no_error_log", "[error]"); + } +}); + +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: resty.core is automatically loaded in the Lua VM +--- config + location = /t { + content_by_lua_block { + local loaded_resty_core = package.loaded["resty.core"] + local resty_core = require "resty.core" + + ngx.say("resty.core loaded: ", loaded_resty_core == resty_core) + } + } +--- response_body +resty.core loaded: true + + + +=== TEST 2: resty.core is automatically loaded in the Lua VM when 'lua_shared_dict' is used +--- http_config + lua_shared_dict dogs 128k; +--- config + location = /t { + content_by_lua_block { + local loaded_resty_core = package.loaded["resty.core"] + local resty_core = require "resty.core" + + ngx.say("resty.core loaded: ", loaded_resty_core == resty_core) + } + } +--- response_body +resty.core loaded: true + + + +=== TEST 3: resty.core is automatically loaded in the Lua VM with 'lua_code_cache off' +--- http_config + lua_code_cache off; +--- config + location = /t { + content_by_lua_block { + local loaded_resty_core = package.loaded["resty.core"] + local resty_core = require "resty.core" + + ngx.say("resty.core loaded: ", loaded_resty_core == resty_core) + } + } +--- response_body +resty.core loaded: true + + + +=== TEST 4: resty.core loading honors the lua_package_path directive +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;;';" +--- config + location = /t { + content_by_lua_block { + local loaded_resty_core = package.loaded["resty.core"] + local resty_core = require "resty.core" + + ngx.say("resty.core loaded: ", loaded_resty_core == resty_core) + + resty_core.go() + } + } +--- response_body +resty.core loaded: true +loaded from html dir +--- user_files +>>> resty/core.lua +return { + go = function () + ngx.say("loaded from html dir") + end +} + + + +=== TEST 5: resty.core not loading aborts the initialization +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;';" +--- config + location = /t { + return 200; + } +--- must_die +--- error_log eval +qr/\[alert\] .*? failed to load the 'resty\.core' module .*? \(reason: module 'resty\.core' not found:/ + + + +=== TEST 6: resty.core not loading produces an error with 'lua_code_cache off' +--- http_config + lua_code_cache off; + + init_by_lua_block { + package.path = "" + } +--- config + location = /t { + content_by_lua_block { + ngx.say("ok") + } + } +--- error_code: 500 +--- error_log eval +qr/\[error\] .*? failed to load the 'resty\.core' module .*? \(reason: module 'resty\.core' not found:/ +--- no_error_log eval +qr/\[alert\] .*? failed to load the 'resty\.core' module/ + + + +=== TEST 7: lua_load_resty_core logs a deprecation warning when specified (on) +--- http_config + lua_load_resty_core on; +--- config + location = /t { + return 200; + } +--- grep_error_log eval: qr/\[warn\] .*? lua_load_resty_core is deprecated.*/ +--- grep_error_log_out eval +[ +qr/\[warn\] .*? lua_load_resty_core is deprecated \(the lua-resty-core library is required since ngx_lua v0\.10\.16\) in .*?nginx\.conf:\d+/, +"" +] + + + +=== TEST 8: lua_load_resty_core logs a deprecation warning when specified (off) +--- http_config + lua_load_resty_core off; +--- config + location = /t { + return 200; + } +--- grep_error_log eval: qr/\[warn\] .*? lua_load_resty_core is deprecated.*/ +--- grep_error_log_out eval +[ +qr/\[warn\] .*? lua_load_resty_core is deprecated \(the lua-resty-core library is required since ngx_lua v0\.10\.16\) in .*?nginx\.conf:\d+/, +"" +] diff --git a/t/162-exit-worker.t b/t/162-exit-worker.t new file mode 100644 index 0000000000..7aff2a619b --- /dev/null +++ b/t/162-exit-worker.t @@ -0,0 +1,242 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +master_on(); +repeat_each(2); + +# NB: the shutdown_error_log block is independent from repeat times +plan tests => repeat_each() * (blocks() * 2 + 1) + 15; + +#log_level("warn"); +no_long_string(); +our $HtmlDir = html_dir; + +run_tests(); + +__DATA__ + +=== TEST 1: simple exit_worker_by_lua_block +--- http_config + exit_worker_by_lua_block { + ngx.log(ngx.NOTICE, "log from exit_worker_by_lua_block") + } +--- config + location /t { + echo "ok"; + } +--- request +GET /t +--- response_body +ok +--- shutdown_error_log +log from exit_worker_by_lua_block + + + +=== TEST 2: simple exit_worker_by_lua_file +--- http_config + exit_worker_by_lua_file html/exit_worker.lua; +--- config + location /t { + echo "ok"; + } +--- user_files +>>> exit_worker.lua +ngx.log(ngx.NOTICE, "log from exit_worker_by_lua_file") +--- request +GET /t +--- response_body +ok +--- shutdown_error_log +log from exit_worker_by_lua_file + + + +=== TEST 3: exit_worker_by_lua (require a global table) +--- http_config eval + qq{lua_package_path '$::HtmlDir/?.lua;;'; + exit_worker_by_lua_block { + foo = require("foo") + ngx.log(ngx.NOTICE, foo.bar) + }} +--- config + location /t { + content_by_lua_block { + foo = require("foo") + foo.bar = "hello, world" + ngx.say("ok") + } + } +--- user_files +>>> foo.lua +return {} +--- request +GET /t +--- response_body +ok +--- shutdown_error_log +hello, world + + + +=== TEST 4: ngx.timer is not allow +--- http_config + exit_worker_by_lua_block { + local function bar() + ngx.log(ngx.ERR, "run the timer!") + end + + local ok, err = ngx.timer.at(0, bar) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + else + ngx.log(ngx.NOTICE, "success") + end + } +--- config + location /t { + echo "ok"; + } +--- request +GET /t +--- response_body +ok +--- shutdown_error_log +API disabled in the context of exit_worker_by_lua* + + + +=== TEST 5: exit_worker_by_lua use shdict +--- http_config + lua_shared_dict dog 1m; + exit_worker_by_lua_block { + local dog = ngx.shared.dog + local val, err = dog:get("foo") + if not val then + ngx.log(ngx.ERR, "failed get shdict: ", err) + else + ngx.log(ngx.NOTICE, "get val: ", val) + end + } +--- config + location /t { + content_by_lua_block { + local dog = ngx.shared.dog + dog:set("foo", 100) + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +ok +--- shutdown_error_log +get val: 100 + + + +=== TEST 6: skip in cache processes (with exit worker and privileged agent) +--- http_config + lua_package_path "../lua-resty-core/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + + proxy_cache_path /tmp/cache levels=1:2 keys_zone=cache:1m; + + init_by_lua_block { + assert(require "ngx.process".enable_privileged_agent()) + } + + exit_worker_by_lua_block { + local process = require "ngx.process" + ngx.log(ngx.INFO, "hello from exit worker by lua, process type: ", process.type()) + } +--- config + location = /t { + return 200; + } +--- request + GET /t +--- no_error_log +[error] +--- shutdown_error_log eval +[ +qr/cache loader process \d+ exited/, +qr/cache manager process \d+ exited/, +qr/hello from exit worker by lua, process type: worker/, +qr/hello from exit worker by lua, process type: privileged agent/, +qr/privileged agent process \d+ exited/, +] + + + +=== TEST 7: skipin cache processes (with init worker but without privileged agent) +--- http_config + lua_package_path "../lua-resty-core/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + + proxy_cache_path /tmp/cache levels=1:2 keys_zone=cache:1m; + + exit_worker_by_lua_block { + local process = require "ngx.process" + ngx.log(ngx.INFO, "hello from exit worker by lua, process type: ", process.type()) + } +--- config + location = /t { + return 200; + } +--- request + GET /t +--- no_error_log +[error] +start privileged agent process +--- shutdown_error_log eval +[ +qr/cache loader process \d+ exited/, +qr/cache manager process \d+ exited/, +qr/hello from exit worker by lua, process type: worker/, +] + + + +=== TEST 8: syntax error in exit_worker_by_lua_block +--- http_config + exit_worker_by_lua_block { + ngx.log(ngx.debug, "pass") + error("failed to init" + ngx.log(ngx.debug, "unreachable") + } +--- config + location /t { + content_by_lua_block { + ngx.say("hello world") + } + } +--- request + GET /t +--- response_body +hello world +--- shutdown_error_log +exit_worker_by_lua error: exit_worker_by_lua(nginx.conf:25):4: ')' expected (to close '(' at line 3) near 'ngx' + + + +=== TEST 9: syntax error in exit_worker_by_lua_file +--- http_config + exit_worker_by_lua_file html/exit.lua; +--- config + location /t { + content_by_lua_block { + ngx.say("hello world") + } + } +--- user_files +>>> exit.lua + ngx.log(ngx.debug, "pass") + error("failed to init" + ngx.log(ngx.debug, "unreachable") + +--- request + GET /t +--- response_body +hello world +--- shutdown_error_log eval +qr|exit_worker_by_lua_file error: .*?t/servroot\w*/html/exit.lua:3: '\)' expected \(to close '\(' at line 2\) near 'ngx'| diff --git a/t/162-socket-tls-handshake.t b/t/162-socket-tls-handshake.t new file mode 100644 index 0000000000..19ad67a749 --- /dev/null +++ b/t/162-socket-tls-handshake.t @@ -0,0 +1,373 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +repeat_each(2); + +plan tests => repeat_each() * 43; + +$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; + +log_level 'debug'; + +no_long_string(); +#no_diff(); + +sub read_file { + my $infile = shift; + open my $in, $infile + or die "cannot open $infile for reading: $!"; + my $cert = do { local $/; <$in> }; + close $in; + $cert; +} + +our $MTLSCA = read_file("t/cert/mtls_ca.crt"); +our $MTLSClient = read_file("t/cert/mtls_client.crt"); +our $MTLSClientKey = read_file("t/cert/mtls_client.key"); +our $MTLSServer = read_file("t/cert/mtls_server.crt"); +our $MTLSServerKey = read_file("t/cert/mtls_server.key"); + +our $HtmlDir = html_dir; + +our $mtls_http_config = <<"_EOC_"; +server { + listen unix:$::HtmlDir/mtls.sock ssl; + + ssl_certificate $::HtmlDir/mtls_server.crt; + ssl_certificate_key $::HtmlDir/mtls_server.key; + ssl_client_certificate $::HtmlDir/mtls_ca.crt; + ssl_verify_client on; + server_tokens off; + + location / { + return 200 "hello, \$ssl_client_s_dn"; + } +} +_EOC_ + +our $mtls_user_files = <<"_EOC_"; +>>> mtls_server.key +$::MTLSServerKey +>>> mtls_server.crt +$::MTLSServer +>>> mtls_ca.crt +$::MTLSCA +>>> mtls_client.key +$::MTLSClientKey +>>> mtls_client.crt +$::MTLSClient +_EOC_ + +run_tests(); + +__DATA__ + +=== TEST 1: sanity: www.google.com +--- config + server_tokens off; + resolver $TEST_NGINX_RESOLVER ipv6=off; + + location /t { + content_by_lua_block { + -- avoid flushing bing in "check leak" testing mode: + local counter = package.loaded.counter + if not counter then + counter = 1 + elseif counter >= 2 then + return ngx.exit(503) + else + counter = counter + 1 + end + + package.loaded.counter = counter + + do + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect("www.google.com", 443) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake() + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + local line, err = sock:receive() + if not line then + ngx.say("failed to receive response status line: ", err) + return + end + + ngx.say("received: ", line) + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + + collectgarbage() + } + } +--- request +GET /t +--- response_body_like chop +\Aconnected: 1 +ssl handshake: cdata +sent http request: 59 bytes. +received: HTTP/1.1 (?:200 OK|302 Found) +close: 1 nil +\z +--- grep_error_log eval: qr/lua ssl (?:set|save|free) session: [0-9A-F]+/ +--- grep_error_log_out eval +qr/^lua ssl save session: ([0-9A-F]+) +lua ssl free session: ([0-9A-F]+) +$/ +--- no_error_log +lua ssl server name: +SSL reused session +[error] +[alert] +--- timeout: 5 + + + +=== TEST 2: mutual TLS handshake, upstream is not accessible without client certs +--- http_config eval: $::mtls_http_config +--- config eval +" + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect('unix:$::HtmlDir/mtls.sock') + if not ok then + ngx.say('failed to connect: ', err) + end + + assert(sock:sslhandshake()) + + ngx.say('connected: ', ok) + + local req = 'GET /\\r\\n' + + local bytes, err = sock:send(req) + if not bytes then + ngx.say('failed to send request: ', err) + return + end + + ngx.say('request sent: ', bytes) + + ngx.say(sock:receive('*a')) + + assert(sock:close()) + } + } +" +--- user_files eval: $::mtls_user_files +--- request +GET /t +--- response_body_like: 400 No required SSL certificate was sent +--- no_error_log +[alert] +[error] +[crit] +[emerg] + + + +=== TEST 3: mutual TLS handshake, upstream is accessible with client certs +--- http_config eval: $::mtls_http_config +--- config eval +" + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect('unix:$::HtmlDir/mtls.sock') + if not ok then + ngx.say('failed to connect: ', err) + end + + local f = assert(io.open('$::HtmlDir/mtls_client.crt')) + local cert_data = f:read('*a') + f:close() + + f = assert(io.open('$::HtmlDir/mtls_client.key')) + local key_data = f:read('*a') + f:close() + + local ssl = require('ngx.ssl') + + local chain = assert(ssl.parse_pem_cert(cert_data)) + local priv = assert(ssl.parse_pem_priv_key(key_data)) + + sock:setclientcert(chain, priv) + + assert(sock:sslhandshake()) + + ngx.say('connected: ', ok) + + local req = 'GET /\\r\\n' + + local bytes, err = sock:send(req) + if not bytes then + ngx.say('failed to send request: ', err) + return + end + + ngx.say('request sent: ', bytes) + + ngx.say(sock:receive('*a')) + + assert(sock:close()) + } + } +" +--- user_files eval: $::mtls_user_files +--- request +GET /t +--- response_body +connected: 1 +request sent: 7 +hello, CN=foo@example.com,O=OpenResty,ST=California,C=US +--- no_error_log +[alert] +[error] +[crit] +[emerg] + + + +=== TEST 4: incorrect type of client cert +--- config + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + + local ok, err = sock:setclientcert("doesnt", "work") + if not ok then + ngx.say('failed to setclientcert: ', err) + return + end + + assert(sock:close()) + } + } +--- request +GET /t +--- response_body +failed to setclientcert: bad cert arg: cdata expected, got string +--- no_error_log +[alert] +[error] +[crit] +[emerg] + + + +=== TEST 5: incorrect type of client key +--- config eval +" + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + + local f = assert(io.open('$::HtmlDir/mtls_client.crt')) + local cert_data = f:read('*a') + f:close() + + local ssl = require('ngx.ssl') + + local chain = assert(ssl.parse_pem_cert(cert_data)) + + local ok, err = sock:setclientcert(chain, 'work') + if not ok then + ngx.say('failed to setclientcert: ', err) + return + end + + assert(sock:close()) + } + } +" +--- user_files eval: $::mtls_user_files +--- request +GET /t +--- response_body +failed to setclientcert: bad pkey arg: cdata expected, got string +--- no_error_log +[alert] +[error] +[crit] +[emerg] + + + +=== TEST 6: missing client cert +--- config + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + + local ok, err = sock:setclientcert(nil, "work") + if not ok then + ngx.say('failed to setclientcert: ', err) + return + end + + assert(sock:close()) + } + } +--- request +GET /t +--- response_body +failed to setclientcert: client certificate must be supplied with corresponding private key +--- no_error_log +[alert] +[error] +[crit] +[emerg] + + + +=== TEST 7: missing private key +--- config + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + + local ok, err = sock:setclientcert('doesnt', nil) + if not ok then + ngx.say('failed to setclientcert: ', err) + return + end + + assert(sock:close()) + } + } +--- request +GET /t +--- response_body +failed to setclientcert: client certificate must be supplied with corresponding private key +--- no_error_log +[alert] +[error] +[crit] +[emerg] diff --git a/t/162-static-module-location.t b/t/162-static-module-location.t new file mode 100644 index 0000000000..9f09dbe631 --- /dev/null +++ b/t/162-static-module-location.t @@ -0,0 +1,103 @@ +use Test::Nginx::Socket::Lua; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +our $HtmlDir = html_dir; + +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: decoded url contains '\0' and '\r\n' +--- config + server_tokens off; + location = /t { + rewrite_by_lua_block { + ngx.req.read_body(); + local args, _ = ngx.req.get_post_args(); + ngx.req.set_uri(args["url"], true, true); + } + } +--- request +POST /t +url=%00%0a%0dset-cookie:1234567 +--- error_code: 301 +--- response_headers +Location: %00%0A%0Dset-cookie:1234567/ +--- response_body_like +.*301 Moved Permanently.* + + + +=== TEST 2: uri contain chinese characters +--- config + server_tokens off; +--- user_files +>>> t/中文/foo.txt +Hello, world +--- request +GET /t/中文 +--- more_headers +host: localhost +--- error_code: 301 +--- response_headers_like +Location: https?:\/\/localhost:\d+\/t\/%E4%B8%AD%E6%96%87\/ +--- response_body_like +.*301 Moved Permanently.* + + + +=== TEST 3: uri contain chinese characters with args +--- config + server_tokens off; +--- user_files +>>> t/中文/foo.txt +Hello, world +--- request +GET /t/中文?q=name +--- more_headers +host: localhost +--- error_code: 301 +--- response_headers_like +Location: https?:\/\/localhost:\d+\/t\/%E4%B8%AD%E6%96%87\/\?q=name +--- response_body_like +.*301 Moved Permanently.* + + + +=== TEST 4: uri already encoded +--- config + server_tokens off; +--- user_files +>>> t/中文/foo.txt +Hello, world +--- request +GET /t/%E4%B8%AD%E6%96%87 +--- more_headers +host: localhost +--- error_code: 301 +--- response_headers_like +Location: https?:\/\/localhost:\d+\/t\/%E4%B8%AD%E6%96%87\/ +--- response_body_like +.*301 Moved Permanently.* + + + +=== TEST 5: uri already encoded with args +--- config + server_tokens off; +--- user_files +>>> t/中文/foo.txt +Hello, world +--- request +GET /t/%E4%B8%AD%E6%96%87?q=name +--- more_headers +host: localhost +--- error_code: 301 +--- response_headers_like +Location: https?://localhost:\d+\/t\/%E4%B8%AD%E6%96%87\/\?q=name +--- response_body_like +.*301 Moved Permanently.* diff --git a/t/163-exit-worker-hup.t b/t/163-exit-worker-hup.t new file mode 100644 index 0000000000..136efdc7ff --- /dev/null +++ b/t/163-exit-worker-hup.t @@ -0,0 +1,89 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +our $SkipReason; + +BEGIN { + if ($ENV{TEST_NGINX_CHECK_LEAK}) { + $SkipReason = "unavailable for the hup tests"; + + } elsif ($ENV{TEST_NGINX_USE_HTTP3}) { + $SkipReason = "http3 does not support hub reload"; + + } else { + $ENV{TEST_NGINX_USE_HUP} = 1; + undef $ENV{TEST_NGINX_USE_STAP}; + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 2 + 1) + 2; + +no_long_string(); + +worker_connections(1024); +run_tests(); + +__DATA__ + +=== TEST 1: simple exit_worker_by_lua_block with hup +--- http_config + exit_worker_by_lua_block { + ngx.log(ngx.NOTICE, "log from exit_worker_by_lua_block") + } +--- config + location /t { + content_by_lua_block { + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +ok +--- shutdown_error_log +log from exit_worker_by_lua_block + + + +=== TEST 2: exit after worker_shutdown_timeout +--- main_config + worker_shutdown_timeout 1; +--- http_config + exit_worker_by_lua_block { + ngx.log(ngx.NOTICE, "log from exit_worker_by_lua_block") + } + + server { + listen $TEST_NGINX_RAND_PORT_1; + + location = /t { + echo 'hello world'; + } + } +--- config + location /t { + content_by_lua_block { + ngx.timer.at(0, function () + local sock = ngx.socket.tcp() + sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1) + local reader = sock:receiveuntil("unknow") + ngx.log(ngx.NOTICE, "reading to block the exiting") + reader() + end) + + ngx.sleep(0) + + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +ok +--- error_log +reading to block the exiting +--- shutdown_error_log +log from exit_worker_by_lua_block diff --git a/t/163-signal.t b/t/163-signal.t new file mode 100644 index 0000000000..a0c2ee8082 --- /dev/null +++ b/t/163-signal.t @@ -0,0 +1,64 @@ +# vi:ft= + +our $SkipReason; + +BEGIN { + if ($ENV{TEST_NGINX_USE_HUP}) { + $SkipReason = "unavailable under hup test mode"; + + } elsif ($ENV{TEST_NGINX_CHECK_LEAK}) { + $SkipReason = "unavailable under check leak test mode"; + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); + +plan tests => 2 * blocks(); + +no_long_string(); + + +run_tests(); + +__DATA__ + +=== TEST 1: SIGHUP followed by SIGQUIT +--- config + location = /t { + content_by_lua_block { + local pid = ngx.worker.pid() + os.execute("kill -HUP " .. pid) + ngx.sleep(0.01) + + os.execute("kill -QUIT " .. pid) + } + } +--- request +GET /t +--- ignore_response +--- wait: 0.1 +--- error_log eval +qr/\[notice\] \d+#\d+: exit$/ +--- no_error_log eval +qr/\[notice\] \d+#\d+: reconfiguring/ +--- curl_error eval +qr/curl: \(28\) Operation timed out after \d+ milliseconds with 0 bytes received|curl: \(56\) Recv failure: Connection reset by peer|curl: \(56\) Failure when receiving data from the peer|curl: \(55\) sendmsg\(\) returned -1 \(errno 111\)/ + + + +=== TEST 2: exit after receiving SIGHUP in single process mode +--- config + location = /t { + content_by_lua_block { + local pid = ngx.worker.pid() + os.execute("kill -HUP " .. pid) + } + } +--- request +GET /t +--- ignore_response +--- wait: 0.1 +--- error_log eval +qr/\[notice\] \d+#\d+: exit$/ +--- no_error_log eval +qr/\[notice\] \d+#\d+: reconfiguring/ diff --git a/t/164-say.t b/t/164-say.t new file mode 100644 index 0000000000..40ec1ff1b4 --- /dev/null +++ b/t/164-say.t @@ -0,0 +1,111 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +repeat_each(2); + +plan tests => blocks() * repeat_each() * 2; + +run_tests(); + +__DATA__ + +=== TEST 1: ngx.say (integer) +--- config + location /lua { + content_by_lua_block { + ngx.say(2) + } + } +--- request +GET /lua +--- response_body +2 + + + +=== TEST 2: ngx.say (floating point number) +the maximum number of significant digits is 14 in lua +--- config + location /lua { + content_by_lua_block { + ngx.say(3.1415926) + ngx.say(3.14159265357939723846) + } + } +--- request +GET /lua +--- response_body +3.1415926 +3.1415926535794 + + + +=== TEST 3: ngx.say (table with number) +--- config + location /lua { + content_by_lua_block { + local data = {123," ", 3.1415926} + ngx.say(data) + } + } +--- request +GET /lua +--- response_body +123 3.1415926 + + + +=== TEST 4: ngx.say min int32 -2147483648 +--- config + location /lua { + content_by_lua_block { + ngx.say(-2147483648) + } + } +--- request +GET /lua +--- response_body +-2147483648 + + + +=== TEST 5: ngx.say big integer 2147483647 +--- config + location /lua { + content_by_lua_block { + ngx.say(2147483647) + } + } +--- request +GET /lua +--- response_body +2147483647 + + + +=== TEST 6: ngx.say big integer -9223372036854775808 +--- config + location /lua { + content_by_lua_block { + ngx.say(-9223372036854775808) + } + } +--- request +GET /lua +--- response_body +-9.2233720368548e+18 + + + +=== TEST 7: ngx.say big integer 18446744073709551615 +--- config + location /lua { + content_by_lua_block { + ngx.say(18446744073709551615) + } + } +--- request +GET /lua +--- response_body +1.844674407371e+19 diff --git a/t/165-thread-cache.t b/t/165-thread-cache.t new file mode 100644 index 0000000000..0adc6c6250 --- /dev/null +++ b/t/165-thread-cache.t @@ -0,0 +1,79 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +#worker_connections(1014); +#master_on(); +#workers(2); +#log_level('warn'); + +repeat_each(2); +#repeat_each(1); + +plan tests => repeat_each() * (blocks() * 4); + +#no_diff(); +#no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: thread cache size == 1 +--- http_config + lua_thread_cache_max_entries 1; + +--- config + location /lua { + # NOTE: the newline escape sequence must be double-escaped, as nginx config + # parser will unescape first! + content_by_lua ' + local ok, err = ngx.print("Hello, Lua!\\n") + if not ok then + ngx.log(ngx.ERR, "print failed: ", err) + end + '; + } +--- request +GET /lua +--- response_body +Hello, Lua! +--- no_error_log +[error] +--- grep_error_log eval: qr/lua caching unused lua thread|lua reusing cached lua thread/ +--- grep_error_log_out eval +[ + "lua caching unused lua thread\n", + "lua reusing cached lua thread +lua caching unused lua thread +", +] + + + +=== TEST 2: thread cache size == 0 +--- http_config + lua_thread_cache_max_entries 0; + +--- config + location /lua { + # NOTE: the newline escape sequence must be double-escaped, as nginx config + # parser will unescape first! + content_by_lua ' + local ok, err = ngx.print("Hello, Lua!\\n") + if not ok then + ngx.log(ngx.ERR, "print failed: ", err) + end + '; + } +--- request +GET /lua +--- response_body +Hello, Lua! +--- no_error_log +[error] +--- grep_error_log eval: qr/lua caching unused lua thread|lua reusing cached lua thread/ +--- grep_error_log_out eval +[ + "", + "", +] diff --git a/t/166-ssl-client-hello.t b/t/166-ssl-client-hello.t new file mode 100644 index 0000000000..4372d0d1f8 --- /dev/null +++ b/t/166-ssl-client-hello.t @@ -0,0 +1,3453 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +repeat_each(3); + +# All these tests need to have new openssl +my $NginxBinary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; +my $openssl_version = eval { `$NginxBinary -V 2>&1` }; + +if ($openssl_version =~ m/built with OpenSSL (0\S*|1\.0\S*|1\.1\.0\S*)/) { + plan(skip_all => "too old OpenSSL, need >= 1.1.1, was $1"); +} elsif ($openssl_version =~ m/running with BoringSSL/) { + plan(skip_all => "does not support BoringSSL"); +} elsif ($ENV{TEST_NGINX_USE_HTTP3}) { + plan tests => repeat_each() * (blocks() * 6 + 6); +} else { + plan tests => repeat_each() * (blocks() * 6 + 10); +} + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; +$ENV{TEST_NGINX_QUIC_IDLE_TIMEOUT} ||= 0.6; + +#log_level 'warn'; +log_level 'debug'; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: simple logging +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { print("ssl client hello by lua is running!") } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] +--- grep_error_log eval: qr/ssl_client_hello_by_lua\(.*?,|\bssl client hello: connection reusable: \d+|\breusable connection: \d+/ +--- grep_error_log_out eval +# Since nginx version 1.17.9, nginx call ngx_reusable_connection(c, 0) +# before call ssl callback function +$Test::Nginx::Util::NginxVersion >= 1.017009 ? +qr/reusable connection: 0 +ssl client hello: connection reusable: 0 +ssl_client_hello_by_lua\(nginx.conf:\d+\):1: ssl client hello by lua is running!,/ +: qr /reusable connection: 1 +ssl client hello: connection reusable: 1 +reusable connection: 0 +ssl_client_hello_by_lua\(nginx.conf:\d+\):1: ssl client hello by lua is running!,/ + + + +=== TEST 2: sleep +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local begin = ngx.now() + ngx.sleep(0.1) + print("elapsed in ssl client hello by lua: ", ngx.now() - begin) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log eval +[ +'lua ssl server name: "test.com"', +qr/elapsed in ssl client hello by lua: 0.(?:09|1\d)\d+,/, +] + +--- no_error_log +[error] +[alert] + + + +=== TEST 3: timer +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local function f() + print("my timer run!") + end + local ok, err = ngx.timer.at(0, f) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +my timer run! + +--- no_error_log +[error] +[alert] + + + +=== TEST 4: cosocket +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if not ok then + ngx.log(ngx.ERR, "failed to connect to memc: ", err) + return + end + + local bytes, err = sock:send("flush_all\r\n") + if not bytes then + ngx.log(ngx.ERR, "failed to send flush_all command: ", err) + return + end + + local res, err = sock:receive() + if not res then + ngx.log(ngx.ERR, "failed to receive memc reply: ", err) + return + end + + print("received memc reply: ", res) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +received memc reply: OK + +--- no_error_log +[error] +[alert] + + + +=== TEST 5: ngx.exit(0) - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + ngx.exit(0) + ngx.log(ngx.ERR, "should never reached here...") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: boolean + +--- error_log +lua exit with code 0 + +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 6: ngx.exit(ngx.ERROR) - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + ngx.exit(ngx.ERROR) + ngx.log(ngx.ERR, "should never reached here...") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +failed to do SSL handshake: handshake failed + +--- error_log eval +[ +'ssl_client_hello_by_lua: handler return value: -1, client hello cb exit code: 0', +qr/\[info\] .*? SSL_do_handshake\(\) failed .*?callback failed/, +'lua exit with code -1', +] + +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 7: ngx.exit(0) - yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + ngx.sleep(0.001) + ngx.exit(0) + + ngx.log(ngx.ERR, "should never reached here...") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: boolean + +--- error_log +lua exit with code 0 + +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 8: ngx.exit(ngx.ERROR) - yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + ngx.sleep(0.001) + ngx.exit(ngx.ERROR) + + ngx.log(ngx.ERR, "should never reached here...") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +failed to do SSL handshake: handshake failed + +--- error_log eval +[ +'ssl_client_hello_by_lua: client hello cb exit code: 0', +qr/\[info\] .*? SSL_do_handshake\(\) failed .*?callback failed/, +'lua exit with code -1', +] + +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 9: lua exception - no yield +--- http_config + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_2 ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + error("bad bad bad") + ngx.log(ngx.ERR, "should never reached here...") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("127.0.0.2", $TEST_NGINX_RAND_PORT_2) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +failed to do SSL handshake: handshake failed + +--- error_log eval +[ +'runtime error: ssl_client_hello_by_lua(nginx.conf:28):2: bad bad bad', +'ssl_client_hello_by_lua: handler return value: 500, client hello cb exit code: 0', +qr/\[info\] .*? SSL_do_handshake\(\) failed .*?callback failed/, +qr/context: ssl_client_hello_by_lua\*, client: \d+\.\d+\.\d+\.\d+, server: \d+\.\d+\.\d+\.\d+:\d+/, +] + +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 10: lua exception - yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + ngx.sleep(0.001) + error("bad bad bad") + ngx.log(ngx.ERR, "should never reached here...") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +failed to do SSL handshake: handshake failed + +--- error_log eval +[ +'runtime error: ssl_client_hello_by_lua(nginx.conf:28):3: bad bad bad', +'ssl_client_hello_by_lua: client hello cb exit code: 0', +qr/\[info\] .*? SSL_do_handshake\(\) failed .*?callback failed/, +] + +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 11: get phase +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block {print("get_phase: ", ngx.get_phase())} + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end + collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +get_phase: ssl_client_hello + +--- no_error_log +[error] +[alert] + + + +=== TEST 12: connection aborted prematurely +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + ngx.sleep(0.3) + print("ssl-client-hello-by-lua: after sleeping") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(150) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t + +--- response_body +connected: 1 +failed to do SSL handshake: timeout + +--- error_log +lua ssl server name: "test.com" +ssl-client-hello-by-lua: after sleeping + +--- no_error_log +[error] +[alert] +--- wait: 0.6 + + + +=== TEST 13: subrequests disabled +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block {ngx.location.capture("/foo")} + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +failed to do SSL handshake: handshake failed + +--- error_log eval +[ +'lua ssl server name: "test.com"', +'ssl_client_hello_by_lua(nginx.conf:28):1: API disabled in the context of ssl_client_hello_by_lua*', +qr/\[info\] .*?callback failed/, +] + +--- no_error_log +[alert] + + + +=== TEST 14: simple logging (by_lua_file) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_file html/a.lua; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } + +--- user_files +>>> a.lua +print("ssl client hello by lua is running!") + +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +a.lua:1: ssl client hello by lua is running! + +--- no_error_log +[error] +[alert] + + + +=== TEST 15: coroutine API +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield + + local function f() + local cnt = 0 + for i = 1, 20 do + print("co yield: ", cnt) + cy() + cnt = cnt + 1 + end + end + + local c = cc(f) + for i = 1, 3 do + print("co resume, status: ", coroutine.status(c)) + cr(c) + end + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- grep_error_log eval: qr/co (?:yield: \d+|resume, status: \w+)/ +--- grep_error_log_out +co resume, status: suspended +co yield: 0 +co resume, status: suspended +co yield: 1 +co resume, status: suspended +co yield: 2 + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] + + + +=== TEST 16: simple user thread wait with yielding +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local function f() + ngx.sleep(0.01) + print("uthread: hello in thread") + return "done" + end + + local t, err = ngx.thread.spawn(f) + if not t then + ngx.log(ngx.ERR, "uthread: failed to spawn thread: ", err) + return ngx.exit(ngx.ERROR) + end + + print("uthread: thread created: ", coroutine.status(t)) + + local ok, res = ngx.thread.wait(t) + if not ok then + print("uthread: failed to wait thread: ", res) + return + end + + print("uthread: ", res) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- no_error_log +[error] +[alert] +--- grep_error_log eval: qr/uthread: [^.,]+/ +--- grep_error_log_out +uthread: thread created: running +uthread: hello in thread +uthread: done + + + +=== TEST 17: simple logging - use ssl_client_hello_by_lua* on the http {} level +GitHub openresty/lua-resty-core#42 +--- http_config + ssl_client_hello_by_lua_block { print("ssl client hello by lua is running!") } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +ssl_client_hello_by_lua(nginx.conf:25):1: ssl client hello by lua is running! + +--- no_error_log +[error] +[alert] + + + +=== TEST 18: simple logging - use ssl_client_hello_by_lua* on the http {} level and server {} level +--- http_config + ssl_client_hello_by_lua_block { print("ssl client hello by lua on the http level is running!") } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_client_hello_by_lua_block { print("ssl client hello by lua on the server level is running!") } + server_name test.com; + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +ssl_client_hello_by_lua(nginx.conf:31):1: ssl client hello by lua on the server level is running! + +--- no_error_log +[error] +[alert] + + + +=== TEST 19: use ssl_client_hello_by_lua* on the server {} level with non-ssl server +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; + ssl_client_hello_by_lua_block { print("ssl client hello by lua is running!") } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + server_name test.com; + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- no_error_log +ssl client hello by lua is running! +[error] +[alert] + + + +=== TEST 20: use ssl_client_hello_by_lua* on the http {} level with non-ssl server +--- http_config + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; + ssl_client_hello_by_lua_block { print("ssl client hello by lua is running!") } + server_name test.com; + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- no_error_log +ssl client hello by lua is running! +[error] +[alert] + + + +=== TEST 21: listen two ports (one for ssl and one for non-ssl) in one server - connect ssl port +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + listen unix:$TEST_NGINX_HTML_DIR/nginx2.sock; + ssl_client_hello_by_lua_block { print("ssl client hello by lua is running!") } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + server_name test.com; + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +ssl_client_hello_by_lua(nginx.conf:28):1: ssl client hello by lua is running! + +--- no_error_log +[error] +[alert] + + + +=== TEST 22: listen two ports (one for ssl and one for non-ssl) in one server - connect non-ssl port +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + listen unix:$TEST_NGINX_HTML_DIR/nginx2.sock; + ssl_client_hello_by_lua_block { print("ssl client hello by lua is running!") } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + server_name test.com; + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx2.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + + +--- no_error_log +ssl client hello by lua is running! +[error] +[alert] + + + +=== TEST 23: simple logging - use ssl_client_hello_by_lua* in multiple virtual servers +--- http_config + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_client_hello_by_lua_block { print("ssl client hello by lua in server1 is running!") } + server_name test.com; + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_client_hello_by_lua_block { print("ssl client hello by lua in server2 is running!") } + server_name test2.com; + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo2") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test2.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 57 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 5 +received: Connection: close +received: +received: foo2 +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +ssl_client_hello_by_lua(nginx.conf:29):1: ssl client hello by lua in server1 is running! + +--- no_error_log +ssl client hello by lua in server2 is running! +[error] +[alert] + + + +=== TEST 24: simple logging (syslog) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + error_log syslog:server=127.0.0.1:12345 debug; + + ssl_client_hello_by_lua_block { print("ssl client hello by lua is running!") } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log eval +[ +qr/\[error\] .*? send\(\) failed/, +'lua ssl server name: "test.com"', +] +--- no_error_log +[alert] +ssl client hello by lua is running! + + + +=== TEST 25: get raw_client_addr - IPv4 +--- http_config + lua_package_path "../lua-resty-core/lib/?.lua;;"; + + server { + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + + ssl_client_hello_by_lua_block { + local ssl = require "ngx.ssl" + local byte = string.byte + local addr, addrtype, err = ssl.raw_client_addr() + local ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2), + byte(addr, 3), byte(addr, 4)) + print("client ip: ", ip) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +client ip: 127.0.0.1 + +--- no_error_log +[error] +[alert] + + + +=== TEST 26: get raw_client_addr - unix domain socket +--- http_config + lua_package_path "../lua-resty-core/lib/?.lua;;"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_client_hello_by_lua_block { + local ssl = require "ngx.ssl" + local addr, addrtyp, err = ssl.raw_client_addr() + print("client socket file: ", addr) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +client socket file: + +--- no_error_log +[error] +[alert] + + + +=== TEST 27: ssl_client_hello_by_lua* can yield when reading early data +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_early_data on; + server_tokens off; + + ssl_client_hello_by_lua_block { + local begin = ngx.now() + ngx.sleep(0.1) + print("elapsed in ssl_client_hello_by_lua*: ", ngx.now() - begin) + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: boolean +--- grep_error_log eval +qr/elapsed in ssl_client_hello_by_lua\*: 0\.(?:09|1\d)\d+,/, +--- grep_error_log_out eval +[ +qr/elapsed in ssl_client_hello_by_lua\*: 0\.(?:09|1\d)\d+,/, +qr/elapsed in ssl_client_hello_by_lua\*: 0\.(?:09|1\d)\d+,/, +qr/elapsed in ssl_client_hello_by_lua\*: 0\.(?:09|1\d)\d+,/, +] +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 28: cosocket (UDP) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + server_tokens off; + + ssl_client_hello_by_lua_block { + local sock = ngx.socket.udp() + + sock:settimeout(1000) + + local ok, err = sock:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if not ok then + ngx.log(ngx.ERR, "failed to connect to memc: ", err) + return + end + + local req = "\0\1\0\0\0\1\0\0flush_all\r\n" + local ok, err = sock:send(req) + if not ok then + ngx.log(ngx.ERR, "failed to send flush_all to memc: ", err) + return + end + + local res, err = sock:receive() + if not res then + ngx.log(ngx.ERR, "failed to receive memc reply: ", err) + return + end + + ngx.log(ngx.INFO, "received memc reply of ", #res, " bytes") + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + -- collectgarbage() + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +--- no_error_log +[error] +[alert] +[emerg] +--- grep_error_log eval: qr/received memc reply of \d+ bytes/ +--- grep_error_log_out eval +[ +'received memc reply of 12 bytes +', +'received memc reply of 12 bytes +', +'received memc reply of 12 bytes +', +'received memc reply of 12 bytes +', +] + + + +=== TEST 29: uthread (kill) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + server_tokens off; + + ssl_client_hello_by_lua_block { + local function f() + ngx.log(ngx.INFO, "uthread: hello from f()") + ngx.sleep(1) + end + + local t, err = ngx.thread.spawn(f) + if not t then + ngx.log(ngx.ERR, "failed to spawn thread: ", err) + return ngx.exit(ngx.ERROR) + end + + local ok, res = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.ERR, "failed to kill thread: ", res) + return + end + + ngx.log(ngx.INFO, "uthread: killed") + + local ok, err = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.INFO, "uthread: failed to kill: ", err) + end + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + -- collectgarbage() + } + } +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +--- no_error_log +[error] +[alert] +[emerg] +--- grep_error_log eval: qr/uthread: [^.,]+/ +--- grep_error_log_out +uthread: hello from f() +uthread: killed +uthread: failed to kill: already waited or killed + + + +=== TEST 30: ngx.exit(ngx.OK) - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + ngx.exit(ngx.OK) + ngx.log(ngx.ERR, "should never reached here...") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(false, nil, true, false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: boolean + +--- error_log eval +[ +'ssl_client_hello_by_lua: handler return value: 0, client hello cb exit code: 1', +qr/\[debug\] .*? SSL_do_handshake: 1/, +'lua exit with code 0', +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 31: ssl_client_hello_by_lua* can yield when reading early data +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_client_hello_by_lua_block { + print("ssl client hello by lua is running!") + ngx.sleep(0.1) + print("ssl client hello by lua is done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl client hello by lua is running!|ssl client hello by lua is done|test completed)/ +--- grep_error_log_out +ssl client hello by lua is running! +ssl client hello by lua is done +test completed +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 32: ssl_client_hello_by_lua* with TCP cosocket (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_client_hello_by_lua_block { + print("ssl client hello: TCP cosocket test start") + + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if not ok then + ngx.log(ngx.ERR, "failed to connect to memc: ", err) + return + end + + local bytes, err = sock:send("flush_all\r\n") + if not bytes then + ngx.log(ngx.ERR, "failed to send flush_all command: ", err) + return + end + + local res, err = sock:receive() + if not res then + ngx.log(ngx.ERR, "failed to receive memc reply: ", err) + return + end + + print("ssl client hello: received TCP memc reply: ", res) + sock:close() + print("ssl client hello: TCP cosocket test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl client hello: TCP cosocket test start|ssl client hello: received TCP memc reply: OK|ssl client hello: TCP cosocket test done|test completed)/ +--- grep_error_log_out +ssl client hello: TCP cosocket test start +ssl client hello: received TCP memc reply: OK +ssl client hello: TCP cosocket test done +test completed + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 33: ssl_client_hello_by_lua* with UDP cosocket (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_client_hello_by_lua_block { + print("ssl client hello: UDP cosocket test start") + + local sock = ngx.socket.udp() + sock:settimeout(1000) + + local ok, err = sock:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if not ok then + ngx.log(ngx.ERR, "failed to connect to memc: ", err) + return + end + + local req = "\0\1\0\0\0\1\0\0flush_all\r\n" + local ok, err = sock:send(req) + if not ok then + ngx.log(ngx.ERR, "failed to send flush_all to memc: ", err) + return + end + + local res, err = sock:receive() + if not res then + ngx.log(ngx.ERR, "failed to receive memc reply: ", err) + return + end + + print("ssl client hello: received UDP memc reply of ", #res, " bytes") + sock:close() + print("ssl client hello: UDP cosocket test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl client hello: UDP cosocket test start|ssl client hello: received UDP memc reply of \d+ bytes|ssl client hello: UDP cosocket test done|test completed)/ +--- grep_error_log_out +ssl client hello: UDP cosocket test start +ssl client hello: received UDP memc reply of 12 bytes +ssl client hello: UDP cosocket test done +test completed + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 34: ssl_client_hello_by_lua* with ngx.timer (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_client_hello_by_lua_block { + print("ssl client hello: timer test start") + + local function timer_handler() + print("ssl client hello: timer executed") + end + + local ok, err = ngx.timer.at(0, timer_handler) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + + print("ssl client hello: timer created") + print("ssl client hello: timer test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl client hello: timer test start|ssl client hello: timer created|ssl client hello: timer test done|ssl client hello: timer executed|test completed)/ +--- grep_error_log_out +ssl client hello: timer test start +ssl client hello: timer created +ssl client hello: timer test done +ssl client hello: timer executed +test completed + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 35: ssl_client_hello_by_lua* with user threads (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_client_hello_by_lua_block { + print("ssl client hello: uthread test start") + + local function worker() + ngx.sleep(0.01) + print("ssl client hello: uthread worker executed") + return "worker_result" + end + + local t, err = ngx.thread.spawn(worker) + if not t then + ngx.log(ngx.ERR, "failed to spawn thread: ", err) + return + end + + print("ssl client hello: uthread spawned") + + local ok, res = ngx.thread.wait(t) + if not ok then + ngx.log(ngx.ERR, "failed to wait thread: ", res) + return + end + + print("ssl client hello: uthread result: ", res) + print("ssl client hello: uthread test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl client hello: uthread test start|ssl client hello: uthread spawned|ssl client hello: uthread worker executed|ssl client hello: uthread result: worker_result|ssl client hello: uthread test done|test completed)/ +--- grep_error_log_out +ssl client hello: uthread test start +ssl client hello: uthread spawned +ssl client hello: uthread worker executed +ssl client hello: uthread result: worker_result +ssl client hello: uthread test done +test completed + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 36: ssl_client_hello_by_lua* with coroutines (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_client_hello_by_lua_block { + print("ssl client hello: coroutine test start") + + local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield + + local function coro_func() + local cnt = 0 + for i = 1, 3 do + print("ssl client hello: coro yield: ", cnt) + cy() + cnt = cnt + 1 + end + return "coro_done" + end + + local c = cc(coro_func) + for i = 1, 4 do + print("ssl client hello: coro resume, status: ", coroutine.status(c)) + local ok, res = cr(c) + if not ok then + print("ssl client hello: coro error: ", res) + break + end + if coroutine.status(c) == "dead" then + print("ssl client hello: coro result: ", res) + break + end + end + + print("ssl client hello: coroutine test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl client hello: coroutine test start|ssl client hello: coro resume, status: \w+|ssl client hello: coro yield: \d+|ssl client hello: coro result: coro_done|ssl client hello: coroutine test done|test completed)/ +--- grep_error_log_out +ssl client hello: coroutine test start +ssl client hello: coro resume, status: suspended +ssl client hello: coro yield: 0 +ssl client hello: coro resume, status: suspended +ssl client hello: coro yield: 1 +ssl client hello: coro resume, status: suspended +ssl client hello: coro yield: 2 +ssl client hello: coro resume, status: suspended +ssl client hello: coro result: coro_done +ssl client hello: coroutine test done +test completed + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 37: ssl_client_hello_by_lua* without yield API (simple logic) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_client_hello_by_lua_block { + print("ssl client hello: simple test start") + + -- Simple calculations without yield + local sum = 0 + for i = 1, 10 do + sum = sum + i + end + + print("ssl client hello: calculated sum: ", sum) + + -- String operations + local str = "hello" + str = str .. " world" + print("ssl client hello: concatenated string: ", str) + + -- Table operations + local t = {a = 1, b = 2, c = 3} + local count = 0 + for k, v in pairs(t) do + count = count + v + end + print("ssl client hello: table sum: ", count) + + print("ssl client hello: simple test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl client hello: simple test start|ssl client hello: calculated sum: 55|ssl client hello: concatenated string: hello world|ssl client hello: table sum: 6|ssl client hello: simple test done|test completed)/ +--- grep_error_log_out +ssl client hello: simple test start +ssl client hello: calculated sum: 55 +ssl client hello: concatenated string: hello world +ssl client hello: table sum: 6 +ssl client hello: simple test done +test completed + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 38: ssl_client_hello_by_lua* with multiple network operations (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_client_hello_by_lua_block { + print("ssl client hello: multiple network test start") + + -- First TCP operation + local sock1 = ngx.socket.tcp() + sock1:settimeout(2000) + local ok, err = sock1:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if ok then + local bytes, err = sock1:send("version\r\n") + if bytes then + local res, err = sock1:receive() + if res then + print("ssl client hello: TCP1 version: ", res) + end + end + sock1:close() + end + + ngx.sleep(0.01) -- Small delay + + -- Second UDP operation + local sock2 = ngx.socket.udp() + sock2:settimeout(1000) + local ok, err = sock2:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if ok then + local req = "\0\1\0\0\0\1\0\0version\r\n" + local ok, err = sock2:send(req) + if ok then + local res, err = sock2:receive() + if res then + print("ssl client hello: UDP version reply length: ", #res) + end + end + sock2:close() + end + + print("ssl client hello: multiple network test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(ssl client hello: multiple network test start|ssl client hello: TCP1 version: VERSION|ssl client hello: UDP version reply length: \d+|ssl client hello: multiple network test done|test completed)/ +--- grep_error_log_out eval +[ +qr/ssl client hello: multiple network test start/, +qr/ssl client hello: TCP1 version: VERSION/, +qr/ssl client hello: UDP version reply length: \d+/, +qr/ssl client hello: multiple network test done/, +qr/test completed/, +] + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 39: ssl_cert_by_lua* and ssl_client_hello_by_lua* with sleep (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} +--- http_config + ssl_certificate_by_lua_block { + print("cert by: starting with sleep") + local begin = ngx.now() + ngx.sleep(0.05) + print("cert by: slept for ", ngx.now() - begin, " seconds") + } + +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_client_hello_by_lua_block { + print("client hello: starting with sleep") + local begin = ngx.now() + ngx.sleep(0.1) + print("client hello: slept for ", ngx.now() - begin, " seconds") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(client hello: starting with sleep|client hello: slept for 0\.\d+|cert by: starting with sleep|cert by: slept for 0\.\d+|test completed)/ +--- grep_error_log_out eval +[ +qr/client hello: starting with sleep/, +qr/client hello: slept for 0\.(?:09|1\d)\d+/, +qr/cert by: starting with sleep/, +qr/cert by: slept for 0\.0[4-6]\d+/, +qr/test completed/, +] + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 40: ssl_cert_by_lua* and ssl_client_hello_by_lua* with cosocket (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} +--- http_config + ssl_certificate_by_lua_block { + print("cert by: cosocket test start") + + local sock = ngx.socket.udp() + sock:settimeout(1000) + + local ok, err = sock:setpeername("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if not ok then + ngx.log(ngx.ERR, "cert by: failed to connect to memc: ", err) + return + end + + local req = "\0\1\0\0\0\1\0\0flush_all\r\n" + local ok, err = sock:send(req) + if not ok then + ngx.log(ngx.ERR, "cert by: failed to send flush_all to memc: ", err) + return + end + + local res, err = sock:receive() + if not res then + ngx.log(ngx.ERR, "cert by: failed to receive memc reply: ", err) + return + end + + print("cert by: received UDP memc reply of ", #res, " bytes") + sock:close() + print("cert by: cosocket test done") + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_client_hello_by_lua_block { + print("client hello: cosocket test start") + + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if not ok then + ngx.log(ngx.ERR, "client hello: failed to connect to memc: ", err) + return + end + + local bytes, err = sock:send("version\r\n") + if not bytes then + ngx.log(ngx.ERR, "client hello: failed to send version command: ", err) + return + end + + local res, err = sock:receive() + if not res then + ngx.log(ngx.ERR, "client hello: failed to receive memc reply: ", err) + return + end + + print("client hello: received memc reply: ", res) + sock:close() + print("client hello: cosocket test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(client hello: cosocket test start|client hello: received memc reply: VERSION|client hello: cosocket test done|cert by: cosocket test start|cert by: received UDP memc reply of \d+ bytes|cert by: cosocket test done|test completed)/ +--- grep_error_log_out eval +[ +qr/client hello: cosocket test start/, +qr/client hello: received memc reply: VERSION/, +qr/client hello: cosocket test done/, +qr/cert by: cosocket test start/, +qr/cert by: received UDP memc reply of \d+ bytes/, +qr/cert by: cosocket test done/, +qr/test completed/, +] + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 41: ssl_cert_by_lua* and ssl_client_hello_by_lua* with timer and uthread (yield API) +--- skip_eval: 6:!$ENV{TEST_NGINX_USE_HTTP3} +--- http_config + ssl_certificate_by_lua_block { + print("cert by: coroutine test start") + + local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield + + local function coro_func() + local cnt = 0 + for i = 1, 2 do + print("cert by: coro yield: ", cnt) + cy() + cnt = cnt + 1 + end + return "cert_by_coro_done" + end + + local c = cc(coro_func) + for i = 1, 3 do + print("cert by: coro resume, status: ", coroutine.status(c)) + local ok, res = cr(c) + if not ok then + print("cert by: coro error: ", res) + break + end + if coroutine.status(c) == "dead" then + print("cert by: coro result: ", res) + break + end + end + + -- Small sleep to allow timer to execute + ngx.sleep(0.01) + print("cert by: coroutine test done") + } + +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_client_hello_by_lua_block { + print("client hello: timer and uthread test start") + + -- Timer test + local function timer_handler() + print("client hello: timer executed") + end + + local ok, err = ngx.timer.at(0, timer_handler) + if not ok then + ngx.log(ngx.ERR, "client hello: failed to create timer: ", err) + return + end + + print("client hello: timer created") + + -- User thread test + local function worker() + ngx.sleep(0.01) + print("client hello: uthread worker executed") + return "client_hello_result" + end + + local t, err = ngx.thread.spawn(worker) + if not t then + ngx.log(ngx.ERR, "client hello: failed to spawn thread: ", err) + return + end + + print("client hello: uthread spawned") + + local ok, res = ngx.thread.wait(t) + if not ok then + ngx.log(ngx.ERR, "client hello: failed to wait thread: ", res) + return + end + + print("client hello: uthread result: ", res) + print("client hello: timer and uthread test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- response_body +test completed + +--- grep_error_log eval: qr/(client hello: timer and uthread test start|client hello: timer created|client hello: uthread spawned|client hello: uthread worker executed|client hello: uthread result: client_hello_result|client hello: timer and uthread test done|cert by: coroutine test start|cert by: coro resume, status: \w+|cert by: coro yield: \d+|cert by: coro result: cert_by_coro_done|cert by: coroutine test done|client hello: timer executed|test completed)/ +--- grep_error_log_out eval +[ +qr/client hello: timer and uthread test start/, +qr/client hello: timer created/, +qr/client hello: uthread spawned/, +qr/client hello: uthread worker executed/, +qr/client hello: uthread result: client_hello_result/, +qr/client hello: timer and uthread test done/, +qr/cert by: coroutine test start/, +qr/cert by: coro resume, status: suspended/, +qr/cert by: coro yield: 0/, +qr/cert by: coro resume, status: suspended/, +qr/cert by: coro yield: 1/, +qr/cert by: coro resume, status: suspended/, +qr/cert by: coro result: cert_by_coro_done/, +qr/cert by: coroutine test done/, +qr/client hello: timer executed/, +qr/test completed/, +] + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 42: ssl_cert_by_lua* with cosocket (yield API) and ssl_client_hello_by_lua* failure +--- skip_eval: 8:!$ENV{TEST_NGINX_USE_HTTP3} +--- http_config + ssl_certificate_by_lua_block { + print("cert by: test start") + ngx.exit(500) + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + + ssl_client_hello_by_lua_block { + print("client hello: cosocket test start") + + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if not ok then + ngx.log(ngx.ERR, "client hello: failed to connect to memc: ", err) + return + end + + local bytes, err = sock:send("version\r\n") + if not bytes then + ngx.log(ngx.ERR, "client hello: failed to send version command: ", err) + return + end + + local res, err = sock:receive() + if not res then + ngx.log(ngx.ERR, "client hello: failed to receive memc reply: ", err) + return + end + + print("client hello: received memc reply: ", res) + sock:close() + print("client hello: cosocket test done") + } + + location /t { + content_by_lua_block { + print("test completed") + ngx.say("test completed") + } + } +--- request +GET /t +--- ignore_response +--- curl_error eval +qr/Connection time/ +--- grep_error_log eval: qr/(client hello: cosocket test start|client hello: received memc reply: VERSION|client hello: cosocket test done|cert by: test start)/ +--- grep_error_log_out eval +[ +qr/client hello: cosocket test start/, +qr/client hello: received memc reply: VERSION/, +qr/client hello: cosocket test done/, +qr/cert by: test start/, +] + +--- no_error_log +[error] +[alert] +[emerg] diff --git a/t/166-worker-thread.t b/t/166-worker-thread.t new file mode 100644 index 0000000000..1cbeec5eba --- /dev/null +++ b/t/166-worker-thread.t @@ -0,0 +1,1675 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +our $SkipReason; + +BEGIN { + if ($ENV{TEST_NGINX_EVENT_TYPE} + && $ENV{TEST_NGINX_EVENT_TYPE} !~ /^kqueue|epoll|eventport$/) + { + $SkipReason = "unavailable for the event type '$ENV{TEST_NGINX_EVENT_TYPE}'"; + } +} + +use Test::Nginx::Socket::Lua $SkipReason ? (skip_all => $SkipReason) : (); + +#worker_connections(1014); +#master_on(); +#workers(2); +#log_level('warn'); + +repeat_each(1); + +plan tests => repeat_each() * (blocks() * 2); + +our $HtmlDir = html_dir; + +our $HttpConfig = qq{ + lua_package_path "$::HtmlDir/?.lua;./?.lua;;"; + lua_worker_thread_vm_pool_size 1; +}; + +#no_diff(); +#no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: hello from worker thread +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return "hello" +end +return {hello=hello} +--- request +GET /hello +--- response_body +true : hello + + + +=== TEST 2: thread_pool not found +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return "hello" +end +return {hello=hello} +--- request +GET /hello +--- response_body +false : thread pool testpool not found + + + +=== TEST 3: pass table +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, ok_or_err = ngx.run_worker_thread("testpool", "hello", "hello", {["hello"]="world", [1]={["embed"]=1}}) + ngx.say(ok, " , ", ok_or_err) + } +} +--- user_files +>>> hello.lua +local function hello(arg1) + if arg1.hello == "world" and arg1[1].embed == 1 then + return true + end + return false +end +return {hello=hello} +--- request +GET /hello +--- response_body +true , true + + + +=== TEST 4: expecting at least 3 arguments +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, err = ngx.run_worker_thread("testpool") + ngx.say(ok, " : ", err) + } +} +--- request +GET /hello +--- response_body +false : expecting at least 3 arguments + + + +=== TEST 5: base64 +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, base64 = ngx.run_worker_thread("testpool", "hello", "enc", "hello") + ngx.say(ok, " , ", base64 == "aGVsbG8=") + } +} +--- user_files +>>> hello.lua +local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + +local function enc(data) + return ((data:gsub('.', function(x) + local r,b='',x:byte() + for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end + return r; + end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) + if (#x < 6) then return '' end + local c=0 + for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end + return b:sub(c+1,c+1) + end)..({ '', '==', '=' })[#data%3+1]) +end + +return {enc=enc} +--- request +GET /hello +--- response_body +true , true + + + +=== TEST 6: return table +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, ret = ngx.run_worker_thread("testpool", "hello", "hello") + if ret.hello == "world" and ret[1].embed == 1 then + ngx.say(ok, " , ", true) + end + } +} +--- user_files +>>> hello.lua +local function hello() + return {["hello"]="world", [1]={["embed"]=1}} +end +return {hello=hello} +--- request +GET /hello +--- response_body +true , true + + + +=== TEST 7: unsupported argument type +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local function dummy() end + local ok, err = ngx.run_worker_thread("testpool", "hello", "hello", dummy) + ngx.say(ok, " : ", err) + } +} +--- user_files +>>> hello.lua +local function hello() + return "hello" +end +return {hello=hello} +--- request +GET /hello +--- response_body +false : unsupported Lua type: LUA_TFUNCTION in the argument + + + +=== TEST 8: multiple return values +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, res1, res2 = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", res1, " , ", res2) + } +} +--- user_files +>>> hello.lua +local function hello() + return "hello", 200 +end +return {hello=hello} +--- request +GET /hello +--- response_body +true : hello , 200 + + + +=== TEST 9: module not found +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", err) + } +} +--- request +GET /hello +--- response_body_like +false : module 'hello' not found.* + + + +=== TEST 10: the number of Lua VM exceeds the pool size +--- no_http2 +--- quic_max_idle_timeout: 5 +--- main_config + thread_pool testpool threads=100; +--- http_config eval: $::HttpConfig +--- config +location /foo { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} + +location /bar { + default_type 'text/plain'; + + content_by_lua_block { + local ok, foobar_or_err = ngx.run_worker_thread("testpool", "foobar", "foobar") + ngx.say(ok, " : ", foobar_or_err) + } +} + +location /t { + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + local function t(path) + local sock = ngx.socket.tcp() + local port = ngx.var.port + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local req = "GET " .. path .. " HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + local ret, err, part = sock:receive("*a") + local _, idx = string.find(ret, "\r\n\r\n"); + idx = idx + 1 + ngx.print(string.sub(ret, idx)) + ok, err = sock:close() + end + + local t1 = ngx.thread.spawn(t, "/foo") + local t2 = ngx.thread.spawn(t, "/bar") + ngx.thread.wait(t1) + ngx.thread.wait(t2) + } +} +--- user_files +>>> hello.lua +local function hello() + os.execute("sleep 3") + return "hello" +end +return {hello=hello} +>>> foobar.lua +local function foobar() + return "foobar" +end +return {foobar=foobar} +--- request +GET /t +--- response_body eval +"false : no available Lua vm\ntrue : hello\n" +--- timeout: 10 + + + +=== TEST 11: kill uthread before worker thread callback +--- no_http2 +--- quic_max_idle_timeout: 10 +--- main_config + thread_pool testpool threads=100; +--- http_config eval: $::HttpConfig +--- config +location /foo { + default_type 'text/plain'; + + content_by_lua_block { + local function t() + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + end + local t1 = ngx.thread.spawn(t) + if ngx.var.arg_kill == "kill" then + ngx.thread.kill(t1) + ngx.say("killed") + end + } +} + +location /t { + set $port $TEST_NGINX_SERVER_PORT; + + content_by_lua_block { + local function t(path) + local sock = ngx.socket.tcp() + local port = ngx.var.port + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local req = "GET " .. path .. " HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n" + + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + local ret, err, part = sock:receive("*a") + local _, idx = string.find(ret, "\r\n\r\n"); + idx = idx + 1 + ngx.print(string.sub(ret, idx)) + ok, err = sock:close() + end + + local t1 = ngx.thread.spawn(t, "/foo?kill=kill") + ngx.thread.wait(t1) + ngx.sleep(4) + local t2 = ngx.thread.spawn(t, "/foo") + ngx.thread.wait(t2) + } +} +--- user_files +>>> hello.lua +local function hello() + os.execute("sleep 1") + return "hello" +end +return {hello=hello} +>>> foobar.lua +local function foobar() + return "foobar" +end +return {foobar=foobar} +--- request +GET /t +--- response_body eval +"killed\ntrue : hello\n" +--- timeout: 10 + + + +=== TEST 12: exit before worker thread callback +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local function t() + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + end + ngx.thread.spawn(t) + ngx.exit(200) + } +} +--- user_files +>>> hello.lua +local function hello() + os.execute("sleep 3") + return "hello" +end +return {hello=hello} +--- request +GET /hello +--- response_body +--- timeout: 10 + + + +=== TEST 13: unsupported argument type in nested table +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local function dummy() end + local ok, err = ngx.run_worker_thread("testpool", "hello", "hello", + {["hello"]="world", [1]={["embed"]=1, ["dummy"]=dummy}}) + ngx.say(ok, " : ", err) + } +} +--- user_files +>>> hello.lua +local function hello() + return "hello" +end +return {hello=hello} +--- request +GET /hello +--- response_body +false : unsupported Lua type: LUA_TFUNCTION in the argument + + + +=== TEST 14: return table with unsupported type +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, ret = ngx.run_worker_thread("testpool", "hello", "hello") + if ok == false then + ngx.say("false", " , ", ret) + end + if ret.hello == "world" and ret[1].embed == 1 then + ngx.say(ok, " , ", true) + end + } +} +--- user_files +>>> hello.lua +local function hello() + local function dummy() end + return {["hello"]="world", [1]={["embed"]=1, ["dummy"]=dummy}} +end +return {hello=hello} +--- request +GET /hello +--- response_body +false , unsupported Lua type: LUA_TFUNCTION in the return value + + + +=== TEST 15: the type of module name is not string +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local function dummy() end + local ok, err = ngx.run_worker_thread("testpool", dummy, "hello") + ngx.say(ok, " : ", err) + } +} +--- user_files +>>> hello.lua +local function hello() + return "hello" +end +return {hello=hello} +--- request +GET /hello +--- response_body +false : module name should be a string + + + +=== TEST 16: the type of function name is not string +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local function dummy() end + local ok, err = ngx.run_worker_thread("testpool", "hello", dummy) + ngx.say(ok, " : ", err) + } +} +--- user_files +>>> hello.lua +local function hello() + return "hello" +end +return {hello=hello} +--- request +GET /hello +--- response_body +false : function name should be a string + + + +=== TEST 17: the type of thread pool name is not string +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local function dummy() end + local ok, err = ngx.run_worker_thread(dummy, "hello", "hello") + ngx.say(ok, " : ", err) + } +} +--- user_files +>>> hello.lua +local function hello() + return "hello" +end +return {hello=hello} +--- request +GET /hello +--- response_body +false : threadpool should be a string + + + +=== TEST 18: ngx.encode_base64 +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return ngx.encode_base64("hello") +end +return {hello=hello} +--- request +GET /hello +--- response_body +true : aGVsbG8= + + + +=== TEST 19: ngx.config.subsystem +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return ngx.config.subsystem +end +return {hello=hello} +--- request +GET /hello +--- response_body +true : http + + + +=== TEST 20: ngx.hmac_sha1 +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + local key = "thisisverysecretstuff" + local src = "some string we want to sign" + local digest = ngx.hmac_sha1(key, src) + return ngx.encode_base64(digest) +end +return {hello=hello} +--- request +GET /hello +--- response_body +true : R/pvxzHC4NLtj7S+kXFg/NePTmk= + + + +=== TEST 21: ngx.encode_args +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return ngx.encode_args({foo = 3, ["b r"] = "hello world"}) +end +return {hello=hello} +--- request +GET /hello +--- response_body eval +qr/foo=3&b%20r=hello%20world|b%20r=hello%20world&foo=3/ + + + +=== TEST 22: ngx.decode_args +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, ret = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", ret.a, " : ", ret.b) + } +} +--- user_files +>>> hello.lua +local function hello() + local args = "a=bar&b=foo" + args = ngx.decode_args(args) + return args +end +return {hello=hello} +--- request +GET /hello +--- response_body +true : bar : foo + + + +=== TEST 23: ngx.quote_sql_str +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config + location /hello { + content_by_lua ' + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello", "a\\026b\\026") + ngx.say(ok, " : ", hello_or_err) + '; + } +--- user_files +>>> hello.lua +local function hello(str) + return ngx.quote_sql_str(str) +end +return {hello=hello} +--- request +GET /hello +--- response_body +true : 'a\Zb\Z' + + + +=== TEST 24: ngx.decode_base64 +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return ngx.decode_base64("aGVsbG8=") +end +return {hello=hello} +--- request +GET /hello +--- response_body +true : hello + + + +=== TEST 25: ngx.crc32_short +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return ngx.crc32_short("hello, world") +end +return {hello=hello} +--- request +GET /hello +--- response_body +true : 4289425978 + + + +=== TEST 26: ngx.crc32_long +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return ngx.crc32_long("hello, world") +end +return {hello=hello} +--- request +GET /hello +--- response_body +true : 4289425978 + + + +=== TEST 27: ngx.md5_bin +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + local s = ngx.md5_bin(45) + s = string.gsub(s, ".", function (c) + return string.format("%02x", string.byte(c)) + end) + return s +end +return {hello=hello} +--- request +GET /hello +--- response_body +true : 6c8349cc7260ae62e3b1396831a8398f + + + +=== TEST 28: ngx.md5 +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return ngx.md5("hello") +end +return {hello=hello} +--- request +GET /hello +--- response_body +true : 5d41402abc4b2a76b9719d911017c592 + + + +=== TEST 29: ngx.config.debug +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return ngx.config.debug +end +return {hello=hello} +--- request +GET /hello +--- response_body_like chop +^true : (?:true|false)$ + + + +=== TEST 30: ngx.config.prefix +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return ngx.config.prefix() +end +return {hello=hello} +--- request +GET /hello +--- response_body_like chop +^true : \/\S+$ + + + +=== TEST 31: ngx.config.nginx_version +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return ngx.config.nginx_version +end +return {hello=hello} +--- request +GET /hello +--- response_body_like chop +^true : \d+$ + + + +=== TEST 32: ngx.config.nginx_configure +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return ngx.config.nginx_configure() +end +return {hello=hello} +--- request +GET /hello +--- response_body_like chop +^\s*\-\-[^-]+ + + + +=== TEST 33: ngx.config.ngx_lua_version +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return ngx.config.ngx_lua_version +end +return {hello=hello} +--- request +GET /hello +--- response_body_like chop +^true : \d+$ + + + +=== TEST 34: write_log_file +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /write_log_file { + default_type 'text/plain'; + + access_by_lua_block { + local ok, err = ngx.run_worker_thread("testpool", "write_log_file", "log", ngx.var.arg_str) + if not ok then + ngx.say(ok, " : ", err) + return + end + ngx.say(ok) + } +} +--- user_files +>>> write_log_file.lua +local function log(str) + local file, err = io.open("/tmp/tmp.log", "w") + if not file then + return false, err + end + file:write(str) + file:flush() + file:close() + return true +end +return {log=log} +--- request +GET /write_log_file?str=hello +--- response_body +true + + + +=== TEST 35: shdict get, int value +--- main_config + thread_pool testpool threads=100; +--- http_config eval +" + lua_shared_dict dogs 10m; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; +" +--- config +location /dictget { + default_type 'text/plain'; + + access_by_lua_block { + local dogs = ngx.shared.dogs + dogs:set("Jim", 8) + local ok, err = ngx.run_worker_thread("testpool", "test_shdict", "dictget") + ngx.say(ok, ",", err) + } +} +--- user_files +>>> test_shdict.lua +local function dictget(str) + local dogs = ngx.shared.dogs + return dogs:get("Jim") +end +return {dictget=dictget} +--- request +GET /dictget +--- response_body +true,8 + + + +=== TEST 36: shdict set nil in main thread +--- main_config + thread_pool testpool threads=100; +--- http_config eval +" + lua_shared_dict dogs 10m; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; +" +--- config +location /dictget { + default_type 'text/plain'; + + access_by_lua_block { + local dogs = ngx.shared.dogs + dogs:set("Jim", 8) + local ok, err = ngx.run_worker_thread("testpool", "test_shdict", "dictget") + ngx.say(ok, ",", err) + dogs:set("Jim", nil) + local ok, err = ngx.run_worker_thread("testpool", "test_shdict", "dictget") + ngx.say(ok, ",", err) + } +} +--- user_files +>>> test_shdict.lua +local function dictget(str) + local dogs = ngx.shared.dogs + return dogs:get("Jim") +end +return {dictget=dictget} +--- request +GET /dictget +--- response_body +true,8 +true,nil + + + +=== TEST 37: shdict set nil in worker thread +--- main_config + thread_pool testpool threads=100; +--- http_config eval +" + lua_shared_dict dogs 10m; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; +" +--- config +location /dictsetnil { + default_type 'text/plain'; + + access_by_lua_block { + local dogs = ngx.shared.dogs + dogs:set("Jim", 8) + local ok, err = ngx.run_worker_thread("testpool", "test_shdict", "dictsetnil") + ngx.say(ok, ",", err) + ngx.say(ok, ",", dogs:get("Jim")) + } +} +--- user_files +>>> test_shdict.lua +local function dictsetnil(str) + local dogs = ngx.shared.dogs + return dogs:set("Jim", nil) +end +return {dictsetnil=dictsetnil} +--- request +GET /dictsetnil +--- response_body +true,true +true,nil + + + +=== TEST 38: shdict get_stale +For http3: curl: (55) ngtcp2_conn_handle_expiry returned error: ERR_IDLE_CLOSE +--- skip_eval: 2:$ENV{TEST_NGINX_USE_HTTP3} +--- main_config + thread_pool testpool threads=100; +--- http_config eval +" + lua_shared_dict dogs 10m; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; +" +--- config +location /dictget { + default_type 'text/plain'; + + access_by_lua_block { + local dogs = ngx.shared.dogs + dogs:set("Jim", 8, 1) + ngx.sleep(2) + local ok, err = ngx.run_worker_thread("testpool", "test_shdict", "dictget") + ngx.say(ok, ",", err) + } +} +--- user_files +>>> test_shdict.lua +local function dictget(str) + local dogs = ngx.shared.dogs + return dogs:get_stale("Jim") +end +return {dictget=dictget} +--- request +GET /dictget +--- response_body +true,8 + + + +=== TEST 39: shdict add failed +--- main_config + thread_pool testpool threads=100; +--- http_config eval +" + lua_shared_dict dogs 10m; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; +" +--- config +location /dictadd { + default_type 'text/plain'; + + access_by_lua_block { + local dogs = ngx.shared.dogs + dogs:set("Jim", 8) + local ok, err, err2 = ngx.run_worker_thread("testpool", "test_shdict", "dictadd") + ngx.say(ok, ",", err, ",", err2) + } +} +--- user_files +>>> test_shdict.lua +local function dictadd(str) + local dogs = ngx.shared.dogs + local success, err = dogs:add("Jim", "hello") + return success, err +end +return {dictadd=dictadd} +--- request +GET /dictadd +--- response_body +true,false,exists + + + +=== TEST 40: shdict force add +--- main_config + thread_pool testpool threads=100; +--- http_config eval +" + lua_shared_dict dogs 6m; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; +" +--- config +location /dictadd { + default_type 'text/plain'; + + access_by_lua_block { + local dogs = ngx.shared.dogs + local bigstr = string.rep("A", 1024*1024*3) + dogs:set("Jim", bigstr) + local ok, ret, err, forcible = ngx.run_worker_thread("testpool", "test_shdict", "dictadd") + ngx.say(ok, ",", ret, ",", forcible, ",", dogs:get("Jim")) + } +} +--- user_files +>>> test_shdict.lua +local function dictadd(str) + local dogs = ngx.shared.dogs + local bigstr = string.rep("A", 1024*1024*5) + local success, err, forcible = dogs:add("King", bigstr) + return success, err, forcible +end +return {dictadd=dictadd} +--- request +GET /dictadd +--- response_body +true,true,true,nil + + + +=== TEST 41: shdict replace +--- main_config + thread_pool testpool threads=100; +--- http_config eval +" + lua_shared_dict dogs 6m; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; +" +--- config +location /dictreplace { + default_type 'text/plain'; + + access_by_lua_block { + local dogs = ngx.shared.dogs + local bigstr = string.rep("A", 1024*1024*3) + dogs:set("Jim", bigstr) + local ok, ret, err = ngx.run_worker_thread("testpool", "test_shdict", "dictreplace") + ngx.say(ok, ",", ret, ",", err, ",", dogs:get("Jim")) + } +} +--- user_files +>>> test_shdict.lua +local function dictreplace(str) + local dogs = ngx.shared.dogs + local success, err = dogs:replace("Jim", 8) + return success, err +end +return {dictreplace=dictreplace} +--- request +GET /dictreplace +--- response_body +true,true,nil,8 + + + +=== TEST 42: shdict replace not found +--- main_config + thread_pool testpool threads=100; +--- http_config eval +" + lua_shared_dict dogs 6m; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; +" +--- config +location /dictreplace { + default_type 'text/plain'; + + access_by_lua_block { + local dogs = ngx.shared.dogs + local ok, ret, err = ngx.run_worker_thread("testpool", "test_shdict", "dictreplace") + ngx.say(ok, ",", ret, ",", err) + } +} +--- user_files +>>> test_shdict.lua +local function dictreplace(str) + local dogs = ngx.shared.dogs + local success, err = dogs:replace("Jim", 8) + return success, err +end +return {dictreplace=dictreplace} +--- request +GET /dictreplace +--- response_body +true,false,not found + + + +=== TEST 43: shdict incr +--- main_config + thread_pool testpool threads=100; +--- http_config eval +" + lua_shared_dict dogs 6m; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; +" +--- config +location /dictincr { + default_type 'text/plain'; + + access_by_lua_block { + local dogs = ngx.shared.dogs + local success, err = dogs:set("Jim", 8) + local ok, ret, err = ngx.run_worker_thread("testpool", "test_shdict", "dictincr") + ngx.say(ok, ",", ret, ",", err, ",", dogs:get("Jim")) + } +} +--- user_files +>>> test_shdict.lua +local function dictincr(str) + local dogs = ngx.shared.dogs + local success, err = dogs:incr("Jim", 1) + return success, err +end +return {dictincr=dictincr} +--- request +GET /dictincr +--- response_body +true,9,nil,9 + + + +=== TEST 44: shdict lpush lpop +--- main_config + thread_pool testpool threads=100; +--- http_config eval +" + lua_shared_dict dogs 6m; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; +" +--- config +location /dictlpush { + default_type 'text/plain'; + + access_by_lua_block { + local dogs = ngx.shared.dogs + dogs:lpush("Jim", 8) + dogs:lpush("Jim", 9) + local ok, val, len, err = ngx.run_worker_thread("testpool", "test_shdict", "dictlpush") + ngx.say(ok, ",", val, ",", len, ",", err, ",", dogs:lpop("Jim")) + } +} +--- user_files +>>> test_shdict.lua +local function dictlpush(str) + local dogs = ngx.shared.dogs + local val = dogs:lpop("Jim") + local len, err = dogs:lpush("Jim", 7) + return val, len, err +end +return {dictlpush=dictlpush} +--- request +GET /dictlpush +--- response_body +true,9,2,nil,7 + + + +=== TEST 45: shdict expire ttl +--- main_config + thread_pool testpool threads=100; +--- http_config eval +" + lua_shared_dict dogs 6m; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; +" +--- config +location /dictexpire { + default_type 'text/plain'; + + access_by_lua_block { + local dogs = ngx.shared.dogs + dogs:set("Jim", 8) + local ok, success, err = ngx.run_worker_thread("testpool", "test_shdict", "dictexpire") + ngx.say(ok, ",", success, ",", err, ",", dogs:ttl("Jim") <= 1) + } +} +--- user_files +>>> test_shdict.lua +local function dictexpire(str) + local dogs = ngx.shared.dogs + local success, err = dogs:expire("Jim", 1) + return success, err +end +return {dictexpire=dictexpire} +--- request +GET /dictexpire +--- response_body +true,true,nil,true + + + +=== TEST 46: shdict flush_all +--- main_config + thread_pool testpool threads=100; +--- http_config eval +" + lua_shared_dict dogs 6m; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; +" +--- config +location /dictexpire { + default_type 'text/plain'; + + access_by_lua_block { + local dogs = ngx.shared.dogs + dogs:set("Jim", 8) + dogs:set("King", 9) + local ok = ngx.run_worker_thread("testpool", "test_shdict", "dictexpire") + ngx.say(ok, ",", dogs:get("Jim"), ",", dogs:get("King")) + } +} +--- user_files +>>> test_shdict.lua +local function dictexpire(str) + local dogs = ngx.shared.dogs + dogs:flush_all() +end +return {dictexpire=dictexpire} +--- request +GET /dictexpire +--- response_body +true,nil,nil + + + +=== TEST 47: shdict get_keys +--- main_config + thread_pool testpool threads=100; +--- http_config eval +" + lua_shared_dict dogs 6m; + lua_package_path '$::HtmlDir/?.lua;./?.lua;;'; +" +--- config +location /dictgetkeys { + default_type 'text/plain'; + + access_by_lua_block { + local dogs = ngx.shared.dogs + dogs:set("Jim", 8) + dogs:set("King", 9) + local ok, keys = ngx.run_worker_thread("testpool", "test_shdict", "dictgetkeys") + ngx.say(ok, ",", table.concat(keys, ":")) + } +} +--- user_files +>>> test_shdict.lua +local function dictgetkeys(str) + local dogs = ngx.shared.dogs + return dogs:get_keys() +end +return {dictgetkeys=dictgetkeys} +--- request +GET /dictgetkeys +--- response_body +true,Jim:King + + + +=== TEST 48: unsupported argument type in self-reference table +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local t = {} + t.a = t + local ok, ok_or_err = ngx.run_worker_thread("testpool", "hello", "hello", t) + ngx.say(ok, " , ", ok_or_err) + } +} +--- user_files +>>> hello.lua +local function hello(arg1) + return true +end +return {hello=hello} +--- request +GET /hello +--- response_body +false , suspicious circular references, table depth exceed max depth: 100 in the argument + + + +=== TEST 49: unsupported argument type in circular-reference table +--- main_config + thread_pool testpool threads=100; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local t = {} + local s = {} + t.a = s + s.b = t + + local ok, ok_or_err = ngx.run_worker_thread("testpool", "hello", "hello", t) + ngx.say(ok, " , ", ok_or_err) + } +} +--- user_files +>>> hello.lua +local function hello(arg1) + return true +end +return {hello=hello} +--- request +GET /hello +--- response_body +false , suspicious circular references, table depth exceed max depth: 100 in the argument + + + +=== TEST 50: call run_worker_thread twice +--- main_config + thread_pool testpool threads=1; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + + ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", hello_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return "hello" +end +return {hello=hello} +--- request +GET /hello +--- response_body +true : hello +true : hello + + + +=== TEST 51: big object +--- main_config + thread_pool testpool threads=1; +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config +location /hello { + default_type 'text/plain'; + + content_by_lua_block { + local ok, hello_or_err = ngx.run_worker_thread("testpool", "hello", "hello") + ngx.say(ok, " : ", #hello_or_err) + + local ok, gcsize_or_err = ngx.run_worker_thread("testpool", "hello", "gcsize") + ngx.say(ok, " : ", gcsize_or_err) + } +} +--- user_files +>>> hello.lua +local function hello() + return string.rep("helloworld", 1000000) +end + +local function gcsize() + return collectgarbage("count") +end + +return { + hello = hello, + gcsize = gcsize +} +--- request +GET /hello +--- response_body eval +qr/\Atrue : 10000000 +true : \d{3,4}\.\d+ +\z/ms diff --git a/t/167-server-rewrite.t b/t/167-server-rewrite.t new file mode 100644 index 0000000000..33a30f3842 --- /dev/null +++ b/t/167-server-rewrite.t @@ -0,0 +1,491 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; +use t::StapThread; + +our $GCScript = <<_EOC_; +$t::StapThread::GCScript + +F(ngx_http_lua_check_broken_connection) { + println("lua check broken conn") +} + +F(ngx_http_lua_request_cleanup) { + println("lua req cleanup") +} +_EOC_ + +our $StapScript = $t::StapThread::StapScript; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 10); + +#log_level("info"); +#no_long_string(); + +run_tests(); + +__DATA__ + +=== TEST 1: server_rewrite_by_lua_block in http +--- http_config + server_rewrite_by_lua_block { + ngx.ctx.a = "server_rewrite_by_lua_block in http" + } +--- config + location /lua { + content_by_lua_block { + ngx.say(ngx.ctx.a) + ngx.log(ngx.INFO, ngx.ctx.a) + } + } +--- request +GET /lua +--- response_body +server_rewrite_by_lua_block in http +--- error_log +server_rewrite_by_lua_block in http +--- no_error_log +[error] + + + +=== TEST 2: server_rewrite_by_lua_block in server +--- config + server_rewrite_by_lua_block { + ngx.log(ngx.INFO, "server_rewrite_by_lua_block in server") + } + location /lua { + content_by_lua_block { + ngx.say("OK") + } + } +--- request +GET /lua +--- response_body +OK +--- error_log +server_rewrite_by_lua_block in server +--- no_error_log +[error] + + + +=== TEST 3: redirect +--- config + server_rewrite_by_lua_block { + ngx.redirect("/foo") + } +--- request +GET /lua +--- raw_response_headers_like eval +qr{[Ll]ocation: /foo\r\n} +--- response_body_like: 302 Found +--- error_code: 302 +--- no_error_log +[error] + + + +=== TEST 4: flush +--- config + server_rewrite_by_lua_block { + ngx.say("foo") + ngx.flush(true) + } + location /lua { + content_by_lua_block { + ngx.say("OK") + } + } +--- request +GET /lua +--- response_body +foo +--- no_error_log +[error] + + + +=== TEST 5: eof +--- config + server_rewrite_by_lua_block { + ngx.say("foo") + ngx.eof() + } + location /lua { + content_by_lua_block { + ngx.say("OK") + } + } +--- request +GET /lua +--- response_body +foo +--- no_error_log +[error] + + + +=== TEST 6: send_headers +--- config + server_rewrite_by_lua_block { + ngx.header["Foox"] = {"conx1", "conx2" } + ngx.header["Fooy"] = {"cony1", "cony2" } + ngx.send_headers() + } + location /lua { + content_by_lua_block { + ngx.say("OK") + } + } +--- request +GET /lua +--- response_body +--- response_headers +Foox: conx1, conx2 +Fooy: cony1, cony2 +--- no_error_log +[error] + + + +=== TEST 7: read_body +--- config + server_rewrite_by_lua_block { + ngx.req.read_body() + ngx.say(ngx.var.request_body) + } +--- request +POST /lua +hello, world +--- response_body +hello, world +--- no_error_log +[error] + + + +=== TEST 8: req_sock +--- config + server_rewrite_by_lua_block { + local sock = ngx.req.socket() + sock:receive(2) + sock:receive(2) + sock:receive(1) + ngx.sleep(1) + } + location /lua { + content_by_lua_block { + ngx.say("OK") + } + } +--- request +POST /lua +hello + +--- stap2 eval: $::StapScript +--- stap eval: $::GCScript +--- stap_out +lua check broken conn +lua check broken conn +lua req cleanup +delete thread 1 + +--- wait: 1 +--- timeout: 0.2 +--- abort +--- ignore_response +--- no_error_log +[error] +--- skip_eval: 2:$ENV{TEST_NGINX_USE_HTTP3} + + + +=== TEST 9: rewrite args (not break cycle by default) +--- config + location /bar { + echo "bar: $uri?$args"; + } + server_rewrite_by_lua_block { + if ngx.var.uri ~= "/bar" then + ngx.req.set_uri_args("hello") + ngx.req.set_uri("/bar", true) + end + } + location /foo { + + echo "foo: $uri?$args"; + } +--- request + GET /foo?world +--- response_body +bar: /bar?hello + + + +=== TEST 10: server_rewrite_by_lua_block overwrite by server +--- http_config + server_rewrite_by_lua_block { + ngx.log(ngx.INFO, "server_rewrite_by_lua_block in http") + } +--- config + server_rewrite_by_lua_block { + ngx.log(ngx.INFO, "server_rewrite_by_lua_block in server") + } + location /lua { + content_by_lua_block { + ngx.say("OK") + } + } +--- request +GET /lua +--- response_body +OK +--- error_log +server_rewrite_by_lua_block in server +--- no_error_log +[error] + + + +=== TEST 11: sleep +--- config + server_rewrite_by_lua_block { + ngx.sleep(0.001) + ngx.log(ngx.INFO, "server_rewrite_by_lua_block in server") + } + location /lua { + content_by_lua_block { + ngx.say("OK") + } + } +--- request +GET /lua +--- response_body +OK +--- error_log +server_rewrite_by_lua_block in server +--- no_error_log +[error] + + + +=== TEST 12: ngx.exit(ngx.OK) +--- config + server_rewrite_by_lua_block { + ngx.log(ngx.INFO, "ngx.exit") + ngx.exit(ngx.OK) + } + location /lua { + content_by_lua_block { + ngx.say("OK") + } + } +--- request +GET /lua +--- response_body +OK +--- error_log +ngx.exit +--- no_error_log +[error] + + + +=== TEST 13: ngx.exit(503) +--- config + server_rewrite_by_lua_block { + ngx.exit(503) + } + location /lua { + content_by_lua_block { + ngx.log(ngx.ERR, "content_by_lua") + ngx.say("OK") + } + } +--- request +GET /lua +--- error_code: 503 +--- no_error_log +[error] + + + +=== TEST 14: subrequests +--- config + server_rewrite_by_lua_block { + ngx.log(ngx.INFO, "is_subrequest:", ngx.is_subrequest) + } + + location /lua { + content_by_lua_block { + local res = ngx.location.capture("/sub") + ngx.print(res.body) + } + } + + location /sub { + content_by_lua_block { + ngx.say("OK") + } + } + +--- request +GET /lua +--- response_body +OK +--- error_log +is_subrequest:false +is_subrequest:true +--- no_error_log +[error] + + + +=== TEST 15: rewrite by ngx_http_rewrite_module +--- config + server_rewrite_by_lua_block { + ngx.log(ngx.INFO, "uri is ", ngx.var.uri) + } + + rewrite ^ /re; + + location /re { + content_by_lua_block { + ngx.say("RE") + } + } + + location /ok { + content_by_lua_block { + ngx.say("OK") + } + } + +--- request +GET /lua +--- response_body +RE +--- error_log +uri is /lua +--- no_error_log +[error] + + + +=== TEST 16: exec +--- config + server_rewrite_by_lua_block { + if ngx.var.uri ~= "/ok" then + ngx.exec("/ok") + end + ngx.log(ngx.INFO, "uri is ", ngx.var.uri) + } + + location /ok { + content_by_lua_block { + ngx.say("OK") + } + } + +--- request +GET /lua +--- response_body +OK +--- error_log +uri is /ok +--- no_error_log +[error] + + + +=== TEST 17: server_rewrite_by_lua and rewrite_by_lua +--- http_config + server_rewrite_by_lua_block { + ngx.log(ngx.INFO, "server_rewrite_by_lua_block in http") + } +--- config + location /lua { + rewrite_by_lua_block { + ngx.log(ngx.INFO, "rewrite_by_lua_block in location") + } + content_by_lua_block { + ngx.say("OK") + } + } +--- request +GET /lua +--- response_body +OK +--- error_log +server_rewrite_by_lua_block in http +rewrite_by_lua_block in location +--- no_error_log +[error] + + + +=== TEST 18: server_rewrite_by_lua_file +--- http_config + server_rewrite_by_lua_file 'html/foo.lua'; +--- config + location /lua { + content_by_lua_block { + ngx.say("OK") + } + } +--- request +GET /lua +--- user_files +>>> foo.lua +ngx.log(ngx.INFO, "rewrite_by_lua_file in server") +--- response_body +OK +--- error_log +rewrite_by_lua_file in server +--- no_error_log +[error] + + + +=== TEST 19: syntax error server_rewrite_by_lua_block in http +--- http_config + server_rewrite_by_lua_block { + 'for end'; + } +--- config + location /lua { + content_by_lua_block { + ngx.say("OK") + } + } +--- request +GET /lua +--- ignore_response +--- error_log +failed to load inlined Lua code: server_rewrite_by_lua(nginx.conf:25):2: unexpected symbol near ''for end'' +--- no_error_log +no_such_error +--- skip_eval: 2:$ENV{TEST_NGINX_USE_HUP} + + + +=== TEST 20: syntax error server_rewrite_by_lua_block in server +--- config + server_rewrite_by_lua_block { + 'for end'; + } + location /lua { + content_by_lua_block { + ngx.say("Hello world") + } + } +--- request +GET /lua +--- ignore_response +--- error_log +failed to load inlined Lua code: server_rewrite_by_lua(nginx.conf:39):2: unexpected symbol near ''for end'' +--- no_error_log +no_such_error diff --git a/t/168-tcp-socket-bind.t b/t/168-tcp-socket-bind.t new file mode 100644 index 0000000000..123b525fe3 --- /dev/null +++ b/t/168-tcp-socket-bind.t @@ -0,0 +1,428 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +# more times than usual(2) for test case 6 +repeat_each(4); + +plan tests => repeat_each() * (blocks() * 3 + 7); + +our $HtmlDir = html_dir; + +# get ip address in the dev which is default route outgoing dev +my $dev = `ip route | awk '/default/ {printf "%s", \$5}'`; +my $local_ip = `ip route | grep $dev | grep -o "src .*" | head -n 1 | awk '{print \$2}'`; +chomp $local_ip; + +$ENV{TEST_NGINX_HTML_DIR} = $HtmlDir; +$ENV{TEST_NGINX_NOT_EXIST_IP} ||= '8.8.8.8'; +$ENV{TEST_NGINX_INVALID_IP} ||= '127.0.0.1:8899'; +$ENV{TEST_NGINX_SERVER_IP} ||= $local_ip; + +no_long_string(); +#no_diff(); + +#log_level 'warn'; +log_level 'debug'; + +no_shuffle(); + +run_tests(); + +__DATA__ + +=== TEST 1: upstream sockets bind 127.0.0.1 +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local ip = "127.0.0.1" + local port = ngx.var.port + + local sock = ngx.socket.tcp() + local ok, err = sock:bind(ip) + if not ok then + ngx.say("failed to bind", err) + return + end + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local bytes, err = sock:send("GET /foo HTTP/1.1\r\nHost: localhost\r\nConnection: keepalive\r\n\r\n") + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + ngx.say("request sent") + + local reader = sock:receiveuntil("\r\n0\r\n\r\n") + local data, err = reader() + + if not data then + ngx.say("failed to receive response body: ", err) + return + end + + ngx.say("received response") + local remote_ip = string.match(data, "(bind: %d+%.%d+%.%d+%.%d+)") + ngx.say(remote_ip) + + ngx.say("done") + } + } + + location /foo { + echo bind: $remote_addr; + } +--- request +GET /t +--- response_body +connected: 1 +request sent +received response +bind: 127.0.0.1 +done +--- no_error_log +["[error]", +"bind(127.0.0.1) failed"] +--- error_log eval +"lua tcp socket bind ip: 127.0.0.1" + + + +=== TEST 2: upstream sockets bind server ip, not 127.0.0.1 +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local ip = "$TEST_NGINX_SERVER_IP" + local port = ngx.var.port + + local sock = ngx.socket.tcp() + local ok, err = sock:bind(ip) + if not ok then + ngx.say("failed to bind", err) + return + end + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local bytes, err = sock:send("GET /foo HTTP/1.1\r\nHost: localhost\r\nConnection: keepalive\r\n\r\n") + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + ngx.say("request sent") + + local reader = sock:receiveuntil("\r\n0\r\n\r\n") + local data, err = reader() + + if not data then + ngx.say("failed to receive response body: ", err) + return + end + + ngx.say("received response") + local remote_ip = string.match(data, "(bind: %d+%.%d+%.%d+%.%d+)") + if remote_ip == "bind: $TEST_NGINX_SERVER_IP" then + ngx.say("ip matched") + end + + ngx.say("done") + } + } + + location /foo { + echo bind: $remote_addr; + } +--- request +GET /t +--- response_body +connected: 1 +request sent +received response +ip matched +done +--- no_error_log eval +["[error]", +"bind($ENV{TEST_NGINX_SERVER_IP}) failed"] +--- error_log eval +"lua tcp socket bind ip: $ENV{TEST_NGINX_SERVER_IP}" + + + +=== TEST 3: add setkeepalive +--- http_config eval + "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';" +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local test = require "test" + local t1 = test.go() + local t2 = test.go() + ngx.say("t2 - t1: ", t2 - t1) + } + } +--- user_files +>>> test.lua +local _M = {} + +function _M.go() + local ip = "127.0.0.1" + local port = ngx.var.port + + local sock = ngx.socket.tcp() + local ok, err = sock:bind(ip) + if not ok then + ngx.say("failed to bind", err) + return + end + + ngx.say("bind: ", ip) + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local reused = sock:getreusedtimes() + + local ok, err = sock:setkeepalive() + if not ok then + ngx.say("failed to set reusable: ", err) + end + + return reused +end + +return _M +--- request +GET /t +--- response_body +bind: 127.0.0.1 +connected: 1 +bind: 127.0.0.1 +connected: 1 +t2 - t1: 1 +--- no_error_log +["[error]", +"bind(127.0.0.1) failed"] +--- error_log eval +"lua tcp socket bind ip: 127.0.0.1" + + + +=== TEST 4: upstream sockets bind not exist ip +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local ip = "$TEST_NGINX_NOT_EXIST_IP" + local port = ngx.var.port + + local sock = ngx.socket.tcp() + local ok, err = sock:bind(ip) + if not ok then + ngx.say("failed to bind", err) + return + end + + ngx.say("bind: ", ip) + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + } + } +--- request +GET /t +--- response_body +bind: 8.8.8.8 +failed to connect: cannot assign requested address +--- error_log eval +["bind(8.8.8.8) failed", +"lua tcp socket bind ip: 8.8.8.8"] + + + +=== TEST 5: upstream sockets bind invalid ip +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local ip = "$TEST_NGINX_INVALID_IP" + local port = ngx.var.port + + local sock = ngx.socket.tcp() + local ok, err = sock:bind(ip) + if not ok then + ngx.say("failed to bind: ", err) + return + end + + ngx.say("bind: ", ip) + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + } + } +--- request +GET /t +--- response_body +failed to bind: bad address +--- no_error_log +[error] + + + +=== TEST 6: tcpsock across request after bind +--- http_config + init_worker_by_lua_block { + -- this is not the recommend way, just for test + local function tcp() + local sock = ngx.socket.tcp() + + local ok, err = sock:bind("127.0.0.1") + if not ok then + ngx.log(ngx.ERR, "failed to bind") + end + + package.loaded.share_sock = sock + end + + local ok, err = ngx.timer.at(0, tcp) + if not ok then + ngx.log(ngx.ERR, "failed to create timer") + end + } +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local port = ngx.var.port + + -- make sure share_sock is created + ngx.sleep(0.002) + + local sock = package.loaded.share_sock + if sock ~= nil then + package.loaded.share_sock = nil + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + sock:close() + collectgarbage("collect") + else + -- the sock from package.loaded.share_sock is just + -- for the first request after worker init + -- add following code to keep the same result for other request + ngx.say("connected: ", 1) + end + } + } +--- request +GET /t +--- response_body +connected: 1 +--- no_error_log +[error] + + + +=== TEST 7: upstream sockets bind with ip port +--- config + server_tokens off; + location /t { + set $port $TEST_NGINX_SERVER_PORT; + content_by_lua_block { + local ip = "127.0.0.1" + local port = ngx.var.port + + local sock = ngx.socket.tcp() + + local ok, err = sock:bind(ip, 12345) + if not ok then + ngx.say("failed to bind", err) + return + end + + local ok, err = sock:connect("127.0.0.1", port) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + local ok, err = sock:setoption("reuseaddr", 1) + if not ok then + ngx.say("setoption reuseaddr failed: ", err) + end + + ngx.say("connected: ", ok) + + local bytes, err = sock:send("GET /foo HTTP/1.1\r\nHost: localhost\r\nConnection: keepalive\r\n\r\n") + if not bytes then + ngx.say("failed to send request: ", err) + return + end + + local reader = sock:receiveuntil("\r\n0\r\n\r\n") + local data, err = reader() + + if not data then + ngx.say("failed to receive response body: ", err) + return + end + sock:close() + ngx.say(data) + + } + } + + location /foo { + echo bind: $remote_addr:$remote_port; + } +--- request +GET /t +--- response_body eval +qr/bind:\s127\.0\.0\.1:12345|failed\s+to\s+connect:\s+address\s+already\s+in\s+use/ +--- error_log eval +"lua tcp socket bind ip: 127.0.0.1" diff --git a/t/169-proxy-ssl-verify.t b/t/169-proxy-ssl-verify.t new file mode 100644 index 0000000000..95ecc1f3a7 --- /dev/null +++ b/t/169-proxy-ssl-verify.t @@ -0,0 +1,1337 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +repeat_each(3); + +# All these tests need to have new openssl +my $NginxBinary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; +my $openssl_version = eval { `$NginxBinary -V 2>&1` }; + +if ($openssl_version =~ m/built with OpenSSL (\d+)\.(\d+)\.(\d+)/) { + my ($major, $minor, $patch) = ($1, $2, $3); + + if ($major < 3 || ($major == 3 && $minor == 0 && $patch < 2)) { + plan(skip_all => "too old OpenSSL, need >= 3.0.2, was " . + "$major.$minor.$patch"); + } else { + plan tests => repeat_each() * (blocks() * 5 + 19); + } +} elsif ($openssl_version =~ m/running with BoringSSL/) { + plan(skip_all => "does not support BoringSSL"); +} else { + die "unknown SSL"; +} + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; +$ENV{TEST_NGINX_QUIC_IDLE_TIMEOUT} ||= 0.6; + +#log_level 'warn'; +log_level 'debug'; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: invalid proxy_pass url +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("hello world") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass http://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + + proxy_ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "hello world") + } + } +--- request +GET /t +--- error_log +proxy_ssl_verify_by_lua* should be used with proxy_pass https url +--- must_die + + + +=== TEST 2: proxy_ssl_verify_by_lua in http {} block +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("hello world") + } + + more_clear_headers Date; + } + } + + proxy_ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "hello world") + } +--- config + location /t { + proxy_pass http://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + } +--- request +GET /t +--- error_log +"proxy_ssl_verify_by_lua_block" directive is not allowed here +--- must_die + + + +=== TEST 3: proxy_ssl_verify_by_lua in server {} block +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("hello world") + } + + more_clear_headers Date; + } + } + +--- config + proxy_ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "hello world") + } + + location /t { + proxy_pass http://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + } +--- request +GET /t +--- error_log +"proxy_ssl_verify_by_lua_block" directive is not allowed here +--- must_die + + + +=== TEST 4: simple logging +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple logging return") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "proxy ssl verify by lua is running!") + } + } +--- request +GET /t +--- response_body +simple logging return +--- error_log +proxy ssl verify by lua is running! +--- no_error_log +[error] +[alert] + + + +=== TEST 5: sleep +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("sleep") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_verify_by_lua_block { + local begin = ngx.now() + ngx.sleep(0.1) + print("elapsed in proxy ssl verify by lua: ", ngx.now() - begin) + } + } +--- request +GET /t +--- response_body +sleep +--- error_log eval +qr/elapsed in proxy ssl verify by lua: 0.(?:09|1\d)\d+ while loading proxy ssl verify by lua,/, +--- no_error_log +[error] +[alert] + + + +=== TEST 6: timer +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("timer") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_verify_by_lua_block { + local function f() + print("my timer run!") + end + local ok, err = ngx.timer.at(0, f) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + } + } +--- request +GET /t +--- response_body +timer +--- error_log +my timer run! +--- no_error_log +[error] +[alert] + + + +=== TEST 7: ngx.exit(0) - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(0) no yield") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_verify_by_lua_block { + ngx.exit(0) + ngx.log(ngx.ERR, "should never reached here...") + } + } +--- request +GET /t +--- response_body +ngx.exit(0) no yield +--- error_log +lua exit with code 0 +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 8: ngx.exit(ngx.ERROR) - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(ngx.ERROR) no yield") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + ngx.exit(ngx.ERROR) + ngx.log(ngx.ERR, "should never reached here...") + } + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'lua exit with code -1', +'proxy_ssl_verify_by_lua: handler return value: -1, cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 9: ngx.exit(0) - yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(0) yield") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + ngx.sleep(0.001) + ngx.exit(0) + + ngx.log(ngx.ERR, "should never reached here...") + } + } +--- request +GET /t +--- response_body +ngx.exit(0) yield +--- error_log +lua exit with code 0 +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 10: ngx.exit(ngx.ERROR) - yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(ngx.ERROR) yield") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + ngx.sleep(0.001) + ngx.exit(ngx.ERROR) + + ngx.log(ngx.ERR, "should never reached here...") + } + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'lua exit with code -1', +'proxy_ssl_verify_by_lua: cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 11: lua exception - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("lua exception - no yield") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + error("bad bad bad") + ngx.log(ngx.ERR, "should never reached here...") + } + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'runtime error: proxy_ssl_verify_by_lua(nginx.conf:65):2: bad bad bad', +'proxy_ssl_verify_by_lua: handler return value: 500, cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 12: lua exception - yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("lua exception - yield") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + ngx.sleep(0.001) + error("bad bad bad") + ngx.log(ngx.ERR, "should never reached here...") + } + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'runtime error: proxy_ssl_verify_by_lua(nginx.conf:65):3: bad bad bad', +'proxy_ssl_verify_by_lua: cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 13: get phase +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("get phase return") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + print("get_phase: ", ngx.get_phase()) + } + } +--- request +GET /t +--- response_body +get phase return +--- error_log +get_phase: proxy_ssl_verify +--- no_error_log +[error] +[alert] + + + +=== TEST 14: subrequests disabled +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("subrequests disabled") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + ngx.location.capture("/foo") + } + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'proxy_ssl_verify_by_lua(nginx.conf:65):2: API disabled in the context of proxy_ssl_verify_by_lua*', +'proxy_ssl_verify_by_lua: handler return value: 500, cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +[alert] + + + +=== TEST 15: simple logging (by_lua_file) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple logging by lua file") + } + + more_clear_headers Date; + } + } +--- user_files +>>> a.lua +print("proxy ssl verify by lua is running!") + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_file html/a.lua; + } +--- request +GET /t +--- response_body +simple logging by lua file +--- error_log +a.lua:1: proxy ssl verify by lua is running! +--- no_error_log +[error] +[alert] + + + +=== TEST 16: coroutine API +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("coroutine API") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield + + local function f() + local cnt = 0 + for i = 1, 20 do + print("co yield: ", cnt) + cy() + cnt = cnt + 1 + end + end + + local c = cc(f) + for i = 1, 3 do + print("co resume, status: ", coroutine.status(c)) + cr(c) + end + } + } +--- request +GET /t +--- response_body +coroutine API +--- grep_error_log eval: qr/co (?:yield: \d+|resume, status: \w+)/ +--- grep_error_log_out +co resume, status: suspended +co yield: 0 +co resume, status: suspended +co yield: 1 +co resume, status: suspended +co yield: 2 +--- no_error_log +[error] +[alert] + + + +=== TEST 17: simple user thread wait with yielding +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple user thread wait with yielding") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + local function f() + ngx.sleep(0.01) + print("uthread: hello in thread") + return "done" + end + + local t, err = ngx.thread.spawn(f) + if not t then + ngx.log(ngx.ERR, "uthread: failed to spawn thread: ", err) + return ngx.exit(ngx.ERROR) + end + + print("uthread: thread created: ", coroutine.status(t)) + + local ok, res = ngx.thread.wait(t) + if not ok then + print("uthread: failed to wait thread: ", res) + return + end + + print("uthread: ", res) + } + } +--- request +GET /t +--- response_body +simple user thread wait with yielding +--- no_error_log +[error] +[alert] +--- grep_error_log eval: qr/uthread: [^.,]+/ +--- grep_error_log_out +uthread: thread created: running while loading proxy ssl verify by lua +uthread: hello in thread while loading proxy ssl verify by lua +uthread: done while loading proxy ssl verify by lua + + + +=== TEST 18: uthread (kill) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("uthread (kill)") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + local function f() + ngx.log(ngx.INFO, "uthread: hello from f()") + ngx.sleep(1) + end + + local t, err = ngx.thread.spawn(f) + if not t then + ngx.log(ngx.ERR, "failed to spawn thread: ", err) + return ngx.exit(ngx.ERROR) + end + + local ok, res = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.ERR, "failed to kill thread: ", res) + return + end + + ngx.log(ngx.INFO, "uthread: killed") + + local ok, err = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.INFO, "uthread: failed to kill: ", err) + end + } + } +--- request +GET /t +--- response_body +uthread (kill) +--- no_error_log +[error] +[alert] +[emerg] +--- grep_error_log eval: qr/uthread: [^.,]+/ +--- grep_error_log_out +uthread: hello from f() while loading proxy ssl verify by lua +uthread: killed while loading proxy ssl verify by lua +uthread: failed to kill: already waited or killed while loading proxy ssl verify by lua + + + +=== TEST 19: ngx.exit(ngx.OK) - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(ngx.OK) - no yield") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + ngx.exit(ngx.OK) + ngx.log(ngx.ERR, "should never reached here...") + } + } +--- request +GET /t +--- response_body +ngx.exit(ngx.OK) - no yield +--- error_log eval +[ +'proxy_ssl_verify_by_lua: handler return value: 0, cert verify callback exit code: 1', +qr/\[debug\] .*? SSL_do_handshake: 1/, +'lua exit with code 0', +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 20: proxy_ssl_verify_by_lua* without yield API (simple logic) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("without yield API, simple logic") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + print("proxy ssl verify: simple test start") + + -- Simple calculations without yield + local sum = 0 + for i = 1, 10 do + sum = sum + i + end + + print("proxy ssl verify: calculated sum: ", sum) + + -- String operations + local str = "hello" + str = str .. " world" + print("proxy ssl verify: concatenated string: ", str) + + -- Table operations + local t = {a = 1, b = 2, c = 3} + local count = 0 + for k, v in pairs(t) do + count = count + v + end + print("proxy ssl verify: table sum: ", count) + + print("proxy ssl verify: simple test done") + } + } +--- request +GET /t +--- response_body +without yield API, simple logic +--- grep_error_log eval: qr/(proxy ssl verify: simple test start|proxy ssl verify: calculated sum: 55|proxy ssl verify: concatenated string: hello world|proxy ssl verify: table sum: 6|proxy ssl verify: simple test done)/ +--- grep_error_log_out +proxy ssl verify: simple test start +proxy ssl verify: calculated sum: 55 +proxy ssl verify: concatenated string: hello world +proxy ssl verify: table sum: 6 +proxy ssl verify: simple test done + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 21: lua_upstream_skip_openssl_default_verify default off +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("lua_upstream_skip_openssl_default_verify default off") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "proxy ssl verify by lua is running!") + } + } +--- request +GET /t +--- error_log +proxy_ssl_verify_by_lua: openssl default verify +--- no_error_log +[error] +[alert] + + + +=== TEST 22: lua_upstream_skip_openssl_default_verify on +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("lua_upstream_skip_openssl_default_verify default off") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_ssl_conf_command VerifyMode Peer; + + lua_upstream_skip_openssl_default_verify on; + + proxy_ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "proxy ssl verify by lua is running!") + } + } +--- request +GET /t +--- response_body +lua_upstream_skip_openssl_default_verify default off +--- error_log +proxy ssl verify by lua is running! +--- no_error_log +proxy_ssl_verify_by_lua: openssl default verify +[error] +[alert] + + + +=== TEST 23: ngx.ctx to pass data from downstream phase to upstream phase +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple logging return") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + rewrite_by_lua_block { + ngx.ctx.greeting = "I am from rewrite phase" + } + + proxy_ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "greeting: ", ngx.ctx.greeting) + } + } +--- request +GET /t +--- response_body +simple logging return +--- error_log +greeting: I am from rewrite phase +--- no_error_log +[error] +[alert] + + + +=== TEST 24: upstream connection aborted +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("hello world") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + proxy_connect_timeout 100ms; + + proxy_ssl_verify_by_lua_block { + ngx.sleep(0.2) + } + } +--- request +GET /t +--- error_code: 504 +--- response_body_like: 504 Gateway Time-out +--- error_log +upstream timed out (110: Connection timed out) while loading proxy ssl verify by lua +proxy_ssl_verify_by_lua: cert verify callback aborted +--- no_error_log +[alert] +--- wait: 0.5 + + + +=== TEST 25: cosocket +--- http_config + server { + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1; + server_name test.com; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { + ngx.sleep(0.1) + + ngx.status = 201 + ngx.say("foo") + ngx.exit(201) + } + more_clear_headers Date; + } + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple logging return") + } + + more_clear_headers Date; + } + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_verify on; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + + proxy_ssl_verify_by_lua_block { + do + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1) + if not ok then + ngx.log(ngx.ERR, "failed to connect: ", err) + return + end + + ngx.log(ngx.INFO, "connected: ", ok) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.log(ngx.ERR, "failed to send http request: ", err) + return + end + + ngx.log(ngx.INFO, "sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.log(ngx.ERR, "failed to receive response status line: ", err) + break + end + + ngx.log(ngx.INFO, "received: ", line) + end + + local ok, err = sock:close() + ngx.log(ngx.INFO, "close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } +--- request +GET /t +--- response_body +simple logging return +--- error_log +connected: 1 +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil +--- no_error_log +[error] +[alert] diff --git a/t/185-ngx-buf-double-free.t b/t/185-ngx-buf-double-free.t new file mode 100644 index 0000000000..699083db27 --- /dev/null +++ b/t/185-ngx-buf-double-free.t @@ -0,0 +1,25 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket 'no_plan'; + +repeat_each(2); + +run_tests(); + +__DATA__ + +=== TEST 1: one buf was linked to multiple ngx_chain_t nodes +--- config + location /t { + content_by_lua_block { + local str = string.rep(".", 1300) + ngx.print(str) + ngx.flush() + ngx.print("small chunk") + ngx.flush() + } + body_filter_by_lua_block {local dummy=1} + } +--- request +GET /t +--- response_body_like: small chunk diff --git a/t/186-cosocket-busy-bufs.t b/t/186-cosocket-busy-bufs.t new file mode 100644 index 0000000000..7439443761 --- /dev/null +++ b/t/186-cosocket-busy-bufs.t @@ -0,0 +1,91 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + + +use Test::Nginx::Socket; +use Test::Nginx::Socket::Lua::Stream; + +log_level('warn'); +repeat_each(2); + +if (defined $ENV{TEST_NGINX_USE_HTTP3}) { + plan(skip_all => "HTTP3 does not support client abort"); +} elsif (defined $ENV{TEST_NGINX_USE_HTTP2}) { + plan(skip_all => "HTTP2 does not support client abort"); +} else { + plan tests => repeat_each() * (blocks() * 2); +} + +run_tests(); + +__DATA__ + +=== TEST 1: ngx.say and cosocket +--- stream_server_config + content_by_lua_block { + local sock = assert(ngx.req.socket(true)) + sock:settimeout(1000) + while true do + local data = sock:receive(5) + if not data then + return + end + ngx.print(data) + ngx.flush(true) + end + } +--- config + location /test { + content_by_lua_block { + ngx.say("hello") + --ngx.flush(true) + + local sock = ngx.socket.tcp() + local ok, err = sock:connect("127.0.0.1", ngx.var.server_port + 1) + assert(ok) + + local last_duration = 0 + local cnt = 0 + local t1, t2 + local err_cnt = 0 + local ERR_THRESHOLD_MS = 100 + + for i = 1,100000 do + if cnt == 0 then + ngx.update_time() + t1 = ngx.now() + end + + cnt = cnt + 1 + + local sent = sock:send("hello") + local data = sock:receive(5) + assert(data=="hello") + + if cnt == 1000 then + cnt = 0 + ngx.update_time() + t2 = ngx.now() + local duration = (t2 - t1) * 1000 + if last_duration > 0 and (duration - last_duration) > ERR_THRESHOLD_MS then + if err_cnt >= 3 then + ngx.log(ngx.ERR, + "more than ", err_cnt, " times, duration larger than ", + ERR_THRESHOLD_MS, " ms, ", + "last_duration: ", math.floor(duration), " ms") + return ngx.exit(500) + end + err_cnt = err_cnt + 1 + end + last_duration = duration + end + end + + sock:close() + ngx.exit(200) + } + } +--- no_error_log +[error] +--- timeout: 30 +--- request +GET /test diff --git a/t/187-ssl-two-verification.t b/t/187-ssl-two-verification.t new file mode 100644 index 0000000000..bea4aaee0f --- /dev/null +++ b/t/187-ssl-two-verification.t @@ -0,0 +1,145 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +repeat_each(3); + +# All these tests need to have new openssl +my $NginxBinary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; +my $openssl_version = eval { `$NginxBinary -V 2>&1` }; + +if ($openssl_version =~ m/built with OpenSSL (0\S*|1\.0\S*|1\.1\.0\S*)/) { + plan(skip_all => "too old OpenSSL, need >= 1.1.1, was $1"); +} elsif ($openssl_version =~ m/running with BoringSSL/) { + plan(skip_all => "does not support BoringSSL"); +} else { + plan tests => repeat_each() * (blocks() * 7); +} + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; + +#log_level 'warn'; +log_level 'debug'; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: simple logging +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + #listen 127.0.0.1:4433 ssl; + server_name test.com; + ssl_client_hello_by_lua_block { print("ssl client hello by lua is running!") } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + #ssl_trusted_certificate ../../cert/test.crt; + ssl_client_certificate ../../cert/test.crt; + ssl_verify_client on; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + log_by_lua_block { + ngx.log(ngx.INFO, "ssl_client_s_dn: ", ngx.var.ssl_client_s_dn) + } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_certificate ../../cert/test.crt; + lua_ssl_certificate_key ../../cert/test.key; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + -- local ok, err = sock:connect("127.0.0.1", 4433) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +ssl_client_s_dn: emailAddress=agentzh@gmail.com,CN=test.com,OU=OpenResty,O=OpenResty,L=San Francisco,ST=California,C=US + +--- no_error_log +[error] +[alert] +--- grep_error_log eval: qr/ssl_client_hello_by_lua\(.*?,|\bssl client hello: connection reusable: \d+|\breusable connection: \d+/ +--- grep_error_log_out eval +# Since nginx version 1.17.9, nginx call ngx_reusable_connection(c, 0) +# before call ssl callback function +$Test::Nginx::Util::NginxVersion >= 1.017009 ? +qr/reusable connection: 0 +ssl client hello: connection reusable: 0 +ssl_client_hello_by_lua\(nginx.conf:\d+\):1: ssl client hello by lua is running!,/ +: qr /reusable connection: 1 +ssl client hello: connection reusable: 1 +reusable connection: 0 +ssl_client_hello_by_lua\(nginx.conf:\d+\):1: ssl client hello by lua is running!,/ diff --git a/t/188-balancer_keepalive_pool_max_retry.t b/t/188-balancer_keepalive_pool_max_retry.t new file mode 100644 index 0000000000..679ee680f8 --- /dev/null +++ b/t/188-balancer_keepalive_pool_max_retry.t @@ -0,0 +1,89 @@ +# vim:set ft= ts=4 sw=4 et: + +use Test::Nginx::Socket::Lua; +use Cwd qw(cwd); + +log_level('info'); +repeat_each(1); + +plan tests => repeat_each() * (blocks() * 6); + +my $pwd = cwd(); + +no_long_string(); + +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- http_config + lua_shared_dict request_counter 1m; + upstream my_upstream { + server 127.0.0.1; + balancer_by_lua_block { + local balancer = require "ngx.balancer" + + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + ngx.ctx.tries = ngx.ctx.tries + 1 + ngx.log(ngx.INFO, "tries ", ngx.ctx.tries) + + if ngx.ctx.tries == 1 then + balancer.set_more_tries(5) + end + + local host = "127.0.0.1" + local port = $TEST_NGINX_RAND_PORT_1; + + local ok, err = balancer.set_current_peer(host, port) + if not ok then + ngx.log(ngx.ERR, "failed to set the current peer: ", err) + return ngx.exit(500) + end + + balancer.set_timeouts(60000, 60000, 60000) + + local ok, err = balancer.enable_keepalive(60, 100) + if not ok then + ngx.log(ngx.ERR, "failed to enable keepalive: ", err) + return ngx.exit(500) + end + } + } + + server { + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1; + location /hello { + content_by_lua_block{ + local request_counter = ngx.shared.request_counter + local first_request = request_counter:get("first_request") + if first_request == nil then + request_counter:set("first_request", "yes") + ngx.print("hello") + else + ngx.exit(ngx.HTTP_CLOSE) + end + } + } + } +--- config + location = /t { + proxy_pass http://my_upstream; + proxy_set_header Connection "keep-alive"; + + rewrite_by_lua_block { + ngx.req.set_uri("/hello") + } + } +--- pipelined_requests eval +["GET /t HTTP/1.1" , "GET /t HTTP/1.1"] +--- response_body eval +["hello", qr/502/] +--- error_code eval +[200, 502] +--- no_error_log eval +qr/tries 7/ diff --git a/t/cert/dst-ca.crt b/t/cert/dst-ca.crt index 738121f63a..7b714c60a9 100644 --- a/t/cert/dst-ca.crt +++ b/t/cert/dst-ca.crt @@ -1,63 +1,26 @@ -subject=/C=US/O=Digital Signature Trust Co./OU=TrustID Server/CN=TrustID Server CA A5 -issuer=/O=Digital Signature Trust Co./CN=DST Root CA X3 +# Comodo AAA Services root -----BEGIN CERTIFICATE----- -MIIGiTCCBXGgAwIBAgIRALCqqJD4YLUEJLy76IZcvf8wDQYJKoZIhvcNAQEFBQAw -PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD -Ew5EU1QgUm9vdCBDQSBYMzAeFw0wNzA4MjQxNzU0MTlaFw0xNDA4MjQxNDEzMzVa -MGsxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVz -dCBDby4xFzAVBgNVBAsTDlRydXN0SUQgU2VydmVyMR0wGwYDVQQDExRUcnVzdElE -IFNlcnZlciBDQSBBNTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOae -jyufM+mrKgTCpPm9ha5UBAORNLhAv3PFlmzA8n6lnEvBTJfeG5vUZYQrlZMMKmZl -F1D3mYRFAhT/qFbBsTMlF9XsDNF40NB21ABTquuEB3Ji8mmC1Z2cZjwa4VeKxGIq -kTdBypQgWekopNVvoOGLPbRXgYKW4UCc6pt7Q+We+eUuonp2XIRIOvmJQtXb/rNz -4B1ahu3QB4HnystmQ9dW8BySSGDhZ/kzIcSU8GuHcL58SHsei07gjFzXRhMkkbJH -MT8zS/hrjNsB8Z5kamI4QL7nLVNuCF1Sg9zgB1POdMPLEnm4uDx+BRLwVMGZMwza -MNfEATnWTvLazzIDarMCAwEAAaOCA1IwggNOMA8GA1UdEwEB/wQFMAMBAf8wDgYD -VR0PAQH/BAQDAgHGMEcGA1UdJQRAMD4GCCsGAQUFBwMBBggrBgEFBQcDAwYIKwYB -BQUHAwUGCCsGAQUFBwMGBggrBgEFBQcDBwYKKwYBBAGCNwoDATCCATIGA1UdIASC -ASkwggElMIIBIQYKYIZIAYb5LwAGAzCCAREwSgYIKwYBBQUHAgEWPmh0dHBzOi8v -c2VjdXJlLmlkZW50cnVzdC5jb20vY2VydGlmaWNhdGVzL3BvbGljeS90cy9pbmRl -eC5odG1sMIHCBggrBgEFBQcCAjCBtRqBslRoaXMgVHJ1c3RJRCBTZXJ2ZXIgQ2Vy -dGlmaWNhdGUgaGFzIGJlZW4gaXNzdWVkIGluIGFjY29yZGFuY2Ugd2l0aCBJZGVu -VHJ1c3QncyBUcnVzdElEIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBhdCBodHRw -czovL3NlY3VyZS5pZGVudHJ1c3QuY29tLyBjZXJ0aWZpY2F0ZXMvcG9saWN5L3Rz -L2luZGV4Lmh0bWwwgbkGA1UdHwSBsTCBrjAuoCygKoYoaHR0cDovL2NybC5pZGVu -dHJ1c3QuY29tL0RTVFJPT1RDQVgzLmNybDB8oHqgeIZ2bGRhcDovL2xkYXAuaWRl -bnRydXN0LmNvbS9jbj1EU1QlMjBSb290JTIwQ0ElMjBYMyxvPURpZ2l0YWwlMjBT -aWduYXR1cmUlMjBUcnVzdCUyMENvLj9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0 -O2JpbmFyeTCBrwYIKwYBBQUHAQEEgaIwgZ8wJQYIKwYBBQUHMAGGGWh0dHA6Ly9v -Y3NwLmlkZW50cnVzdC5jb20wdgYIKwYBBQUHMAKGamxkYXA6Ly9sZGFwLmlkZW50 -cnVzdC5jb20vY249RFNUJTIwUm9vdCUyMENBJTIwWDMsbz1EaWdpdGFsJTIwU2ln -bmF0dXJlJTIwVHJ1c3QlMjBDby4/Y0FDZXJ0aWZpY2F0ZTtiaW5hcnkwHwYDVR0j -BBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAwHQYDVR0OBBYEFLeoy2iu3EVD0tyJ -EamBZhn5+3XpMA0GCSqGSIb3DQEBBQUAA4IBAQAtreoVqLQx+G35L8byM/IvGqNa -MzbWYzGIidH3+hT8B2e0duWfZCDazgu272ErWL/T4HL5psUEOIHfzNxEWHA24XhY -zBFsYR64vaTRV7T+iYKv5Nxptidk26zOZ2/F+TKZvOfhPg5IqnH3FyXHEcRDWm1/ -QJsZ4mqgUFx38pc50i1ed6XUdvBObMnGJc1fGdfVqcNqbvHACNrw+WJE9PIeMKJQ -HC/NkE87xFP/Q1OW7hPmqQvmIIbm6BLZ7U0kxqNL3HmegxkJ2yJQntsX+5BooJqj -/uVpwzAEei7IjKsdtomxkHCDPFGEJYBldDLBH0ScSlngQZ6V5HNM473l8bWs +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== -----END CERTIFICATE----- - -subject=/O=Digital Signature Trust Co./CN=DST Root CA X3 -issuer=/O=Digital Signature Trust Co./CN=DST Root CA X3 ------BEGIN CERTIFICATE----- -MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ -MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT -DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow -PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD -Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O -rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq -OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b -xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw -7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD -aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG -SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 -ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr -AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz -R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 -JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo -Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ------END CERTIFICATE----- - diff --git a/t/cert/gen-test-passphrase.sh b/t/cert/gen-test-passphrase.sh new file mode 100755 index 0000000000..45e6806a39 --- /dev/null +++ b/t/cert/gen-test-passphrase.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Set variables +SUBJECT="/C=CN/ST=Guangdong/L=ShenZhen/O=OpenResty/OU=OpenResty/CN=test.com/emailAddress=guanglinlv@gmail.com" +DAYS=49000 # Approximately 134 years +KEY_FILE="test_passphrase.key" +CERT_FILE="test_passphrase.crt" +PASSWORD="123456" + +# Generate a new 2048-bit RSA private key, encrypted with the password +openssl genrsa -aes256 -passout pass:$PASSWORD -out $KEY_FILE 2048 + +# Generate a new self-signed certificate +openssl req -x509 -new -nodes -key $KEY_FILE -sha256 -days $DAYS \ + -out $CERT_FILE -subj "$SUBJECT" -passin pass:$PASSWORD + +# Display information about the new certificate +openssl x509 -in $CERT_FILE -text -noout + +echo "New 2048-bit certificate generated successfully!" +echo "Private key (encrypted): $KEY_FILE" +echo "Certificate: $CERT_FILE" + diff --git a/t/cert/gen-test2.sh b/t/cert/gen-test2.sh new file mode 100755 index 0000000000..ed7a9de398 --- /dev/null +++ b/t/cert/gen-test2.sh @@ -0,0 +1,3 @@ +openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \ + -subj "/C=US/ST=California/L=San Francisco/O=OpenResty/CN=test2.com/emailAddress=openresty@gmail.com" \ + -keyout test2.key -out test2.crt diff --git a/t/cert/http3/http3.crt b/t/cert/http3/http3.crt new file mode 100644 index 0000000000..8683f41adf --- /dev/null +++ b/t/cert/http3/http3.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCTCCAvGgAwIBAgIUHLeNm7JwH368JWXBYJ1Dv+xcL6kwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIxMTEwNTA2NTQxNVoXDTQxMDEw +NDA2NTQxNVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAx5/M18tKWKfacgldf9gBguTdLA3JWiblRWM/38fSZGKL +Mcb1vLErY/qQyLYxoLKBht0FUZAEZ08y/iheYFZT7H082b8bNvwY+v6bScOQOvcX +hkTWNlSQORDH4qIFxVXq0soXga+0ukSZ2RQRcCUWeKUaZwhGCYNIj04/FB9Lef95 +Ku7LkNauTBmRGIwXgQWhfnoPM21o9D9i54R9L2RHU7fGeTGhiS0nCe1nPPB7KBgO +s9rBHMXV9qHxCNMWWiVNsMX049S7aD9yvRrV7NAHssVQdaRR234IPJxb7BYSUPi8 +5U+8l43Ornd+cY/R/sXDQluFidlnZvHT+akdy0ObWDK0lMUweDWvFVLY3ZvMvsGR +rQE8RR5/fy83y7w//0734EBk4ttEzlilmjA8UvnumOpK0UYaW8LkikvxB+48e89s +NOEKZf3Mfw/fDRz0tO+cr4cIgUPQ4ru6KNVnGH/ZiD97AVMPXJO9nPOUIRVH9aXM +wC74CSt5idWOwpTKy+sLg7anM235NvZ9bTiS+V5CTzBlqL/wKzEI0Injds3kBc1a +Y8Lk/cIdNhuwlN01fYluA8XyB3DWoeQYbySEoHC5ksvaFLBM3yPomWrmyM5lJrj5 +QbWa46b0bONJQ2qmrQa5KREVvDthHQvKELsabHX1qbCShSxoG45aclqpmKy2AjcC +AwEAAaNTMFEwHQYDVR0OBBYEFA2T9Sgv31hCl3INL5MB++NrMu0iMB8GA1UdIwQY +MBaAFA2T9Sgv31hCl3INL5MB++NrMu0iMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADggIBAJdxtUq4T/sh5Ww7pMOB4+JF5zsxEBpZiFMzT2hYZ+6a2mYh +UTy7ff1lNfNl0BfslaOD404Qt0SSgI2TByLs838/sKpVk+QePcw6kl6IFIPERUBO +uxq/+QXqsDHOXb6m4qpEBmrknUa65dThdUw5r4sHo0XD2l/y2/zEZtd4e3ZGAWts +spdjPBk4UHPtG3p29eY/8ehw+zDXuWG4nlJYjn+6PRnLhvpgt2/E/Wr9PeYcv3IC +UgavxXVtk9fnclg8BuKlnZYki7txn8+F9Rh03CVVZ9R16Q/aVALI2iTs8T4gCt7Z +eexfLGfBkLBkLvpL7wpzxNsvOC2b5bJZCDUTprLVmdpmMQu75qLg1mOfubMo983H +8G91V4XOokRoCRub/SLKA16/gpEwnE2aDsVMUVSxwpRu2Rjw4GpzbCNAHUzmblrh +zYMSAsEuTcsZEAdZQrzmhGc1Yg5Q88V4o+qyywzkgd86O65QUozhnkCs+eS9ikMV +cPLXoW5SDIsrrcoTR6bH5MdDjS7ILKUUC5+x0qo6EhK94Fx49TkRBNIYk3o0fG7j +o/0YvozXjqTRnodYegL4LKoGZyfL4qbuh3t8ZGQ/Z0ECmvjcmJzPyObIiMe2InT3 +GRY+ypPTyeiumjHFFVO0zx+DAv+HFPtq1XaygWvxKY1DTP6FNN0BzQdzAgKm +-----END CERTIFICATE----- diff --git a/t/cert/http3/http3.key b/t/cert/http3/http3.key new file mode 100644 index 0000000000..5825540f58 --- /dev/null +++ b/t/cert/http3/http3.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDHn8zXy0pYp9py +CV1/2AGC5N0sDclaJuVFYz/fx9JkYosxxvW8sStj+pDItjGgsoGG3QVRkARnTzL+ +KF5gVlPsfTzZvxs2/Bj6/ptJw5A69xeGRNY2VJA5EMfiogXFVerSyheBr7S6RJnZ +FBFwJRZ4pRpnCEYJg0iPTj8UH0t5/3kq7suQ1q5MGZEYjBeBBaF+eg8zbWj0P2Ln +hH0vZEdTt8Z5MaGJLScJ7Wc88HsoGA6z2sEcxdX2ofEI0xZaJU2wxfTj1LtoP3K9 +GtXs0AeyxVB1pFHbfgg8nFvsFhJQ+LzlT7yXjc6ud35xj9H+xcNCW4WJ2Wdm8dP5 +qR3LQ5tYMrSUxTB4Na8VUtjdm8y+wZGtATxFHn9/LzfLvD//TvfgQGTi20TOWKWa +MDxS+e6Y6krRRhpbwuSKS/EH7jx7z2w04Qpl/cx/D98NHPS075yvhwiBQ9Diu7oo +1WcYf9mIP3sBUw9ck72c85QhFUf1pczALvgJK3mJ1Y7ClMrL6wuDtqczbfk29n1t +OJL5XkJPMGWov/ArMQjQieN2zeQFzVpjwuT9wh02G7CU3TV9iW4DxfIHcNah5Bhv +JISgcLmSy9oUsEzfI+iZaubIzmUmuPlBtZrjpvRs40lDaqatBrkpERW8O2EdC8oQ +uxpsdfWpsJKFLGgbjlpyWqmYrLYCNwIDAQABAoICAENb1qESRbn4matVIamb15a1 +ZzQQStsSuNZbERiPspyQ6+sV+aF8HuoTiHtRjxlsYmyBc+P7tqCthsVgFchoGNV5 +xOispaA+HKfE9d1EEgzzh4qU+7tFeYzn7qq4hT37KcuKybfG9DLOJyOqs9+lhBmd +jHUrw4Y+OGOywXImxS8bV2V3QlVTO2kOT3l6/AtbPQ0SXsK5rmqMYPFCMYOmULMd +FembJ6jEBaJB604Sz1vOElf5/qOY1gPszQpvP+GXKMn3YhTmmX4puqu4vGq2H4Lh +Na8cjUqFEn5xPEtDf1a3N/Ygm8B/5zfTtmTXZMKVNLfVbg//vfZsr1xVBmqqG2Zk +PUxZ9pyRn25zAYIgIC6DBgmM32S5Zl0axeoWIHdgOTAOwqLLF/ZFozdVSN+skJhl +q1ndTw+MmVHi19HDUt8YfIICn8g+Dw9NRdX+VjXrRMBweCL41JzHzP6YKo73VJty +WIVt/LwH2sxdGH0QeP6+Dajel3/ouXAcS31ZpeWZ0QUfa5I0DOn50mvZSuaC0m7Z +baHBGqawq9EKdjbU5WN6popOCWCx23BZDorKmPGBQ4x54LxfUeet0HAYjsvzUZ0q +a/AC6pMMHUjG4i5dPhOAhjxkpwfhLjIBTC8VrelGfap50Mlst5InCjClUsSKg43w +tsRg0HQflxYsPXUGtBqxAoIBAQD29Q6gPNsd7JMzmVzK7fy5YaKq7aI/vOU/tp7U +LQqhBfsy70U3UNsclsQQ/1RG2cgpFVctlpE8ITpFqSlUDRtlvrK9NaIPEYBV84Xn +bWPqFjGiRowuXiCR8axbPnsEquO2+VHNuVD/PsCocvbpqtpbfEGmcuyWiO4uFMyW +beOcRxEsn3U7ABZGDIhxYjpmkiVMu1pRjj+ddqtNMia398K86c3ETRg+zrHnpzio +kuhxv6TjtnXT1ibI0yKzdQ5sKpa3DYUN/wJgeFVHsSuCZjCzufcMdjCjG3kTTljT +FrMbweISSTJNs6KsbCqzmc6dmpwuU1ySX07F9sgdJ/7lBCXTAoIBAQDO7wy9Zr8C +Ridte1EaMP04OSfqc2Jha/U2te47R1VDyHKzpFHvHwWHqFng6J181M9iubUsh/63 +d12uyjZoToMgT7AJ7PMwbEKJbMxpIjqF7Dp1aM11TjRq0mQbZ3wkeuYdK7jYc92j +X4hF+tJpCYo/0lMA0UFYhMyUPbi33xqlJKLghVncBYmKHluf3k0g73eRg4MGlYHG +tZ5qVdgwFTztpBb4ySJMOYmjb/OoYPdfoLC8P4ZoHiax4MU30R8b/oGCmYzwOsMC +dNz9dZgeCFo3+lrzMUnIh3y+YcGo2JJ9ZW1LxGvEIU0aEQ7mSPV8k7m3QiH2MiPQ +P0t17w1hQP+NAoIBAHJch1pi9CGGZaB2e8cpsGf0s8yt4P3dLthzbFfbR9nLmEk9 +DnOQSPeTRdaNNuzce1mzHTzqRfVvebm6nX3j1/Uk+0atqI+Lzj9/V1oViThk8LUy +MEZkpnaPUP6sD3HY5TzddilrkPuyhqs7GeaZjSbigtBe1frcDFhgn2FmIApFysk8 +SqB46NelhCXllB/du9ItzKSJ2CHGS4ujFtUIsjCjoPsvrHOhajdZc950sZnDYstk +umnP+QP06lPqeDRVAJhidWRG3EXqU6uwevKW+iSwkJw/u0Q9O7NaC74s++J1xYgs +R1Q+RK3OJXQoXMsVRxAY4HyUEDmSj5cY52wMoKsCggEAQMXu9PJOY8XV3Z02G76t +5IVviyGm79u9G+0Cryd69watcLHEu9a4AmieCZqGgWaTq9F5doDzKDaC6o19TlUV +Em4fKlwzGzsn8KBPs7D1JKp2+f1eIpPiMHW+xB02bKzTjtn6uDY8cEEdBNqoNhy4 +W5XYSW82xyB6cQSI53U8f+jh2umi4Q4SqVsrTvVkqySKBtBlmQ//WVXMSniofRSI +x9IPJry+saFpBfGrEU+Y3yQLbkFsLvcRIai70ubwl/CoVVr/FMsv83rlGalPfkcb +Bl6lTW5mLBDM6ULsPY/c+sde2NKY8QGDgt9IDKlVvjL3dPeMbeXv8+V8F2RGieSw +mQKCAQEA1n4t4CLKZMWZ4oonBEp8u9rjK23ObCSxANsfoIw94v2zHTFqLraFg9aH +s+DIc6M/XWX+Ie55v1QuYt8LrMtc9/rtOJdISybAtnheSqjQ+IkTEvBab/8pqhYg +Jhv/RxmBnCLwiIzRGVpxv9/5bbKXq1JKgdQKO3RBG53lcFFMO1O5srhKqj2KgAHv +XCQxBmtj83e6gp6hvUOU4YC2aKyL9QqNVndGzPJikEguyvJPUFw6RoB0StVQnzLY +UOgIH+8VTjzL90nQ/JitVT8uGdw1Ge5xJfCXDe/PEYpDsY65DKPrIMWFeQs6T/Mb +nvNeBReQOuYYpcCc2GM96RMUz3iyoA== +-----END PRIVATE KEY----- diff --git a/t/cert/mtls_ca.crt b/t/cert/mtls_ca.crt new file mode 100644 index 0000000000..1fe7e1f985 --- /dev/null +++ b/t/cert/mtls_ca.crt @@ -0,0 +1,78 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 32:ed:21:56:d8:4e:aa:03:89:a9:4a:a4:e2:85:2d:8a:3b:2b:89:22 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C = US, ST = California, O = OpenResty, CN = OpenResty Testing Root CA + Validity + Not Before: Mar 13 15:49:00 2022 GMT + Not After : Mar 8 15:49:00 2042 GMT + Subject: C = US, ST = California, O = OpenResty, CN = OpenResty Testing Root CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:e6:37:d2:c6:17:36:c7:b2:7f:7d:cf:d0:62:87: + 99:d9:21:b8:de:ff:d8:e2:3a:1c:68:90:8f:ce:17: + 68:22:b0:60:30:cc:29:e8:34:ee:ff:b2:25:de:6e: + 1a:d4:df:10:19:11:4b:40:61:d3:a9:4d:80:ed:97: + 81:4e:c5:74:e8:4d:63:e3:5f:21:bc:5a:6e:22:a0: + 17:91:c1:cb:25:53:9b:9d:4e:e1:51:5b:f6:52:e7: + 0a:27:f6:16:c2:31:cb:6c:47:f4:89:51:15:cc:06: + be:31:3e:1c:ea:ee:81:9b:c4:97:96:fd:e5:1c:95: + 9e:c0:65:cd:a9:9a:cb:68:67:f2:62:a0:21:eb:5a: + c5:a1:92:ed:32:41:28:f9:47:34:eb:44:ae:d6:e7: + 76:71:11:98:c9:2e:ce:6c:7c:10:1b:c7:4c:c3:14: + 89:4e:d9:4c:d9:c7:43:e9:3c:29:ca:62:a9:91:b3: + 87:e7:d7:b4:18:ab:65:f9:6b:ed:82:ca:a1:36:35: + 18:05:cb:5c:24:26:13:13:f8:99:ac:99:be:9b:a6: + 73:df:0d:16:95:b1:dc:be:fe:7a:c2:b6:dc:c8:93: + cf:10:e0:29:03:0e:28:78:18:84:ee:14:92:ab:be: + 5a:a0:14:a2:4a:2f:d3:d0:b8:0e:00:d2:5a:cd:e4: + bd:a1 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + F0:D7:4B:14:73:E1:67:00:6B:54:B4:19:20:76:12:9F:9D:8E:C8:09 + Signature Algorithm: sha256WithRSAEncryption + 6d:52:21:6d:6e:8c:e5:4a:28:07:65:6d:d8:7c:23:2e:c6:c1: + d0:ec:27:b3:b0:c3:d3:e8:fa:72:b9:de:32:4e:ff:97:8d:86: + a9:6d:b3:a9:b4:2d:77:ca:28:97:6a:3d:7b:a2:15:ed:34:dc: + 72:9f:6f:e7:01:0c:d3:28:6a:80:1b:50:09:fd:d7:2c:d8:92: + d5:10:c4:73:15:20:7d:99:dc:de:30:7b:3c:6e:e9:66:b2:0e: + 4e:1a:c1:51:57:6e:5b:b0:a9:f6:ff:0b:8f:07:67:31:40:5b: + 11:a9:06:d3:d3:76:c5:d2:56:95:9a:9e:4a:16:44:4b:32:e5: + af:dd:4b:4d:5d:57:b8:85:69:36:93:2a:c6:0c:8f:e1:42:35: + be:8e:f3:e7:35:d3:2c:3a:03:31:40:75:8e:e8:dd:57:35:20: + 5e:18:a9:76:ce:85:be:7e:3a:cf:6e:08:58:5b:47:d5:e9:c4: + ec:0e:e9:8e:3c:2d:5c:7b:59:20:5b:24:92:a0:e0:1e:a3:5a: + 67:d8:ff:7f:a5:82:f1:df:db:05:65:79:88:b1:3c:e6:01:d1: + 5a:c7:d2:6e:9a:e6:a2:da:4a:c7:19:78:d9:14:71:6e:1f:70: + f3:41:e5:b3:78:31:d5:22:0e:7c:1a:b2:43:d9:86:ff:53:ea: + 2b:ba:d2:27 +-----BEGIN CERTIFICATE----- +MIIDhDCCAmygAwIBAgIUMu0hVthOqgOJqUqk4oUtijsriSIwDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAoT +CU9wZW5SZXN0eTEiMCAGA1UEAxMZT3BlblJlc3R5IFRlc3RpbmcgUm9vdCBDQTAe +Fw0yMjAzMTMxNTQ5MDBaFw00MjAzMDgxNTQ5MDBaMFoxCzAJBgNVBAYTAlVTMRMw +EQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQKEwlPcGVuUmVzdHkxIjAgBgNVBAMT +GU9wZW5SZXN0eSBUZXN0aW5nIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDmN9LGFzbHsn99z9Bih5nZIbje/9jiOhxokI/OF2gisGAwzCno +NO7/siXebhrU3xAZEUtAYdOpTYDtl4FOxXToTWPjXyG8Wm4ioBeRwcslU5udTuFR +W/ZS5won9hbCMctsR/SJURXMBr4xPhzq7oGbxJeW/eUclZ7AZc2pmstoZ/JioCHr +WsWhku0yQSj5RzTrRK7W53ZxEZjJLs5sfBAbx0zDFIlO2UzZx0PpPCnKYqmRs4fn +17QYq2X5a+2CyqE2NRgFy1wkJhMT+Jmsmb6bpnPfDRaVsdy+/nrCttzIk88Q4CkD +Dih4GITuFJKrvlqgFKJKL9PQuA4A0lrN5L2hAgMBAAGjQjBAMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTw10sUc+FnAGtUtBkgdhKf +nY7ICTANBgkqhkiG9w0BAQsFAAOCAQEAbVIhbW6M5UooB2Vt2HwjLsbB0Owns7DD +0+j6crneMk7/l42GqW2zqbQtd8ool2o9e6IV7TTccp9v5wEM0yhqgBtQCf3XLNiS +1RDEcxUgfZnc3jB7PG7pZrIOThrBUVduW7Cp9v8LjwdnMUBbEakG09N2xdJWlZqe +ShZESzLlr91LTV1XuIVpNpMqxgyP4UI1vo7z5zXTLDoDMUB1jujdVzUgXhipds6F +vn46z24IWFtH1enE7A7pjjwtXHtZIFskkqDgHqNaZ9j/f6WC8d/bBWV5iLE85gHR +WsfSbprmotpKxxl42RRxbh9w80Hls3gx1SIOfBqyQ9mG/1PqK7rSJw== +-----END CERTIFICATE----- diff --git a/t/cert/mtls_ca.key b/t/cert/mtls_ca.key new file mode 100644 index 0000000000..d39b42f9d9 --- /dev/null +++ b/t/cert/mtls_ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA5jfSxhc2x7J/fc/QYoeZ2SG43v/Y4jocaJCPzhdoIrBgMMwp +6DTu/7Il3m4a1N8QGRFLQGHTqU2A7ZeBTsV06E1j418hvFpuIqAXkcHLJVObnU7h +UVv2UucKJ/YWwjHLbEf0iVEVzAa+MT4c6u6Bm8SXlv3lHJWewGXNqZrLaGfyYqAh +61rFoZLtMkEo+Uc060Su1ud2cRGYyS7ObHwQG8dMwxSJTtlM2cdD6TwpymKpkbOH +59e0GKtl+WvtgsqhNjUYBctcJCYTE/iZrJm+m6Zz3w0WlbHcvv56wrbcyJPPEOAp +Aw4oeBiE7hSSq75aoBSiSi/T0LgOANJazeS9oQIDAQABAoIBAQDhH9+uNE8uUv/X +MNvvLfklWpOlBf25o+fZ3NuzRjJgEafOsCee2fyI8FWVwIfeeE8OpFm5GLDZk1+r +dwdM10xuSheO5Z1gyfF/TJwfvamA09SNrPArFkm3YhUNZNl2hykMtwSLL06oWEOu +dbXjit4VS9aNIbTlEe7O5/6Ih0W3zmr1yvUua2swmAZMx3GFA4kbjZZ9vDs27sdu +K+VY3DYRbq1HkiNFT0otfke5bObFBCG7Yp8JLyhYaIkGYFoBXuZ6JNY8EuU2+YyP +6r40tJ7StR1Q6eZJh9/1leaYGZLCh5oFyKpilTuxHbRbr5A28RJKjKvPsdDgTtQn +yHGg70FRAoGBAOhC3TQlFcT2WCCZHHql9JEEHnHVBWnL3Jg7VJuL1i6pEIz7qQkW +AtBEIY/nnTcVNfJ6eXznYtutYvvRSgQTUsBNRoj3s1z9wKOo4uw4LoIUXDEmHCr+ +49DiQyIO21SNMHA+dVxvGRDDjLI9Uc+Scb64QOodoX75HLRZG++24mtdAoGBAP2/ +gCjga2p8Jx9UnhIcrEIIGANyxEQeBdhF56Nt9CJy/Iwi3a6qQ/GkbeoDm5FhXnXo +xcBaHyv2lwi4uO/hONY8eRnYxAWMwAKMZe6VnU1hWI2Ytkh+OcMPMh7NIGQf6X1o +JZrBtnTms060TuuDjLeIlaubDR/xDrMWTMKjKbsVAoGAVLuYAZ8J6xpIGlRhbGlA +6OrMxJCHcgpahvsWKc0BLXKmRBjHmTX7fslsSRihZWgKj1SZH7U2fpgpxV6cFxKJ +nPhUJEHhoKo+bjZ92tnANdqBq7iQjCsDJ8Bz52fuIlGD+1795+PsDA6bNKdkQkrV +zlNf80kuEqmFDFJ5+6EHx00CgYAf+jkpbZa71aeMgDpnZ+uhaqm0DYuEVhBAgBa/ +9sRUbw86jc5IC7cCRcmAOzIosQ+ZZls9cV4KSUohVD4iJMzn2rkcM8AIPwOXjp/t +4DbxoHnrZjpaimW3Gjwju5AAbjEbl7tddFoNA2HHYlurvGlIW9MYzDJsOxGyKfZE +dRF2PQKBgQDUKNHgDYEjLJ99S5Fm5zN/64bKzzDtktGdqOxik5pBKcs/BvOdLM0i +eCjGz/3qrEoenFIBwF/IRz3ug90Zr8bWOu6DudReflAKI/N13dZ2gOTAfaX4ljJF +w0ohSi6xs+mu1GmtipGtNxHi/J3na2BeSnSRFSUg6Zd+oh8BZQKmNg== +-----END RSA PRIVATE KEY----- diff --git a/t/cert/mtls_cert_gen/.gitignore b/t/cert/mtls_cert_gen/.gitignore new file mode 100644 index 0000000000..f375caaefe --- /dev/null +++ b/t/cert/mtls_cert_gen/.gitignore @@ -0,0 +1,4 @@ +*.pem +*.csr +cfssl +cfssljson diff --git a/t/cert/mtls_cert_gen/generate.sh b/t/cert/mtls_cert_gen/generate.sh new file mode 100755 index 0000000000..46625fdd07 --- /dev/null +++ b/t/cert/mtls_cert_gen/generate.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +rm *.pem *.csr cfssl cfssljson + +wget -O cfssl https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssl_1.6.1_linux_amd64 +wget -O cfssljson https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssljson_1.6.1_linux_amd64 +chmod +x cfssl cfssljson + +./cfssl gencert -initca -config profile.json mtls_ca.json | ./cfssljson -bare mtls_ca + +./cfssl gencert -ca mtls_ca.pem -ca-key mtls_ca-key.pem -config profile.json -profile=client mtls_client.json | ./cfssljson -bare mtls_client +./cfssl gencert -ca mtls_ca.pem -ca-key mtls_ca-key.pem -config profile.json -profile=server mtls_server.json | ./cfssljson -bare mtls_server + +openssl x509 -in mtls_ca.pem -text > ../mtls_ca.crt +mv mtls_ca-key.pem ../mtls_ca.key + +openssl x509 -in mtls_client.pem -text > ../mtls_client.crt +mv mtls_client-key.pem ../mtls_client.key + +openssl x509 -in mtls_server.pem -text > ../mtls_server.crt +mv mtls_server-key.pem ../mtls_server.key + +rm *.pem *.csr cfssl cfssljson diff --git a/t/cert/mtls_cert_gen/mtls_ca.json b/t/cert/mtls_cert_gen/mtls_ca.json new file mode 100644 index 0000000000..0a4a7ab139 --- /dev/null +++ b/t/cert/mtls_cert_gen/mtls_ca.json @@ -0,0 +1,18 @@ +{ + "CA": { + "expiry": "175200h", + "pathlen": 0 + }, + "CN": "OpenResty Testing Root CA", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "US", + "O": "OpenResty", + "ST": "California" + } + ] +} diff --git a/t/cert/mtls_cert_gen/mtls_client.json b/t/cert/mtls_cert_gen/mtls_client.json new file mode 100644 index 0000000000..4d59f47a5b --- /dev/null +++ b/t/cert/mtls_cert_gen/mtls_client.json @@ -0,0 +1,18 @@ +{ + "CN": "foo@example.com", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "US", + "O": "OpenResty", + "ST": "California" + } + ], + "hosts": [ + "foo@example.com", + "bar@example.com" + ] +} diff --git a/t/cert/mtls_cert_gen/mtls_server.json b/t/cert/mtls_cert_gen/mtls_server.json new file mode 100644 index 0000000000..655af54ef8 --- /dev/null +++ b/t/cert/mtls_cert_gen/mtls_server.json @@ -0,0 +1,17 @@ +{ + "CN": "example.com", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "US", + "O": "OpenResty", + "ST": "California" + } + ], + "hosts": [ + "example.com" + ] +} diff --git a/t/cert/mtls_cert_gen/profile.json b/t/cert/mtls_cert_gen/profile.json new file mode 100644 index 0000000000..d3b6ab16cb --- /dev/null +++ b/t/cert/mtls_cert_gen/profile.json @@ -0,0 +1,27 @@ +{ + "signing": { + "default": { + "expiry": "175200h" + }, + "profiles": { + "server": { + "usages": [ + "signing", + "digital signing", + "key encipherment", + "server auth" + ], + "expiry": "175199h" + }, + "client": { + "usages": [ + "signing", + "digital signature", + "key encipherment", + "client auth" + ], + "expiry": "175199h" + } + } + } +} diff --git a/t/cert/mtls_client.crt b/t/cert/mtls_client.crt new file mode 100644 index 0000000000..dd0efdf7f8 --- /dev/null +++ b/t/cert/mtls_client.crt @@ -0,0 +1,87 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 19:0a:a3:a8:9c:d4:0f:dc:c6:fa:23:7b:f8:fc:bd:f4:73:4e:7e:b1 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C = US, ST = California, O = OpenResty, CN = OpenResty Testing Root CA + Validity + Not Before: Mar 13 15:49:00 2022 GMT + Not After : Mar 8 14:49:00 2042 GMT + Subject: C = US, ST = California, O = OpenResty, CN = foo@example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:be:5b:09:4c:94:71:d3:82:54:4a:42:6a:76:aa: + 34:5d:28:d9:45:e6:44:9a:74:9f:a6:e6:78:49:9e: + c6:20:75:32:5f:92:3b:ec:6e:4b:7b:b0:75:1c:75: + 09:00:05:77:d6:59:ca:55:5b:13:b6:76:3a:c6:18: + dc:37:6a:20:93:e6:26:56:5d:0b:96:8c:01:f2:96: + 38:08:08:36:a2:64:12:21:a0:8d:48:cd:9a:26:78: + 92:29:b6:63:eb:14:d9:b6:e5:87:f7:d5:55:a4:cc: + 53:1c:a3:7c:b8:bd:ad:7c:a4:d4:86:1f:a7:1c:43: + c5:1a:b5:f1:03:bd:fe:19:98:1d:b7:13:2b:93:a2: + 2a:0e:21:7e:42:a9:bb:28:69:49:59:e7:89:0e:7d: + 5a:ce:fb:d4:0c:20:6a:e1:db:b2:6a:e5:a7:55:e0: + d0:58:4a:e2:08:78:82:b9:06:0c:65:f9:24:06:e6: + 8a:13:b2:9a:ef:1b:4a:b2:3a:b4:98:7f:dd:3c:0e: + 85:0b:a6:c6:47:2f:63:c2:73:52:41:db:7c:06:c3: + 2a:b5:2d:d1:e1:30:d5:c4:79:c9:b9:35:68:46:ad: + c4:45:57:ea:11:88:27:37:ed:ac:49:2d:c4:d6:c6: + a6:74:8d:d3:bc:e0:d9:69:25:0c:0c:b0:e3:b7:cb: + 8d:99 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 22:70:5E:30:8C:4D:66:39:E7:60:C9:29:A2:ED:95:32:34:63:5C:C0 + X509v3 Authority Key Identifier: + keyid:F0:D7:4B:14:73:E1:67:00:6B:54:B4:19:20:76:12:9F:9D:8E:C8:09 + + X509v3 Subject Alternative Name: + email:foo@example.com, email:bar@example.com + Signature Algorithm: sha256WithRSAEncryption + 96:e7:2a:fc:2a:56:16:80:e2:d3:79:0c:46:db:c3:88:ab:d3: + ef:39:66:4b:a9:ab:6c:0e:30:08:07:7c:fc:03:6c:f7:dd:fb: + 3e:a8:c8:68:28:ab:4e:73:97:80:27:5d:c5:9d:52:00:aa:08: + 25:c8:f9:dc:df:64:73:a4:58:5b:bd:5f:1a:53:a4:33:a3:b1: + 45:38:2d:be:d7:f3:a4:c4:f4:7a:07:71:44:f1:a2:65:02:e4: + 71:84:01:b5:83:4b:de:83:b5:ad:ac:b9:3c:17:42:0c:9a:7d: + eb:7f:ab:26:dd:9b:3a:fd:95:37:55:cc:01:c3:3f:20:df:e5: + ed:49:51:7a:42:ea:f3:8a:3f:da:6e:c1:1a:11:b9:45:4d:6e: + c9:21:f4:e3:4f:31:72:5b:bb:01:92:b6:7f:f1:8a:9e:6c:d0: + 7f:96:d7:eb:29:09:53:38:26:41:00:f2:33:04:77:bd:a9:ee: + 60:9e:06:b7:7d:26:ae:1c:4f:56:bd:a5:b6:50:40:be:be:84: + 2a:54:21:59:47:7d:a5:1e:63:6d:28:36:4d:a6:e4:62:69:9b: + 9b:fa:2b:48:e8:64:d7:14:f4:62:a2:26:17:a5:05:58:4a:38: + d2:44:e7:33:90:b9:c1:8c:85:02:99:b8:03:1a:03:d2:cf:ac: + a5:6b:44:98 +-----BEGIN CERTIFICATE----- +MIID3DCCAsSgAwIBAgIUGQqjqJzUD9zG+iN7+Py99HNOfrEwDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAoT +CU9wZW5SZXN0eTEiMCAGA1UEAxMZT3BlblJlc3R5IFRlc3RpbmcgUm9vdCBDQTAe +Fw0yMjAzMTMxNTQ5MDBaFw00MjAzMDgxNDQ5MDBaMFAxCzAJBgNVBAYTAlVTMRMw +EQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQKEwlPcGVuUmVzdHkxGDAWBgNVBAMM +D2Zvb0BleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AL5bCUyUcdOCVEpCanaqNF0o2UXmRJp0n6bmeEmexiB1Ml+SO+xuS3uwdRx1CQAF +d9ZZylVbE7Z2OsYY3DdqIJPmJlZdC5aMAfKWOAgINqJkEiGgjUjNmiZ4kim2Y+sU +2bblh/fVVaTMUxyjfLi9rXyk1IYfpxxDxRq18QO9/hmYHbcTK5OiKg4hfkKpuyhp +SVnniQ59Ws771AwgauHbsmrlp1Xg0FhK4gh4grkGDGX5JAbmihOymu8bSrI6tJh/ +3TwOhQumxkcvY8JzUkHbfAbDKrUt0eEw1cR5ybk1aEatxEVX6hGIJzftrEktxNbG +pnSN07zg2WklDAyw47fLjZkCAwEAAaOBozCBoDAOBgNVHQ8BAf8EBAMCBaAwEwYD +VR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUInBeMIxN +ZjnnYMkpou2VMjRjXMAwHwYDVR0jBBgwFoAU8NdLFHPhZwBrVLQZIHYSn52OyAkw +KwYDVR0RBCQwIoEPZm9vQGV4YW1wbGUuY29tgQ9iYXJAZXhhbXBsZS5jb20wDQYJ +KoZIhvcNAQELBQADggEBAJbnKvwqVhaA4tN5DEbbw4ir0+85Zkupq2wOMAgHfPwD +bPfd+z6oyGgoq05zl4AnXcWdUgCqCCXI+dzfZHOkWFu9XxpTpDOjsUU4Lb7X86TE +9HoHcUTxomUC5HGEAbWDS96Dta2suTwXQgyafet/qybdmzr9lTdVzAHDPyDf5e1J +UXpC6vOKP9puwRoRuUVNbskh9ONPMXJbuwGStn/xip5s0H+W1+spCVM4JkEA8jME +d72p7mCeBrd9Jq4cT1a9pbZQQL6+hCpUIVlHfaUeY20oNk2m5GJpm5v6K0joZNcU +9GKiJhelBVhKONJE5zOQucGMhQKZuAMaA9LPrKVrRJg= +-----END CERTIFICATE----- diff --git a/t/cert/mtls_client.key b/t/cert/mtls_client.key new file mode 100644 index 0000000000..7d77e5506d --- /dev/null +++ b/t/cert/mtls_client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAvlsJTJRx04JUSkJqdqo0XSjZReZEmnSfpuZ4SZ7GIHUyX5I7 +7G5Le7B1HHUJAAV31lnKVVsTtnY6xhjcN2ogk+YmVl0LlowB8pY4CAg2omQSIaCN +SM2aJniSKbZj6xTZtuWH99VVpMxTHKN8uL2tfKTUhh+nHEPFGrXxA73+GZgdtxMr +k6IqDiF+Qqm7KGlJWeeJDn1azvvUDCBq4duyauWnVeDQWEriCHiCuQYMZfkkBuaK +E7Ka7xtKsjq0mH/dPA6FC6bGRy9jwnNSQdt8BsMqtS3R4TDVxHnJuTVoRq3ERVfq +EYgnN+2sSS3E1samdI3TvODZaSUMDLDjt8uNmQIDAQABAoIBACqRsUKu78WdH7x7 +ndNrvMoYmH5JQI5KBmoMoFnWZ/haPSmiSkRVZgwDKi1y/tBCaMpGyjjMZVwolHw4 +kwbRdPeeQHSP2keQh974OQ+SxqUKPAPJI89kK1TvIcCySSYJQ6bjLcT+sGhqSSve +Y8XspR96vQxBh92KSknu5jcwBeMy/eG0mmszzP3y2R0BPztuZdE6dq/KxWQ/R4/P +JG9V1rNkIY+1JZvIICIH1Ehn4UKjiE+FJmyDbDlPKEi7W4CpRnShMLOF4cCFnQLW +RQds3Dj9GcVY+8Q/GLZF0ATjekIyEsKZEgrMAUF5ZSGRpjJQEHX7oseAiQGQxtHT +nj5b1AECgYEAwewXbbd1MqRQ6ohfsQ8j5HSMY6ahvUzs1dZUckr2jw8B98tfi/uj +a6Jq1KZe12+4dfwruRSaYdTsSVuvNiSJOxElY0C1p+lXdprFf7XfoQ6UNtg22jcH +9f8cftnlJoV5whh3YKjqnnnAWUQZ61FTNJ258/t+x0ZgpBJvqBoHwDUCgYEA+0qp +FZ5xS4FLJMc+Xf/hUeXo+04e4OD/se3atYqyuh1ghmQZfRRPOC110HG99H+rzq/x +xPMvRFahkAMyi+/3oIcBEuXvoQyqscIsAhkWD/e9t3Qc9OsWe1hlAgWKZxr6oR2U +KKR1FD7UVecOH+FKCKaL5UpEt4yEigc1NtSlTFUCgYBnV5agrIyzQSex5J0CMWxS +Od362PkGdXEc/8we4F4GnNvSnrm7Uo2jNXmy+zo9mtb1YT43sogXLK4C5e44bz4G +kTuYagqkgdBPb2lihpy3KprHo2+P2JXQfXRFEX9xiN37Fqi/hSUK8R0VNRqO8dbi +ik9nexXzwkiMBxsjvUN2JQKBgFy62FpZ9YTfWVNhEuqtGgCWzrqtwUdKwBBwrVyA +qiNz48Kz/ZPigrlATVF2J5qp4kSLOLRs6OxW65exFl39V2utZgALSbosanDeLk83 +4qRRz3h7KJRYjBtIKz3rvX7+va3mtF2rEmk+Jizs7pFlGWTH0Kf0GBeDiwVEU6bA +IZ9hAoGAQTjnRGMjvyhq0aPYP+mRFiMKSkcL1nyXizYInfAnbfbL/uEODH7D+iMf +kak+UgmeD9ce5d/APmZp3/FzYH/M8ivBgG+MnaI+MLVMhmQdLZyMtbSKKaDpiim7 +DdN1wCXYbur0HlO2t+wemMZPpQu7wybgEOLlIG7Yj/0OWDcal1c= +-----END RSA PRIVATE KEY----- diff --git a/t/cert/mtls_server.crt b/t/cert/mtls_server.crt new file mode 100644 index 0000000000..7cbc2a804b --- /dev/null +++ b/t/cert/mtls_server.crt @@ -0,0 +1,87 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 2f:d5:41:13:5a:ff:c7:1c:5b:ce:28:cd:a6:f7:a5:5a:0d:c0:e2:d2 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C = US, ST = California, O = OpenResty, CN = OpenResty Testing Root CA + Validity + Not Before: Mar 13 15:49:00 2022 GMT + Not After : Mar 8 14:49:00 2042 GMT + Subject: C = US, ST = California, O = OpenResty, CN = example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:d7:03:80:a7:42:7d:06:5a:7b:70:d8:11:96:dd: + 63:35:53:07:28:71:52:05:40:55:83:61:a7:14:ac: + cf:4b:9b:ab:b7:4e:9d:79:e9:13:3d:bc:c3:67:8f: + dd:88:d9:8b:c2:31:aa:b8:28:9e:13:70:db:76:b0: + 12:1c:f8:35:c6:2e:33:9c:b9:04:e3:47:e0:f9:e4: + 7f:a5:55:03:0c:2d:b2:54:17:29:12:dd:61:6e:5c: + 33:9f:e5:8f:8a:2b:41:53:dc:e1:98:49:63:df:e3: + 00:30:2d:1b:bb:f0:8f:cb:04:ec:c9:98:c4:09:5b: + b4:ba:a9:a0:0a:77:d2:42:76:7c:ac:64:c3:97:85: + 50:5d:7d:02:61:2a:00:93:d0:69:5e:87:22:f0:c1: + 1e:53:46:02:40:37:c9:55:77:99:7d:9d:3d:35:14: + 74:84:e3:73:ca:e7:4a:ab:33:98:26:aa:41:4b:b5: + e6:63:7c:a4:1e:25:6a:88:f4:56:d9:2c:63:dd:89: + 19:fa:25:41:44:95:87:40:a7:9b:4e:3a:91:29:32: + 79:66:05:f4:2f:68:2c:06:53:df:4d:60:be:ac:09: + 20:61:9c:6f:1a:a6:07:5a:e7:41:91:9d:36:77:38: + 18:3a:69:7b:67:29:9f:1d:e0:c2:d2:8f:16:5b:14: + e8:e1 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 16:07:B5:C2:4C:B5:2D:4F:B8:E9:D6:FA:2F:3F:C0:1B:B6:4F:20:E6 + X509v3 Authority Key Identifier: + keyid:F0:D7:4B:14:73:E1:67:00:6B:54:B4:19:20:76:12:9F:9D:8E:C8:09 + + X509v3 Subject Alternative Name: + DNS:example.com + Signature Algorithm: sha256WithRSAEncryption + d9:c0:c0:d6:8b:44:04:26:b3:98:24:2c:12:82:6d:15:79:92: + 76:c9:77:94:c1:be:8f:8a:18:78:96:04:68:c9:0a:d1:84:c5: + de:cd:ba:b5:a2:3b:d4:0a:70:be:00:49:19:c0:6e:ca:e9:e5: + 8b:b6:e3:a2:39:0d:d8:ee:55:1a:08:73:39:19:d3:07:07:33: + 8c:d8:1b:0f:1b:73:0e:84:72:cf:e6:c1:a1:da:39:aa:c0:2e: + 3d:b9:a6:8f:ec:98:3a:07:58:34:c2:5e:4c:1a:6b:db:ce:51: + 92:25:1d:ba:78:4b:11:b6:f1:69:02:cb:ac:32:bb:80:f9:15: + 91:bf:4e:6a:ab:51:51:7c:7b:1a:72:80:96:eb:0c:fa:56:0e: + f2:87:3c:16:8a:04:aa:8a:9d:0c:d9:e0:c4:2a:20:42:5a:12: + 41:52:30:50:3d:85:f8:07:31:6b:af:a4:d2:44:38:69:ab:88: + 05:d4:5b:68:34:02:dc:99:5a:6c:b7:ea:fc:79:76:fe:68:29: + df:94:22:58:46:f2:40:cb:e1:92:17:d8:1e:3d:fa:a2:56:4f: + ac:3c:3d:ae:f7:90:12:ac:3b:6c:1e:1f:26:48:08:87:9a:0e: + 8d:9d:75:ef:86:1e:63:ac:e9:14:47:ad:3f:4f:10:57:2a:d1: + 95:ec:6f:24 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIUL9VBE1r/xxxbzijNpvelWg3A4tIwDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAoT +CU9wZW5SZXN0eTEiMCAGA1UEAxMZT3BlblJlc3R5IFRlc3RpbmcgUm9vdCBDQTAe +Fw0yMjAzMTMxNTQ5MDBaFw00MjAzMDgxNDQ5MDBaMEwxCzAJBgNVBAYTAlVTMRMw +EQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQKEwlPcGVuUmVzdHkxFDASBgNVBAMT +C2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1wOA +p0J9Blp7cNgRlt1jNVMHKHFSBUBVg2GnFKzPS5urt06deekTPbzDZ4/diNmLwjGq +uCieE3DbdrASHPg1xi4znLkE40fg+eR/pVUDDC2yVBcpEt1hblwzn+WPiitBU9zh +mElj3+MAMC0bu/CPywTsyZjECVu0uqmgCnfSQnZ8rGTDl4VQXX0CYSoAk9BpXoci +8MEeU0YCQDfJVXeZfZ09NRR0hONzyudKqzOYJqpBS7XmY3ykHiVqiPRW2Sxj3YkZ ++iVBRJWHQKebTjqRKTJ5ZgX0L2gsBlPfTWC+rAkgYZxvGqYHWudBkZ02dzgYOml7 +ZymfHeDC0o8WWxTo4QIDAQABo4GOMIGLMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE +DDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQWB7XCTLUtT7jp +1vovP8Abtk8g5jAfBgNVHSMEGDAWgBTw10sUc+FnAGtUtBkgdhKfnY7ICTAWBgNV +HREEDzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEA2cDA1otEBCaz +mCQsEoJtFXmSdsl3lMG+j4oYeJYEaMkK0YTF3s26taI71ApwvgBJGcBuyunli7bj +ojkN2O5VGghzORnTBwczjNgbDxtzDoRyz+bBodo5qsAuPbmmj+yYOgdYNMJeTBpr +285RkiUdunhLEbbxaQLLrDK7gPkVkb9OaqtRUXx7GnKAlusM+lYO8oc8FooEqoqd +DNngxCogQloSQVIwUD2F+Acxa6+k0kQ4aauIBdRbaDQC3JlabLfq/Hl2/mgp35Qi +WEbyQMvhkhfYHj36olZPrDw9rveQEqw7bB4fJkgIh5oOjZ1174YeY6zpFEetP08Q +VyrRlexvJA== +-----END CERTIFICATE----- diff --git a/t/cert/mtls_server.key b/t/cert/mtls_server.key new file mode 100644 index 0000000000..f5b85d18d8 --- /dev/null +++ b/t/cert/mtls_server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA1wOAp0J9Blp7cNgRlt1jNVMHKHFSBUBVg2GnFKzPS5urt06d +eekTPbzDZ4/diNmLwjGquCieE3DbdrASHPg1xi4znLkE40fg+eR/pVUDDC2yVBcp +Et1hblwzn+WPiitBU9zhmElj3+MAMC0bu/CPywTsyZjECVu0uqmgCnfSQnZ8rGTD +l4VQXX0CYSoAk9BpXoci8MEeU0YCQDfJVXeZfZ09NRR0hONzyudKqzOYJqpBS7Xm +Y3ykHiVqiPRW2Sxj3YkZ+iVBRJWHQKebTjqRKTJ5ZgX0L2gsBlPfTWC+rAkgYZxv +GqYHWudBkZ02dzgYOml7ZymfHeDC0o8WWxTo4QIDAQABAoIBAEnmZUiXnJsbbEPr +r5f3vYptYA9xa2xsoTeHz8JWZuUouwtE1PE6v6c/grXMh6rqgpObOH8VTseFyZhw +ibk1Ql48MPcTzG9FnDinZYvwvRxpdFpcn3xhZIRm4kN5xi0KEuj9CPireM1RmxXz +2w1scC+qIKxlejNxNpvVgzE136mBqEFKJzecP+yZuH/A86MQCgwqqa3jSz5ApNg+ +1aJE34cGFieDbAN+9sdqWA3OkRrHoy8EakUf4JEvwX1AwUN832mj+N/LfmcCGMeD +YhzybzlPBV2q2T1+pHIdNT99JVNPkgdTe1903EjnG5oSDGHt2i9MdnNkMsffDWNt +pJiqSHECgYEA2hL6l8Py4oa5AJ2WXriuHRJykAs90K0akftQt4i4lWCbeRhaGh7h +kPgpDS33RkE4SymVVr0c05abMCKabQBwbu4PNCqetCFtfmIQdQCTUbLbXjL8UuD2 +QnF7nbHiwyGBKRMU/F74oX3z7lXLgRtIiyyo5yYgIAQqpz3oJAaXNTUCgYEA/GhE +Ziez8FXVAg3XwwrE3SexRFKv1JqipYE4mr+ouzfpn9yn8mttxbOORiAAEBl3ZPhd +ZUBzLy19fdFZ8RJ0zPsqoZxsd09/XetaBU56C/g9u0fycj1L2elh9rQAlOW0Grus +l8jBh01TGtlg0xobK0zjwdGPcbYkp1IzIqyD9n0CgYEAicBvVyrJ5FnhxwfEkrTq +FycuAtt3Arg2DnzH8geFQaayzv2Y/OMA7Yg0tkSQ7GoKW0A7O31eFjIOeYuCLNSY +MRpjtDov4e0zsx/S8XWZmYP3mjtutBOyuyngQi655TTm18FcAkcjmy9qxOShFj7b +xj5BuzGUHWVEZDxwxUD8hvkCgYBnrcyqyZQ4HImqllUSYNIMpclC71QaWIqGwVWm ++yMsBAOLDvBNu6MTmnXOiEZ+VnecmgiDFr45ms35aI0xYQtpR6JzT/Wd7KG8ynfn +xhyL3iQ9UYhdNKB7mkoLNFUo1FHuyThUALq+AR0p4jDLheWzG5pSeuoZI2Ba+oDW +tVZfYQKBgC5phtERR5LKU5Wkzm+uY2j+Nzh4kuKkdLosB9pUW8VnrwFDLZ+r1CxG +L6CxOZ0AylCMIlrFeUXMa91kLDJYch0NUPHuGBkdIBDXi2kqN7GflTdV3Z8uev20 +uMjErA93yVOWHTR3Wo8WIHy5mdsNRQgGAPw1RVW7rnYIyXJW/mTs +-----END RSA PRIVATE KEY----- diff --git a/t/cert/test.crl b/t/cert/test.crl index 098fd54bf9..0abfa77ac2 100644 --- a/t/cert/test.crl +++ b/t/cert/test.crl @@ -1,11 +1,14 @@ -----BEGIN X509 CRL----- -MIIBjzCB+QIBATANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UEBhMCVVMxEzARBgNV -BAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEjAQBgNVBAoM -CU9wZW5SZXN0eTESMBAGA1UECwwJT3BlblJlc3R5MREwDwYDVQQDDAh0ZXN0LmNv -bTEgMB4GCSqGSIb3DQEJARYRYWdlbnR6aEBnbWFpbC5jb20XDTE0MDcyMTIxNDEy -MloXDTE0MDgyMDIxNDEyMlowHDAaAgkApQ5tVpK3luIXDTE0MDcyMTIxNDEwMlqg -DzANMAsGA1UdFAQEAgIQATANBgkqhkiG9w0BAQUFAAOBgQBDZ6UY0Qg7qDoLrXXl -gJElFilZ7LiKPqjE3+Rfx7XkgdbPxjGCr77TfMm+smdvawk7WHv1AOvRH7kGrgGT -kGJZwqJ4vKa/NpEWJIMAZ1Gq9BIH/Ig6ffmPk+S9ozcVHKJDW7x4nMuotyj1hILN -EePv78DZCYMZgf8WwMElNgz6Hw== +MIICGzCCAQMCAQEwDQYJKoZIhvcNAQELBQAwgZcxCzAJBgNVBAYTAlVTMRMwEQYD +VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRIwEAYDVQQK +DAlPcGVuUmVzdHkxEjAQBgNVBAsMCU9wZW5SZXN0eTERMA8GA1UEAwwIdGVzdC5j +b20xIDAeBgkqhkiG9w0BCQEWEWFnZW50emhAZ21haWwuY29tFw0yMzEwMDYwNjM4 +MzlaFw0zMTEyMjMwNjM4MzlaMCcwJQIUImcuJ8MJpeNhvYBEoEGGz5oCBg4XDTIz +MTAwNjA2MjkyNVqgDjAMMAoGA1UdFAQDAgEEMA0GCSqGSIb3DQEBCwUAA4IBAQBj +FciorrAuXxn1ULW0XJ7PElwTxZtBrb838EHYvkZ5OdT5tZcucYR6XTZpfT1Up/Px +rC9EZ1/aZ0wSQfYEQuctafyVCJoPN71IV9IWpPTWm8JyEvnE1W3SgHJujItanyZ3 +aMDihljxV9eKyEQnZPQZkaOjAKhj8d2/XZLQ3uIrjWy+/OxcaBQK4a8bDoSM3GZT +J6YCD2UBVYKSiROMZZAj3m1thLAGm1RM7A6vjEcH7rPyoxhok5SdxXH5ERY94/ro +McjNCv6zV8/Ue5/+ajz5pu/48T901mlIicHry8uImJvlnqAXlH8ReJ+hiSfGhbZ5 +WYNf0wN81NXwTxPIb+v2 -----END X509 CRL----- diff --git a/t/cert/test.crt b/t/cert/test.crt index a69a01141a..9fed1bc3b4 100644 --- a/t/cert/test.crt +++ b/t/cert/test.crt @@ -1,17 +1,22 @@ -----BEGIN CERTIFICATE----- -MIICqTCCAhICCQClDm1WkreW4jANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UEBhMC -VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x -EjAQBgNVBAoMCU9wZW5SZXN0eTESMBAGA1UECwwJT3BlblJlc3R5MREwDwYDVQQD -DAh0ZXN0LmNvbTEgMB4GCSqGSIb3DQEJARYRYWdlbnR6aEBnbWFpbC5jb20wIBcN -MTQwNzIxMDMyMzQ3WhgPMjE1MTA2MTMwMzIzNDdaMIGXMQswCQYDVQQGEwJVUzET -MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzESMBAG -A1UECgwJT3BlblJlc3R5MRIwEAYDVQQLDAlPcGVuUmVzdHkxETAPBgNVBAMMCHRl -c3QuY29tMSAwHgYJKoZIhvcNAQkBFhFhZ2VudHpoQGdtYWlsLmNvbTCBnzANBgkq -hkiG9w0BAQEFAAOBjQAwgYkCgYEA6P18zUvtmaKQK2xePy8ZbFwSyTLw+jW6t9eZ -aiTec8X3ibN9WemrxHzkTRikxP3cAQoITRuZiQvF4Q7DO6wMkz/b0zwfgX5uedGq -047AJP6n/mwlDOjGSNomBLoXQzo7tVe60ikEm3ZyDUqnJPJMt3hImO5XSop4MPMu -Za9WhFcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQA4OBb9bOyWB1//93nSXX1mdENZ -IQeyTK0Dd6My76lnZxnZ4hTWrvvd0b17KLDU6JnS2N5ee3ATVkojPidRLWLIhnh5 -0eXrcKalbO2Ce6nShoFvQCQKXN2Txmq2vO/Mud2bHAWwJALg+qi1Iih/gVYB9sct -FLg8zFOzRlYiU+6Mmw== +MIIDtzCCAp8CFCJnLifDCaXjYb2ARKBBhs+aAgYOMA0GCSqGSIb3DQEBCwUAMIGX +MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu +IEZyYW5jaXNjbzESMBAGA1UECgwJT3BlblJlc3R5MRIwEAYDVQQLDAlPcGVuUmVz +dHkxETAPBgNVBAMMCHRlc3QuY29tMSAwHgYJKoZIhvcNAQkBFhFhZ2VudHpoQGdt +YWlsLmNvbTAeFw0yMzA5MDUwNDE5MjhaFw0zMzA5MDIwNDE5MjhaMIGXMQswCQYD +VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j +aXNjbzESMBAGA1UECgwJT3BlblJlc3R5MRIwEAYDVQQLDAlPcGVuUmVzdHkxETAP +BgNVBAMMCHRlc3QuY29tMSAwHgYJKoZIhvcNAQkBFhFhZ2VudHpoQGdtYWlsLmNv +bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJzRMFoLDuYOwJ8szrS4 +nOibtiimiXZJGx3I/RcFZxaH4nL/WcEb1fwftMQxx73IBJnqnDYkDOzUmzItPMn0 +t2WrNYesC5GqLNRm87m6PVt010tZvq/WxTn6+9qruiGm1PhFxzLQfrClpEeOshlG +UeoQjPOMrhCmofDM2NQo3D4wIQT0kCJxIPq6wCZt22/Yqz1EmR0UnF/R3ZtiB8O+ +SQGcsUKy4se3919xq+ZkzBdMxLneO5sofUiDC9MgRfiU960tbHPGX9I9P+kLK89S +yajPEYaRUkSBFjV5kdDK3+L6XckdMbY2pvwhAnVXSmd13Bf2V9XisUrX2Mr4YlnS +sy0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAVPY/z6Mvjg5EGHzU8bXyuXqxrx8Q +GBwf3PY25aDF6ofRrTCzMdIhthv8eRtGwHinkpgaK34D7hI/dPB7aswQTzED5c+l +S2au5OzzCj454oXdhSRA5Rt0mu/+pxmQ+iNk+7XJxgTN0mk1dYQqodyZ+vC4NIYb +javMlU4zDm4JPtwDs0Mz/d7gf14MU60jppF2vl6AYFHKYBLMHBmqxjy6H9YHjRjQ +oe4TNpn0zxJAPu5LqMkfB2+eLOe6ced7DcLLbbeVJ4Xtqj6Y5KsAyVojWQxrk4vW +3WO/953pHofO5F2ricS/rsf+5ivTmfiP8mQYTtp7k3T11sIZ4DOmtNwO4A== -----END CERTIFICATE----- diff --git a/t/cert/test.key b/t/cert/test.key index 6c13527117..4c8c905b2f 100644 --- a/t/cert/test.key +++ b/t/cert/test.key @@ -1,15 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXgIBAAKBgQDo/XzNS+2ZopArbF4/LxlsXBLJMvD6Nbq315lqJN5zxfeJs31Z -6avEfORNGKTE/dwBCghNG5mJC8XhDsM7rAyTP9vTPB+Bfm550arTjsAk/qf+bCUM -6MZI2iYEuhdDOju1V7rSKQSbdnINSqck8ky3eEiY7ldKingw8y5lr1aEVwIDAQAB -AoGBANgB66sKMga2SKN5nQdHS3LDCkevCutu1OWM5ZcbB4Kej5kC57xsf+tzPtab -emeIVGhCPOAALqB4YcT+QtMX967oM1MjcFbtH7si5oq6UYyp3i0G9Si6jIoVHz3+ -8yOUaqwKbK+bRX8VS0YsHZmBsPK5ryN50iUwsU08nemoA94BAkEA9GS9Q5OPeFkM -tFxsIQ1f2FSsZAuN/1cpZgJqY+YaAN7MSPGTWyfd7nWG/Zgk3GO9/2ihh4gww+7B -To09GkmW4QJBAPQOHC2V+t2TA98+6Lj6+TYwcGEkhOENfVpH25mQ+kXgF/1Bd6rA -nosT1bdAY+SnmWXbSw6Kv5C20Em+bEX8WjcCQCSRRjhsRdVODbaW9Z7kb2jhEoJN -sEt6cTlQNzcHYPCsZYisjM3g4zYg47fiIfHQAsfKkhDDcfh/KvFj9LaQOEECQQCH -eBWYEDpSJ7rsfqT7mQQgWj7nDThdG/nK1TxGP71McBmg0Gg2dfkLRhVJRQqt74Is -kc9V4Rp4n6F6baL4Lh19AkEA6pZZer0kg3Kv9hjhaITIKUYdfIp9vYnDRWbQlBmR -atV8V9u9q2ETZvqfHpN+9Lu6NYR4yXIEIRf1bnIZ/mr9eQ== ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCc0TBaCw7mDsCf +LM60uJzom7Yopol2SRsdyP0XBWcWh+Jy/1nBG9X8H7TEMce9yASZ6pw2JAzs1Jsy +LTzJ9LdlqzWHrAuRqizUZvO5uj1bdNdLWb6v1sU5+vvaq7ohptT4Rccy0H6wpaRH +jrIZRlHqEIzzjK4QpqHwzNjUKNw+MCEE9JAicSD6usAmbdtv2Ks9RJkdFJxf0d2b +YgfDvkkBnLFCsuLHt/dfcavmZMwXTMS53jubKH1IgwvTIEX4lPetLWxzxl/SPT/p +CyvPUsmozxGGkVJEgRY1eZHQyt/i+l3JHTG2Nqb8IQJ1V0pnddwX9lfV4rFK19jK ++GJZ0rMtAgMBAAECggEABjaOkcllis1o/yrVZMPPabLpAHV6tZ5MuKfNiUOMSPr+ +HfF1OFQL7MxCdfyFQ1prqOp/9nAut+puMgp99wAfDQ7qanNGq7vgQKkfPSD+dy4V +rUquELBJH6nh9SZqfpSqKaJgHlNe6vehHuRYikJRkrJwVzegGjuekm3B+y6Zl/gc +e0p5Ha3MTLTFjocwYzgTjJlxD40wlbjpuVnmzKjo8AKNv1F1azMaqBmt1VfPiDn0 +Xyq4SPEsWKnEAl2kZdaIBR6zIx7Z3zNUwkfb32QwNoSyo8wS7lCgf2GVS7r1Eul6 +iiCE/Gd7w10alW4Pu96shVqkvKn7ROF2nBP9xOSPwQKBgQDCuD6mlNpA07iOX364 +aAzIAYookceVA0I9L/fbOQW7RgpvYpM8lxr31TQ3fBDkXSgjzMMYjnk4kz+xN+BB +WFdjb4raUBtrvip8Q8QZ53DVQK/LodHh0XhipbOxZrDm+6o5nQD0fTqHCBIHSVFF +tXX2Y90t1cxWMMleRhfNEuzkQQKBgQDOK0rs7mf04Xhc4ZIRIxOtNFnthGp4Kqp7 +SD8VQpbPOLV8iqZEtXIy/hvoTpfQW30c1931KgDQ3Pv5MZYpI7PLqrqkj4tGCQ91 +DJ03GWkSXcMwlPmJRbvgWIeCLgShU5PLxmQu3mH2DP+uGFUBq5/6miDDVjF9z6vb +BwYlG66j7QKBgA0n/bOrowN2SqXz9c/n19U7pWYQU3fR/Iu9zfVV6Pk6RkI4WtJh +M0VDdn+5Njr3wFqK3zOtjKsx57/FkrVXjq/9PVh6yR+CfcRfn8RQSuNdt4L+r/ud +95BSuc1mrtUsc9for8PVIjs1ZGJxpbgcBphbLvqF04SPT0u7WKhWewMBAoGAcJO/ +RAUiitsbaExcADORKQDvIf0uThOuJ8dZevhzdQ/YOftTsy0JAMM05fMUfteWR8uw +DZE0BNjGVlo3TpuKL+o4JGele0azRAzxRAcCEt9UGBEg+U40utpclD8glB8ZEypv +xg/0mfCbJKtwr4rRvnuu7DsCp1pg0ybQui6VfDkCgYBXHwcrZwmv7kgr4pUG6oZj +fzjFenQFqibvb2h7QESyCW13O885GxU13DKv4zg1yi6EqPIopz16qCiUNCvWr5Us +6sI74wEVI3MzmzG0Htgl29q5yWpeY+7libC/fbZYG8GFgdINq58ko9be1u/8644S +t2hoKM9/vrVFh9p9qGzckg== +-----END PRIVATE KEY----- diff --git a/t/cert/test2.crt b/t/cert/test2.crt index edc3b0df3b..6ba864c3fe 100644 --- a/t/cert/test2.crt +++ b/t/cert/test2.crt @@ -1,16 +1,24 @@ -----BEGIN CERTIFICATE----- -MIIChzCCAfACCQDjCkJpJUtZmjANBgkqhkiG9w0BAQUFADCBhjELMAkGA1UEBhMC -VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x -EjAQBgNVBAoMCU9wZW5SZXN0eTESMBAGA1UEAwwJdGVzdDIuY29tMSIwIAYJKoZI -hvcNAQkBFhNvcGVucmVzdHlAZ21haWwuY29tMCAXDTE0MDkxMzAwMTgxMFoYDzIx -MTQwODIwMDAxODEwWjCBhjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3Ju -aWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEjAQBgNVBAoMCU9wZW5SZXN0eTES -MBAGA1UEAwwJdGVzdDIuY29tMSIwIAYJKoZIhvcNAQkBFhNvcGVucmVzdHlAZ21h -aWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDy+OVI2u5NBOeB2Cyz -Gnwy9b7Ao4CSi05XtUxh2IoVdzYZz6c4PFb9C1ad52LDdRStiQT5A7+RKLj6Kr7f -JrKFziJxMy4g4Kdn9G659vE7CWu/UAVjRUtc+mTBAEfjdbumizmHLG7DmnNhGl3R -NGiVNLsUInSMGfUlJRzZJXhI4QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAEMmRvyN -N7uE24Tc6TR19JadNHK8g3YGktRoXWiqd/y0HY4NRPgvnK/nX7CY/wXa1j+uDO8K -e6/Ldm5RZrjtvfHJmTSAu8zkqTJz8bqRDH7kzL5Ni2Ky2x8r9dtB0ImpOiSlwvZN -snMvbrxEdwBiqlC9prV2f9aG+ACo1KnPL0j6 +MIID7zCCAtegAwIBAgIUS1f/CoGJaRaJvBgrXEXeTSm7kM4wDQYJKoZIhvcNAQEL +BQAwgYYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH +DA1TYW4gRnJhbmNpc2NvMRIwEAYDVQQKDAlPcGVuUmVzdHkxEjAQBgNVBAMMCXRl +c3QyLmNvbTEiMCAGCSqGSIb3DQEJARYTb3BlbnJlc3R5QGdtYWlsLmNvbTAeFw0y +NDAzMjQxMDI1NDhaFw0zNDAzMjIxMDI1NDhaMIGGMQswCQYDVQQGEwJVUzETMBEG +A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzESMBAGA1UE +CgwJT3BlblJlc3R5MRIwEAYDVQQDDAl0ZXN0Mi5jb20xIjAgBgkqhkiG9w0BCQEW +E29wZW5yZXN0eUBnbWFpbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQChXBMRP5X9Y0l5NogyIZatou1YDT2BDE9tza6dYKUdraJlGo1T4DZf7LoD +mKFvIpVqLedppZ9z9SrlrsZ8Be6OUbw3kxSyUPDvpauI4XXBR2ZuAHIzTy/Uare1 +1iZSjbT0KATiWviMWCWWblyBU5QPMFJJOmcjRDAZocfIUs4CzglRaDorl5qUFJHr +mROaIsag/10nnClTwaGwj1pHmFitKRHTIrYICYd5wGgstte9mDFz15s9a3f/Dwhc +/7neEHYaryRVhR627aDGVHHpcMRWr+FC8QmZTTkiFmO80eeaiyGGTS0AEW51EPI9 +Nr142PEaQETpjQaQnfNhZGnwTZHTAgMBAAGjUzBRMB0GA1UdDgQWBBQb73HlJHF7 +B4a1PWRkb3aovk7EtDAfBgNVHSMEGDAWgBQb73HlJHF7B4a1PWRkb3aovk7EtDAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCQJcLh7+NzAukTG4hV +nVd572U4sCnlXwTN4lnRIkSGbQxOWwbuI7p05iAT9HJrFLm+rV0EOzxyl/fYkPgR +DKYfj7sGPp4Lo0NyQ3xhoVypqDDmv7Wm9CLy2mVTJ07khW5tV8GHg9mFwgb/XBTQ +c+RnNalIapqdlkQbxms5HLzKzTVRuDt+wvqcAsiKFwKDAL9DFrp36TP1t023w+vd +Vb6Ms+l+xFFcxMCxFEOkSPc7szwSce9Q+Hk2C6JdCU30gJPyEBUMl6vD+0l0IFG0 +ZEw1BhBp4d92xHiYgdnDEH1RdLPI0q1r7LJL+5ic+VFWycgbC0KRHCarduYgvMYy +GbRG -----END CERTIFICATE----- diff --git a/t/cert/test2.key b/t/cert/test2.key index 82ce6ce91d..3bd546da0e 100644 --- a/t/cert/test2.key +++ b/t/cert/test2.key @@ -1,15 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDy+OVI2u5NBOeB2CyzGnwy9b7Ao4CSi05XtUxh2IoVdzYZz6c4 -PFb9C1ad52LDdRStiQT5A7+RKLj6Kr7fJrKFziJxMy4g4Kdn9G659vE7CWu/UAVj -RUtc+mTBAEfjdbumizmHLG7DmnNhGl3RNGiVNLsUInSMGfUlJRzZJXhI4QIDAQAB -AoGAEqBB83PVENJvbOTFiHVfUAjGtr3R/Wnwd4jOcjHHZB3fZ9sjVoxJntxfp3s1 -dwZir2rxlqVS6i3VAFiGiVTOGo2Vvzhw2J7f58twCECmnLb2f863AkGEYe4dAndD -GHGD0WI0CBMD1sT18YCj561o0Wol5deWH0gM9pr2N3HkeIECQQD6hUKFlFhrpaHP -WNJsl6BxgE6pB5kxLcMcpIQ7P+kHUvtyvCJl5QZJqPrpPGjRsAI5Ph92rpsp/zDp -/IZNWGVjAkEA+Ele31Rt+XbV32MrLKZgBDBk+Pzss5LTn9fZ5v1k/7hrMk2VVWvk -AD6n5QiGe/g59woANpPb1T9l956SBf0d6wJABTXOS17pc9uvANP1FGMW6CVl/Wf2 -DKrJ+weE5IKQwyE7r4gwIvRfbBrClSU3fNzvPueG2f4JphbzmnoxBNzIxwJAYivY -mGNwzHehXx99/byXMHDWK+EN0n8WsBgP75Z3rekEcbJdfpYXY8Via1vwmOnwOW65 -4NqbzHix37PSNw37GwJBALxaGNpREO2Tk+oWOvsD2QyviMVae3mXAJHc6nLVdKDM -q0YvDT6VdeNYYFTkAuzJacsVXOpn6AnUMFj0OBedMhc= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQChXBMRP5X9Y0l5 +NogyIZatou1YDT2BDE9tza6dYKUdraJlGo1T4DZf7LoDmKFvIpVqLedppZ9z9Srl +rsZ8Be6OUbw3kxSyUPDvpauI4XXBR2ZuAHIzTy/Uare11iZSjbT0KATiWviMWCWW +blyBU5QPMFJJOmcjRDAZocfIUs4CzglRaDorl5qUFJHrmROaIsag/10nnClTwaGw +j1pHmFitKRHTIrYICYd5wGgstte9mDFz15s9a3f/Dwhc/7neEHYaryRVhR627aDG +VHHpcMRWr+FC8QmZTTkiFmO80eeaiyGGTS0AEW51EPI9Nr142PEaQETpjQaQnfNh +ZGnwTZHTAgMBAAECggEAAhemuvqx8A25YQQsgvM9jXXckbTmPxtQ7QcVmb6BlZ9v +834DJtB0VA0b97pNyZpPXrJGsG+F4E6QZlcRsCpcMWXs72Q4Xfw/tj6AjsSsWWZw +H8K2Dbqb+lYB7i6J463TvWDGzhqeOM+seRq9l1hlRfHQogN1spbf8CJ8Fo8VqHf3 +OyId5db0nRUFYV9ciPvJSnk8xi+0aJ2LWVAkCaXQkntbI3529x8KPQm9bGUjdMgo +A+HXAKZTZL7GtGZoSU4jjyymGLM4hPtzXBExbkX7s7HOP1AeWGom4reOC6XNScQq +ysfs0wVzJAVSl0vFbQfUT6GZ2ZsaTdkT+JgXrYwp5QKBgQDIzY1O/Hj2koVgfKmS +2+1JVehX8+yjgz3rSNVUauISLoMaYN4hipsKb7Vi6aDFl+Db296Qql7kIzzd/nFk +UwdBjShUi/xbewGcrYEXbdcXSIxt9ius76eJ9INfa4EmLrZoiX/3Am1u+CcdtcKm +jkqy1o9zoIPbD6lc242zGFK2nQKBgQDNtukEZop/PngCX0yDGWLxisrvFYSrHxnW +x9/Fw0B56qmyG2FpFg5WDlErenHXh+KSlQ3pV1sWK/+3pVzgGP2ZK+nv3ThEQJmg +IbI32dABmUG9+HckxgOuAzfqGW6Y4m5I8YpODjn26V6GeS+xQj0bolr4lZ00QSEG +R5BxxLLHLwKBgQDAzYzWwFgs+HaUNF5lokFt3syeVplqTsOPPHmI+q9iocJD+6qN +Lha7qJLTDFvQHYpL0AsdgFhoWOVCieK6X3ZiHHUS6O4/sBXWKEoBAvg5ZPFhS7KX +8+w768iQQBrbXJLMQOnbdDs7B44XWsQxRAK5QIawkPjJx3norO6bfck9iQKBgA05 +Ze9ffCXD6UkVwUBoQGEQsA0AkB/EBxA0lyEKdTmhKTmG4xMzVMaZcwRdgbX+SUVt +CZDnibZ6K50tpzPMx3iyRv6hdP2GPZn6sI9AlEuWA5DnyRj3FcN49344FlEDtV1B +EcgEyBskU2xHnBZEENOW80wpqgpy6WGS9ikqIOgHAoGAeAUetHWIdZZ+Qt6SS1s2 +R17YAiZe88QFAo3uK/dyApLgY7wvbM0n7Cor/3tbpzpzAujUb/qNqTdTXsZIeRfq +bI767g4/pNl7+jhDUKmDI001YdfHvcRSwjzyjshuSinJY9rydXLUIEWtkTG3f9v8 +d+yLXD2Dz9gF6l2Msj+C3To= +-----END PRIVATE KEY----- diff --git a/t/cert/test_der.crt b/t/cert/test_der.crt new file mode 100644 index 0000000000..273b98638f Binary files /dev/null and b/t/cert/test_der.crt differ diff --git a/t/cert/test_der.key b/t/cert/test_der.key new file mode 100644 index 0000000000..14e9fa5821 Binary files /dev/null and b/t/cert/test_der.key differ diff --git a/t/cert/test_passphrase.crt b/t/cert/test_passphrase.crt new file mode 100644 index 0000000000..78d57577b0 --- /dev/null +++ b/t/cert/test_passphrase.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEDTCCAvWgAwIBAgIUZn0PL6eNqoKWughHd0SFO4aE/UswDQYJKoZIhvcNAQEL +BQAwgZQxCzAJBgNVBAYTAkNOMRIwEAYDVQQIDAlHdWFuZ2RvbmcxETAPBgNVBAcM +CFNoZW5aaGVuMRIwEAYDVQQKDAlPcGVuUmVzdHkxEjAQBgNVBAsMCU9wZW5SZXN0 +eTERMA8GA1UEAwwIdGVzdC5jb20xIzAhBgkqhkiG9w0BCQEWFGd1YW5nbGlubHZA +Z21haWwuY29tMCAXDTI1MDMwNDAxNDkwNFoYDzIxNTkwNTAxMDE0OTA0WjCBlDEL +MAkGA1UEBhMCQ04xEjAQBgNVBAgMCUd1YW5nZG9uZzERMA8GA1UEBwwIU2hlblpo +ZW4xEjAQBgNVBAoMCU9wZW5SZXN0eTESMBAGA1UECwwJT3BlblJlc3R5MREwDwYD +VQQDDAh0ZXN0LmNvbTEjMCEGCSqGSIb3DQEJARYUZ3VhbmdsaW5sdkBnbWFpbC5j +b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCmsvo9t9/lhfavK6S8 +7lqP8lfpV1gZE4ahyRCQI0vgQitlhGjEzvBc7qTtkunQpSWkuHSczOR1WuKzO+PG +ubH0gjeyvEE2X/cipQ1kjiO9Mv8w2qOm9BkC9Wrob01X4mOhGZUgthfwRs0QFSUh +tzvjg+aApWyIlpmgE/T8bnCwtPS7BuzwEpTt5a51q8JKOr4AMTy64mrMFQRqtcIk +GYL9CnoxAjYT6NAcEqoapSLDm1xJ7w2RDL1mqkAdXYUQeNPwMivrtb+rCDD4p99A +TZ8rOuKQzDSxZ3BdCmR5WUXp9z+NeQE5SBu+vkOYMTvEN+e9ApRRnZTO8UHeRB0+ +6vJZAgMBAAGjUzBRMB0GA1UdDgQWBBSmnXGC+KOqNzyGXoujr3TMe3pepDAfBgNV +HSMEGDAWgBSmnXGC+KOqNzyGXoujr3TMe3pepDAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4IBAQAokEqnbJNDUICvbsRQBFrDBZXjZaEPu69GrTg9OceB +N9Qu4CKRTL4WsV6uSbdj+Ox/r21UtCI1590oBpC3RiJ+WnGmpdUGVytGIODt/vJ7 +/R03EBy0o9e7XdlR4GfKMC7FVBsP/kkeacR2fswHYXtQrNlyuLhZW04HTDdYNNqb +LNCnsmwQ4WR4ek9PdcpeFfMY1Up6hTYZ441K8b6xSzMV0/LjzfmHXWoT3U1rQrfM +esm9t/R3sOvB/wBWQaRe0BbLYpkS7pcq8NQsWvSUFNl5TzrGtUf8KTyvEEdcUcZf +im48Ea7WYpE3RYs0laLBk/D3sz7vsK5wfiD0R/ASLE+s +-----END CERTIFICATE----- diff --git a/t/cert/test_passphrase.key b/t/cert/test_passphrase.key new file mode 100644 index 0000000000..2cabedfb57 --- /dev/null +++ b/t/cert/test_passphrase.key @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ2v8WxwirSc2M7jaZ +mrWsPgICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEAlDrk7LLHI/jJqN +QJ6z8MYEggTQtV86xHehGFn/Qkn5qSZicJ/sDLXQ+m4dFXgw9KMnm0zLG8kzEmb7 +sobUJ4CnwmpBv/GmpPxgy5v1fuvc8HXRJWr7xwv1FoD4lwVOWVFv/qTaII4TfcC6 +3vKqO7MbRaksyI/NmmepGKTGhbzW6XlEdE1TCG7tO02I3puSN0MbJACTpop53TG9 +g+b8HXegPXkLJ2p+ECjUqPwA+h6jZi2pIAztMHQpIizokomrLJ4+2ol3czJpA5nn +n8JbuYt1XWekq+PBi4l0+lAbk/RiLLK1PCN/Njp7iBBAtNtz9+yJd2Ev7wNDDxKk +dW73s8uTGde/n6hR4sl/akK8HLaw9tDcWsaHbIMl8zn3WZXksVUfb6CZVOU+BjBa +yZu3CnP1JTda16HgdZ5dGSNPNbrUeLLIRzM/OxK0yTtmkexOVIgSW0hGE1BXxFXk +v+mnzrjFrHNRQFhoCFciPqv+WfXsecSASeN0aLWP/RP6CLz6QXWQaK68Dn92v/WP +xc1NKk8l54wNCLqGaSXadUoTqEdHQH3GV5EkXP7MTb+fYi9/Y/S8wcXJtjUi3ABe +W+XCOh4zuuQPG7C3i6l/jLIL1ogQNYldad7MoM8eBU8u+TF+NqiJjTLCB1Quycqn +sN5oVS1VZfsx8XCFsvUAZgxzhjeXViBfwxKSKyT0b1drQZaY58LnyzjmzGkOpRfO +uzGzWPvHRxkGb9071XaRxD8YDLIngHCw3F2USto88efzmvBfB8ZIxk90S9TZZDeJ +rqIKpNXsfM4nCR07DW48n3IoiHLCtRFinfpboS5/47ZN4XBE7Q/QNJrWa2P07aWR +HdOBdD/N3DnM5bbSz7m+5TA+xC4/hEWLXt7Au+vnnhVyDdU80a3+3JY7y4vScHLS +Fn2grTk5F9pqieNhwawpXUCYNGFtJOY8LOQpUZdMpy6n6N5qzSjZ+vgE1Nwr0G/S ++n8ORUcJzF3s8h+u+SN8OC9W6chnftSIWKiqGsU4oM0FFkr2nOrfNNKqAQo2OU3W ++5lJovyqAcxtj+4qQsBrYzH7vRzdkigXhb/R7tRDnBRWBpWlMmnUhkaWG8EI0JLs +gjKNOUFCT1HgTSfonRxScDE5IAg3JxieNz9ppDCphvc0ii3wwdS7uwstKGZCC+Wr +lQsiV0Wa4WlWVMfLCONI13+zeOli4BmwepgkjJc+YogvLodblcHN14kTTCDvhDMp +KZglwFT+3zkxH9ArnO7/a8WQCWZV6bJ29HFUfAioE8Sie04WD4zgYEfU02r9A+vq +wl/w5UK6eXOO6Hz2OrfYh2dZXiCOLp+DhftKq6MKIIAcqFuAzrPvzxd7Uup/fxyX +n2Ub4YKmUV89RzJ2ipZRjiBcQo+9h4O9dIh4+2I0m7NPALqirutCFBe2cHu/Fwho +SzacLI21OzWLKbyZ2D6ueH4NrhnOZ10bo/lKbc5d3JzuGv6KW1NmGo32qiKeKNzG +Q52+sm0/TLiLxy2RlqJ1p7R9NLLZTY8bZTD125n2RtNaJWT564vseY8SaiUW62r8 +ksU5aPINa7Qkf4xgsmkSJymFs/9AjXfSCS/aZ13PQMUuI5A4fY9IZjSoiUlSx1bX ++2B91azW4yfHcAJrluIjZ13LvBU08ooPKPm2TazDi89/8GjBMpHUQN4= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/t/data/fake-shm-module/ngx_http_lua_fake_shm_module.c b/t/data/fake-shm-module/ngx_http_lua_fake_shm_module.c index 0286853e54..ffc32f5795 100644 --- a/t/data/fake-shm-module/ngx_http_lua_fake_shm_module.c +++ b/t/data/fake-shm-module/ngx_http_lua_fake_shm_module.c @@ -213,10 +213,9 @@ ngx_http_lua_fake_shm_preload(lua_State *L) ngx_uint_t i; ngx_shm_zone_t **zone; + ngx_shm_zone_t **zone_udata; - lua_getglobal(L, "__ngx_cycle"); - cycle = lua_touserdata(L, -1); - lua_pop(L, 1); + cycle = (ngx_cycle_t *) ngx_cycle; hmcf_ctx = (ngx_http_conf_ctx_t *) cycle->conf_ctx[ngx_http_module.index]; lfsmcf = hmcf_ctx->main_conf[ngx_http_lua_fake_shm_module.ctx_index]; @@ -242,7 +241,9 @@ ngx_http_lua_fake_shm_preload(lua_State *L) lua_createtable(L, 1 /* narr */, 0 /* nrec */); /* table of zone[i] */ - lua_pushlightuserdata(L, zone[i]); /* shared mt key ud */ + zone_udata = lua_newuserdata(L, sizeof(ngx_shm_zone_t *)); + /* shared mt key ud */ + *zone_udata = zone[i]; lua_rawseti(L, -2, 1); /* {zone[i]} */ lua_pushvalue(L, -3); /* shared mt key ud mt */ lua_setmetatable(L, -2); /* shared mt key ud */ @@ -262,9 +263,10 @@ ngx_http_lua_fake_shm_preload(lua_State *L) static int ngx_http_lua_fake_shm_get_info(lua_State *L) { - ngx_int_t n; - ngx_shm_zone_t *zone; - ngx_http_lua_fake_shm_ctx_t *ctx; + ngx_int_t n; + ngx_shm_zone_t *zone; + ngx_shm_zone_t **zone_udata; + ngx_http_lua_fake_shm_ctx_t *ctx; n = lua_gettop(L); @@ -276,13 +278,15 @@ ngx_http_lua_fake_shm_get_info(lua_State *L) luaL_checktype(L, 1, LUA_TTABLE); lua_rawgeti(L, 1, 1); - zone = lua_touserdata(L, -1); + zone_udata = lua_touserdata(L, -1); lua_pop(L, 1); - if (zone == NULL) { + if (zone_udata == NULL) { return luaL_error(L, "bad \"zone\" argument"); } + zone = *zone_udata; + ctx = (ngx_http_lua_fake_shm_ctx_t *) zone->data; lua_pushlstring(L, (char *) zone->shm.name.data, zone->shm.name.len); diff --git a/t/lib/CRC32.lua b/t/lib/CRC32.lua index fcf260cd4c..8ca0d5634e 100755 --- a/t/lib/CRC32.lua +++ b/t/lib/CRC32.lua @@ -156,7 +156,7 @@ end -- -- CRC32.lua -- --- A pure Lua implementation of a CRC32 hashing algorithm. Slower than using a C implemtation, +-- A pure Lua implementation of a CRC32 hashing algorithm. Slower than using a C implementation, -- but useful having no other dependencies. -- -- diff --git a/t/lib/Memcached.lua b/t/lib/Memcached.lua index e3288ac4fb..1f8c7f9578 100755 --- a/t/lib/Memcached.lua +++ b/t/lib/Memcached.lua @@ -541,7 +541,7 @@ end -- -- memcache:set_hash(hashfunc) -- Sets a custom hash function for key values. The default is a CRC32 hashing function. --- 'hashfunc' should be defined receiving a single string parameter and returing a single integer value. +-- 'hashfunc' should be defined receiving a single string parameter and returning a single integer value. -- -- memcache:set_encode(func) -- Sets a custom encode function for serialising table values. 'func' should be defined receiving a single diff --git a/util/build-with-dd.sh b/util/build-with-dd.sh new file mode 100755 index 0000000000..4f4e785f53 --- /dev/null +++ b/util/build-with-dd.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +# this script is for developers only. +# dependent on the ngx-build script from the nginx-devel-utils repository: +# https://github.com/openresty/nginx-devel-utils/blob/master/ngx-build +# the resulting nginx is located at ./work/nginx/sbin/nginx + +root=`pwd` +version=${1:-1.4.1} +home=~ +force=$2 + +add_fake_shm_module="--add-module=$root/t/data/fake-shm-module" + +time ngx-build $force $version \ + --with-threads \ + --with-pcre-jit \ + --with-ipv6 \ + --with-cc-opt="-DNGX_LUA_USE_ASSERT -I$PCRE2_INC -I$OPENSSL_INC -DDDEBUG=1" \ + --with-http_v2_module \ + --with-http_v3_module \ + --with-http_realip_module \ + --with-http_ssl_module \ + --add-module=$root/../ndk-nginx-module \ + --add-module=$root/../set-misc-nginx-module \ + --with-ld-opt="-L$PCRE2_LIB -L$OPENSSL_LIB -Wl,-rpath,$PCRE2_LIB:$LIBDRIZZLE_LIB:$OPENSSL_LIB" \ + --without-mail_pop3_module \ + --without-mail_imap_module \ + --with-http_image_filter_module \ + --without-mail_smtp_module \ + --with-stream \ + --with-stream_ssl_module \ + --without-http_upstream_ip_hash_module \ + --without-http_memcached_module \ + --without-http_auth_basic_module \ + --without-http_userid_module \ + --with-http_auth_request_module \ + --add-module=$root/../echo-nginx-module \ + --add-module=$root/../memc-nginx-module \ + --add-module=$root/../srcache-nginx-module \ + --add-module=$root \ + --add-module=$root/../lua-upstream-nginx-module \ + --add-module=$root/../headers-more-nginx-module \ + --add-module=$root/../drizzle-nginx-module \ + --add-module=$root/../rds-json-nginx-module \ + --add-module=$root/../coolkit-nginx-module \ + --add-module=$root/../redis2-nginx-module \ + --add-module=$root/../stream-lua-nginx-module \ + --add-module=$root/t/data/fake-module \ + $add_fake_shm_module \ + --add-module=$root/t/data/fake-delayed-load-module \ + --with-http_gunzip_module \ + --with-http_dav_module \ + --with-select_module \ + --with-poll_module \ + $opts \ + --with-debug + diff --git a/util/build-without-ssl.sh b/util/build-without-ssl.sh new file mode 100755 index 0000000000..022d9617e2 --- /dev/null +++ b/util/build-without-ssl.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# this script is for developers only. +# dependent on the ngx-build script from the nginx-devel-utils repository: +# https://github.com/openresty/nginx-devel-utils/blob/master/ngx-build +# the resulting nginx is located at ./work/nginx/sbin/nginx + +root=`pwd` +version=${1:-1.4.1} +home=~ +force=$2 + +add_fake_shm_module="--add-module=$root/t/data/fake-shm-module" + +rm -fr buildroot +time ngx-build $force $version \ + --with-threads \ + --with-pcre-jit \ + --with-ipv6 \ + --with-cc-opt="-DNGX_LUA_USE_ASSERT -I$PCRE2_INC" \ + --with-http_v2_module \ + --with-http_realip_module \ + --add-module=$root/../ndk-nginx-module \ + --add-module=$root/../set-misc-nginx-module \ + --with-ld-opt="-L$PCRE2_LIB -Wl,-rpath,$PCRE2_LIB:$LIBDRIZZLE_LIB" \ + --without-mail_pop3_module \ + --without-mail_imap_module \ + --with-http_image_filter_module \ + --without-mail_smtp_module \ + --with-stream \ + --without-http_upstream_ip_hash_module \ + --without-http_memcached_module \ + --without-http_auth_basic_module \ + --without-http_userid_module \ + --with-http_auth_request_module \ + --add-module=$root/../echo-nginx-module \ + --add-module=$root/../memc-nginx-module \ + --add-module=$root/../srcache-nginx-module \ + --add-module=$root \ + --add-module=$root/../lua-upstream-nginx-module \ + --add-module=$root/../headers-more-nginx-module \ + --add-module=$root/../drizzle-nginx-module \ + --add-module=$root/../rds-json-nginx-module \ + --add-module=$root/../coolkit-nginx-module \ + --add-module=$root/../redis2-nginx-module \ + --add-module=$root/../stream-lua-nginx-module \ + --add-module=$root/t/data/fake-module \ + $add_fake_shm_module \ + --add-module=$root/t/data/fake-delayed-load-module \ + --with-http_gunzip_module \ + --with-http_dav_module \ + --with-select_module \ + --with-poll_module \ + $opts \ + --with-debug + diff --git a/util/build.sh b/util/build.sh index 164bf9f9a2..501f74bdb9 100755 --- a/util/build.sh +++ b/util/build.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # this script is for developers only. -# dependent on the ngx-build script from the nginx-devel-utils repostory: +# dependent on the ngx-build script from the nginx-devel-utils repository: # https://github.com/openresty/nginx-devel-utils/blob/master/ngx-build # the resulting nginx is located at ./work/nginx/sbin/nginx @@ -22,45 +22,51 @@ force=$2 #--without-http_referer_module \ #--with-http_spdy_module \ +add_fake_shm_module="--add-module=$root/t/data/fake-shm-module" + +answer=`$root/util/ver-ge "$version" 1.25.1` + time ngx-build $force $version \ - --with-pcre-jit \ - --with-ipv6 \ - --with-cc-opt="-I$PCRE_INC -I$OPENSSL_INC" \ - --with-http_v2_module \ - --with-http_realip_module \ - --with-http_ssl_module \ - --add-module=$root/../ndk-nginx-module \ - --add-module=$root/../set-misc-nginx-module \ - --with-ld-opt="-L$PCRE_LIB -L$OPENSSL_LIB -Wl,-rpath,$PCRE_LIB:$LIBDRIZZLE_LIB:$OPENSSL_LIB" \ - --without-mail_pop3_module \ - --without-mail_imap_module \ - --with-http_image_filter_module \ - --without-mail_smtp_module \ - --with-stream \ - --with-stream_ssl_module \ - --without-http_upstream_ip_hash_module \ - --without-http_memcached_module \ - --without-http_auth_basic_module \ - --without-http_userid_module \ - --with-http_auth_request_module \ - --add-module=$root/../echo-nginx-module \ - --add-module=$root/../memc-nginx-module \ - --add-module=$root/../srcache-nginx-module \ - --add-module=$root \ - --add-module=$root/../lua-upstream-nginx-module \ - --add-module=$root/../headers-more-nginx-module \ - --add-module=$root/../drizzle-nginx-module \ - --add-module=$root/../rds-json-nginx-module \ - --add-module=$root/../coolkit-nginx-module \ - --add-module=$root/../redis2-nginx-module \ - --add-module=$root/../stream-lua-nginx-module \ - --add-module=$root/t/data/fake-module \ - --add-module=$root/t/data/fake-shm-module \ - --add-module=$root/t/data/fake-delayed-load-module \ - --with-http_gunzip_module \ - --with-http_dav_module \ + --with-threads \ + --with-pcre-jit \ + --with-ipv6 \ + --with-cc-opt="-DNGX_LUA_USE_ASSERT -I$PCRE2_INC -I$OPENSSL_INC" \ + --with-http_v2_module \ + --with-http_v3_module \ + --with-http_realip_module \ + --with-http_ssl_module \ + --add-module=$root/../ndk-nginx-module \ + --add-module=$root/../set-misc-nginx-module \ + --with-ld-opt="-L$PCRE2_LIB -L$OPENSSL_LIB -Wl,-rpath,$PCRE2_LIB:$LIBDRIZZLE_LIB:$OPENSSL_LIB" \ + --without-mail_pop3_module \ + --without-mail_imap_module \ + --with-http_image_filter_module \ + --without-mail_smtp_module \ + --with-stream \ + --with-stream_ssl_module \ + --without-http_upstream_ip_hash_module \ + --without-http_memcached_module \ + --without-http_auth_basic_module \ + --without-http_userid_module \ + --with-http_auth_request_module \ + --add-module=$root/../echo-nginx-module \ + --add-module=$root/../memc-nginx-module \ + --add-module=$root/../srcache-nginx-module \ + --add-module=$root \ + --add-module=$root/../lua-upstream-nginx-module \ + --add-module=$root/../headers-more-nginx-module \ + --add-module=$root/../drizzle-nginx-module \ + --add-module=$root/../rds-json-nginx-module \ + --add-module=$root/../coolkit-nginx-module \ + --add-module=$root/../redis2-nginx-module \ + --add-module=$root/../stream-lua-nginx-module \ + --add-module=$root/t/data/fake-module \ + $add_fake_shm_module \ + --add-module=$root/t/data/fake-delayed-load-module \ + --with-http_gunzip_module \ + --with-http_dav_module \ --with-select_module \ --with-poll_module \ - $opts \ - --with-debug + $opts \ + --with-debug diff --git a/util/nc_server.py b/util/nc_server.py new file mode 100644 index 0000000000..48c6f4bf15 --- /dev/null +++ b/util/nc_server.py @@ -0,0 +1,25 @@ +import select, socket +server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +server.setblocking(0) +server.bind(('localhost', 65110)) +server.listen(5) +inputs = [server] + +while inputs: + outputs = [] + readable, writable, exceptional = select.select( + inputs, outputs, inputs) + for s in readable: + if s is server: + connection, client_address = s.accept() + connection.setblocking(0) + inputs.append(connection) + else: + data = s.recv(1024) + if not data: + inputs.remove(s) + s.close() + + for s in exceptional: + inputs.remove(s) + s.close() diff --git a/util/run-ci.sh b/util/run-ci.sh new file mode 100755 index 0000000000..bb23926165 --- /dev/null +++ b/util/run-ci.sh @@ -0,0 +1,193 @@ +#!/bin/bash + +#export CC=clang +export CC=gcc +export NGX_BUILD_CC=$CC + +mkdir -p download-cache + +export JOBS=$(nproc) +export NGX_BUILD_JOBS=$JOBS +export VALGRIND_INC=/usr/include/valgrind/ + +export LUAJIT_PREFIX=/opt/luajit21 +export LUAJIT_LIB=$LUAJIT_PREFIX/lib +export LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 +export LUA_INCLUDE_DIR=$LUAJIT_INC + +export PCRE2_VER=10.45 +export PCRE2_PREFIX=/opt/pcre2 +export PCRE2_LIB=$PCRE2_PREFIX/lib +export PCRE2_INC=$PCRE2_PREFIX/include + +export OPENSSL_VER=3.5.0 +export OPENSSL_PATCH_VER=3.5.0 +export OPENSSL_PREFIX=/opt/ssl3 +export OPENSSL_LIB=$OPENSSL_PREFIX/lib +export OPENSSL_INC=$OPENSSL_PREFIX/include + +export LIBDRIZZLE_PREFIX=/opt/drizzle +export LIBDRIZZLE_INC=$LIBDRIZZLE_PREFIX/include/libdrizzle-1.0 +export LIBDRIZZLE_LIB=$LIBDRIZZLE_PREFIX/lib +export DRIZZLE_VER=2011.07.21 + +#export TEST_NGINX_SLEEP=0.006 +export NGINX_VERSION=1.27.1 +#export NGX_BUILD_ASAN=1 + +export PATH=/opt/bin:$PWD/work/nginx/sbin:$PWD/openresty-devel-utils:$PATH + +if [ ! -f /opt/bin/curl ]; then + wget https://github.com/stunnel/static-curl/releases/download/8.14.1/curl-linux-x86_64-glibc-8.14.1.tar.xz + tar -xf curl-linux-x86_64-glibc-8.14.1.tar.xz + tar -xf curl-linux-x86_64-glibc-8.14.1.tar.xz + sudo mkdir -p /opt/bin + sudo mv curl /opt/bin/ +fi + +function git_download() +{ + dir=${!#} + if [ ! -d $dir ]; then + git clone $@ + fi +} + +function download_deps() +{ + if [ ! -f download-cache/drizzle7-$DRIZZLE_VER.tar.gz ]; then + wget -P download-cache https://github.com/openresty/openresty-deps-prebuild/releases/download/v20230902/drizzle7-$DRIZZLE_VER.tar.gz + fi + + if [ ! -f download-cache/pcre2-$PCRE2_VER.tar.gz ]; then + wget -P download-cache https://github.com/PCRE2Project/pcre2/releases/download/pcre2-${PCRE2_VER}/pcre2-${PCRE2_VER}.tar.gz + fi + + if [ ! -f download-cache/openssl-$OPENSSL_VER.tar.gz ]; then + wget -P download-cache https://github.com/openssl/openssl/releases/download/openssl-$OPENSSL_VER/openssl-$OPENSSL_VER.tar.gz + fi + + git_download https://github.com/openresty/test-nginx.git + git_download https://github.com/openresty/openresty.git ../openresty + git_download https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx + git_download https://github.com/openresty/openresty-devel-utils.git + git_download https://github.com/openresty/mockeagain.git + git_download https://github.com/openresty/lua-cjson.git lua-cjson + git_download https://github.com/openresty/lua-upstream-nginx-module.git ../lua-upstream-nginx-module + git_download https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module + git_download https://github.com/openresty/nginx-eval-module.git ../nginx-eval-module + git_download https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module + git_download https://github.com/FRiCKLE/ngx_coolkit.git ../coolkit-nginx-module + git_download https://github.com/openresty/headers-more-nginx-module.git ../headers-more-nginx-module + git_download https://github.com/openresty/drizzle-nginx-module.git ../drizzle-nginx-module + git_download https://github.com/openresty/set-misc-nginx-module.git ../set-misc-nginx-module + git_download https://github.com/openresty/memc-nginx-module.git ../memc-nginx-module + git_download https://github.com/openresty/rds-json-nginx-module.git ../rds-json-nginx-module + git_download https://github.com/openresty/srcache-nginx-module.git ../srcache-nginx-module + git_download https://github.com/openresty/redis2-nginx-module.git ../redis2-nginx-module + git_download https://github.com/openresty/lua-resty-core.git ../lua-resty-core + git_download https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache + git_download https://github.com/openresty/lua-resty-mysql.git ../lua-resty-mysql + git_download https://github.com/openresty/lua-resty-string.git ../lua-resty-string + git_download https://github.com/openresty/stream-lua-nginx-module.git ../stream-lua-nginx-module + git_download -b v2.1-agentzh https://github.com/openresty/luajit2.git luajit2 +} + +function make_deps() +{ + if [ "$TEST_NGINX_BUILD_DEPS" = "n" ]; then + return + fi + + cd luajit2/ + make clean + make -j"$JOBS" CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS="-DLUAJIT_USE_VALGRIND -I$VALGRIND_INC -DLUAJIT_USE_SYSMALLOC -DLUA_USE_APICHECK -DLUA_USE_ASSERT -msse4.2" > build.log 2>&1 || (cat build.log && exit 1) + sudo make install PREFIX=$LUAJIT_PREFIX + cd .. + + tar zxf download-cache/openssl-$OPENSSL_VER.tar.gz + cd openssl-$OPENSSL_VER/ + patch -p1 < ../../openresty/patches/openssl-$OPENSSL_PATCH_VER-sess_set_get_cb_yield.patch > ssl.log + ./config -DOPENSSL_TLS_SECURITY_LEVEL=1 shared enable-ssl3 enable-ssl3-method -g -O2 --prefix=$OPENSSL_PREFIX --libdir=lib -DPURIFY >> ssl.log + make -j$JOBS >> ssl.log + sudo make PATH=$PATH install_sw >> ssl.log + cd .. + + tar zxf download-cache/pcre2-$PCRE2_VER.tar.gz; + cd pcre2-$PCRE2_VER/; + ./configure --prefix=$PCRE2_PREFIX --enable-jit --enable-utf + sudo PATH=$PATH make install + cd .. + + tar xzf download-cache/drizzle7-$DRIZZLE_VER.tar.gz && cd drizzle7-$DRIZZLE_VER + ./configure --prefix=$LIBDRIZZLE_PREFIX --without-server + make libdrizzle-1.0 -j$JOBS + sudo make install-libdrizzle-1.0 + cd .. + + cd mockeagain/ && make CC=$CC -j$JOBS + cd .. + + cd lua-cjson/ && make -j$JOBS && sudo make install + cd .. +} + +function make_ngx() +{ + if [ "$TEST_NGINX_FRESH_BUILD" = "y" ]; then + rm -fr buildroot + fi + + sh util/build.sh $NGINX_VERSION 2>&1 | tee build.log +} + +download_deps +make_deps +make_ngx + +find t -name "*.t" | xargs reindex >/dev/null 2>&1 + +#nginx -V + +export LD_PRELOAD=$PWD/mockeagain/mockeagain.so +export LD_LIBRARY_PATH=$PWD/mockeagain:$LD_LIBRARY_PATH + +#export ASAN_OPTIONS=detect_leaks=0,log_path=/tmp/asan/asan,log_exe_name=true +#export LD_PRELOAD=/lib64/libasan.so.5:$PWD/mockeagain/mockeagain.so + +export LD_LIBRARY_PATH=$LUAJIT_LIB:$OPENSSL_LIB:$LD_LIBRARY_PATH +export TEST_NGINX_RESOLVER=8.8.4.4 + +#export TEST_NGINX_NO_CLEAN=1 +#export TEST_NGINX_CHECK_LEAK=1 +#export TEST_NGINX_CHECK_LEAK_COUNT=100 +#export TEST_NGINX_TIMEOUT=5 + +#export TEST_NGINX_USE_VALGRIND=1 +#export TEST_NGINX_VALGRIND_EXIT_ON_FIRST_ERR=1 +#export TEST_NGINX_USE_HTTP2=1 + +export MALLOC_PERTURB_=33 +export TEST_NGINX_HTTP3_CRT=$PWD/t/cert/http3/http3.crt +export TEST_NGINX_HTTP3_KEY=$PWD/t/cert/http3/http3.key +#export TEST_NGINX_USE_HTTP3=1 + +#export TEST_NGINX_VERBOSE=1 + +#export TEST_NGINX_EVENT_TYPE=poll +#export TEST_NGINX_POSTPONE_OUTPUT=1 +#export MOCKEAGAIN=r +#export MOCKEAGAIN=w +#export MOCKEAGAIN=rw +#export MOCKEAGAIN_VERBOSE=1 + +ldd `which nginx`|grep -E 'luajit|ssl|pcre' +which nginx +nginx -V + +#export TEST_NGINX_INIT_BY_LUA="debug.sethook(function () collectgarbage() end, 'l') jit.off() package.path = '/usr/share/lua/5.1/?.lua;$PWD/../lua-resty-core/lib/?.lua;$PWD/../lua-resty-lrucache/lib/?.lua;' .. (package.path or '') require 'resty.core' require('resty.core.base').set_string_buf_size(1) require('resty.core.regex').set_buf_grow_ratio(1)" + + +# comment out TEST_NGINX_RANDOMIZE when debugging one test case +export TEST_NGINX_RANDOMIZE=1 +prove -j$JOBS -I. -Itest-nginx/inc -Itest-nginx/lib -r t/ diff --git a/util/ver-ge b/util/ver-ge new file mode 100755 index 0000000000..8d7a401e94 --- /dev/null +++ b/util/ver-ge @@ -0,0 +1,41 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +sub usage { + die "Usage: $0 \n"; +} + +my $a = shift or usage(); +my $b = shift or usage(); + +my @as = split /\./, $a; +my @bs = split /\./, $b; + +my $n = @as > @bs ? scalar(@as) : scalar(@bs); + +for (my $i = 0; $i < $n; $i++) { + my $x = $as[$i]; + my $y = $bs[$i]; + + if (!defined $x) { + $x = 0; + } + + if (!defined $y) { + $y = 0; + } + + if ($x > $y) { + print "Y\n"; + exit; + + } elsif ($x < $y) { + print "N\n"; + exit; + } +} + +print "Y\n"; + diff --git a/valgrind.suppress b/valgrind.suppress index 727398637e..743fb67d77 100644 --- a/valgrind.suppress +++ b/valgrind.suppress @@ -22,16 +22,6 @@ fun:ngx_epoll_process_events fun:ngx_process_events_and_timers } -{ - - Memcheck:Cond - fun:ngx_vslprintf - fun:ngx_snprintf - fun:ngx_sock_ntop - fun:ngx_event_accept - fun:ngx_epoll_process_events - fun:ngx_process_events_and_timers -} { Memcheck:Addr1 @@ -181,12 +171,136 @@ fun:ngx_create_pool fun:main } +{ + + Memcheck:Param + epoll_pwait(sigmask) + fun:epoll_pwait + fun:epoll_wait + fun:ngx_epoll_process_events + fun:ngx_process_events_and_timers +} +{ + + Memcheck:Param + epoll_pwait(sigmask) + fun:epoll_pwait + fun:epoll_wait + fun:ngx_epoll_test_rdhup + fun:ngx_epoll_init + fun:ngx_event_process_init +} +{ + + Memcheck:Param + epoll_pwait(sigmask) + fun:epoll_pwait + fun:ngx_epoll_process_events + fun:ngx_process_events_and_timers +} +{ + + Memcheck:Param + epoll_pwait(sigmask) + fun:epoll_pwait + fun:ngx_epoll_test_rdhup + fun:ngx_epoll_init + fun:ngx_event_process_init +} +{ + + Memcheck:Param + sendmsg(msg.msg_iov[0]) + fun:__sendmsg_nocancel + fun:ngx_write_channel + fun:ngx_pass_open_channel + fun:ngx_start_worker_processes +} +{ + + Memcheck:Param + sendmsg(msg.msg_iov[0]) + fun:__sendmsg_nocancel + fun:ngx_write_channel + fun:ngx_pass_open_channel + fun:ngx_start_cache_manager_processes +} +{ + + Memcheck:Param + sendmsg(msg.msg_iov[0]) + fun:__sendmsg_nocancel + fun:ngx_write_channel + fun:ngx_pass_open_channel + fun:ngx_start_privileged_agent_processes +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:ngx_alloc + fun:ngx_regex_malloc + fun:pcre2_compile_context_create_8 + fun:ngx_regex_compile + fun:ngx_http_regex_compile + fun:ngx_http_core_regex_location + fun:ngx_http_core_location + fun:ngx_conf_handler + fun:ngx_conf_parse + fun:ngx_http_core_server + fun:ngx_conf_handler + fun:ngx_conf_parse + fun:ngx_http_block + fun:ngx_conf_handler + fun:ngx_conf_parse + fun:ngx_init_cycle + fun:main +} { Memcheck:Leak match-leak-kinds: definite fun:malloc fun:ngx_alloc - fun:ngx_set_environment - fun:ngx_single_process_cycle + fun:ngx_regex_malloc + fun:pcre2_compile_context_create_8 + fun:ngx_regex_compile + fun:ngx_http_regex_compile + fun:ngx_http_rewrite + fun:ngx_conf_handler + fun:ngx_conf_parse + fun:ngx_http_core_location + fun:ngx_conf_handler + fun:ngx_conf_parse + fun:ngx_http_core_server + fun:ngx_conf_handler + fun:ngx_conf_parse + fun:ngx_http_block + fun:ngx_conf_handler + fun:ngx_conf_parse + fun:ngx_init_cycle + fun:main +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:ngx_alloc + fun:ngx_regex_malloc + fun:pcre2_compile_context_create_8 + fun:ngx_regex_compile + fun:ngx_http_regex_compile + fun:ngx_http_rewrite + fun:ngx_conf_handler + fun:ngx_conf_parse + fun:ngx_http_core_server + fun:ngx_conf_handler + fun:ngx_conf_parse + fun:ngx_http_block + fun:ngx_conf_handler + fun:ngx_conf_parse + fun:ngx_init_cycle + fun:main }