commit fd02afef8ec1ceb8a816dc202d05c6ece9887d31 Author: Théophile Diot Date: Fri Jun 30 15:38:28 2023 -0400 Squashed 'src/deps/src/lua-resty-core/' content from commit 31fae862a git-subtree-dir: src/deps/src/lua-resty-core git-subtree-split: 31fae862a1ed64033591f991fadb0dd80358ba0b diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..6fe6f35ce --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.t linguist-language=Text diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..22fd00e70 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,27 @@ +This place is for bug reports and development discussions only. For general questions and +discussions, please join the openresty-en mailing list instead: https://groups.google.com/group/openresty-en. +If you want to use Chinese, please join the openresty (Chinese) mailing list instead: https://groups.google.com/group/openresty. +Do not use Chinese in this place. + +Before you open a new issue, please search the internet and make sure it is not duplicate. + +Ensure you have provided the following details while reporting a problem: + +- [ ] The exact version of the related software, including but not limited to the OpenResty version +(if any), the NGINX core version, the `ngx_lua` module version(via `openresty -V` or `nginx -V`), +and the `lua-resty-core` version(via `resty -e 'print(require("resty.core").version)'`), +and your operating system version(via `uname -a`). +- [ ] **A minimal and standalone test case** that others can easily run on their side and +reproduce the issue you are seeing. +- [ ] Do not simply say "something is broken" or "something does not work". Always provide +as much details as possible. Always describe **the symptoms and your expected results**. + +You can (temporarily) enable the nginx debugging logs to see the internal workings +of NGINX in your nginx''s `error.log` file. See http://nginx.org/en/docs/debugging_log.html +The same instructions apply equally well to OpenResty. + +If you are seeing crashes, please provide the full backtrace for the crash. See +https://www.nginx.com/resources/wiki/start/topics/tutorials/debugging/#core-dump +for more details. + +Thanks for your cooperation. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..9d3889429 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,2 @@ +I hereby granted the copyright of the changes in this pull request +to the authors of this lua-resty-core project. diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml new file mode 100644 index 000000000..12b87cb3c --- /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 new file mode 100644 index 000000000..ae4497fd5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.swp +*.swo +*~ +go +t/servroot* +reindex +nginx +ctags +tags +a.lua +mockeagain.so diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 000000000..61dd5c772 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,6 @@ +std = 'ngx_lua' +globals = { 'ngx', 'ndk' } +unused_args = false +read_globals = { + "coroutine._yield" +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..2223d31ba --- /dev/null +++ b/.travis.yml @@ -0,0 +1,105 @@ +--- +sudo: required +dist: focal + +branches: + only: + - "master" + +os: linux + +language: c + +compiler: + - gcc + +addons: + apt: + packages: + - axel + - luarocks + - daemonize + +cache: + directories: + - download-cache + +env: + global: + - JOBS=2 + - NGX_BUILD_JOBS=$JOBS + - LUAJIT_PREFIX=/opt/luajit21 + - LUAJIT_LIB=$LUAJIT_PREFIX/lib + - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 + - LUA_INCLUDE_DIR=$LUAJIT_INC + - LUA_CMODULE_DIR=/lib + - PCRE_VER=8.45 + - PCRE_PREFIX=/opt/pcre + - PCRE_LIB=$PCRE_PREFIX/lib + - PCRE_INC=$PCRE_PREFIX/include + - OPENSSL_PREFIX=/opt/ssl + - OPENSSL_LIB=$OPENSSL_PREFIX/lib + - OPENSSL_INC=$OPENSSL_PREFIX/include + - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH + - TEST_NGINX_SLEEP=0.005 + - TEST_NGINX_RANDOMIZE=1 + - LUACHECK_VER=0.21.1 + matrix: + - NGINX_VERSION=1.21.4 OPENSSL_VER=1.1.1u OPENSSL_PATCH_VER=1.1.1f + +services: + - memcache + +before_install: + - sudo luarocks install luacheck $LUACHECK_VER + - luacheck --globals coroutine -q . + - '! grep -n -P ''(?<=.{80}).+'' --color `find . -name ''*.lua''` || (echo "ERROR: Found Lua source lines exceeding 80 columns." > /dev/stderr; exit 1)' + - '! grep -n -P ''\t+'' --color `find . -name ''*.lua''` || (echo "ERROR: Cannot use tabs." > /dev/stderr; exit 1)' + - cpanm --sudo --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1) + +install: + - if [ ! -d download-cache ]; then mkdir download-cache; 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 || wget -P download-cache https://www.openssl.org/source/old/${OPENSSL_VER//[a-z]/}/openssl-$OPENSSL_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 + - git clone https://github.com/openresty/openresty.git ../openresty + - git clone https://github.com/openresty/openresty-devel-utils.git + - git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module + - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module + - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx + - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module + - git clone https://github.com/openresty/lua-resty-lrucache.git + - git clone https://github.com/openresty/headers-more-nginx-module.git ../headers-more-nginx-module + - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git luajit2 + - git clone https://github.com/openresty/set-misc-nginx-module.git ../set-misc-nginx-module + - git clone https://github.com/openresty/mockeagain.git + - git clone https://github.com/openresty/test-nginx.git + - git clone https://github.com/openresty/stream-lua-nginx-module.git ../stream-lua-nginx-module + +script: + - cd luajit2/ + - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT -msse4.2 -O1 -DLUAJIT_SECURITY_STRID=0 -DLUAJIT_SECURITY_STRHASH=0 -DLUAJIT_SECURITY_PRNG=0 -DLUAJIT_SECURITY_MCODE=0 -DLUAJIT_TEST_FIXED_ORDER' > build.log 2>&1 || (cat build.log && exit 1) + - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) + - cd .. + - cd lua-resty-lrucache && sudo make DESTDIR=$LUAJIT_PREFIX LUA_LIB_DIR=/share/lua/5.1 install && cd .. + - tar zxf download-cache/openssl-$OPENSSL_VER.tar.gz + - cd openssl-$OPENSSL_VER/ + - if [ -n "$OPENSSL_PATCH_VER" ]; then patch -p1 < ../../openresty/patches/openssl-$OPENSSL_PATCH_VER-sess_set_get_cb_yield.patch; fi + - ./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 ../mockeagain/ && make CC=$CC -j$JOBS && 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 .. + - export PATH=$PWD/work/nginx/sbin:$PWD/openresty-devel-utils:$PATH + - export LD_PRELOAD=$PWD/mockeagain/mockeagain.so + - export LD_LIBRARY_PATH=$PWD/mockeagain:$LD_LIBRARY_PATH + - export TEST_NGINX_RESOLVER=8.8.4.4 + - export NGX_BUILD_CC=$CC + - ngx-build $NGINX_VERSION --with-ipv6 --with-http_realip_module --with-http_ssl_module --with-pcre-jit --with-cc-opt="-I$OPENSSL_INC -I$PCRE_INC" --with-ld-opt="-L$OPENSSL_LIB -Wl,-rpath,$OPENSSL_LIB -L$PCRE_LIB -Wl,-rpath,$PCRE_LIB" --add-module=../ndk-nginx-module --add-module=../echo-nginx-module --add-module=../set-misc-nginx-module --add-module=../headers-more-nginx-module --add-module=../lua-nginx-module --with-debug --with-stream_ssl_module --with-stream --with-ipv6 --add-module=../stream-lua-nginx-module > build.log 2>&1 || (cat build.log && exit 1) + - nginx -V + - ldd `which nginx`|grep -E 'luajit|ssl|pcre' + - prove -I. -Itest-nginx/lib -j$JOBS -r t diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..3caabe2ce --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +OPENRESTY_PREFIX=/usr/local/openresty + +#LUA_VERSION := 5.1 +PREFIX ?= /usr/local +LUA_INCLUDE_DIR ?= $(PREFIX)/include +LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) +INSTALL ?= install + +.PHONY: all test install + +all: ; + +install: all + $(INSTALL) -d $(DESTDIR)$(LUA_LIB_DIR)/resty/core/ + $(INSTALL) -d $(DESTDIR)$(LUA_LIB_DIR)/ngx/ + $(INSTALL) -d $(DESTDIR)$(LUA_LIB_DIR)/ngx/ssl + $(INSTALL) lib/resty/*.lua $(DESTDIR)$(LUA_LIB_DIR)/resty/ + $(INSTALL) lib/resty/core/*.lua $(DESTDIR)$(LUA_LIB_DIR)/resty/core/ + $(INSTALL) lib/ngx/*.lua $(DESTDIR)$(LUA_LIB_DIR)/ngx/ + $(INSTALL) lib/ngx/ssl/*.lua $(DESTDIR)$(LUA_LIB_DIR)/ngx/ssl/ + +test: all + PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t + diff --git a/README.markdown b/README.markdown new file mode 100644 index 000000000..ea5212d8f --- /dev/null +++ b/README.markdown @@ -0,0 +1,439 @@ +Name +==== + +lua-resty-core - New FFI-based Lua API for ngx_http_lua_module and/or ngx_stream_lua_module + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Prerequisites](#prerequisites) +* [API Implemented](#api-implemented) + * [resty.core.hash](#restycorehash) + * [resty.core.base64](#restycorebase64) + * [resty.core.uri](#restycoreuri) + * [resty.core.regex](#restycoreregex) + * [resty.core.exit](#restycoreexit) + * [resty.core.shdict](#restycoreshdict) + * [resty.core.var](#restycorevar) + * [resty.core.ctx](#restycorectx) + * [get_ctx_table](#get_ctx_table) + * [resty.core.request](#restycorerequest) + * [resty.core.response](#restycoreresponse) + * [resty.core.misc](#restycoremisc) + * [resty.core.time](#restycoretime) + * [resty.core.worker](#restycoreworker) + * [resty.core.phase](#restycorephase) + * [resty.core.ndk](#restycorendk) + * [resty.core.socket](#restycoresocket) + * [resty.core.param](#restycoreparam) + * [ngx.semaphore](#ngxsemaphore) + * [ngx.balancer](#ngxbalancer) + * [ngx.ssl](#ngxssl) + * [ngx.ssl.clienthello](#ngxsslclienthello) + * [ngx.ssl.session](#ngxsslsession) + * [ngx.re](#ngxre) + * [ngx.resp](#ngxresp) + * [ngx.pipe](#ngxpipe) + * [ngx.process](#ngxprocess) + * [ngx.errlog](#ngxerrlog) + * [ngx.base64](#ngxbase64) +* [Caveat](#caveat) +* [TODO](#todo) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This library is production ready. + +Synopsis +======== + +This library is automatically loaded by default in OpenResty 1.15.8.1. This +behavior can be disabled via the +[lua_load_resty_core](https://github.com/openresty/lua-nginx-module#lua_load_resty_core) +directive, but note that the use of this library is vividly recommended, as its +FFI implementation is both faster, safer, and more complete than the Lua C API +of the ngx_lua module. + +If you are using an older version of OpenResty, you must load this library like +so: + +```nginx + # nginx.conf + + http { + # you do NOT need to configure the following line when you + # are using the OpenResty bundle 1.4.3.9+. + lua_package_path "/path/to/lua-resty-core/lib/?.lua;;"; + + init_by_lua_block { + require "resty.core" + collectgarbage("collect") -- just to collect any garbage + } + + ... + } +``` + +Description +=========== + +This pure Lua library reimplements part of the [ngx_lua](https://github.com/openresty/lua-nginx-module#readme) module's +[Nginx API for Lua](https://github.com/openresty/lua-nginx-module#nginx-api-for-lua) +with LuaJIT FFI and installs the new FFI-based Lua API into the ngx.* and ndk.* namespaces +used by the ngx_lua module. + +In addition, this Lua library implements any significant new Lua APIs of +the [ngx_lua](https://github.com/openresty/lua-nginx-module#readme) module +as proper Lua modules, like [ngx.semaphore](#ngxsemaphore) and [ngx.balancer](#ngxbalancer). + +The FFI-based Lua API can work with LuaJIT's JIT compiler. ngx_lua's default API is based on the standard +Lua C API, which will never be JIT compiled and the user Lua code is always interpreted (slowly). + +Support for the new [ngx_stream_lua_module](https://github.com/openresty/stream-lua-nginx-module) has also begun. + +This library is shipped with the OpenResty bundle by default. So you do not really need to worry about the dependencies +and requirements. + +[Back to TOC](#table-of-contents) + +Prerequisites +============= + +**WARNING** This library is included with every OpenResty release. You should use the bundled version +of this library in the particular OpenResty release you are using. Otherwise you may run +into serious compatibility issues. + +* LuaJIT 2.1 (for now, it is the v2.1 git branch in the official luajit-2.0 git repository: http://luajit.org/download.html ) +* [ngx_http_lua_module](https://github.com/openresty/lua-nginx-module) v0.10.21. +* [ngx_stream_lua_module](https://github.com/openresty/stream-lua-nginx-module) v0.0.11. +* [lua-resty-lrucache](https://github.com/openresty/lua-resty-lrucache) + +[Back to TOC](#table-of-contents) + +API Implemented +=============== + +[Back to TOC](#table-of-contents) + +## resty.core.hash + +* [ngx.md5](https://github.com/openresty/lua-nginx-module#ngxmd5) +* [ngx.md5_bin](https://github.com/openresty/lua-nginx-module#ngxmd5_bin) +* [ngx.sha1_bin](https://github.com/openresty/lua-nginx-module#ngxsha1_bin) + +[Back to TOC](#table-of-contents) + +## resty.core.base64 + +* [ngx.encode_base64](https://github.com/openresty/lua-nginx-module#ngxencode_base64) +* [ngx.decode_base64](https://github.com/openresty/lua-nginx-module#ngxdecode_base64) + +[Back to TOC](#table-of-contents) + +## resty.core.uri + +* [ngx.escape_uri](https://github.com/openresty/lua-nginx-module#ngxescape_uri) +* [ngx.unescape_uri](https://github.com/openresty/lua-nginx-module#ngxunescape_uri) + +[Back to TOC](#table-of-contents) + +## resty.core.regex + +* [ngx.re.match](https://github.com/openresty/lua-nginx-module#ngxrematch) +* [ngx.re.gmatch](https://github.com/openresty/lua-nginx-module#ngxregmatch) +* [ngx.re.find](https://github.com/openresty/lua-nginx-module#ngxrefind) +* [ngx.re.sub](https://github.com/openresty/lua-nginx-module#ngxresub) +* [ngx.re.gsub](https://github.com/openresty/lua-nginx-module#ngxregsub) + +[Back to TOC](#table-of-contents) + +## resty.core.exit + +* [ngx.exit](https://github.com/openresty/lua-nginx-module#ngxexit) + +[Back to TOC](#table-of-contents) + +## resty.core.shdict + +* [ngx.shared.DICT.get](https://github.com/openresty/lua-nginx-module#ngxshareddictget) +* [ngx.shared.DICT.get_stale](https://github.com/openresty/lua-nginx-module#ngxshareddictget_stale) +* [ngx.shared.DICT.incr](https://github.com/openresty/lua-nginx-module#ngxshareddictincr) +* [ngx.shared.DICT.set](https://github.com/openresty/lua-nginx-module#ngxshareddictset) +* [ngx.shared.DICT.safe_set](https://github.com/openresty/lua-nginx-module#ngxshareddictsafe_set) +* [ngx.shared.DICT.add](https://github.com/openresty/lua-nginx-module#ngxshareddictadd) +* [ngx.shared.DICT.safe_add](https://github.com/openresty/lua-nginx-module#ngxshareddictsafe_add) +* [ngx.shared.DICT.replace](https://github.com/openresty/lua-nginx-module#ngxshareddictreplace) +* [ngx.shared.DICT.delete](https://github.com/openresty/lua-nginx-module#ngxshareddictdelete) +* [ngx.shared.DICT.ttl](https://github.com/openresty/lua-nginx-module#ngxshareddictttl) +* [ngx.shared.DICT.expire](https://github.com/openresty/lua-nginx-module#ngxshareddictexpire) +* [ngx.shared.DICT.flush_all](https://github.com/openresty/lua-nginx-module#ngxshareddictflush_all) +* [ngx.shared.DICT.free_space](https://github.com/openresty/lua-nginx-module#ngxshareddictfree_space) +* [ngx.shared.DICT.capacity](https://github.com/openresty/lua-nginx-module#ngxshareddictcapacity) + +[Back to TOC](#table-of-contents) + +## resty.core.var + +* [ngx.var.VARIABLE](https://github.com/openresty/lua-nginx-module#ngxvarvariable) + +[Back to TOC](#table-of-contents) + +## resty.core.ctx + +* [ngx.ctx](https://github.com/openresty/lua-nginx-module#ngxctx) + +[Back to TOC](#table-of-contents) + +## get_ctx_table + +**syntax:** *ctx = resty.core.ctx.get_ctx_table(ctx?)* + +Similar to [ngx.ctx](#restycorectx) but it accepts an optional `ctx` argument. +It will use the `ctx` from caller instead of creating a new table +when the `ctx` table does not exist. + +Notice: the `ctx` table will be used in the current request's whole life cycle. +Please be very careful when you try to reuse the `ctx` table. +You need to make sure there is no Lua code using or going to use the `ctx` table +in the current request before you reusing the `ctx` table in some other place. + +[Back to TOC](#table-of-contents) + +## resty.core.request + +* [ngx.req.get_headers](https://github.com/openresty/lua-nginx-module#ngxreqget_headers) +* [ngx.req.get_uri_args](https://github.com/openresty/lua-nginx-module#ngxreqget_uri_args) +* [ngx.req.start_time](https://github.com/openresty/lua-nginx-module#ngxreqstart_time) +* [ngx.req.get_method](https://github.com/openresty/lua-nginx-module#ngxreqget_method) +* [ngx.req.set_method](https://github.com/openresty/lua-nginx-module#ngxreqset_method) +* [ngx.req.set_header](https://github.com/openresty/lua-nginx-module#ngxreqset_header) +* [ngx.req.clear_header](https://github.com/openresty/lua-nginx-module#ngxreqclear_header) + +[Back to TOC](#table-of-contents) + +## resty.core.response + +* [ngx.header.HEADER](https://github.com/openresty/lua-nginx-module#ngxheaderheader) + +[Back to TOC](#table-of-contents) + +## resty.core.misc + +* [ngx.status](https://github.com/openresty/lua-nginx-module#ngxstatus) +* [ngx.is_subrequest](https://github.com/openresty/lua-nginx-module#ngxis_subrequest) +* [ngx.headers_sent](https://github.com/openresty/lua-nginx-module#ngxheaders_sent) +* [ngx.req.is_internal](https://github.com/openresty/lua-nginx-module#ngxreqis_internal) + +[Back to TOC](#table-of-contents) + +## resty.core.time + +* [ngx.time](https://github.com/openresty/lua-nginx-module#ngxtime) +* [ngx.now](https://github.com/openresty/lua-nginx-module#ngxnow) +* [ngx.update_time](https://github.com/openresty/lua-nginx-module#ngxupdate_time) +* [ngx.localtime](https://github.com/openresty/lua-nginx-module#ngxlocaltime) +* [ngx.utctime](https://github.com/openresty/lua-nginx-module#ngxutctime) +* [ngx.cookie_time](https://github.com/openresty/lua-nginx-module#ngxcookie_time) +* [ngx.http_time](https://github.com/openresty/lua-nginx-module#ngxhttp_time) +* [ngx.parse_http_time](https://github.com/openresty/lua-nginx-module#ngxparse_http_time) +* [monotonic_msec](./lib/resty/core/time.md#monotonic_msec) +* [monotonic_time](./lib/resty/core/time.md#monotonic_time) + +[Back to TOC](#table-of-contents) + +## resty.core.worker + +* [ngx.worker.exiting](https://github.com/openresty/lua-nginx-module#ngxworkerexiting) +* [ngx.worker.pid](https://github.com/openresty/lua-nginx-module#ngxworkerpid) +* [ngx.worker.id](https://github.com/openresty/lua-nginx-module#ngxworkerid) +* [ngx.worker.count](https://github.com/openresty/lua-nginx-module#ngxworkercount) + +[Back to TOC](#table-of-contents) + +## resty.core.phase + +* [ngx.get_phase](https://github.com/openresty/lua-nginx-module#ngxget_phase) + +[Back to TOC](#table-of-contents) + +## resty.core.ndk + +* [ndk.set_var](https://github.com/openresty/lua-nginx-module#ndkset_vardirective) + +[Back to TOC](#table-of-contents) + +## resty.core.socket + +* [socket.setoption](https://github.com/openresty/lua-nginx-module#tcpsocksetoption) +* [socket.setclientcert](https://github.com/openresty/lua-nginx-module#tcpsocksetclientcert) +* [socket.sslhandshake](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake) + +[Back to TOC](#table-of-contents) + +## resty.core.param + +* [ngx.arg](https://github.com/openresty/lua-nginx-module#ngxarg) (getter only) + +[Back to TOC](#table-of-contents) + +## ngx.semaphore + +This Lua module implements a semaphore API for efficient "light thread" synchronization, +which can work across different requests (but not across nginx worker processes). + +See the [documentation](./lib/ngx/semaphore.md) for this Lua module for more details. + +[Back to TOC](#table-of-contents) + +## ngx.balancer + +This Lua module implements for defining dynamic upstream balancers in Lua. + +See the [documentation](./lib/ngx/balancer.md) for this Lua module for more details. + +[Back to TOC](#table-of-contents) + +## ngx.ssl + +This Lua module provides a Lua API for controlling SSL certificates, private keys, +SSL protocol versions, and etc in NGINX downstream SSL handshakes. + +See the [documentation](./lib/ngx/ssl.md) for this Lua module for more details. + +[Back to TOC](#table-of-contents) + +## ngx.ssl.clienthello + +This Lua module provides a Lua API for post-processing SSL client hello message +for NGINX downstream SSL connections. + +See the [documentation](./lib/ngx/ssl/clienthello.md) for this Lua module for more details. + +[Back to TOC](#table-of-contents) + +## ngx.ssl.session + +This Lua module provides a Lua API for manipulating SSL session data and IDs +for NGINX downstream SSL connections. + +See the [documentation](./lib/ngx/ssl/session.md) for this Lua module for more details. + +[Back to TOC](#table-of-contents) + +## ngx.re + +This Lua module provides a Lua API which implements convenience utilities for +the `ngx.re` API. + +See the [documentation](./lib/ngx/re.md) for this Lua module for more details. + +[Back to TOC](#table-of-contents) + +## ngx.resp + +This Lua module provides Lua API which could be used to handle HTTP response. + +See the [documentation](./lib/ngx/resp.md) for this Lua module for more details. + +[Back to TOC](#table-of-contents) + +## ngx.pipe + +This module provides a Lua API to spawn processes and communicate with them in +a non-blocking fashion. + +See the [documentation](./lib/ngx/pipe.md) for this Lua module for more +details. + +This module was first introduced in lua-resty-core v0.1.16. + +[Back to TOC](#table-of-contents) + +## ngx.process + +This Lua module is used to manage the nginx process in Lua. + +See the [documentation](./lib/ngx/process.md) for this Lua module for more details. + +This module was first introduced in lua-resty-core v0.1.12. + +[Back to TOC](#table-of-contents) + +## ngx.errlog + +This Lua module provides Lua API to capture and manage nginx error log messages. + +See the [documentation](./lib/ngx/errlog.md) for this Lua module for more details. + +This module was first introduced in lua-resty-core v0.1.12. + +[Back to TOC](#table-of-contents) + +## ngx.base64 + +This Lua module provides Lua API to urlsafe base64 encode/decode. + +See the [documentation](./lib/ngx/base64.md) for this Lua module for more details. + +This module was first introduced in lua-resty-core v0.1.14. + +[Back to TOC](#table-of-contents) + +Caveat +====== + +If the user Lua code is not JIT compiled, then use of this library may +lead to performance drop in interpreted mode. You will only observe +speedup when you get a good part of your user Lua code JIT compiled. + +[Back to TOC](#table-of-contents) + +TODO +==== + +* Re-implement `ngx_lua`'s cosocket API with FFI. +* Re-implement `ngx_lua`'s `ngx.eof` and `ngx.flush` API functions with FFI. + +[Back to TOC](#table-of-contents) + +Author +====== + +Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2013-2019, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the ngx_lua module: https://github.com/openresty/lua-nginx-module#readme +* LuaJIT FFI: http://luajit.org/ext_ffi.html + +[Back to TOC](#table-of-contents) + diff --git a/dist.ini b/dist.ini new file mode 100644 index 000000000..1231145e8 --- /dev/null +++ b/dist.ini @@ -0,0 +1,10 @@ +name=lua-resty-core +abstract=New FFI-based Lua API for the ngx_lua module +author=Yichun "agentzh" Zhang (agentzh) +is_original=yes +license=2bsd +lib_dir=lib +doc_dir=lib +repo_link=https://github.com/openresty/lua-resty-core +main_module=lib/resty/core/base.lua +requires = luajit >= 2.1.0, nginx >= 1.13.6, ngx_http_lua = 0.10.13, openresty/lua-resty-lrucache >= 0.08 diff --git a/lib/ngx/balancer.lua b/lib/ngx/balancer.lua new file mode 100644 index 000000000..7d64d63e4 --- /dev/null +++ b/lib/ngx/balancer.lua @@ -0,0 +1,234 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local base = require "resty.core.base" +base.allows_subsystem('http', 'stream') + + +local ffi = require "ffi" +local C = ffi.C +local ffi_str = ffi.string +local errmsg = base.get_errmsg_ptr() +local FFI_OK = base.FFI_OK +local FFI_ERROR = base.FFI_ERROR +local int_out = ffi.new("int[1]") +local get_request = base.get_request +local error = error +local type = type +local tonumber = tonumber +local max = math.max +local subsystem = ngx.config.subsystem +local ngx_lua_ffi_balancer_set_current_peer +local ngx_lua_ffi_balancer_set_more_tries +local ngx_lua_ffi_balancer_get_last_failure +local ngx_lua_ffi_balancer_set_timeouts -- used by both stream and http + + +if subsystem == 'http' then + ffi.cdef[[ + int ngx_http_lua_ffi_balancer_set_current_peer(ngx_http_request_t *r, + const unsigned char *addr, size_t addr_len, int port, char **err); + + int ngx_http_lua_ffi_balancer_set_more_tries(ngx_http_request_t *r, + int count, char **err); + + int ngx_http_lua_ffi_balancer_get_last_failure(ngx_http_request_t *r, + int *status, char **err); + + int ngx_http_lua_ffi_balancer_set_timeouts(ngx_http_request_t *r, + long connect_timeout, long send_timeout, + long read_timeout, char **err); + + int ngx_http_lua_ffi_balancer_recreate_request(ngx_http_request_t *r, + char **err); + ]] + + ngx_lua_ffi_balancer_set_current_peer = + C.ngx_http_lua_ffi_balancer_set_current_peer + + ngx_lua_ffi_balancer_set_more_tries = + C.ngx_http_lua_ffi_balancer_set_more_tries + + ngx_lua_ffi_balancer_get_last_failure = + C.ngx_http_lua_ffi_balancer_get_last_failure + + ngx_lua_ffi_balancer_set_timeouts = + C.ngx_http_lua_ffi_balancer_set_timeouts + +elseif subsystem == 'stream' then + ffi.cdef[[ + int ngx_stream_lua_ffi_balancer_set_current_peer( + ngx_stream_lua_request_t *r, + const unsigned char *addr, size_t addr_len, int port, char **err); + + int ngx_stream_lua_ffi_balancer_set_more_tries(ngx_stream_lua_request_t *r, + int count, char **err); + + int ngx_stream_lua_ffi_balancer_get_last_failure( + ngx_stream_lua_request_t *r, int *status, char **err); + + int ngx_stream_lua_ffi_balancer_set_timeouts(ngx_stream_lua_request_t *r, + long connect_timeout, long timeout, char **err); + ]] + + ngx_lua_ffi_balancer_set_current_peer = + C.ngx_stream_lua_ffi_balancer_set_current_peer + + ngx_lua_ffi_balancer_set_more_tries = + C.ngx_stream_lua_ffi_balancer_set_more_tries + + ngx_lua_ffi_balancer_get_last_failure = + C.ngx_stream_lua_ffi_balancer_get_last_failure + + local ngx_stream_lua_ffi_balancer_set_timeouts = + C.ngx_stream_lua_ffi_balancer_set_timeouts + + ngx_lua_ffi_balancer_set_timeouts = + function(r, connect_timeout, send_timeout, read_timeout, err) + local timeout = max(send_timeout, read_timeout) + + return ngx_stream_lua_ffi_balancer_set_timeouts(r, connect_timeout, + timeout, err) + end + +else + error("unknown subsystem: " .. subsystem) +end + + +local peer_state_names = { + [1] = "keepalive", + [2] = "next", + [4] = "failed", +} + + +local _M = { version = base.version } + + +function _M.set_current_peer(addr, port) + local r = get_request() + if not r then + error("no request found") + end + + if not port then + port = 0 + elseif type(port) ~= "number" then + port = tonumber(port) + end + + local rc = ngx_lua_ffi_balancer_set_current_peer(r, addr, #addr, + port, errmsg) + if rc == FFI_OK then + return true + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.set_more_tries(count) + local r = get_request() + if not r then + error("no request found") + end + + local rc = ngx_lua_ffi_balancer_set_more_tries(r, count, errmsg) + if rc == FFI_OK then + if errmsg[0] == nil then + return true + end + return true, ffi_str(errmsg[0]) -- return the warning + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.get_last_failure() + local r = get_request() + if not r then + error("no request found") + end + + local state = ngx_lua_ffi_balancer_get_last_failure(r, int_out, errmsg) + + if state == 0 then + return nil + end + + if state == FFI_ERROR then + return nil, nil, ffi_str(errmsg[0]) + end + + return peer_state_names[state] or "unknown", int_out[0] +end + + +function _M.set_timeouts(connect_timeout, send_timeout, read_timeout) + local r = get_request() + if not r then + error("no request found") + end + + if not connect_timeout then + connect_timeout = 0 + elseif type(connect_timeout) ~= "number" or connect_timeout <= 0 then + error("bad connect timeout", 2) + else + connect_timeout = connect_timeout * 1000 + end + + if not send_timeout then + send_timeout = 0 + elseif type(send_timeout) ~= "number" or send_timeout <= 0 then + error("bad send timeout", 2) + else + send_timeout = send_timeout * 1000 + end + + if not read_timeout then + read_timeout = 0 + elseif type(read_timeout) ~= "number" or read_timeout <= 0 then + error("bad read timeout", 2) + else + read_timeout = read_timeout * 1000 + end + + local rc + + rc = ngx_lua_ffi_balancer_set_timeouts(r, connect_timeout, + send_timeout, read_timeout, + errmsg) + + if rc == FFI_OK then + return true + end + + return false, ffi_str(errmsg[0]) +end + + +if subsystem == 'http' then + function _M.recreate_request() + local r = get_request() + if not r then + error("no request found") + end + + local rc = C.ngx_http_lua_ffi_balancer_recreate_request(r, errmsg) + if rc == FFI_OK then + return true + end + + if errmsg[0] ~= nil then + return nil, ffi_str(errmsg[0]) + end + + return nil, "failed to recreate the upstream request" + end +end + + +return _M diff --git a/lib/ngx/balancer.md b/lib/ngx/balancer.md new file mode 100644 index 000000000..9f024671e --- /dev/null +++ b/lib/ngx/balancer.md @@ -0,0 +1,335 @@ +Name +==== + +ngx.balancer - Lua API for defining dynamic upstream balancers in Lua + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) + * [http subsystem](#http-subsystem) + * [stream subsystem](#stream-subsystem) +* [Description](#description) +* [Methods](#methods) + * [set_current_peer](#set_current_peer) + * [set_more_tries](#set_more_tries) + * [get_last_failure](#get_last_failure) + * [set_timeouts](#set_timeouts) + * [recreate_request](#recreate_request) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered experimental. + +Synopsis +======== + +http subsystem +-------------- + +```nginx +http { + upstream backend { + server 0.0.0.1; # just an invalid address as a place holder + + balancer_by_lua_block { + local balancer = require "ngx.balancer" + + -- well, usually we calculate the peer's host and port + -- according to some balancing policies instead of using + -- hard-coded values like below + local host = "127.0.0.2" + local port = 8080 + + 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 + } + + keepalive 10; # connection pool + } + + server { + # this is the real entry point + listen 80; + + location / { + # make use of the upstream named "backend" defined above: + proxy_pass http://backend/fake; + } + } + + server { + # this server is just for mocking up a backend peer here... + listen 127.0.0.2:8080; + + location = /fake { + echo "this is the fake backend peer..."; + } + } +} +``` + +[Back to TOC](#table-of-contents) + +stream subsystem +---------------- + +```nginx +stream { + upstream backend { + server 0.0.0.1:1234; # just an invalid address as a place holder + + balancer_by_lua_block { + local balancer = require "ngx.balancer" + + -- well, usually we calculate the peer's host and port + -- according to some balancing policies instead of using + -- hard-coded values like below + local host = "127.0.0.2" + local port = 8080 + + 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(ngx.ERROR) + end + } + } + + server { + # this is the real entry point + listen 10000; + + # make use of the upstream named "backend" defined above: + proxy_pass backend; + } + + server { + # this server is just for mocking up a backend peer here... + listen 127.0.0.2:8080; + + echo "this is the fake backend peer..."; + } +} +``` + +[Back to TOC](#table-of-contents) + +Description +=========== + +This Lua module provides API functions to allow defining highly dynamic NGINX load balancers for +any existing nginx upstream modules like [ngx_http_proxy_module](http://nginx.org/en/docs/http/ngx_http_proxy_module.html), +[ngx_http_fastcgi_module](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html) and +[ngx_stream_proxy_module](https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html). + +It allows you to dynamically select a backend peer to connect to (or retry) on a per-request +basis from a list of backend peers which may also be dynamic. + +[Back to TOC](#table-of-contents) + +Methods +======= + +All the methods of this module are static (or module-level). That is, you do not need an object (or instance) +to call these methods. + +[Back to TOC](#table-of-contents) + +set_current_peer +---------------- +**syntax:** *ok, err = balancer.set_current_peer(host, port)* + +**context:** *balancer_by_lua** + +Sets the peer address (host and port) for the current backend query (which may be a retry). + +Domain names in `host` do not make sense. You need to use OpenResty libraries like +[lua-resty-dns](https://github.com/openresty/lua-resty-dns) to obtain IP address(es) from +all the domain names before entering the `balancer_by_lua*` handler (for example, +you can perform DNS lookups in an earlier phase like [access_by_lua*](https://github.com/openresty/lua-nginx-module#access_by_lua) +and pass the results to the `balancer_by_lua*` handler via [ngx.ctx](https://github.com/openresty/lua-nginx-module#ngxctx). + +[Back to TOC](#table-of-contents) + +set_more_tries +-------------- +**syntax:** *ok, err = balancer.set_more_tries(count)* + +**context:** *balancer_by_lua** + +Sets the tries performed when the current attempt (which may be a retry) fails (as determined +by directives like [proxy_next_upstream](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream), depending on what +particular nginx uptream module you are currently using). Note that the current attempt is *excluded* in the `count` number set here. + +Please note that, the total number of tries in a single downstream request cannot exceed the +hard limit configured by directives like [proxy_next_upstream_tries](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream_tries), +depending on what concrete nginx upstream module you are using. When exceeding this limit, +the `count` value will get reduced to meet the limit and the second return value will be +the string `"reduced tries due to limit"`, which is a warning, while the first return value +is still a `true` value. + +[Back to TOC](#table-of-contents) + +get_last_failure +---------------- +**syntax:** *state_name, status_code = balancer.get_last_failure()* + +**context:** *balancer_by_lua** + +Retrieves the failure details about the previous failed attempt (if any) when the `next_upstream` retrying +mechanism is in action. When there was indeed a failed previous attempt, it returned a string describing +that attempt's state name, as well as an integer describing the status code of that attempt. + +Possible state names are as follows: +* `"next"` + Failures due to bad status codes sent from the backend server. The origin's response is same though, which means the backend connection +can still be reused for future requests. +* `"failed"` + Fatal errors while communicating to the backend server (like connection timeouts, connection resets, and etc). In this case, +the backend connection must be aborted and cannot get reused. + +Possible status codes are those HTTP error status codes like `502` and `504`. + +For stream module, `status_code` will always be 0 (ngx.OK) and is provided for compatibility reasons. + +When the current attempt is the first attempt for the current downstream request (which means +there is no previous attempts at all), this +method always returns a single `nil` value. + +[Back to TOC](#table-of-contents) + +set_timeouts +------------ +**syntax:** `ok, err = balancer.set_timeouts(connect_timeout, send_timeout, read_timeout)` + +**context:** *balancer_by_lua** + +Sets the upstream timeout (connect, send and read) in seconds for the current and any +subsequent backend requests (which might be a retry). + +If you want to inherit the timeout value of the global `nginx.conf` configuration (like `proxy_connect_timeout`), then +just specify the `nil` value for the corresponding argument (like the `connect_timeout` argument). + +Zero and negative timeout values are not allowed. + +You can specify millisecond precision in the timeout values by using floating point numbers like 0.001 (which means 1ms). + +**Note:** `send_timeout` and `read_timeout` are controlled by the same config +[`proxy_timeout`](https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_timeout) +for `ngx_stream_proxy_module`. To keep API compatibility, this function will use `max(send_timeout, read_timeout)` +as the value for setting `proxy_timeout`. + +Returns `true` when the operation is successful; returns `nil` and a string describing the error +otherwise. + +This only affects the current downstream request. It is not a global change. + +For the best performance, you should use the [OpenResty](https://openresty.org/) bundle. + +This function was first added in the `0.1.7` version of this library. + +[Back to TOC](#table-of-contents) + +recreate_request +---------------- +**syntax:** `ok, err = balancer.recreate_request()` + +**context:** *balancer_by_lua** + +Recreates the request buffer for sending to the upstream server. This is useful, for example +if you want to change a request header field to the new upstream server on balancer retries. + +Normally this does not work because the request buffer is created once during upstream module +initialization and won't be regenerated for subsequent retries. However you can use +`proxy_set_header My-Header $my_header` and set the `ngx.var.my_header` variable inside the +balancer phase. Calling `balancer.recreate_request()` after updating a header field will +cause the request buffer to be re-generated and the `My-Header` header will thus contain +the new value. + +**Warning:** because the request buffer has to be recreated and such allocation occurs on the +request memory pool, the old buffer has to be thrown away and will only be freed after the request +finishes. Do not call this function too often or memory leaks may be noticeable. Even so, a call +to this function should be made **only** if you know the request buffer must be regenerated, +instead of unconditionally in each balancer retries. + +This function was first added in the `0.1.20` version of this library. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Yichun Zhang <agentzh@gmail.com> (agentzh), OpenResty Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2015-2017, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* the [balancer_by_lua*](https://github.com/openresty/lua-nginx-module#balancer_by_lua_block) directive. +* the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) diff --git a/lib/ngx/base64.lua b/lib/ngx/base64.lua new file mode 100644 index 000000000..a8a6aaf44 --- /dev/null +++ b/lib/ngx/base64.lua @@ -0,0 +1,89 @@ +-- Copyright (C) by Yichun Zhang (agentzh) +-- Copyright (C) by OpenResty Inc. + +local ffi = require("ffi") +local base = require("resty.core.base") + + +local ffi_str = ffi.string +local type = type +local C = ffi.C +local NGX_ERROR = ngx.ERROR + + +local _M = { version = base.version } + + +ffi.cdef[[ +typedef intptr_t ngx_int_t; + +void ngx_encode_base64url(ngx_str_t *dst, ngx_str_t *src); +ngx_int_t ngx_decode_base64url(ngx_str_t *dst, ngx_str_t *src); +]] + + +local get_string_buf = base.get_string_buf + + +local dst_str_t = ffi.new("ngx_str_t[1]") +local src_str_t = ffi.new("ngx_str_t[1]") + + +local function base64_encoded_length(len) + return ((len + 2) / 3) * 4 +end + + +local function base64_decoded_length(len) + return ((len + 3) / 4) * 3 +end + + +function _M.encode_base64url(s) + if type(s) ~= "string" then + return nil, "must provide a string" + end + + local len = #s + local trans_len = base64_encoded_length(len) + local src = src_str_t[0] + local dst = dst_str_t[0] + + src.data = s + src.len = len + + dst.data = get_string_buf(trans_len) + dst.len = trans_len + + C.ngx_encode_base64url(dst_str_t, src_str_t) + + return ffi_str(dst.data, dst.len) +end + + +function _M.decode_base64url(s) + if type(s) ~= "string" then + return nil, "must provide a string" + end + + local len = #s + local trans_len = base64_decoded_length(len) + local src = src_str_t[0] + local dst = dst_str_t[0] + + src.data = s + src.len = len + + dst.data = get_string_buf(trans_len) + dst.len = trans_len + + local ret = C.ngx_decode_base64url(dst_str_t, src_str_t) + if ret == NGX_ERROR then + return nil, "invalid input" + end + + return ffi_str(dst.data, dst.len) +end + + +return _M diff --git a/lib/ngx/base64.md b/lib/ngx/base64.md new file mode 100644 index 000000000..0005cc2c9 --- /dev/null +++ b/lib/ngx/base64.md @@ -0,0 +1,137 @@ +Name +==== + +`ngx.base64` - urlsafe base64 encode/decode functions OpenResty/ngx\_lua. + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Methods](#methods) + * [encode\_base64url](#encode_base64url) + * [decode\_base64url](#decode_base64url) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is production ready. + +Synopsis +======== + +```lua +local b64 = require("ngx.base64") +local res, err + +res = b64.encode_base64url("foo") + +res, err = b64.decode_base64url(res) +if not res then + -- invalid input + ngx.log(ngx.ERR, err) +end + +assert(res == "foo") +``` + +[Back to TOC](#table-of-contents) + +Methods +======= + +encode\_base64url +----------------- +**syntax:** *encoded = base64.encode_base64url(input)* + +**context:** *any* + +Encode `input` using base64url rules. Returns the encoded string. + +[Back to TOC](#table-of-contents) + +decode\_base64url +----------------- +**syntax:** *decoded, err = base64.decode_base64url(input)* + +**context:** *any* + +Decode `input` using base64url rules. Returns the decoded string. + +If the `input` is not a valid base64url encoded string, `decoded `will be `nil` +and `err` will be a string describing the error. + +[Back to TOC](#table-of-contents) + + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Datong Sun <datong@openresty.com> (dndx), OpenResty Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2017, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) + diff --git a/lib/ngx/errlog.lua b/lib/ngx/errlog.lua new file mode 100644 index 000000000..890315f7c --- /dev/null +++ b/lib/ngx/errlog.lua @@ -0,0 +1,170 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local base = require "resty.core.base" +base.allows_subsystem('http', 'stream') + + +local ffi = require 'ffi' +local ffi_string = ffi.string +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local C = ffi.C +local new_tab = base.new_tab +local ffi_new = ffi.new +local charpp = ffi_new("char *[1]") +local intp = ffi.new("int[1]") +local num_value = ffi_new("double[1]") +local get_request = base.get_request +local tonumber = tonumber +local type = type +local error = error +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_errlog_set_filter_level +local ngx_lua_ffi_errlog_get_msg +local ngx_lua_ffi_errlog_get_sys_filter_level +local ngx_lua_ffi_raw_log + + +local _M = { version = base.version } + + +if subsystem == 'http' then + ffi.cdef[[ +int ngx_http_lua_ffi_errlog_set_filter_level(int level, unsigned char *err, + size_t *errlen); +int ngx_http_lua_ffi_errlog_get_msg(char **log, int *loglevel, + unsigned char *err, size_t *errlen, double *log_time); + +int ngx_http_lua_ffi_errlog_get_sys_filter_level(ngx_http_request_t *r); + +int ngx_http_lua_ffi_raw_log(ngx_http_request_t *r, int level, + const unsigned char *s, size_t s_len); + ]] + + ngx_lua_ffi_errlog_set_filter_level = + C.ngx_http_lua_ffi_errlog_set_filter_level + ngx_lua_ffi_errlog_get_msg = C.ngx_http_lua_ffi_errlog_get_msg + ngx_lua_ffi_errlog_get_sys_filter_level = + C.ngx_http_lua_ffi_errlog_get_sys_filter_level + ngx_lua_ffi_raw_log = C.ngx_http_lua_ffi_raw_log + +elseif subsystem == 'stream' then + ffi.cdef[[ +int ngx_stream_lua_ffi_errlog_set_filter_level(int level, unsigned char *err, + size_t *errlen); +int ngx_stream_lua_ffi_errlog_get_msg(char **log, int *loglevel, + unsigned char *err, size_t *errlen, double *log_time); + +int ngx_stream_lua_ffi_errlog_get_sys_filter_level(ngx_stream_lua_request_t *r); + +int ngx_stream_lua_ffi_raw_log(ngx_stream_lua_request_t *r, int level, + const unsigned char *s, size_t s_len); + ]] + + ngx_lua_ffi_errlog_set_filter_level = + C.ngx_stream_lua_ffi_errlog_set_filter_level + ngx_lua_ffi_errlog_get_msg = C.ngx_stream_lua_ffi_errlog_get_msg + ngx_lua_ffi_errlog_get_sys_filter_level = + C.ngx_stream_lua_ffi_errlog_get_sys_filter_level + ngx_lua_ffi_raw_log = C.ngx_stream_lua_ffi_raw_log +end + + +local ERR_BUF_SIZE = 128 +local FFI_ERROR = base.FFI_ERROR + + +function _M.set_filter_level(level) + if not level then + return nil, [[missing "level" argument]] + end + + local err = get_string_buf(ERR_BUF_SIZE) + local errlen = get_size_ptr() + errlen[0] = ERR_BUF_SIZE + local rc = ngx_lua_ffi_errlog_set_filter_level(level, err, errlen) + + if rc == FFI_ERROR then + return nil, ffi_string(err, errlen[0]) + end + + return true +end + + +function _M.get_logs(max, logs) + local err = get_string_buf(ERR_BUF_SIZE) + local errlen = get_size_ptr() + errlen[0] = ERR_BUF_SIZE + + local log = charpp + local loglevel = intp + local log_time = num_value + + max = max or 10 + + if not logs then + logs = new_tab(max * 3 + 1, 0) + end + + local count = 0 + + for i = 1, max do + local loglen = ngx_lua_ffi_errlog_get_msg(log, loglevel, err, errlen, + log_time) + if loglen == FFI_ERROR then + return nil, ffi_string(err, errlen[0]) + end + + if loglen > 0 then + logs[count + 1] = loglevel[0] + logs[count + 2] = log_time[0] + logs[count + 3] = ffi_string(log[0], loglen) + + count = count + 3 + end + + if loglen < 0 then -- no error log + logs[count + 1] = nil + break + end + + if i == max then -- last one + logs[count + 1] = nil + break + end + end + + return logs +end + + +function _M.get_sys_filter_level() + local r = get_request() + return tonumber(ngx_lua_ffi_errlog_get_sys_filter_level(r)) +end + + +function _M.raw_log(level, msg) + if type(level) ~= "number" then + error("bad argument #1 to 'raw_log' (must be a number)", 2) + end + + if type(msg) ~= "string" then + error("bad argument #2 to 'raw_log' (must be a string)", 2) + end + + local r = get_request() + + local rc = ngx_lua_ffi_raw_log(r, level, msg, #msg) + + if rc == FFI_ERROR then + error("bad log level", 2) + end +end + + +return _M diff --git a/lib/ngx/errlog.md b/lib/ngx/errlog.md new file mode 100644 index 000000000..1b28fee74 --- /dev/null +++ b/lib/ngx/errlog.md @@ -0,0 +1,429 @@ +Name +==== + +`ngx.errlog` - manage nginx error log data in Lua for OpenResty/ngx_lua. + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) + * [Capturing nginx error logs with specified log filtering level](#capturing-nginx-error-logs-with-specified-log-filtering-level) +* [Methods](#methods) + * [set_filter_level](#set_filter_level) + * [get_logs](#get_logs) + * [get_sys_filter_level](#get_sys_filter_level) + * [raw_log](#raw_log) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered experimental. + +The API is still in flux and may change in the future without notice. + +Synopsis +======== + +Capturing nginx error logs with specified log filtering level +------------------------------------------------------------- + +```nginx +error_log logs/error.log info; + +http { + # enable capturing error logs + lua_capture_error_log 32m; + + init_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + ngx.log(ngx.ERR, err) + return + end + ngx.log(ngx.WARN, "set error filter level: WARN") + } + + server { + # ... + location = /t { + content_by_lua_block { + local errlog = require "ngx.errlog" + ngx.log(ngx.INFO, "test1") + ngx.log(ngx.WARN, "test2") + ngx.log(ngx.ERR, "test3") + + local logs, err = errlog.get_logs(10) + if not logs then + ngx.say("FAILED ", err) + return + end + + for i = 1, #logs, 3 do + ngx.say("level: ", logs[i], " time: ", logs[i + 1], + " data: ", logs[i + 2]) + end + } + } + } +} + +``` + +The example location above produces a response like this: + +``` +level: 5 time: 1498546995.304 data: 2017/06/27 15:03:15 [warn] 46877#0: + [lua] init_by_lua:8: set error filter level: WARN +level: 5 time: 1498546999.178 data: 2017/06/27 15:03:19 [warn] 46879#0: *1 + [lua] test.lua:5: test2, client: 127.0.0.1, server: localhost, ...... +level: 4 time: 1498546999.178 data: 2017/06/27 15:03:19 [error] 46879#0: *1 + [lua] test.lua:6: test3, client: 127.0.0.1, server: localhost, ...... +``` + +[Back to TOC](#table-of-contents) + +Methods +======= + +set_filter_level +----------------- +**syntax:** *status, err = log_module.set_filter_level(log_level)* + +**context:** *init_by_lua** + +Specifies the filter log level, only to capture and buffer the error logs with a log level +no lower than the specified level. + +If we don't call this API, all of the error logs will be captured by default. + +In case of error, `nil` will be returned as well as a string describing the +error. + +This API should always work with directive +[lua_capture_error_log](https://github.com/openresty/lua-nginx-module#lua_capture_error_log). + +See [Nginx log level constants](https://github.com/openresty/lua-nginx-module#nginx-log-level-constants) for all nginx log levels. + +For example, + +```lua + init_by_lua_block { + local errlog = require "ngx.errlog" + errlog.set_filter_level(ngx.WARN) + } +``` + +*NOTE:* The debugging logs since when OpenResty or NGINX is not built with `--with-debug`, all the debug level logs are suppressed regardless. + +[Back to TOC](#table-of-contents) + +get_logs +-------- +**syntax:** *res, err = log_module.get_logs(max?, res?)* + +**context:** *any* + +Fetches the captured nginx error log messages if any in the global data buffer +specified by `ngx_lua`'s +[lua_capture_error_log](https://github.com/openresty/lua-nginx-module#lua_capture_error_log) +directive. Upon return, this Lua function also *removes* those messages from +that global capturing buffer to make room for future new error log data. + +In case of error, `nil` will be returned as well as a string describing the +error. + +The optional `max` argument is a number that when specified, will prevent +`errlog.get_logs` from adding more than `max` messages to the `res` array. + +```lua +for i = 1, 20 do + ngx.log(ngx.ERR, "test") +end + +local errlog = require "ngx.errlog" +local res = errlog.get_logs(10) +-- the number of messages in the `res` table is 10 and the `res` table +-- has 30 elements. +``` + +The resulting table has the following structure: + +```lua +{ level1, time1, msg1, level2, time2, msg2, ... } +``` + +The `levelX` values are constants defined below: + +https://github.com/openresty/lua-nginx-module/#nginx-log-level-constants + +The `timeX` values are UNIX timestamps in seconds with millisecond precision. The sub-second part is presented as the decimal part. +The time format is exactly the same as the value returned by [ngx.now](https://github.com/openresty/lua-nginx-module/#ngxnow). It is +also subject to NGINX core's time caching. + +The `msgX` values are the error log message texts. + +So to traverse this array, the user can use a loop like this: + +```lua +for i = 1, #res, 3 do + local level = res[i] + if not level then + break + end + + local time = res[i + 1] + local msg = res[i + 2] + + -- handle the current message with log level in `level`, + -- log time in `time`, and log message body in `msg`. +end +``` + +Specifying `max <= 0` disables this behavior, meaning that the number of +results won't be limited. + +The optional 2th argument `res` can be a user-supplied Lua table +to hold the result instead of creating a brand new table. This can avoid +unnecessary table dynamic allocations on hot Lua code paths. It is used like this: + +```lua +local errlog = require "ngx.errlog" +local new_tab = require "table.new" + +local buffer = new_tab(100 * 3, 0) -- for 100 messages + +local errlog = require "ngx.errlog" +local res, err = errlog.get_logs(0, buffer) +if res then + -- res is the same table as `buffer` + for i = 1, #res, 3 do + local level = res[i] + if not level then + break + end + local time = res[i + 1] + local msg = res[i + 2] + ... + end +end +``` + +When provided with a `res` table, `errlog.get_logs` won't clear the table +for performance reasons, but will rather insert a trailing `nil` value +after the last table element. + +When the trailing `nil` is not enough for your purpose, you should +clear the table yourself before feeding it into the `errlog.get_logs` function. + +[Back to TOC](#table-of-contents) + +get_sys_filter_level +-------------------- +**syntax:** *log_level = log_module.get_sys_filter_level()* + +**context:** *any* + +Return the nginx core's error log filter level (defined via the [error_log](http://nginx.org/r/error_log) +configuration directive in `nginx.conf`) as an integer value matching the nginx error log level +constants documented below: + +https://github.com/openresty/lua-nginx-module/#nginx-log-level-constants + +For example: + +```lua +local errlog = require "ngx.errlog" +local log_level = errlog.get_sys_filter_level() +-- Now the filter level is always one level higher than system default log level on priority +local status, err = errlog.set_filter_level(log_level - 1) +if not status then + ngx.log(ngx.ERR, err) + return +end +``` + +[Back to TOC](#table-of-contents) + +raw_log +------- +**syntax:** *log_module.raw_log(log_level, msg)* + +**context:** *any* + +Log `msg` to the error logs with the given logging level. + +Just like the [ngx.log](https://github.com/openresty/lua-nginx-module#ngxlog) +API, the `log_level` argument can take constants like `ngx.ERR` and `ngx.WARN`. +Check out [Nginx log level constants for +details.](https://github.com/openresty/lua-nginx-module#nginx-log-level-constants) + +However, unlike the `ngx.log` API which accepts variadic arguments, this +function only accepts a single string as its second argument `msg`. + +This function differs from `ngx.log` in the way that it will not prefix the +written logs with any sort of debug information (such as the caller's file +and line number). + +For example, while `ngx.log` would produce + +``` +2017/07/09 19:36:25 [notice] 25932#0: *1 [lua] content_by_lua(nginx.conf:51):5: hello world, client: 127.0.0.1, server: localhost, request: "GET /log HTTP/1.1", host: "localhost" +``` + +from + +```lua +ngx.log(ngx.NOTICE, "hello world") +``` + +the `errlog.raw_log()` call produces + +``` +2017/07/09 19:36:25 [notice] 25932#0: *1 hello world, client: 127.0.0.1, server: localhost, request: "GET /log HTTP/1.1", host: "localhost" +``` + +from + +```lua +local errlog = require "ngx.errlog" +errlog.raw_log(ngx.NOTICE, "hello world") +``` + +This function is best suited when the format and/or stack level of the debug +information proposed by `ngx.log` is not desired. A good example of this would +be a custom logging function which prefixes each log with a namespace in +an application: + +``` +1. local function my_log(lvl, ...) +2. ngx.log(lvl, "[prefix] ", ...) +3. end +4. +5. my_log(ngx.ERR, "error") +``` + +Here, the produced log would indicate that this error was logged at line `2.`, +when in reality, we wish the investigator of that log to realize it was logged +at line `5.` right away. + +For such use cases (or other formatting reasons), one may use `raw_log` to +create a logging utility that supports such requirements. Here is a suggested +implementation: + +```lua +local errlog = require "ngx.errlog" + +local function my_log(lvl, ...) + -- log to error logs with our custom prefix, stack level + -- and separator + local n = select("#", ...) + local t = { ... } + local info = debug.getinfo(2, "Sl") + + local prefix = string.format("(%s):%d:", info.short_src, info.currentline) + local buf = { prefix } + + for i = 1, n do + buf[i + 1] = tostring(t[i]) + end + + local msg = table.concat(buf, " ") + + errlog.raw_log(lvl, msg) -- line 19. +end + +local function my_function() + -- do something and log + + my_log(ngx.ERR, "hello from", "raw_log:", true) -- line 25. +end + +my_function() +``` + +This utility function will produce the following log, explicitly stating that +the error was logged on line `25.`: + +``` +2017/07/09 20:03:07 [error] 26795#0: *2 (/path/to/file.lua):25: hello from raw_log: true, context: ngx.timer +``` + +As a reminder to the reader, one must be wary of the cost of string +concatenation on the Lua land, and should prefer the combined use of a buffer +table and `table.concat` to avoid unnecessary GC pressure. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Yuansheng Wang <membphis@gmail.com> (membphis), OpenResty Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2017, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) + diff --git a/lib/ngx/ocsp.lua b/lib/ngx/ocsp.lua new file mode 100644 index 000000000..51ce06f06 --- /dev/null +++ b/lib/ngx/ocsp.lua @@ -0,0 +1,150 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local base = require "resty.core.base" +base.allows_subsystem('http') + + +local ffi = require "ffi" +local C = ffi.C +local ffi_str = ffi.string +local get_request = base.get_request +local error = error +local tonumber = tonumber +local errmsg = base.get_errmsg_ptr() +local get_string_buf = base.get_string_buf +local get_string_buf_size = base.get_string_buf_size +local get_size_ptr = base.get_size_ptr +local FFI_DECLINED = base.FFI_DECLINED +local FFI_OK = base.FFI_OK +local FFI_BUSY = base.FFI_BUSY + + +ffi.cdef[[ +int ngx_http_lua_ffi_ssl_get_ocsp_responder_from_der_chain( + const char *chain_data, size_t chain_len, char *out, size_t *out_size, + char **err); + +int ngx_http_lua_ffi_ssl_create_ocsp_request(const char *chain_data, + size_t chain_len, unsigned char *out, size_t *out_size, char **err); + +int ngx_http_lua_ffi_ssl_validate_ocsp_response(const unsigned char *resp, + size_t resp_len, const char *chain_data, size_t chain_len, + unsigned char *errbuf, size_t *errbuf_size); + +int ngx_http_lua_ffi_ssl_set_ocsp_status_resp(ngx_http_request_t *r, + const unsigned char *resp, size_t resp_len, char **err); +]] + + +local _M = { version = base.version } + + +function _M.get_ocsp_responder_from_der_chain(certs, maxlen) + + local buf_size = maxlen + if not buf_size then + buf_size = get_string_buf_size() + end + local buf = get_string_buf(buf_size) + + local sizep = get_size_ptr() + sizep[0] = buf_size + + local rc = C.ngx_http_lua_ffi_ssl_get_ocsp_responder_from_der_chain(certs, + #certs, buf, sizep, errmsg) + + if rc == FFI_DECLINED then + return nil + end + + if rc == FFI_OK then + return ffi_str(buf, sizep[0]) + end + + if rc == FFI_BUSY then + return ffi_str(buf, sizep[0]), "truncated" + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.create_ocsp_request(certs, maxlen) + + local buf_size = maxlen + if not buf_size then + buf_size = get_string_buf_size() + end + local buf = get_string_buf(buf_size) + + local sizep = get_size_ptr() + sizep[0] = buf_size + + local rc = C.ngx_http_lua_ffi_ssl_create_ocsp_request(certs, + #certs, buf, sizep, + errmsg) + + if rc == FFI_OK then + return ffi_str(buf, sizep[0]) + end + + if rc == FFI_BUSY then + return nil, ffi_str(errmsg[0]) .. ": " .. tonumber(sizep[0]) + .. " > " .. buf_size + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.validate_ocsp_response(resp, chain, max_errmsg_len) + + local errbuf_size = max_errmsg_len + if not errbuf_size then + errbuf_size = get_string_buf_size() + end + local errbuf = get_string_buf(errbuf_size) + + local sizep = get_size_ptr() + sizep[0] = errbuf_size + + local rc = C.ngx_http_lua_ffi_ssl_validate_ocsp_response( + resp, #resp, chain, #chain, errbuf, sizep) + + if rc == FFI_OK then + return true + end + + -- rc == FFI_ERROR + + return nil, ffi_str(errbuf, sizep[0]) +end + + +function _M.set_ocsp_status_resp(ocsp_resp) + local r = get_request() + if not r then + error("no request found") + end + + local rc = C.ngx_http_lua_ffi_ssl_set_ocsp_status_resp(r, ocsp_resp, + #ocsp_resp, + errmsg) + + if rc == FFI_DECLINED then + -- no client status req + return true, "no status req" + end + + if rc == FFI_OK then + return true + end + + -- rc == FFI_ERROR + + return nil, ffi_str(errmsg[0]) +end + + +return _M diff --git a/lib/ngx/ocsp.md b/lib/ngx/ocsp.md new file mode 100644 index 000000000..5c33bf258 --- /dev/null +++ b/lib/ngx/ocsp.md @@ -0,0 +1,298 @@ +Name +==== + +ngx.ocsp - Lua API for implementing OCSP stapling in ssl_certificate_by_lua* + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Methods](#methods) + * [get_ocsp_responder_from_der_chain](#get_ocsp_responder_from_der_chain) + * [create_ocsp_request](#create_ocsp_request) + * [validate_ocsp_response](#validate_ocsp_response) + * [set_ocsp_status_resp](#set_ocsp_status_resp) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered experimental. + +Synopsis +======== + +```nginx +# Note: you do not need the following line if you are using +# OpenResty 1.9.7.2+. +lua_package_path "/path/to/lua-resty-core/lib/?.lua;;"; + +server { + listen 443 ssl; + server_name test.com; + + # useless placeholders: just to shut up NGINX configuration + # loader errors: + ssl_certificate /path/to/fallback.crt; + ssl_certificate_key /path/to/fallback.key; + + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + local http = require "resty.http.simple" + + -- assuming the user already defines the my_load_certificate_chain() + -- herself. + local pem_cert_chain = assert(my_load_certificate_chain()) + + local der_cert_chain, err = ssl.cert_pem_to_der(pem_cert_chain) + if not der_cert_chain then + ngx.log(ngx.ERR, "failed to convert certificate chain ", + "from PEM to DER: ", err) + return ngx.exit(ngx.ERROR) + end + + local ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(der_cert_chain) + if not ocsp_url then + ngx.log(ngx.ERR, "failed to get OCSP responder: ", err) + return ngx.exit(ngx.ERROR) + end + + print("ocsp_url: ", ocsp_url) + + -- use cosocket-based HTTP client libraries like lua-resty-http-simple + -- to send the request (url + ocsp_req as POST params or URL args) to + -- CA's OCSP server. assuming the server returns the OCSP response + -- in the Lua variable, resp. + + local schema, host, port, ocsp_uri, err = parse_url(ocsp_url) + + local ocsp_req, err = ocsp.create_ocsp_request(der_cert_chain) + if not ocsp_req then + ngx.log(ngx.ERR, "failed to create OCSP request: ", err) + return ngx.exit(ngx.ERROR) + end + + local res, err = http.request(host, port, { + path = ocsp_uri, + headers = { Host = host, + ["Content-Type"] = "application/ocsp-request" }, + timeout = 10000, -- 10 sec + method = "POST", + body = ocsp_req, + maxsize = 102400, -- 100KB + }) + + if not res then + ngx.log(ngx.ERR, "OCSP responder query failed: ", err) + return ngx.exit(ngx.ERROR) + end + + local http_status = res.status + + if http_status ~= 200 then + ngx.log(ngx.ERR, "OCSP responder returns bad HTTP status code ", + http_status) + return ngx.exit(ngx.ERROR) + end + + local ocsp_resp = res.body + + if ocsp_resp and #ocsp_resp > 0 then + local ok, err = ocsp.validate_ocsp_response(ocsp_resp, der_cert_chain) + if not ok then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return ngx.exit(ngx.ERROR) + end + + -- set the OCSP stapling + ok, err = ocsp.set_ocsp_status_resp(ocsp_resp) + if not ok then + ngx.log(ngx.ERR, "failed to set ocsp status resp: ", err) + return ngx.exit(ngx.ERROR) + end + end + } + + location / { + root html; + } +} + +``` + +Description +=========== + +This Lua module provides API to perform OCSP queries, OCSP response validations, and +OCSP stapling planting. + +Usually, this module is used together with the [ngx.ssl](ssl.md) module in the +context of [ssl_certificate_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_certificate_by_lua_block) +(of the [ngx_lua](https://github.com/openresty/lua-nginx-module#readme) module). + +To load the `ngx.ocsp` module in Lua, just write + +```lua +local ocsp = require "ngx.ocsp" +``` + +[Back to TOC](#table-of-contents) + +Methods +======= + +get_ocsp_responder_from_der_chain +--------------------------------- +**syntax:** *ocsp_url, err = ocsp.get_ocsp_responder_from_der_chain(der_cert_chain, max_len)* + +**context:** *any* + +Extracts the OCSP responder URL (like `"http://test.com/ocsp/"`) from the SSL server certificate chain in the DER format. + +Usually the SSL server certificate chain is originally formatted in PEM. You can use the Lua API +provided by the [ngx.ssl](ssl.md) module to do the PEM to DER conversion. + +The optional `max_len` argument specifies the maximum length of OCSP URL allowed. This determines +the buffer size; so do not specify an unnecessarily large value here. It defaults to the internal +string buffer size used throughout this `lua-resty-core` library (usually default to 4KB). + +In case of failures, returns `nil` and a string describing the error. + +[Back to TOC](#table-of-contents) + +create_ocsp_request +------------------- +**syntax:** *ocsp_req, err = ocsp.create_ocsp_request(der_cert_chain, max_len)* + +**context:** *any* + +Builds an OCSP request from the SSL server certificate chain in the DER format, which +can be used to send to the OCSP server for validation. + +The optional `max_len` argument specifies the maximum length of the OCSP request allowed. +This value determines the size of the internal buffer allocated, so do not specify an +unnecessarily large value here. It defaults to the internal string buffer size used +throughout this `lua-resty-core` library (usually defaults to 4KB). + +In case of failures, returns `nil` and a string describing the error. + +The raw OCSP response data can be used as the request body directly if the POST method +is used for the OCSP request. But for GET requests, you need to do base64 encoding and +then URL encoding on the data yourself before appending it to the OCSP URL obtained +by the [get_ocsp_responder_from_der_chain](#get_ocsp_responder_from_der_chain) function. + +[Back to TOC](#table-of-contents) + +validate_ocsp_response +---------------------- +**syntax:** *ok, err = ocsp.validate_ocsp_response(ocsp_resp, der_cert_chain, max_err_msg_len)* + +**context:** *any* + +Validates the raw OCSP response data specified by the `ocsp_resp` argument using the SSL +server certificate chain in DER format as specified in the `der_cert_chain` argument. + +Returns true when the validation is successful. + +In case of failures, returns `nil` and a string +describing the failure. The maximum +length of the error string is controlled by the optional `max_err_msg` argument (which defaults +to the default internal string buffer size used throughout this `lua-resty-core` library, usually +being 4KB). + +[Back to TOC](#table-of-contents) + +set_ocsp_status_resp +-------------------- +**syntax:** *ok, err = ocsp.set_ocsp_status_resp(ocsp_resp)* + +**context:** *ssl_certificate_by_lua** + +Sets the OCSP response as the OCSP stapling for the current SSL connection. + +Returns `true` in case of successes. If the SSL client does not send a "status request" +at all, then this method still returns `true` but also with a string as the warning +`"no status req"`. + +In case of failures, returns `nil` and a string describing the error. + +The OCSP response is returned from CA's OCSP server. See the [create_ocsp_request](#create_ocsp_request) +function for how to create an OCSP request and also [validate_ocsp_response](#validate_ocsp_response) +for how to validate the OCSP response. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Yichun Zhang <agentzh@gmail.com> (agentzh), OpenResty Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2015-2017, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* the [ngx.ssl](ssl.md) module. +* the [ssl_certificate_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_certificate_by_lua_block) directive. +* the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) diff --git a/lib/ngx/pipe.lua b/lib/ngx/pipe.lua new file mode 100644 index 000000000..3757ab7c5 --- /dev/null +++ b/lib/ngx/pipe.lua @@ -0,0 +1,645 @@ +-- Copyright (C) by OpenResty Inc. + + +local base = require "resty.core.base" +base.allows_subsystem("http") + + +require "resty.core.phase" -- for ngx.get_phase + +local assert = assert +local error = error +local ipairs = ipairs +local tonumber = tonumber +local tostring = tostring +local type = type +local str_find = string.find +local table_concat = table.concat +local ffi = require "ffi" +local C = ffi.C +local ffi_new = ffi.new +local ffi_str = ffi.string +local ngx_phase = ngx.get_phase +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local get_request = base.get_request +local FFI_AGAIN = base.FFI_AGAIN +local FFI_BAD_CONTEXT = base.FFI_BAD_CONTEXT +local FFI_DECLINED = base.FFI_DECLINED +local FFI_ERROR = base.FFI_ERROR +local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX +local FFI_OK = base.FFI_OK +local co_yield = coroutine._yield + + +ffi.cdef[[ +typedef int ngx_pid_t; +typedef uintptr_t ngx_msec_t; +typedef unsigned char u_char; +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; + ngx_http_lua_pipe_t *pipe; +} ngx_http_lua_ffi_pipe_proc_t; + +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 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 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); + +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); + +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); + +int ngx_http_lua_ffi_pipe_proc_shutdown_stdin( + ngx_http_lua_ffi_pipe_proc_t *proc, u_char *errbuf, size_t *errbuf_size); + +int ngx_http_lua_ffi_pipe_proc_shutdown_stdout( + ngx_http_lua_ffi_pipe_proc_t *proc, u_char *errbuf, size_t *errbuf_size); + +int ngx_http_lua_ffi_pipe_proc_shutdown_stderr( + ngx_http_lua_ffi_pipe_proc_t *proc, u_char *errbuf, size_t *errbuf_size); + +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 ngx_http_lua_ffi_pipe_proc_kill(ngx_http_lua_ffi_pipe_proc_t *proc, + int signal, u_char *errbuf, size_t *errbuf_size); + +void ngx_http_lua_ffi_pipe_proc_destroy(ngx_http_lua_ffi_pipe_proc_t *proc); +]] + + +if not pcall(function() return C.ngx_http_lua_ffi_pipe_spawn end) then + error("pipe API is not supported due to either a platform issue " .. + "or lack of the HAVE_SOCKET_CLOEXEC_PATCH patch", 2) +end + + +local _M = { version = base.version } + + +local ERR_BUF_SIZE = 256 +local VALUE_BUF_SIZE = 512 +local PIPE_READ_ALL = 0 +local PIPE_READ_BYTES = 1 +local PIPE_READ_LINE = 2 +local PIPE_READ_ANY = 3 + + +local proc_set_timeouts +do + local MAX_TIMEOUT = 0xffffffff + + function proc_set_timeouts(proc, write_timeout, stdout_read_timeout, + stderr_read_timeout, wait_timeout) + + -- the implementation below is straightforward but could not be JIT + -- compiled by the latest LuaJIT. When called in loops, LuaJIT will try + -- to unroll it, and fall back to interpreter after it reaches the + -- unroll limit. + --[[ + local function set_timeout(proc, attr, timeout) + if timeout then + if timeout > MAX_TIMEOUT then + error("bad timeout value", 3) + end + proc[attr] = timeout + end + end + set_timeout(...) + ]] + + if write_timeout then + if write_timeout < 0 or MAX_TIMEOUT < write_timeout then + error("bad write_timeout option", 3) + end + + proc.write_timeout = write_timeout + end + + if stdout_read_timeout then + if stdout_read_timeout < 0 or MAX_TIMEOUT < stdout_read_timeout then + error("bad stdout_read_timeout option", 3) + end + + proc.stdout_read_timeout = stdout_read_timeout + end + + if stderr_read_timeout then + if stderr_read_timeout < 0 or MAX_TIMEOUT < stderr_read_timeout then + error("bad stderr_read_timeout option", 3) + end + + proc.stderr_read_timeout = stderr_read_timeout + end + + if wait_timeout then + if wait_timeout < 0 or MAX_TIMEOUT < wait_timeout then + error("bad wait_timeout option", 3) + end + + proc.wait_timeout = wait_timeout + end + end +end + + +local function check_proc_instance(proc) + if type(proc) ~= "cdata" then + error("not a process instance", 3) + end +end + + +local proc_read +do + local value_buf = ffi_new("char[?]", VALUE_BUF_SIZE) + local buf = ffi_new("char *[1]") + local buf_size = ffi_new("size_t[1]") + + function proc_read(proc, stderr, reader_type, len) + check_proc_instance(proc) + + local r = get_request() + if not r then + error("no request found") + end + + buf[0] = value_buf + buf_size[0] = VALUE_BUF_SIZE + local errbuf = get_string_buf(ERR_BUF_SIZE) + local errbuf_size = get_size_ptr() + errbuf_size[0] = ERR_BUF_SIZE + local rc = C.ngx_http_lua_ffi_pipe_proc_read(r, proc, stderr, + reader_type, len, buf, + buf_size, errbuf, + errbuf_size) + if rc == FFI_NO_REQ_CTX then + error("no request ctx found") + end + + if rc == FFI_BAD_CONTEXT then + error(ffi_str(errbuf, errbuf_size[0]), 2) + end + + while true do + if rc == FFI_ERROR then + return nil, ffi_str(errbuf, errbuf_size[0]) + end + + if rc == FFI_OK then + local p = buf[0] + if p ~= value_buf then + p = ffi_new("char[?]", buf_size[0]) + buf[0] = p + C.ngx_http_lua_ffi_pipe_get_read_result(r, proc, stderr, + buf, buf_size, + errbuf, errbuf_size) + assert(p == buf[0]) + end + + return ffi_str(p, buf_size[0]) + end + + if rc == FFI_DECLINED then + local err = ffi_str(errbuf, errbuf_size[0]) + + local p = buf[0] + if p ~= value_buf then + p = ffi_new("char[?]", buf_size[0]) + buf[0] = p + C.ngx_http_lua_ffi_pipe_get_read_result(r, proc, stderr, + buf, buf_size, + errbuf, errbuf_size) + assert(p == buf[0]) + end + + local partial = ffi_str(p, buf_size[0]) + return nil, err, partial + end + + assert(rc == FFI_AGAIN) + + co_yield() + + buf[0] = value_buf + buf_size[0] = VALUE_BUF_SIZE + errbuf = get_string_buf(ERR_BUF_SIZE) + errbuf_size = get_size_ptr() + errbuf_size[0] = ERR_BUF_SIZE + rc = C.ngx_http_lua_ffi_pipe_get_read_result(r, proc, stderr, buf, + buf_size, errbuf, + errbuf_size) + end + end + +end + + +local function proc_write(proc, data) + check_proc_instance(proc) + + local r = get_request() + if not r then + error("no request found", 2) + end + + local data_type = type(data) + if data_type ~= "string" then + if data_type == "table" then + data = table_concat(data, "") + + elseif data_type == "number" then + data = tostring(data) + + else + error("bad data arg: string, number, or table expected, got " + .. data_type, 2) + end + end + + local errbuf = get_string_buf(ERR_BUF_SIZE) + local errbuf_size = get_size_ptr() + errbuf_size[0] = ERR_BUF_SIZE + local rc = C.ngx_http_lua_ffi_pipe_proc_write(r, proc, data, #data, errbuf, + errbuf_size) + if rc == FFI_NO_REQ_CTX then + error("no request ctx found", 2) + end + + if rc == FFI_BAD_CONTEXT then + error(ffi_str(errbuf, errbuf_size[0]), 2) + end + + while true do + if rc == FFI_ERROR then + return nil, ffi_str(errbuf, errbuf_size[0]) + end + + if rc >= 0 then + -- rc holds the bytes sent + return tonumber(rc) + end + + assert(rc == FFI_AGAIN) + + co_yield() + + errbuf = get_string_buf(ERR_BUF_SIZE) + errbuf_size = get_size_ptr() + errbuf_size[0] = ERR_BUF_SIZE + rc = C.ngx_http_lua_ffi_pipe_get_write_result(r, proc, errbuf, + errbuf_size) + end +end + + +local function proc_shutdown(proc, direction) + check_proc_instance(proc) + + local rc + local errbuf = get_string_buf(ERR_BUF_SIZE) + local errbuf_size = get_size_ptr() + errbuf_size[0] = ERR_BUF_SIZE + + if direction == "stdin" then + rc = C.ngx_http_lua_ffi_pipe_proc_shutdown_stdin(proc, errbuf, + errbuf_size) + + elseif direction == "stdout" then + rc = C.ngx_http_lua_ffi_pipe_proc_shutdown_stdout(proc, errbuf, + errbuf_size) + + elseif direction == "stderr" then + rc = C.ngx_http_lua_ffi_pipe_proc_shutdown_stderr(proc, errbuf, + errbuf_size) + + else + error("bad shutdown arg: " .. direction, 2) + end + + if rc == FFI_ERROR then + return nil, ffi_str(errbuf, errbuf_size[0]) + end + + return true +end + + +local proc_wait +do + local reason = ffi_new("char *[1]") + local status = ffi_new("int[1]") + + function proc_wait(proc) + check_proc_instance(proc) + + local r = get_request() + if not r then + error("no request found", 2) + end + + local errbuf = get_string_buf(ERR_BUF_SIZE) + local errbuf_size = get_size_ptr() + errbuf_size[0] = ERR_BUF_SIZE + local rc = C.ngx_http_lua_ffi_pipe_proc_wait(r, proc, reason, status, + errbuf, errbuf_size) + if rc == FFI_NO_REQ_CTX then + error("no request ctx found", 2) + end + + if rc == FFI_BAD_CONTEXT then + error(ffi_str(errbuf, errbuf_size[0]), 2) + end + + if rc == FFI_ERROR then + return nil, ffi_str(errbuf, errbuf_size[0]) + end + + if rc == FFI_OK then + return true, ffi_str(reason[0]), tonumber(status[0]) + end + + if rc == FFI_DECLINED then + return false, ffi_str(reason[0]), tonumber(status[0]) + end + + local ok, exit_reason, exit_status + ok, exit_reason, exit_status = co_yield() + return ok, exit_reason, exit_status + end +end + + +local function proc_kill(proc, signal) + check_proc_instance(proc) + + if type(signal) ~= "number" then + error("bad signal arg: number expected, got " .. tostring(signal), 2) + end + + local errbuf = get_string_buf(ERR_BUF_SIZE) + local errbuf_size = get_size_ptr() + errbuf_size[0] = ERR_BUF_SIZE + + local rc = C.ngx_http_lua_ffi_pipe_proc_kill(proc, signal, errbuf, + errbuf_size) + if rc == FFI_ERROR then + return nil, ffi_str(errbuf, errbuf_size[0]) + end + + return true +end + + +local mt = { + __gc = C.ngx_http_lua_ffi_pipe_proc_destroy, + + __index = { + pid = function (proc) + return proc._pid + end, + + set_timeouts = function (proc, write_timeout, stdout_read_timeout, + stderr_read_timeout, wait_timeout) + proc_set_timeouts(proc, write_timeout, stdout_read_timeout, + stderr_read_timeout, wait_timeout) + end, + + stdout_read_all = function (proc) + local data, err, partial = proc_read(proc, 0, PIPE_READ_ALL, 0) + return data, err, partial + end, + + stdout_read_bytes = function (proc, len) + if len <= 0 then + if len < 0 then + error("bad len argument", 2) + end + + return "" + end + + local data, err, partial = proc_read(proc, 0, PIPE_READ_BYTES, len) + return data, err, partial + end, + + stdout_read_line = function (proc) + local data, err, partial = proc_read(proc, 0, PIPE_READ_LINE, 0) + return data, err, partial + end, + + stdout_read_any = function (proc, max) + if type(max) ~= "number" then + max = tonumber(max) + end + + if not max or max <= 0 then + error("bad max argument", 2) + end + + local data, err, partial = proc_read(proc, 0, PIPE_READ_ANY, max) + return data, err, partial + end, + + stderr_read_all = function (proc) + local data, err, partial = proc_read(proc, 1, PIPE_READ_ALL, 0) + return data, err, partial + end, + + stderr_read_bytes = function (proc, len) + if len <= 0 then + if len < 0 then + error("bad len argument", 2) + end + + return "" + end + + local data, err, partial = proc_read(proc, 1, PIPE_READ_BYTES, len) + return data, err, partial + end, + + stderr_read_line = function (proc) + local data, err, partial = proc_read(proc, 1, PIPE_READ_LINE, 0) + return data, err, partial + end, + + stderr_read_any = function (proc, max) + if type(max) ~= "number" then + max = tonumber(max) + end + + if not max or max <= 0 then + error("bad max argument", 2) + end + + local data, err, partial = proc_read(proc, 1, PIPE_READ_ANY, max) + return data, err, partial + end, + + write = proc_write, + shutdown = proc_shutdown, + wait = proc_wait, + kill = proc_kill, + } +} +local Proc = ffi.metatype("ngx_http_lua_ffi_pipe_proc_t", mt) + + +local pipe_spawn +do + local sh_exe = "/bin/sh" + local opt_c = "-c" + local shell_args = ffi_new("const char* [?]", 4) + shell_args[0] = sh_exe + shell_args[1] = opt_c + shell_args[3] = nil + + local write_timeout = 10000 + local stdout_read_timeout = 10000 + local stderr_read_timeout = 10000 + local wait_timeout = 10000 + + -- reference shell cmd's constant strings here to prevent them from getting + -- collected by the Lua GC. + _M._gc_ref_c_opt = opt_c + + function pipe_spawn(args, opts) + if ngx_phase() == "init" then + error("API disabled in the current context", 2) + end + + local exe + local proc_args + local proc_envs + + local args_type = type(args) + if args_type == "table" then + local nargs = 0 + + for i, arg in ipairs(args) do + nargs = nargs + 1 + + if type(arg) ~= "string" then + args[i] = tostring(arg) + end + end + + if nargs == 0 then + error("bad args arg: non-empty table expected", 2) + end + + exe = args[1] + proc_args = ffi_new("const char* [?]", nargs + 1, args) + proc_args[nargs] = nil + + elseif args_type == "string" then + exe = sh_exe + shell_args[2] = args + proc_args = shell_args + + else + error("bad args arg: table expected, got " .. args_type, 2) + end + + local merge_stderr = 0 + local buffer_size = 4096 + local proc = Proc() + + if opts then + merge_stderr = opts.merge_stderr and 1 or 0 + + if opts.buffer_size then + buffer_size = tonumber(opts.buffer_size) + + if not buffer_size or buffer_size < 1 then + error("bad buffer_size option", 2) + end + end + + if opts.environ then + local environ = opts.environ + local environ_type = type(environ) + if environ_type ~= "table" then + error("bad environ option: table expected, got " .. + environ_type, 2) + end + + local nenv = 0 + + for i, env in ipairs(environ) do + nenv = nenv + 1 + + local env_type = type(env) + if env_type ~= "string" then + error("bad value at index " .. i .. " of environ " .. + "option: string expected, got " .. env_type, 2) + end + + if not str_find(env, "=", 2, true) then + error("bad value at index " .. i .. " of environ " .. + "option: 'name=[value]' format expected, got '" .. + env .. "'", 2) + end + end + + if nenv > 0 then + proc_envs = ffi_new("const char* [?]", nenv + 1, environ) + proc_envs[nenv] = nil + end + end + + proc_set_timeouts(proc, + opts.write_timeout or write_timeout, + opts.stdout_read_timeout or stdout_read_timeout, + opts.stderr_read_timeout or stderr_read_timeout, + opts.wait_timeout or wait_timeout) + + else + proc_set_timeouts(proc, + write_timeout, + stdout_read_timeout, + stderr_read_timeout, + wait_timeout) + end + + local errbuf = get_string_buf(ERR_BUF_SIZE) + local errbuf_size = get_size_ptr() + local r = get_request() + errbuf_size[0] = ERR_BUF_SIZE + local rc = C.ngx_http_lua_ffi_pipe_spawn(r, proc, exe, proc_args, + merge_stderr, buffer_size, + proc_envs, errbuf, errbuf_size) + if rc == FFI_ERROR then + return nil, ffi_str(errbuf, errbuf_size[0]) + end + + return proc + end +end -- do + + +_M.spawn = pipe_spawn + + +return _M diff --git a/lib/ngx/pipe.md b/lib/ngx/pipe.md new file mode 100644 index 000000000..131c4da3c --- /dev/null +++ b/lib/ngx/pipe.md @@ -0,0 +1,584 @@ +Name +==== + +`ngx.pipe` - spawn and communicate with OS processes via stdin/stdout/stderr in +a non-blocking fashion. + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Methods](#methods) + * [spawn](#spawn) + * [set_timeouts](#set_timeouts) + * [wait](#wait) + * [pid](#pid) + * [kill](#kill) + * [shutdown](#shutdown) + * [write](#write) + * [stderr_read_all](#stderr_read_all) + * [stdout_read_all](#stdout_read_all) + * [stderr_read_line](#stderr_read_line) + * [stdout_read_line](#stdout_read_line) + * [stderr_read_bytes](#stderr_read_bytes) + * [stdout_read_bytes](#stdout_read_bytes) + * [stderr_read_any](#stderr_read_any) + * [stdout_read_any](#stdout_read_any) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered experimental. + +Synopsis +======== + +```nginx +location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local select = select + + local function count_char(...) + local proc = ngx_pipe.spawn({'wc', '-c'}) + local n = select('#', ...) + for i = 1, n do + local arg = select(i, ...) + local bytes, err = proc:write(arg) + if not bytes then + ngx.say(err) + return + end + end + + local ok, err = proc:shutdown('stdin') + if not ok then + ngx.say(err) + return + end + + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + return + end + + ngx.say(data) + end + + count_char(("1234"):rep(2048)) + } +} +``` + +This example counts characters (bytes) directly fed by OpenResty to the UNIX +command `wc`. + +You could not do this with either `io.popen` or `os.execute` because `wc` will +not output the result until its stdin is closed. + +[Back to TOC](#table-of-contents) + +Description +=========== + +This module does not support non-POSIX operating systems like Windows yet. + +If you are not using the Nginx core shipped with OpenResty, you will need to +apply the `socket_cloexec` patch to the standard Nginx core. + +Under the hood, this module uses `fork` and `execvp` with the user-specified +command, and communicate with such spawned processes via the POSIX `pipe` API, +which contributes to the name of this module. + +A signal handler for `SIGCHLD` is registered so that we can receive a +notification once the spawned processes exited. + +We combine the above implementation with Nginx's event mechanism and +OpenResty's Lua coroutine scheduler, in order to ensure communication with the +spawned processes is non-blocking. + +The communication APIs do not work in phases which do not support yielding, +such as `init_worker_by_lua*` or `log_by_lua*`, because there is no way to +yield the current light thread to avoid blocking the OS thread when +communicating with processes in those phases. + +[Back to TOC](#table-of-contents) + +Methods +======= + +spawn +----- +**syntax:** *proc, err = pipe_module.spawn(args, opts?)* + +**context:** *all phases except init_by_lua** + +Creates and returns a new sub-process instance we can communicate with later. + +For example: + +```lua +local ngx_pipe = require "ngx.pipe" +local proc, err = ngx_pipe.spawn({"sh", "-c", "sleep 0.1 && exit 2"}) +if not proc then + ngx.say(err) + return +end +``` + +In case of failure, this function returns `nil` and a string describing the +error. + +The sub-process will be killed via `SIGKILL` if it is still alive when the +instance is collected by the garbage collector. + +Note that `args` should either be a single level array-like Lua table with +string values, or just a single string. + +Some more examples: + +```lua +local proc, err = ngx_pipe.spawn({"ls", "-l"}) + +local proc, err = ngx_pipe.spawn({"perl", "-e", "print 'hello, wolrd'"}) +``` + +If `args` is specified as a string, it will be executed by the operating system +shell, just like `os.execute`. The above example could thus be rewritten as: + +```lua +local ngx_pipe = require "ngx.pipe" +local proc, err = ngx_pipe.spawn("sleep 0.1 && exit 2") +if not proc then + ngx.say(err) + return +end +``` + +In the shell mode, you should be very careful about shell injection attacks +when interpolating variables into command string, especially variables from +untrusted sources. Please make sure that you escape those variables while +assembling the command string. For this reason, it is highly recommended to use +the multi-arguments form (`args` as a table) to specify each command-line +argument explicitly. + +Since by default, Nginx does not pass along the `PATH` system environment +variable, you will need to configure the `env PATH` directive if you wish for +it to be respected during the searching of sub-processes: + +```nginx +env PATH; +... +content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + + local proc = ngx_pipe.spawn({'ls'}) +} +``` + +The optional table argument `opts` can be used to control the behavior of +spawned processes. For instance: + +```lua +local opts = { + merge_stderr = true, + buffer_size = 256, + environ = {"PATH=/tmp/bin", "CWD=/tmp/work"} +} +local proc, err = ngx_pipe.spawn({"sh", "-c", ">&2 echo data"}, opts) +if not proc then + ngx.say(err) + return +end +``` + +The following options are supported: + +* `merge_stderr`: when set to `true`, the output to stderr will be redirected + to stdout in the spawned process. This is similar to doing `2>&1` in a shell. +* `buffer_size`: specifies the buffer size used by reading operations, in + bytes. The default buffer size is `4096`. +* `environ`: specifies environment variables for the spawned process. The value + must be a single-level, array-like Lua table with string values. If the + current platform does not support this option, `nil` plus a string `"environ + option not supported"` will be returned. +* `write_timeout`: specifies the write timeout threshold, in milliseconds. The + default threshold is `10000`. If the threshold is `0`, the write operation + will never time out. +* `stdout_read_timeout`: specifies the stdout read timeout threshold, in + milliseconds. The default threshold is `10000`. If the threshold is `0`, the + stdout read operation will never time out. +* `stderr_read_timeout`: specifies the stderr read timeout threshold, in + milliseconds. The default threshold is `10000`. If the threshold is `0`, the + stderr read operation will never time out. +* `wait_timeout`: specifies the wait timeout threshold, in milliseconds. The + default threshold is `10000`. If the threshold is `0`, the wait operation + will never time out. + +[Back to TOC](#table-of-contents) + +set_timeouts +------------ +**syntax:** *proc:set_timeouts(write_timeout?, stdout_read_timeout?, stderr_read_timeout?, wait_timeout?)* + +Respectively sets: the write timeout threshold, stdout read timeout threshold, +stderr read timeout threshold, and wait timeout threshold. All timeouts are in +milliseconds. + +The default threshold for each timeout is 10 seconds. + +If the specified timeout argument is `nil`, the corresponding timeout threshold +will not be changed. For example: + +```lua +local proc, err = ngx_pipe.spawn({"sleep", "10s"}) + +-- only change the wait_timeout to 0.1 second. +proc:set_timeouts(nil, nil, nil, 100) + +-- only change the send_timeout to 0.1 second. +proc:set_timeouts(100) +``` + +If the specified timeout argument is `0`, the corresponding operation will +never time out. + +[Back to TOC](#table-of-contents) + +wait +---- +**syntax:** *ok, reason, status = proc:wait()* + +**context:** *phases that support yielding* + +Waits until the current sub-process exits. + +It is possible to control how long to wait via [set_timeouts](#set_timeouts). +The default timeout is 10 seconds. + +If process exited with status code zero, the `ok` return value will be `true`. + +If process exited abnormally, the `ok` return value will be `false`. + +The second return value, `reason`, will be a string. Its values may be: + +* `exit`: the process exited by calling `exit(3)`, `_exit(2)`, or by + returning from `main()`. In this case, `status` will be the exit code. +* `signal`: the process was terminated by a signal. In this case, `status` will + be the signal number. + +Note that only one light thread can wait on a process at a time. If another +light thread tries to wait on a process, the return values will be `nil` and +the error string `"pipe busy waiting"`. + +If a thread tries to wait an exited process, the return values will be `nil` +and the error string `"exited"`. + +[Back to TOC](#table-of-contents) + +pid +--- +**syntax:** *pid = proc:pid()* + +Returns the pid number of the sub-process. + +[Back to TOC](#table-of-contents) + +kill +---- +**syntax:** *ok, err = proc:kill(signum)* + +Sends a signal to the sub-process. + +Note that the `signum` argument should be signal's numerical value. If the +specified `signum` is not a number, an error will be thrown. + +You should use [lua-resty-signal's signum() +function](https://github.com/openresty/lua-resty-signal#signum) to convert +signal names to signal numbers in order to ensure portability of your +application. + +In case of success, this method returns `true`. Otherwise, it returns `nil` and +a string describing the error. + +Killing an exited sub-process will return `nil` and the error string +`"exited"`. + +Sending an invalid signal to the process will return `nil` and the error string +`"invalid signal"`. + +[Back to TOC](#table-of-contents) + +shutdown +-------- +**syntax:** *ok, err = proc:shutdown(direction)* + +Closes the specified direction of the current sub-process. + +The `direction` argument should be one of these three values: `stdin`, `stdout` +and `stderr`. + +In case of success, this method returns `true`. Otherwise, it returns `nil` and +a string describing the error. + +If the `merge_stderr` option is specified in [spawn](#spawn), closing the +`stderr` direction will return `nil` and the error string `"merged to stdout"`. + +Shutting down a direction when a light thread is waiting on it (such as during +reading or writing) will abort the light thread and return `true`. + +Shutting down directions of an exited process will return `nil` and the error +string `"closed"`. + +It is fine to shut down the same direction of the same stream multiple times; +no side effects are to be expected. + +[Back to TOC](#table-of-contents) + +write +----- +**syntax:** *nbytes, err = proc:write(data)* + +**context:** *phases that support yielding* + +Writes data to the current sub-process's stdin stream. + +The `data` argument can be a string or a single level array-like Lua table with +string values. + +This method is a synchronous and non-blocking operation that will not return +until *all* the data has been flushed to the sub-process's stdin buffer, or +an error occurs. + +In case of success, it returns the total number of bytes that have been sent. +Otherwise, it returns `nil` and a string describing the error. + +The timeout threshold of this `write` operation can be controlled by the +[set_timeouts](#set_timeouts) method. The default timeout threshold is 10 +seconds. + +When a timeout occurs, the data may be partially written into the sub-process's +stdin buffer and read by the sub-process. + +Only one light thread is allowed to write to the sub-process at a time. If +another light thread tries to write to it, this method will return `nil` and +the error string `"pipe busy writing"`. + +If the `write` operation is aborted by the [shutdown](#shutdown) method, +it will return `nil` and the error string `"aborted"`. + +Writing to an exited sub-process will return `nil` and the error string +`"closed"`. + +[Back to TOC](#table-of-contents) + +stderr_read_all +--------------- +**syntax:** *data, err, partial = proc:stderr_read_all()* + +**context:** *phases that support yielding* + +Reads all data from the current sub-process's stderr stream until it is closed. + +This method is a synchronous and non-blocking operation, just like the +[write](#write) method. + +The timeout threshold of this reading operation can be controlled by +[set_timeouts](#set_timeouts). The default timeout is 10 seconds. + +In case of success, it returns the data received. Otherwise, it returns three +values: `nil`, a string describing the error, and, optionally, the partial data +received so far. + +When `merge_stderr` is specified in [spawn](#spawn), calling `stderr_read_all` +will return `nil` and the error string `"merged to stdout"`. + +Only one light thread is allowed to read from a sub-process's stderr or stdout +stream at a time. If another thread tries to read from the same stream, this +method will return `nil` and the error string `"pipe busy reading"`. + +If the reading operation is aborted by the [shutdown](#shutdown) method, +it will return `nil` and the error string `"aborted"`. + +Streams for stdout and stderr are separated, so at most two light threads may +be reading from a sub-process at a time (one for each stream). + +The same way, a light thread may read from a stream while another light thread +is writing to the sub-process stdin stream. + +Reading from an exited process's stream will return `nil` and the error string +`"closed"`. + +[Back to TOC](#table-of-contents) + +stdout_read_all +--------------- +**syntax:** *data, err, partial = proc:stdout_read_all()* + +**context:** *phases that support yielding* + +Similar to the [stderr_read_all](#stderr_read_all) method, but reading from the +stdout stream of the sub-process. + +[Back to TOC](#table-of-contents) + +stderr_read_line +---------------- +**syntax:** *data, err, partial = proc:stderr_read_line()* + +**context:** *phases that support yielding* + +Reads from stderr like [stderr_read_all](#stderr_read_all), but only reads a +single line of data. + +When `merge_stderr` is specified in [spawn](#spawn), calling `stderr_read_line` +will return `nil` plus the error string `"merged to stdout"`. + +When the data stream is truncated without a new-line character, it returns 3 +values: `nil`, the error string `"closed"`, and the partial data received so +far. + +The line should be terminated by a `Line Feed` (LF) character (ASCII 10), +optionally preceded by a `Carriage Return` (CR) character (ASCII 13). The CR +and LF characters are not included in the returned line data. + +[Back to TOC](#table-of-contents) + +stdout_read_line +---------------- +**syntax:** *data, err, partial = proc:stdout_read_line()* + +**context:** *phases that support yielding* + +Similar to [stderr_read_line](#stderr_read_line), but reading from the +stdout stream of the sub-process. + +[Back to TOC](#table-of-contents) + +stderr_read_bytes +----------------- +**syntax:** *data, err, partial = proc:stderr_read_bytes(len)* + +**context:** *phases that support yielding* + +Reads from stderr like [stderr_read_all](#stderr_read_all), but only reads the +specified number of bytes. + +If `merge_stderr` is specified in [spawn](#spawn), calling `stderr_read_bytes` +will return `nil` plus the error string `"merged to stdout"`. + +If the data stream is truncated (fewer bytes of data available than requested), +this method returns 3 values: `nil`, the error string `"closed"`, and the +partial data string received so far. + +[Back to TOC](#table-of-contents) + +stdout_read_bytes +----------------- +**syntax:** *data, err, partial = proc:stdout_read_bytes(len)* + +**context:** *phases that support yielding* + +Similar to [stderr_read_bytes](#stderr_read_bytes), but reading from the +stdout stream of the sub-process. + +[Back to TOC](#table-of-contents) + +stderr_read_any +--------------- +**syntax:** *data, err = proc:stderr_read_any(max)* + +**context:** *phases that support yielding* + +Reads from stderr like [stderr_read_all](#stderr_read_all), but returns +immediately when any amount of data is received. + +At most `max` bytes are received. + +If `merge_stderr` is specified in [spawn](#spawn), calling `stderr_read_any` +will return `nil` plus the error string `"merged to stdout"`. + +If the received data is more than `max` bytes, this method will return with +exactly `max` bytes of data. The remaining data in the underlying receive +buffer can be fetched with a subsequent reading operation. + +[Back to TOC](#table-of-contents) + +stdout_read_any +--------------- +**syntax:** *data, err = proc:stdout_read_any(max)* + +**context:** *phases that support yielding* + +Similar to [stderr_read_any](#stderr_read_any), but reading from the stdout +stream of the sub-process. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list +is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for +Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2018, by OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) diff --git a/lib/ngx/process.lua b/lib/ngx/process.lua new file mode 100644 index 000000000..fbf4bd9b5 --- /dev/null +++ b/lib/ngx/process.lua @@ -0,0 +1,115 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local base = require "resty.core.base" +base.allows_subsystem('http', 'stream') + +local ffi = require 'ffi' +local errmsg = base.get_errmsg_ptr() +local FFI_ERROR = base.FFI_ERROR +local ffi_str = ffi.string +local tonumber = tonumber +local subsystem = ngx.config.subsystem + +if subsystem == 'http' then + require "resty.core.phase" -- for ngx.get_phase +end + +local ngx_phase = ngx.get_phase + +local process_type_names = { + [0 ] = "single", + [1 ] = "master", + [2 ] = "signaller", + [3 ] = "worker", + [4 ] = "helper", + [99] = "privileged agent", +} + + +local C = ffi.C +local _M = { version = base.version } + +local ngx_lua_ffi_enable_privileged_agent +local ngx_lua_ffi_get_process_type +local ngx_lua_ffi_process_signal_graceful_exit +local ngx_lua_ffi_master_pid + +if subsystem == 'http' then + ffi.cdef[[ + int ngx_http_lua_ffi_enable_privileged_agent(char **err, + unsigned int connections); + int ngx_http_lua_ffi_get_process_type(void); + void ngx_http_lua_ffi_process_signal_graceful_exit(void); + int ngx_http_lua_ffi_master_pid(void); + ]] + + ngx_lua_ffi_enable_privileged_agent = + C.ngx_http_lua_ffi_enable_privileged_agent + ngx_lua_ffi_get_process_type = C.ngx_http_lua_ffi_get_process_type + ngx_lua_ffi_process_signal_graceful_exit = + C.ngx_http_lua_ffi_process_signal_graceful_exit + ngx_lua_ffi_master_pid = C.ngx_http_lua_ffi_master_pid + +else + ffi.cdef[[ + int ngx_stream_lua_ffi_enable_privileged_agent(char **err, + unsigned int connections); + int ngx_stream_lua_ffi_get_process_type(void); + void ngx_stream_lua_ffi_process_signal_graceful_exit(void); + int ngx_stream_lua_ffi_master_pid(void); + ]] + + ngx_lua_ffi_enable_privileged_agent = + C.ngx_stream_lua_ffi_enable_privileged_agent + ngx_lua_ffi_get_process_type = C.ngx_stream_lua_ffi_get_process_type + ngx_lua_ffi_process_signal_graceful_exit = + C.ngx_stream_lua_ffi_process_signal_graceful_exit + ngx_lua_ffi_master_pid = C.ngx_stream_lua_ffi_master_pid +end + + +function _M.type() + local typ = ngx_lua_ffi_get_process_type() + return process_type_names[tonumber(typ)] +end + + +function _M.enable_privileged_agent(connections) + if ngx_phase() ~= "init" then + return nil, "API disabled in the current context" + end + + connections = connections or 512 + + if type(connections) ~= "number" or connections < 0 then + return nil, "bad 'connections' argument: " .. + "number expected and greater than 0" + end + + local rc = ngx_lua_ffi_enable_privileged_agent(errmsg, connections) + + if rc == FFI_ERROR then + return nil, ffi_str(errmsg[0]) + end + + return true +end + + +function _M.signal_graceful_exit() + ngx_lua_ffi_process_signal_graceful_exit() +end + + +function _M.get_master_pid() + local pid = ngx_lua_ffi_master_pid() + if pid == FFI_ERROR then + return nil + end + + return tonumber(pid) +end + + +return _M diff --git a/lib/ngx/process.md b/lib/ngx/process.md new file mode 100644 index 000000000..250356bc6 --- /dev/null +++ b/lib/ngx/process.md @@ -0,0 +1,232 @@ +Name +==== + +`ngx.process` - manage the nginx processes for OpenResty/ngx_lua. + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Functions](#functions) + * [type](#type) + * [enable_privileged_agent](#enable_privileged_agent) + * [signal_graceful_exit](#signal_graceful_exit) + * [get_master_pid](#get_master_pid) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered experimental. +The API is still in flux and may change in the future without notice. + +Synopsis +======== + +Enables privileged agent process, gets process type, and then gets the master process PID: + +```nginx +# http config +init_by_lua_block { + local process = require "ngx.process" + + -- enables privileged agent process + local ok, err = process.enable_privileged_agent() + if not ok then + ngx.log(ngx.ERR, "enables privileged agent failed error:", err) + end + + -- output process type + ngx.log(ngx.INFO, "process type: ", process.type()) +} + +init_worker_by_lua_block { + local process = require "ngx.process" + ngx.log(ngx.INFO, "process type: ", process.type()) +} + +server { + # ... + location = /t { + content_by_lua_block { + local process = require "ngx.process" + ngx.say("process type: ", process.type()) + ngx.say("master process pid: ", process.get_master_pid() or "-") + } + } +} + +``` + +The example config above produces an output to `error.log` when +server starts: + +``` +[lua] init_by_lua:11: process type: master +[lua] init_worker_by_lua:3: process type: privileged agent +[lua] init_worker_by_lua:3: process type: worker +``` + +The example location above produces the following response body: + +``` +process type: worker +master process pid: 8261 +``` + +[Back to TOC](#table-of-contents) + +Functions +========= + +type +---- +**syntax:** *type_name = process_module.type()* + +**context:** *any* + +Returns the type of the current Nginx process. Depending on the calling context +and current process, the type can be one of: + +* `master`: returned when this function is called from within the master + process +* `worker`: returned when this function is called from within a worker process +* `single`: returned when Nginx is running in the single process mode +* `signaller`: returned when Nginx is running as a signaller process +* `privileged agent`: returned when this funtion is called from within a + privileged agent process + +For example: + +```lua +local process = require "ngx.process" +ngx.say("process type:", process.type()) -- RESPONSE: worker +``` + +[Back to TOC](#table-of-contents) + +enable_privileged_agent +----------------------- +**syntax:** *ok, err = process_module.enable_privileged_agent(connections)* + +**context:** *init_by_lua** + +Enables the privileged agent process in Nginx. + +The privileged agent process does not listen on any virtual server ports like those worker processes. +And it uses the same system account as the nginx master process, which is usually a privileged account +like `root`. + +The `init_worker_by_lua*` directive handler still runs in the privileged agent process. And one can +use the [type](#type) function provided by this module to check if the current process is a privileged +agent. + +The argument connections sets the maximum number of simultaneous connections that can be opened by privileged agent process. + +In case of failures, returns `nil` and a string describing the error. + +[Back to TOC](#table-of-contents) + +signal_graceful_exit +-------------------- +**syntax:** *process_module.signal_graceful_exit()* + +**context:** *any* + +Signals the *current* nginx (worker) process to quit gracefully, i.e., after all the timers have expired (in time or expired prematurely). + +Note that this API function simply sets the nginx global C variable `ngx_quit` to signal the nginx event +loop directly. No UNIX signals or IPC are involved here. + +WARNING: the official NGINX core does not perform the graceful exiting procedure when the [master_process](http://nginx.org/r/master_process) +directive is turned `off`. The OpenResty's NGINX core has a +[custom patch](https://github.com/openresty/openresty/blob/master/patches/nginx-1.11.2-single_process_graceful_exit.patch) +applied, which fixes this issue. + +[Back to TOC](#table-of-contents) + +get_master_pid +-------------- +**syntax:** *pid = process_module.get_master_pid()* + +**context:** *any* + +Returns a number value for the nginx master process's process ID (or PID). + +This function requires NGINX 1.13.8+ cores to work properly. Otherwise it returns `nil`. + +This feature first appeared in lua-resty-core v0.1.14. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Yuansheng Wang <membphis@gmail.com> (membphis), OpenResty Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2017, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) + diff --git a/lib/ngx/re.lua b/lib/ngx/re.lua new file mode 100644 index 000000000..cb2c886dc --- /dev/null +++ b/lib/ngx/re.lua @@ -0,0 +1,318 @@ +-- I hereby assign copyright in this code to the lua-resty-core project, +-- to be licensed under the same terms as the rest of the code. + + +local base = require "resty.core.base" +local ffi = require 'ffi' +local bit = require "bit" +local core_regex = require "resty.core.regex" + + +if core_regex.no_pcre then + error("no support for 'ngx.re' module: OpenResty was " .. + "compiled without PCRE support", 3) +end + + +local C = ffi.C +local ffi_str = ffi.string +local sub = string.sub +local error = error +local type = type +local band = bit.band +local new_tab = base.new_tab +local tostring = tostring +local math_max = math.max +local math_min = math.min +local is_regex_cache_empty = core_regex.is_regex_cache_empty +local re_match_compile = core_regex.re_match_compile +local destroy_compiled_regex = core_regex.destroy_compiled_regex +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local FFI_OK = base.FFI_OK +local subsystem = ngx.config.subsystem + + +local MAX_ERR_MSG_LEN = 128 +local FLAG_DFA = 0x02 +local PCRE_ERROR_NOMATCH = -1 +local DEFAULT_SPLIT_RES_SIZE = 4 + + +local split_ctx = new_tab(0, 1) +local ngx_lua_ffi_set_jit_stack_size +local ngx_lua_ffi_exec_regex + + +if subsystem == 'http' then + ffi.cdef[[ +int ngx_http_lua_ffi_set_jit_stack_size(int size, unsigned char *errstr, + size_t *errstr_size); + ]] + + ngx_lua_ffi_exec_regex = C.ngx_http_lua_ffi_exec_regex + ngx_lua_ffi_set_jit_stack_size = C.ngx_http_lua_ffi_set_jit_stack_size + +elseif subsystem == 'stream' then + ffi.cdef[[ +int ngx_stream_lua_ffi_set_jit_stack_size(int size, unsigned char *errstr, + size_t *errstr_size); + ]] + + ngx_lua_ffi_exec_regex = C.ngx_stream_lua_ffi_exec_regex + ngx_lua_ffi_set_jit_stack_size = C.ngx_stream_lua_ffi_set_jit_stack_size +end + + +local _M = { version = base.version } + + +local function re_split_helper(subj, compiled, compile_once, flags, ctx) + local rc + do + local pos = math_max(ctx.pos, 0) + + rc = ngx_lua_ffi_exec_regex(compiled, flags, subj, #subj, pos) + end + + if rc == PCRE_ERROR_NOMATCH then + return nil, nil, nil + end + + if rc < 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, nil, "pcre_exec() failed: " .. rc + end + + if rc == 0 then + if band(flags, FLAG_DFA) == 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, nil, "capture size too small" + end + + rc = 1 + end + + local caps = compiled.captures + local ncaps = compiled.ncaptures + + local from = caps[0] + local to = caps[1] + + if from < 0 or to < 0 then + return nil, nil, nil + end + + if from == to then + -- empty match, skip to next char + ctx.pos = to + 1 + + else + ctx.pos = to + end + + -- convert to Lua string indexes + + from = from + 1 + to = to + 1 + + -- retrieve the first sub-match capture if any + + if ncaps > 0 and rc > 1 then + return from, to, sub(subj, caps[2] + 1, caps[3]) + end + + return from, to +end + + +function _M.split(subj, regex, opts, ctx, max, res) + -- we need to cast this to strings to avoid exceptions when they are + -- something else. + -- needed because of further calls to string.sub in this function. + subj = tostring(subj) + + if not ctx then + ctx = split_ctx + ctx.pos = 1 -- set or reset upvalue field + + elseif not ctx.pos then + -- ctx provided by user but missing pos field + ctx.pos = 1 + end + + max = max or 0 + + if not res then + -- limit the initial arr_n size of res to a reasonable value + -- 0 < narr <= DEFAULT_SPLIT_RES_SIZE + local narr = DEFAULT_SPLIT_RES_SIZE + if max > 0 then + -- the user specified a valid max limiter if max > 0 + narr = math_min(narr, max) + end + + res = new_tab(narr, 0) + + elseif type(res) ~= "table" then + error("res is not a table", 2) + end + + local len = #subj + if ctx.pos > len then + res[1] = nil + return res + end + + -- compile regex + + local compiled, compile_once, flags = re_match_compile(regex, opts) + if compiled == nil then + -- compiled_once holds the error string + return nil, compile_once + end + + local sub_idx = ctx.pos + local res_idx = 0 + local last_empty_match + + -- update to split_helper PCRE indexes + ctx.pos = sub_idx - 1 + + -- splitting: with and without a max limiter + + if max > 0 then + local count = 1 + + while count < max do + local from, to, capture, err = re_split_helper(subj, compiled, + compile_once, flags, ctx) + if err then + return nil, err + end + + if not from then + break + end + + if last_empty_match then + sub_idx = last_empty_match + end + + if from == to then + last_empty_match = from + end + + if from > sub_idx or not last_empty_match then + count = count + 1 + res_idx = res_idx + 1 + res[res_idx] = sub(subj, sub_idx, from - 1) + + if capture then + res_idx = res_idx + 1 + res[res_idx] = capture + end + + sub_idx = to + + if sub_idx > len then + break + end + end + end + + else + while true do + local from, to, capture, err = re_split_helper(subj, compiled, + compile_once, flags, ctx) + if err then + return nil, err + end + + if not from then + break + end + + if last_empty_match then + sub_idx = last_empty_match + end + + if from == to then + last_empty_match = from + end + + if from > sub_idx or not last_empty_match then + res_idx = res_idx + 1 + res[res_idx] = sub(subj, sub_idx, from - 1) + + if capture then + res_idx = res_idx + 1 + res[res_idx] = capture + end + + sub_idx = to + + if sub_idx > len then + break + end + end + end + + end + + if not compile_once then + destroy_compiled_regex(compiled) + end + + -- trailing nil for non-cleared res tables + + -- delete empty trailing ones (without max) + if max <= 0 and sub_idx > len then + for ety_idx = res_idx, 1, -1 do + if res[ety_idx] ~= "" then + res_idx = ety_idx + break + end + + res[ety_idx] = nil + end + else + res_idx = res_idx + 1 + res[res_idx] = sub(subj, sub_idx) + end + + res[res_idx + 1] = nil + + return res +end + + +function _M.opt(option, value) + if option == "jit_stack_size" then + if not is_regex_cache_empty() then + error("changing jit stack size is not allowed when some " .. + "regexs have already been compiled and cached", 2) + end + + local errbuf = get_string_buf(MAX_ERR_MSG_LEN) + local sizep = get_size_ptr() + sizep[0] = MAX_ERR_MSG_LEN + + local rc = ngx_lua_ffi_set_jit_stack_size(value, errbuf, sizep) + + if rc == FFI_OK then + return + end + + error(ffi_str(errbuf, sizep[0]), 2) + end + + error("unrecognized option name", 2) +end + + +return _M diff --git a/lib/ngx/re.md b/lib/ngx/re.md new file mode 100644 index 000000000..c946bbd76 --- /dev/null +++ b/lib/ngx/re.md @@ -0,0 +1,249 @@ +Name +==== + +ngx.re - Lua API for convenience utilities for `ngx.re`. + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Methods](#methods) + * [split](#split) + * [opt](#opt) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered experimental. + +Synopsis +======== + +```lua +local ngx_re = require "ngx.re" + +-- split +local res, err = ngx_re.split("a,b,c,d", ",") +--> res is now {"a", "b", "c", "d"} + +-- opt +ngx_re.opt("jit_stack_size", 128 * 1024) +--> the PCRE jit stack can now handle more complex regular expressions +``` + +[Back to TOC](#table-of-contents) + +Description +=========== + +This Lua module provides a Lua API which implements convenience utilities for +the `ngx.re` API. + +[Back to TOC](#table-of-contents) + +Methods +======= + +All the methods of this module are static (or module-level). That is, you do +not need an object (or instance) to call these methods. + +[Back to TOC](#table-of-contents) + +split +----- +**syntax:** *res, err = ngx_re.split(subject, regex, options?, ctx?, max?, res?)* + +Splits the `subject` string using the Perl compatible regular expression +`regex` with the optional `options`. + +This function returns a Lua (array) table (with integer keys) containing the +split values. + +In case of error, `nil` will be returned as well as a string describing the +error. + +When `regex` contains a sub-match capturing group, and when such a match is +found, the first submatch capture will be inserted in between each split +value, like so: + +```lua +local ngx_re = require "ngx.re" + +local res, err = ngx_re.split("a,b,c,d", "(,)") +-- res is now {"a", ",", "b", ",", "c", ",", "d"} +``` + +When `regex` is empty string `""`, the `subject` will be split into chars, +like so: + +```lua +local ngx_re = require "ngx.re" + +local res, err = ngx_re.split("abcd", "") +-- res is now {"a", "b", "c", "d"} +``` + +The optional `ctx` table argument can be a Lua table holding an optional `pos` +field. When the `pos` field in the `ctx` table argument is specified, +`ngx_re.split` will start splitting the `subject` from that index: + +```lua +local ngx_re = require "ngx.re" + +local res, err = ngx_re.split("a,b,c,d", ",", nil, {pos = 5}) +-- res is now {"c", "d"} +``` + +The optional `max` argument is a number that when specified, will prevent +`ngx_re.split` from adding more than `max` matches to the `res` array: + +```lua +local ngx_re = require "ngx.re" + +local res, err = ngx_re.split("a,b,c,d", ",", nil, nil, 3) +-- res is now {"a", "b", "c,d"} +``` + +Specifying `max <= 0` disables this behavior, meaning that the number of +results won't be limited. + +The optional 6th argument `res` can be a table that `ngx_re.split` will re-use +to hold the results instead of creating a new one, which can improve +performance in hot code paths. It is used like so: + +```lua +local ngx_re = require "ngx.re" + +local my_table = {"hello world"} + +local res, err = ngx_re.split("a,b,c,d", ",", nil, nil, nil, my_table) +-- res/my_table is now {"a", "b", "c", "d"} +``` + +When provided with a `res` table, `ngx_re.split` won't clear the table +for performance reasons, but will rather insert a trailing `nil` value +when the split is completed: + +```lua +local ngx_re = require "ngx.re" + +local my_table = {"W", "X", "Y", "Z"} + +local res, err = ngx_re.split("a,b", ",", nil, nil, nil, my_table) +-- res/my_table is now {"a", "b", nil, "Z"} +``` + +When the trailing `nil` is not enough for your purpose, you should +clear the table yourself before feeding it into the `split` function. + +[Back to TOC](#table-of-contents) + +opt +----- +**syntax:** *ngx_re.opt(option, value)* + +Allows changing of regex settings. Currently, it can only change the +`jit_stack_size` of the PCRE engine, like so: + +```nginx + + init_by_lua_block { require "ngx.re".opt("jit_stack_size", 200 * 1024) } + + server { + location /re { + content_by_lua_block { + -- full regex and string are taken from https://github.com/JuliaLang/julia/issues/8278 + local very_long_string = [[71.163.72.113 - - [30/Jul/2014:16:40:55 -0700] ...]] + local very_complicated_regex = [[([\d\.]+) ([\w.-]+) ([\w.-]+) (\[.+\]) ...]] + local from, to, err = ngx.re.find(very_long_string, very_complicated_regex, "jo") + + -- with the regular jit_stack_size, we would get the error 'pcre_exec() failed: -27' + -- instead, we get a match + ngx.print(from .. "-" .. to) -- prints '1-1563' + } + } + } +``` + +The `jit_stack_size` cannot be set to a value lower than PCRE's default of 32K. + +This method requires the PCRE library enabled in Nginx. + +This feature was first introduced in the `v0.1.12` release. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list +is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for +Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Thibault Charbonnier - ([@thibaultcha](https://github.com/thibaultcha)) + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2016-2017, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) diff --git a/lib/ngx/req.lua b/lib/ngx/req.lua new file mode 100644 index 000000000..33bdd2d3c --- /dev/null +++ b/lib/ngx/req.lua @@ -0,0 +1,20 @@ +-- Copyright (C) by OpenResty Inc. + + +local base = require "resty.core.base" +base.allows_subsystem("http") + + +local core_request = require "resty.core.request" +local set_req_header = core_request.set_req_header + + +local _M = { version = base.version } + + +function _M.add_header(key, value) + set_req_header(key, value, false) +end + + +return _M diff --git a/lib/ngx/req.md b/lib/ngx/req.md new file mode 100644 index 000000000..c1beedf1c --- /dev/null +++ b/lib/ngx/req.md @@ -0,0 +1,139 @@ +Name +==== + +ngx.req - Lua API for HTTP request handling. + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Methods](#methods) + * [add_header](#add_header) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered experimental. + +Synopsis +======== + +```lua +local ngx_req = require "ngx.req" + +-- add_header +ngx_req.add_header("Foo", "bar") +ngx_req.add_header("Foo", "baz") + +--> there will be two new headers in the HTTP request: +--> Foo: bar and Foo: baz +``` + +[Back to TOC](#table-of-contents) + +Description +=========== + +This module provides a Lua API to handle HTTP requests. + +[Back to TOC](#table-of-contents) + +Methods +======= + +All methods provided by this module are static (or module-level). That is, you +do not need an object (or instance) to call these methods. + +[Back to TOC](#table-of-contents) + +add_header +---------- +**syntax:** *ngx_req.add_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** + +This method adds the specified header and its value to the current +request. It works similarly as +[ngx.req.set_header](https://github.com/openresty/lua-nginx-module#ngxreqset_header), +with the exception that when the header already exists, the specified value(s) +will be appended instead of overriden. + +The first argument `header_name` must be a non-empty string. + +When the specified `header_name` is a builtin header (e.g. `User-Agent`), this +method will override its values. + +The `header_value` argument can either be a string or a non-empty, array-like +table. A `nil` or empty table value will cause this function to throw an error. + +This feature was first introduced in the `v0.1.18` release. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list +is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for +Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2016-2019, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* library [lua-resty-core](https://github.com/openresty/lua-resty-core) +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) + diff --git a/lib/ngx/resp.lua b/lib/ngx/resp.lua new file mode 100644 index 000000000..b02847fc9 --- /dev/null +++ b/lib/ngx/resp.lua @@ -0,0 +1,18 @@ +-- Copyright (C) Yichun Zhang. All rights reserved. + + +local base = require "resty.core.base" +base.allows_subsystem('http') +local core_response = require "resty.core.response" +local set_resp_header = core_response.set_resp_header + + +local _M = { version = base.version } + + +function _M.add_header(key, value) + set_resp_header(nil, key, value, true) +end + + +return _M diff --git a/lib/ngx/resp.md b/lib/ngx/resp.md new file mode 100644 index 000000000..f8253629a --- /dev/null +++ b/lib/ngx/resp.md @@ -0,0 +1,133 @@ +Name +==== + +ngx.resp - Lua API for HTTP response handling. + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Methods](#methods) + * [add_header](#add_header) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered experimental. + +Synopsis +======== + +```lua +local ngx_resp = require "ngx.resp" + +-- add_header +ngx_resp.add_header("Foo", "bar") +ngx_resp.add_header("Foo", "baz") +--> there will be two new headers in HTTP response: +--> Foo: bar and Foo: baz +``` + +[Back to TOC](#table-of-contents) + +Description +=========== + +This Lua module provides Lua API which could be used to handle HTTP response. + +[Back to TOC](#table-of-contents) + +Methods +======= + +All the methods of this module are static (or module-level). That is, you do +not need an object (or instance) to call these methods. + +[Back to TOC](#table-of-contents) + +add_header +---------- +**syntax:** *ngx_resp.add_header(header_name, header_value)* + +This function adds specified header with corresponding value to the response of +current request. The `header_value` could be either a string or a table. + +The `ngx.resp.add_header` works mostly like: +* [ngx.header.HEADER](https://github.com/openresty/lua-nginx-module#ngxheaderheader) +* Nginx's [add_header](http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header) directive. + +However, unlike `ngx.header.HEADER`, this method appends new header to the old +one instead of overriding it. + +Unlike `add_header` directive, this method will override the builtin header +instead of appending it. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list +is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for +Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2018, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) + diff --git a/lib/ngx/semaphore.lua b/lib/ngx/semaphore.lua new file mode 100644 index 000000000..58293c5e4 --- /dev/null +++ b/lib/ngx/semaphore.lua @@ -0,0 +1,193 @@ +-- Copyright (C) Yichun Zhang (agentzh) +-- Copyright (C) cuiweixie +-- I hereby assign copyright in this code to the lua-resty-core project, +-- to be licensed under the same terms as the rest of the code. + + +local base = require "resty.core.base" +base.allows_subsystem('http', 'stream') + + +local ffi = require 'ffi' +local FFI_OK = base.FFI_OK +local FFI_ERROR = base.FFI_ERROR +local FFI_DECLINED = base.FFI_DECLINED +local ffi_new = ffi.new +local ffi_str = ffi.string +local ffi_gc = ffi.gc +local C = ffi.C +local type = type +local error = error +local tonumber = tonumber +local get_request = base.get_request +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local setmetatable = setmetatable +local co_yield = coroutine._yield +local ERR_BUF_SIZE = 128 +local subsystem = ngx.config.subsystem + + +local errmsg = base.get_errmsg_ptr() +local psem +local ngx_lua_ffi_sema_new +local ngx_lua_ffi_sema_post +local ngx_lua_ffi_sema_count +local ngx_lua_ffi_sema_wait +local ngx_lua_ffi_sema_gc + + +if subsystem == 'http' then + ffi.cdef[[ + struct ngx_http_lua_sema_s; + typedef struct ngx_http_lua_sema_s ngx_http_lua_sema_t; + + int ngx_http_lua_ffi_sema_new(ngx_http_lua_sema_t **psem, + int n, char **errmsg); + + int ngx_http_lua_ffi_sema_post(ngx_http_lua_sema_t *sem, int n); + + int ngx_http_lua_ffi_sema_count(ngx_http_lua_sema_t *sem); + + int ngx_http_lua_ffi_sema_wait(ngx_http_request_t *r, + ngx_http_lua_sema_t *sem, int wait_ms, + unsigned char *errstr, size_t *errlen); + + void ngx_http_lua_ffi_sema_gc(ngx_http_lua_sema_t *sem); + ]] + + + psem = ffi_new("ngx_http_lua_sema_t *[1]") + ngx_lua_ffi_sema_new = C.ngx_http_lua_ffi_sema_new + ngx_lua_ffi_sema_post = C.ngx_http_lua_ffi_sema_post + ngx_lua_ffi_sema_count = C.ngx_http_lua_ffi_sema_count + ngx_lua_ffi_sema_wait = C.ngx_http_lua_ffi_sema_wait + ngx_lua_ffi_sema_gc = C.ngx_http_lua_ffi_sema_gc + +elseif subsystem == 'stream' then + ffi.cdef[[ + struct ngx_stream_lua_sema_s; + typedef struct ngx_stream_lua_sema_s ngx_stream_lua_sema_t; + + int ngx_stream_lua_ffi_sema_new(ngx_stream_lua_sema_t **psem, + int n, char **errmsg); + + int ngx_stream_lua_ffi_sema_post(ngx_stream_lua_sema_t *sem, int n); + + int ngx_stream_lua_ffi_sema_count(ngx_stream_lua_sema_t *sem); + + int ngx_stream_lua_ffi_sema_wait(ngx_stream_lua_request_t *r, + ngx_stream_lua_sema_t *sem, int wait_ms, + unsigned char *errstr, size_t *errlen); + + void ngx_stream_lua_ffi_sema_gc(ngx_stream_lua_sema_t *sem); + ]] + + + psem = ffi_new("ngx_stream_lua_sema_t *[1]") + ngx_lua_ffi_sema_new = C.ngx_stream_lua_ffi_sema_new + ngx_lua_ffi_sema_post = C.ngx_stream_lua_ffi_sema_post + ngx_lua_ffi_sema_count = C.ngx_stream_lua_ffi_sema_count + ngx_lua_ffi_sema_wait = C.ngx_stream_lua_ffi_sema_wait + ngx_lua_ffi_sema_gc = C.ngx_stream_lua_ffi_sema_gc +end + + +local _M = { version = base.version } +local mt = { __index = _M } + + +function _M.new(n) + n = tonumber(n) or 0 + if n < 0 then + error("no negative number", 2) + end + + local ret = ngx_lua_ffi_sema_new(psem, n, errmsg) + if ret == FFI_ERROR then + return nil, ffi_str(errmsg[0]) + end + + local sem = psem[0] + + ffi_gc(sem, ngx_lua_ffi_sema_gc) + + return setmetatable({ sem = sem }, mt) +end + + +function _M.wait(self, seconds) + if type(self) ~= "table" or type(self.sem) ~= "cdata" then + error("not a semaphore instance", 2) + end + + local r = get_request() + if not r then + error("no request found") + end + + local milliseconds = tonumber(seconds) * 1000 + if milliseconds < 0 then + error("no negative number", 2) + end + + local cdata_sem = self.sem + + local err = get_string_buf(ERR_BUF_SIZE) + local errlen = get_size_ptr() + errlen[0] = ERR_BUF_SIZE + + local ret = ngx_lua_ffi_sema_wait(r, cdata_sem, + milliseconds, err, errlen) + + if ret == FFI_ERROR then + return nil, ffi_str(err, errlen[0]) + end + + if ret == FFI_OK then + return true + end + + if ret == FFI_DECLINED then + return nil, "timeout" + end + + -- Note: we cannot use the tail-call form here since we + -- might need the current function call's activation + -- record to hold the reference to our semaphore object + -- to prevent it from getting GC'd prematurely. + local ok + ok, err = co_yield() + return ok, err +end + + +function _M.post(self, n) + if type(self) ~= "table" or type(self.sem) ~= "cdata" then + error("not a semaphore instance", 2) + end + + local cdata_sem = self.sem + + local num = n and tonumber(n) or 1 + if num < 1 then + error("positive number required", 2) + end + + -- always return NGX_OK + ngx_lua_ffi_sema_post(cdata_sem, num) + + return true +end + + +function _M.count(self) + if type(self) ~= "table" or type(self.sem) ~= "cdata" then + error("not a semaphore instance", 2) + end + + return ngx_lua_ffi_sema_count(self.sem) +end + + +return _M diff --git a/lib/ngx/semaphore.md b/lib/ngx/semaphore.md new file mode 100644 index 000000000..0d78b7baa --- /dev/null +++ b/lib/ngx/semaphore.md @@ -0,0 +1,356 @@ +Name +==== + +ngx.semaphore - light thread semaphore for OpenResty/ngx_lua. + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) + * [Synchronizing threads in the same context](#synchronizing-threads-in-the-same-context) + * [Synchronizing threads in different contexts](#synchronizing-threads-in-different-contexts) +* [Description](#description) +* [Methods](#methods) + * [new](#new) + * [post](#post) + * [wait](#wait) + * [count](#count) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is production ready. + +Synopsis +======== + +Synchronizing threads in the same context +----------------------------------------- + +```nginx +location = /t { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sema = semaphore.new() + + local function handler() + ngx.say("sub thread: waiting on sema...") + + local ok, err = sema:wait(1) -- wait for a second at most + if not ok then + ngx.say("sub thread: failed to wait on sema: ", err) + else + ngx.say("sub thread: waited successfully.") + end + end + + local co = ngx.thread.spawn(handler) + + ngx.say("main thread: sleeping for a little while...") + + ngx.sleep(0.1) -- wait a bit + + ngx.say("main thread: posting to sema...") + + sema:post(1) + + ngx.say("main thread: end.") + } +} +``` + +The example location above produces a response output like this: + +``` +sub thread: waiting on sema... +main thread: sleeping for a little while... +main thread: posting to sema... +main thread: end. +sub thread: waited successfully. +``` + +[Back to TOC](#table-of-contents) + +Synchronizing threads in different contexts +------------------------------------------- + +```nginx +location = /t { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sema = semaphore.new() + + local outputs = {} + local i = 1 + + local function out(s) + outputs[i] = s + i = i + 1 + end + + local function handler() + out("timer thread: sleeping for a little while...") + + ngx.sleep(0.1) -- wait a bit + + out("timer thread: posting on sema...") + + sema:post(1) + end + + assert(ngx.timer.at(0, handler)) + + out("main thread: waiting on sema...") + + local ok, err = sema:wait(1) -- wait for a second at most + if not ok then + out("main thread: failed to wait on sema: ", err) + else + out("main thread: waited successfully.") + end + + out("main thread: end.") + + ngx.say(table.concat(outputs, "\n")) + } +} +``` + +The example location above produces a response body like this + +``` +main thread: waiting on sema... +timer thread: sleeping for a little while... +timer thread: posting on sema... +main thread: waited successfully. +main thread: end. +``` + +The same applies to different request contexts as long as these requests are served +by the same nginx worker process. + +[Back to TOC](#table-of-contents) + +Description +=========== + +This module provides an efficient semaphore API for the OpenResty/ngx_lua module. With +semaphores, "light threads" (created by [ngx.thread.spawn](https://github.com/openresty/lua-nginx-module#ngxthreadspawn), +[ngx.timer.at](https://github.com/openresty/lua-nginx-module#ngxtimerat), and etc.) can +synchronize among each other very efficiently without constant polling and sleeping. + +"Light threads" in different contexts (like in different requests) can share the same +semaphore instance as long as these "light threads" reside in the same NGINX worker +process and the [lua_code_cache](https://github.com/openresty/lua-nginx-module#lua_code_cache) +directive is turned on (which is the default). For inter-process "light thread" synchronization, +it is recommended to use the [lua-resty-lock](https://github.com/openresty/lua-resty-lock) library instead +(which is a bit less efficient than this semaphore API though). + +This semaphore API has a pure userland implementation which does not involve any system calls nor +block any operating system threads. It works closely with the event model of NGINX without +introducing any extra delay. + +Like other APIs provided by this `lua-resty-core` library, the LuaJIT FFI feature is required. + +[Back to TOC](#table-of-contents) + +Methods +======= + +[Back to TOC](#table-of-contents) + +new +--- +**syntax:** *sema, err = semaphore_module.new(n?)* + +**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.** + +Creates and returns a new semaphore instance that has `n` (default to `0`) resources. + +For example, + +```lua + local semaphore = require "ngx.semaphore" + local sema, err = semaphore.new() + if not sema then + ngx.say("create semaphore failed: ", err) + end +``` + +Often the semaphore object created is shared on the NGINX worker process by mounting in a custom Lua module, as +documented below: + +https://github.com/openresty/lua-nginx-module#data-sharing-within-an-nginx-worker + +[Back to TOC](#table-of-contents) + +post +-------- +**syntax:** *sema:post(n?)* + +**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.** + +Releases `n` (default to `1`) "resources" to the semaphore instance. + +This will not yield the current running "light thread". + +At most `n` "light threads" will be waken up when the current running "light thread" later yields (or terminates). + +```lua +-- typically, we get the semaphore instance from upvalue or globally shared data +-- See https://github.com/openresty/lua-nginx-module#data-sharing-within-an-nginx-worker + +local semaphore = require "ngx.semaphore" +local sema = semaphore.new() + +sema:post(2) -- releases 2 resources +``` + +[Back to TOC](#table-of-contents) + +wait +---- +**syntax:** *ok, err = sema:wait(timeout)* + +**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.** + +Requests a resource from the semaphore instance. + +Returns `true` immediately when there is resources available for the current running "light thread". +Otherwise the current "light thread" will enter the waiting queue and yield execution. +The current "light thread" will be automatically waken up and the `wait` function call +will return `true` when there is resources available for it, or return `nil` and a string describing +the error in case of failure (like `"timeout"`). + +The `timeout` argument specifies the maximum time this function call should wait for (in seconds). + +When the `timeout` argument is 0, it means "no wait", that is, when there is no readily available +"resources" for the current running "light thread", this `wait` function call returns immediately +`nil` and the error string `"timeout"`. + +You can specify millisecond precision in the timeout value by using floating point numbers like 0.001 (which means 1ms). + +"Light threads" created by different contexts (like request handlers) can wait on the +same semaphore instance without problem. + +See [Synopsis](#synopsis) for code examples. + +[Back to TOC](#table-of-contents) + +count +-------- +**syntax:** *count = sema:count()* + +**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.** + +Returns the number of resources readily available in the `sema` semaphore instance (if any). + +When the returned number is negative, it means the number of "light threads" waiting on +this semaphore. + +Consider the following example, + +```lua +local semaphore = require "ngx.semaphore" +local sema = semaphore.new(0) + +ngx.say("count: ", sema:count()) -- count: 0 + +local function handler(id) + local ok, err = sema:wait(1) + if not ok then + ngx.say("err: ", err) + else + ngx.say("wait success") + end +end + +local co1 = ngx.thread.spawn(handler) +local co2 = ngx.thread.spawn(handler) + +ngx.say("count: ", sema:count()) -- count: -2 + +sema:post(1) + +ngx.say("count: ", sema:count()) -- count: -1 + +sema:post(2) + +ngx.say("count: ", sema:count()) -- count: 1 +``` + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Weixie Cui, Kugou Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2015-2017, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) + diff --git a/lib/ngx/ssl.lua b/lib/ngx/ssl.lua new file mode 100644 index 000000000..8792be0f0 --- /dev/null +++ b/lib/ngx/ssl.lua @@ -0,0 +1,470 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local base = require "resty.core.base" +base.allows_subsystem('http', 'stream') + + +local ffi = require "ffi" +local C = ffi.C +local ffi_str = ffi.string +local ffi_gc = ffi.gc +local get_request = base.get_request +local error = error +local tonumber = tonumber +local errmsg = base.get_errmsg_ptr() +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local FFI_DECLINED = base.FFI_DECLINED +local FFI_OK = base.FFI_OK +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_ssl_set_der_certificate +local ngx_lua_ffi_ssl_clear_certs +local ngx_lua_ffi_ssl_set_der_private_key +local ngx_lua_ffi_ssl_raw_server_addr +local ngx_lua_ffi_ssl_server_port +local ngx_lua_ffi_ssl_server_name +local ngx_lua_ffi_ssl_raw_client_addr +local ngx_lua_ffi_cert_pem_to_der +local ngx_lua_ffi_priv_key_pem_to_der +local ngx_lua_ffi_ssl_get_tls1_version +local ngx_lua_ffi_parse_pem_cert +local ngx_lua_ffi_parse_pem_priv_key +local ngx_lua_ffi_set_cert +local ngx_lua_ffi_set_priv_key +local ngx_lua_ffi_free_cert +local ngx_lua_ffi_free_priv_key +local ngx_lua_ffi_ssl_verify_client + + +if subsystem == 'http' then + ffi.cdef[[ + int ngx_http_lua_ffi_ssl_set_der_certificate(ngx_http_request_t *r, + const char *data, size_t len, char **err); + + int ngx_http_lua_ffi_ssl_clear_certs(ngx_http_request_t *r, char **err); + + int ngx_http_lua_ffi_ssl_set_der_private_key(ngx_http_request_t *r, + const char *data, size_t len, char **err); + + int ngx_http_lua_ffi_ssl_raw_server_addr(ngx_http_request_t *r, char **addr, + size_t *addrlen, int *addrtype, char **err); + + int ngx_http_lua_ffi_ssl_server_port(ngx_http_request_t *r, + unsigned short *server_port, char **err); + + int ngx_http_lua_ffi_ssl_server_name(ngx_http_request_t *r, char **name, + size_t *namelen, char **err); + + int ngx_http_lua_ffi_ssl_raw_client_addr(ngx_http_request_t *r, char **addr, + size_t *addrlen, int *addrtype, char **err); + + 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_get_tls1_version(ngx_http_request_t *r, + char **err); + + void *ngx_http_lua_ffi_parse_pem_cert(const unsigned char *pem, + size_t pem_len, char **err); + + void *ngx_http_lua_ffi_parse_pem_priv_key(const unsigned char *pem, + size_t pem_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_free_cert(void *cdata); + + void ngx_http_lua_ffi_free_priv_key(void *cdata); + + int ngx_http_lua_ffi_ssl_verify_client(void *r, + void *cdata, int depth, char **err); + ]] + + ngx_lua_ffi_ssl_set_der_certificate = + C.ngx_http_lua_ffi_ssl_set_der_certificate + ngx_lua_ffi_ssl_clear_certs = C.ngx_http_lua_ffi_ssl_clear_certs + ngx_lua_ffi_ssl_set_der_private_key = + C.ngx_http_lua_ffi_ssl_set_der_private_key + ngx_lua_ffi_ssl_raw_server_addr = C.ngx_http_lua_ffi_ssl_raw_server_addr + ngx_lua_ffi_ssl_server_port = C.ngx_http_lua_ffi_ssl_server_port + ngx_lua_ffi_ssl_server_name = C.ngx_http_lua_ffi_ssl_server_name + ngx_lua_ffi_ssl_raw_client_addr = C.ngx_http_lua_ffi_ssl_raw_client_addr + ngx_lua_ffi_cert_pem_to_der = C.ngx_http_lua_ffi_cert_pem_to_der + ngx_lua_ffi_priv_key_pem_to_der = C.ngx_http_lua_ffi_priv_key_pem_to_der + ngx_lua_ffi_ssl_get_tls1_version = C.ngx_http_lua_ffi_ssl_get_tls1_version + ngx_lua_ffi_parse_pem_cert = C.ngx_http_lua_ffi_parse_pem_cert + ngx_lua_ffi_parse_pem_priv_key = C.ngx_http_lua_ffi_parse_pem_priv_key + ngx_lua_ffi_set_cert = C.ngx_http_lua_ffi_set_cert + ngx_lua_ffi_set_priv_key = C.ngx_http_lua_ffi_set_priv_key + ngx_lua_ffi_free_cert = C.ngx_http_lua_ffi_free_cert + ngx_lua_ffi_free_priv_key = C.ngx_http_lua_ffi_free_priv_key + ngx_lua_ffi_ssl_verify_client = C.ngx_http_lua_ffi_ssl_verify_client + +elseif subsystem == 'stream' then + ffi.cdef[[ + int ngx_stream_lua_ffi_ssl_set_der_certificate(ngx_stream_lua_request_t *r, + const char *data, size_t len, char **err); + + int ngx_stream_lua_ffi_ssl_clear_certs(ngx_stream_lua_request_t *r, + char **err); + + int ngx_stream_lua_ffi_ssl_set_der_private_key(ngx_stream_lua_request_t *r, + const char *data, size_t len, char **err); + + int ngx_stream_lua_ffi_ssl_raw_server_addr(ngx_stream_lua_request_t *r, + char **addr, size_t *addrlen, int *addrtype, char **err); + + int ngx_stream_lua_ffi_ssl_server_port(ngx_stream_lua_request_t *r, + unsigned short *server_port, char **err); + + int ngx_stream_lua_ffi_ssl_server_name(ngx_stream_lua_request_t *r, + char **name, size_t *namelen, char **err); + + int ngx_stream_lua_ffi_ssl_raw_client_addr(ngx_stream_lua_request_t *r, + char **addr, size_t *addrlen, int *addrtype, char **err); + + int ngx_stream_lua_ffi_cert_pem_to_der(const unsigned char *pem, + size_t pem_len, unsigned char *der, char **err); + + int ngx_stream_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_stream_lua_ffi_ssl_get_tls1_version(ngx_stream_lua_request_t *r, + char **err); + + void *ngx_stream_lua_ffi_parse_pem_cert(const unsigned char *pem, + size_t pem_len, char **err); + + void *ngx_stream_lua_ffi_parse_pem_priv_key(const unsigned char *pem, + size_t pem_len, char **err); + + int ngx_stream_lua_ffi_set_cert(void *r, void *cdata, char **err); + + int ngx_stream_lua_ffi_set_priv_key(void *r, void *cdata, char **err); + + void ngx_stream_lua_ffi_free_cert(void *cdata); + + void ngx_stream_lua_ffi_free_priv_key(void *cdata); + + int ngx_stream_lua_ffi_ssl_verify_client(void *r, + void *cdata, int depth, char **err); + ]] + + ngx_lua_ffi_ssl_set_der_certificate = + C.ngx_stream_lua_ffi_ssl_set_der_certificate + ngx_lua_ffi_ssl_clear_certs = C.ngx_stream_lua_ffi_ssl_clear_certs + ngx_lua_ffi_ssl_set_der_private_key = + C.ngx_stream_lua_ffi_ssl_set_der_private_key + ngx_lua_ffi_ssl_raw_server_addr = C.ngx_stream_lua_ffi_ssl_raw_server_addr + ngx_lua_ffi_ssl_server_port = C.ngx_stream_lua_ffi_ssl_server_port + ngx_lua_ffi_ssl_server_name = C.ngx_stream_lua_ffi_ssl_server_name + ngx_lua_ffi_ssl_raw_client_addr = C.ngx_stream_lua_ffi_ssl_raw_client_addr + ngx_lua_ffi_cert_pem_to_der = C.ngx_stream_lua_ffi_cert_pem_to_der + ngx_lua_ffi_priv_key_pem_to_der = C.ngx_stream_lua_ffi_priv_key_pem_to_der + ngx_lua_ffi_ssl_get_tls1_version = C.ngx_stream_lua_ffi_ssl_get_tls1_version + ngx_lua_ffi_parse_pem_cert = C.ngx_stream_lua_ffi_parse_pem_cert + ngx_lua_ffi_parse_pem_priv_key = C.ngx_stream_lua_ffi_parse_pem_priv_key + ngx_lua_ffi_set_cert = C.ngx_stream_lua_ffi_set_cert + ngx_lua_ffi_set_priv_key = C.ngx_stream_lua_ffi_set_priv_key + ngx_lua_ffi_free_cert = C.ngx_stream_lua_ffi_free_cert + ngx_lua_ffi_free_priv_key = C.ngx_stream_lua_ffi_free_priv_key + ngx_lua_ffi_ssl_verify_client = C.ngx_stream_lua_ffi_ssl_verify_client +end + + +local _M = { version = base.version } + + +local charpp = ffi.new("char*[1]") +local intp = ffi.new("int[1]") +local ushortp = ffi.new("unsigned short[1]") + + +function _M.clear_certs() + local r = get_request() + if not r then + error("no request found") + end + + local rc = ngx_lua_ffi_ssl_clear_certs(r, errmsg) + if rc == FFI_OK then + return true + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.set_der_cert(data) + local r = get_request() + if not r then + error("no request found") + end + + local rc = ngx_lua_ffi_ssl_set_der_certificate(r, data, #data, errmsg) + if rc == FFI_OK then + return true + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.set_der_priv_key(data) + local r = get_request() + if not r then + error("no request found") + end + + local rc = ngx_lua_ffi_ssl_set_der_private_key(r, data, #data, errmsg) + if rc == FFI_OK then + return true + end + + return nil, ffi_str(errmsg[0]) +end + + +local addr_types = { + [0] = "unix", + [1] = "inet", + [2] = "inet6", +} + + +function _M.raw_server_addr() + local r = get_request() + if not r then + error("no request found") + end + + local sizep = get_size_ptr() + + local rc = ngx_lua_ffi_ssl_raw_server_addr(r, charpp, sizep, intp, errmsg) + if rc == FFI_OK then + local typ = addr_types[intp[0]] + if not typ then + return nil, nil, "unknown address type: " .. intp[0] + end + return ffi_str(charpp[0], sizep[0]), typ + end + + return nil, nil, ffi_str(errmsg[0]) +end + + +function _M.server_port() + local r = get_request() + if not r then + error("no request found") + end + + local rc = ngx_lua_ffi_ssl_server_port(r, ushortp, errmsg) + if rc == FFI_OK then + return ushortp[0] + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.server_name() + local r = get_request() + if not r then + error("no request found") + end + + local sizep = get_size_ptr() + + local rc = ngx_lua_ffi_ssl_server_name(r, charpp, sizep, errmsg) + if rc == FFI_OK then + return ffi_str(charpp[0], sizep[0]) + end + + if rc == FFI_DECLINED then + return nil + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.raw_client_addr() + local r = get_request() + if not r then + error("no request found") + end + + local sizep = get_size_ptr() + + local rc = ngx_lua_ffi_ssl_raw_client_addr(r, charpp, sizep, intp, errmsg) + if rc == FFI_OK then + local typ = addr_types[intp[0]] + if not typ then + return nil, nil, "unknown address type: " .. intp[0] + end + return ffi_str(charpp[0], sizep[0]), typ + end + + return nil, nil, ffi_str(errmsg[0]) +end + + +function _M.cert_pem_to_der(pem) + local outbuf = get_string_buf(#pem) + + local sz = ngx_lua_ffi_cert_pem_to_der(pem, #pem, outbuf, errmsg) + if sz > 0 then + return ffi_str(outbuf, sz) + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.priv_key_pem_to_der(pem, passphrase) + local outbuf = get_string_buf(#pem) + + local sz = ngx_lua_ffi_priv_key_pem_to_der(pem, #pem, + passphrase, outbuf, errmsg) + if sz > 0 then + return ffi_str(outbuf, sz) + end + + return nil, ffi_str(errmsg[0]) +end + + +local function get_tls1_version() + + local r = get_request() + if not r then + error("no request found") + end + + local ver = ngx_lua_ffi_ssl_get_tls1_version(r, errmsg) + + ver = tonumber(ver) + + if ver >= 0 then + return ver + end + + -- rc == FFI_ERROR + + return nil, ffi_str(errmsg[0]) +end +_M.get_tls1_version = get_tls1_version + + +function _M.parse_pem_cert(pem) + local cert = ngx_lua_ffi_parse_pem_cert(pem, #pem, errmsg) + if cert ~= nil then + return ffi_gc(cert, ngx_lua_ffi_free_cert) + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.parse_pem_priv_key(pem) + local pkey = ngx_lua_ffi_parse_pem_priv_key(pem, #pem, errmsg) + if pkey ~= nil then + return ffi_gc(pkey, ngx_lua_ffi_free_priv_key) + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.set_cert(cert) + local r = get_request() + if not r then + error("no request found") + end + + local rc = ngx_lua_ffi_set_cert(r, cert, errmsg) + if rc == FFI_OK then + return true + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.set_priv_key(priv_key) + local r = get_request() + if not r then + error("no request found") + end + + local rc = ngx_lua_ffi_set_priv_key(r, priv_key, errmsg) + if rc == FFI_OK then + return true + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.verify_client(ca_certs, depth) + local r = get_request() + if not r then + error("no request found") + end + + if not depth then + depth = -1 + end + + local rc = ngx_lua_ffi_ssl_verify_client(r, ca_certs, depth, errmsg) + if rc == FFI_OK then + return true + end + + return nil, ffi_str(errmsg[0]) +end + + +do + _M.SSL3_VERSION = 0x0300 + _M.TLS1_VERSION = 0x0301 + _M.TLS1_1_VERSION = 0x0302 + _M.TLS1_2_VERSION = 0x0303 + _M.TLS1_3_VERSION = 0x0304 + + local map = { + [_M.SSL3_VERSION] = "SSLv3", + [_M.TLS1_VERSION] = "TLSv1", + [_M.TLS1_1_VERSION] = "TLSv1.1", + [_M.TLS1_2_VERSION] = "TLSv1.2", + [_M.TLS1_3_VERSION] = "TLSv1.3", + } + + function _M.get_tls1_version_str() + local ver, err = get_tls1_version() + if not ver then + return nil, err + end + + local ver_str = map[ver] + if not ver_str then + return nil, "unknown version" + end + + return ver_str + end +end + + +return _M diff --git a/lib/ngx/ssl.md b/lib/ngx/ssl.md new file mode 100644 index 000000000..b71ed5733 --- /dev/null +++ b/lib/ngx/ssl.md @@ -0,0 +1,586 @@ +Name +==== + +ngx.ssl - Lua API for controlling NGINX downstream SSL handshakes + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Methods](#methods) + * [clear_certs](#clear_certs) + * [cert_pem_to_der](#cert_pem_to_der) + * [set_der_cert](#set_der_cert) + * [priv_key_pem_to_der](#priv_key_pem_to_der) + * [set_der_priv_key](#set_der_priv_key) + * [server_name](#server_name) + * [server_port](#server_port) + * [raw_server_addr](#raw_server_addr) + * [raw_client_addr](#raw_client_addr) + * [get_tls1_version](#get_tls1_version) + * [get_tls1_version_str](#get_tls1_version_str) + * [parse_pem_cert](#parse_pem_cert) + * [parse_pem_priv_key](#parse_pem_priv_key) + * [set_cert](#set_cert) + * [set_priv_key](#set_priv_key) + * [verify_client](#verify_client) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is production ready. + +Synopsis +======== + +```nginx +# Note: you do not need the following line if you are using +# OpenResty 1.9.7.2+. +lua_package_path "/path/to/lua-resty-core/lib/?.lua;;"; + +server { + listen 443 ssl; + server_name test.com; + + # useless placeholders: just to shut up NGINX configuration + # loader errors: + ssl_certificate /path/to/fallback.crt; + ssl_certificate_key /path/to/fallback.key; + + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + -- clear the fallback certificates and private keys + -- set by the ssl_certificate and ssl_certificate_key + -- directives above: + local ok, err = ssl.clear_certs() + if not ok then + ngx.log(ngx.ERR, "failed to clear existing (fallback) certificates") + return ngx.exit(ngx.ERROR) + end + + -- assuming the user already defines the my_load_certificate_chain() + -- herself. + local pem_cert_chain = assert(my_load_certificate_chain()) + + local der_cert_chain, err = ssl.cert_pem_to_der(pem_cert_chain) + if not der_cert_chain then + ngx.log(ngx.ERR, "failed to convert certificate chain ", + "from PEM to DER: ", err) + return ngx.exit(ngx.ERROR) + end + + local ok, err = ssl.set_der_cert(der_cert_chain) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return ngx.exit(ngx.ERROR) + end + + -- assuming the user already defines the my_load_private_key() + -- function herself. + local pem_pkey = assert(my_load_private_key()) + local passphrase = "password" -- or nil + + local der_pkey, err = ssl.priv_key_pem_to_der(pem_pkey, passphrase) + if not der_pkey then + ngx.log(ngx.ERR, "failed to convert private key ", + "from PEM to DER: ", err) + return ngx.exit(ngx.ERROR) + end + + local ok, err = ssl.set_der_priv_key(der_pkey) + if not ok then + ngx.log(ngx.ERR, "failed to set DER private key: ", err) + return ngx.exit(ngx.ERROR) + end + } + + location / { + root html; + } +} +``` + +Description +=========== + +This Lua module provides API functions to control the SSL handshake process in contexts like +[ssl_certificate_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_certificate_by_lua_block) +(of the [ngx_lua](https://github.com/openresty/lua-nginx-module#readme) module). + +For web servers serving many (like millions of) https sites, it is often desired to lazily +load and cache the SSL certificate chain and private key data for the https sites actually +being served by a particular server. This Lua module provides API to support such use cases +in the context of the [ssl_certificate_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_certificate_by_lua_block) +directive. + +To load the `ngx.ssl` module in Lua, just write + +```lua +local ssl = require "ngx.ssl" +``` + +[Back to TOC](#table-of-contents) + +Methods +======= + +clear_certs +----------- +**syntax:** *ok, err = ssl.clear_certs()* + +**context:** *ssl_certificate_by_lua** + +Clears any existing SSL certificates and/or private keys set on the current SSL connection. + +Returns `true` on success, or a `nil` value and a string describing the error otherwise. + +[Back to TOC](#table-of-contents) + +cert_pem_to_der +--------------- +**syntax:** *der_cert_chain, err = ssl.cert_pem_to_der(pem_cert_chain)* + +**context:** *any* + +Converts the PEM-formatted SSL certificate chain data into the DER format (for later uses +in the [set_der_cert](#set_der_cert) +function, for example). + +In case of failures, returns `nil` and a string describing the error. + +It is known that the `openssl` command-line utility may not convert the whole SSL +certificate chain from PEM to DER correctly. So always use this Lua function to do +the conversion. You can always use libraries like [lua-resty-lrucache](https://github.com/openresty/lua-resty-lrucache#readme) +and/or ngx_lua APIs like [lua_shared_dict](https://github.com/openresty/lua-nginx-module#lua_shared_dict) +to do the caching of the DER-formatted results, for example. + +This function can be called in any context. + +[Back to TOC](#table-of-contents) + +set_der_cert +------------ +**syntax:** *ok, err = ssl.set_der_cert(der_cert_chain)* + +**context:** *ssl_certificate_by_lua** + +Sets the DER-formatted SSL certificate chain data for the current SSL connection. Note that +the DER data is +directly in the Lua string argument. *No* external file names are supported here. + +Returns `true` on success, or a `nil` value and a string describing the error otherwise. + +Note that, the SSL certificate chain is usually encoded in the PEM format. So you need +to use the [cert_pem_to_der](#cert_pem_to_der) +function to do the conversion first. + +[Back to TOC](#table-of-contents) + +priv_key_pem_to_der +------------------- +**syntax:** *der_priv_key, err = ssl.priv_key_pem_to_der(pem_priv_key, passphrase)* + +**context:** *any* + +Converts the PEM-formatted SSL private key data into the DER format (for later uses +in the [set_der_priv_key](#set_der_priv_key) +function, for example). + +The `passphrase` is the passphrase for `pem_priv_key` if the private key is password protected. + +In case of failures, returns `nil` and a string describing the error. + +Alternatively, you can do the PEM to DER conversion *offline* with the `openssl` command-line utility, like below + +```bash +openssl rsa -in key.pem -outform DER -out key.der +``` + +This function can be called in any context. + +[Back to TOC](#table-of-contents) + +set_der_priv_key +---------------- +**syntax:** *ok, err = ssl.set_der_priv_key(der_priv_key)* + +**context:** *ssl_certificate_by_lua** + +Sets the DER-formatted prviate key for the current SSL connection. + +Returns `true` on success, or a `nil` value and a string describing the error otherwise. + +Usually, the private keys are encoded in the PEM format. You can either use the +[priv_key_pem_to_der](#priv_key_pem_to_der) function +to do the PEM to DER conversion or just use +the `openssl` command-line utility offline, like below + +```bash +openssl rsa -in key.pem -outform DER -out key.der +``` + +[Back to TOC](#table-of-contents) + +server_name +----------- +**syntax:** *name, err = ssl.server_name()* + +**context:** *any* + +Returns the TLS SNI (Server Name Indication) name set by the client. Returns `nil` +when the client does not set it. + +In case of failures, it returns `nil` *and* a string describing the error. + +Usually we use this SNI name as the domain name (like `www.openresty.org`) to +identify the current web site while loading the corresponding SSL certificate +chain and private key for the site. + +Please note that not all https clients set the SNI name, so when the SNI name is +missing from the client handshake request, we use the server IP address accessed +by the client to identify the site. See the [raw_server_addr](#raw_server_addr) method +for more details. + +This function can be called in any context where downstream https is used. + +[Back to TOC](#table-of-contents) + +server_port +----------- +**syntax:** port, err = ssl.server_port() + +**context:** *any* + +Returns the server port. Returns `nil` +when server dont have a port. + +In case of failures, it returns `nil` *and* a string describing the error. + +This function can be called in any context where downstream https is used. + +[Back to TOC](#table-of-contents) + +raw_server_addr +--------------- +**syntax:** *addr_data, addr_type, err = ssl.raw_server_addr()* + +**context:** *any* + +Returns the raw server address actually accessed by the client in the current SSL connection. + +The first two return values are strings representing the address data and the address type, respectively. +The address values are interpreted differently according to the address type values: + +* `unix` +: The address data is a file path for the UNIX domain socket. +* `inet` +: The address data is a binary IPv4 address of 4 bytes long. +* `inet6` +: The address data is a binary IPv6 address of 16 bytes long. + +Returns two `nil` values and a Lua string describing the error. + +The following code snippet shows how to print out the UNIX domain socket address and +the IPv4 address as human-readable strings: + +```lua +local ssl = require "ngx.ssl" +local byte = string.byte + +local addr, addrtyp, err = ssl.raw_server_addr() +if not addr then + ngx.log(ngx.ERR, "failed to fetch raw server addr: ", err) + return +end + +if addrtyp == "inet" then -- IPv4 + ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2), + byte(addr, 3), byte(addr, 4)) + print("Using IPv4 address: ", ip) + +elseif addrtyp == "unix" then -- UNIX + print("Using unix socket file ", addr) + +else -- IPv6 + -- leave as an exercise for the readers +end +``` + +This function can be called in any context where downstream https is used. + +[Back to TOC](#table-of-contents) + +raw_client_addr +--------------- +**syntax:** *addr_data, addr_type, err = ssl.raw_client_addr()* + +**context:** *any* + +Returns the raw client address of the current SSL connection. + +The first two return values are strings representing the address data and the address type, respectively. +The address values are interpreted differently according to the address type values: + +* `unix` +: The address data is a file path for the UNIX domain socket. +* `inet` +: The address data is a binary IPv4 address of 4 bytes long. +* `inet6` +: The address data is a binary IPv6 address of 16 bytes long. + +Returns two `nil` values and a Lua string describing the error. + +The following code snippet shows how to print out the UNIX domain socket address and +the IPv4 address as human-readable strings: + +```lua +local ssl = require "ngx.ssl" +local byte = string.byte + +local addr, addrtyp, err = ssl.raw_client_addr() +if not addr then + ngx.log(ngx.ERR, "failed to fetch raw client addr: ", err) + return +end + +if addrtyp == "inet" then -- IPv4 + ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2), + byte(addr, 3), byte(addr, 4)) + print("Client IPv4 address: ", ip) + +elseif addrtyp == "unix" then -- UNIX + print("Client unix socket file ", addr) + +else -- IPv6 + -- leave as an exercise for the readers +end +``` + +This function can be called in any context where downstream https is used. + +This function was first introduced in lua-resty-core 0.1.14. + +[Back to TOC](#table-of-contents) + +get_tls1_version +---------------- +**syntax:** *ver, err = ssl.get_tls1_version()* + +**context:** *any* + +Returns the TLS 1.x version number used by the current SSL connection. Returns `nil` and +a string describing the error otherwise. + +Typical return values are: + +* `0x0300`(SSLv3) +* `0x0301`(TLSv1) +* `0x0302`(TLSv1.1) +* `0x0303`(TLSv1.2) +* `0x0304`(TLSv1.3) + +This function can be called in any context where downstream https is used. + +[Back to TOC](#table-of-contents) + +get_tls1_version_str +-------------------- +**syntax:** *ver, err = ssl.get_tls1_version_str()* + +**context:** *any* + +Returns the TLS 1.x version string used by the current SSL connection. Returns `nil` and +a string describing the error otherwise. + +If the TLS 1.x version number used by the current SSL connection is not +recognized, the return values will be `nil` and the string "unknown version". + +Typical return values are: + +* `SSLv3` +* `TLSv1` +* `TLSv1.1` +* `TLSv1.2` +* `TLSv1.3` + +This function can be called in any context where downstream https is used. + +[Back to TOC](#table-of-contents) + +parse_pem_cert +-------------- +**syntax:** *cert_chain, err = ssl.parse_pem_cert(pem_cert_chain)* + +**context:** *any* + +Converts the PEM-formated SSL certificate chain data into an opaque cdata pointer (for later uses +in the [set_cert](#set_cert) +function, for example). + +In case of failures, returns `nil` and a string describing the error. + +You can always use libraries like [lua-resty-lrucache](https://github.com/openresty/lua-resty-lrucache#readme) +to cache the cdata result. + +This function can be called in any context. + +This function was first added in version `0.1.7`. + +[Back to TOC](#table-of-contents) + +parse_pem_priv_key +------------------ +**syntax:** *priv_key, err = ssl.parse_pem_priv_key(pem_priv_key)* + +**context:** *any* + +Converts the PEM-formatted SSL private key data into an opaque cdata pointer (for later uses +in the [set_priv_key](#set_priv_key) +function, for example). + +In case of failures, returns `nil` and a string describing the error. + +This function can be called in any context. + +This function was first added in version `0.1.7`. + +[Back to TOC](#table-of-contents) + +set_cert +-------- +**syntax:** *ok, err = ssl.set_cert(cert_chain)* + +**context:** *ssl_certificate_by_lua** + +Sets the SSL certificate chain opaque pointer returned by the +[parse_pem_cert](#parse_pem_cert) function for the current SSL connection. + +Returns `true` on success, or a `nil` value and a string describing the error otherwise. + +Note that this `set_cert` function will run slightly faster, in terms of CPU cycles wasted, than the +[set_der_cert](#set_der_cert) variant, since the first function uses opaque cdata pointers +which do not require any additional conversion needed to be performed by the SSL library during the SSL handshake. + +This function was first added in version `0.1.7`. + +[Back to TOC](#table-of-contents) + +set_priv_key +------------ +**syntax:** *ok, err = ssl.set_priv_key(priv_key)* + +**context:** *ssl_certificate_by_lua** + +Sets the SSL private key opaque pointer returned by the +[parse_pem_priv_key](#parse_pem_priv_key) function for the current SSL connection. + +Returns `true` on success, or a `nil` value and a string describing the error otherwise. + +Note that this `set_priv_key` function will run slightly faster, in terms of CPU cycles wasted, than the +[set_der_priv_key](#set_der_priv_key) variant, since the first function uses opaque cdata pointers +which do not require any additional conversion needed to be performed by the SSL library during the SSL handshake. + +This function was first added in version `0.1.7`. + +[Back to TOC](#table-of-contents) + +verify_client +------------- +**syntax:** *ok, err = ssl.verify_client(ca_certs?, depth?)* + +**context:** *ssl_certificate_by_lua** + +Requires a client certificate during TLS handshake. + +The `ca_certs` is the CA certificate chain opaque pointer returned by the +[parse_pem_cert](#parse_pem_cert) function for the current SSL connection. +The list of certificates will be sent to clients. Also, they will be added to trusted store. +If omitted, will not send any CA certificate to clients. + +The `depth` is the verification depth in the client certificates chain. +If omitted, will use the value specified by `ssl_verify_depth`. + +Returns `true` on success, or a `nil` value and a string describing the error otherwise. + +Note that TLS is not terminated when verification fails. You need to examine Nginx variable `$ssl_client_verify` +later to determine next steps. + +This function was first added in version `0.1.20`. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Yichun Zhang <agentzh@gmail.com> (agentzh), OpenResty Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2015-2017, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* the [ngx.ocsp](ocsp.md) module. +* the [ssl_certificate_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_certificate_by_lua_block) directive. +* the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) diff --git a/lib/ngx/ssl/clienthello.lua b/lib/ngx/ssl/clienthello.lua new file mode 100644 index 000000000..b08351813 --- /dev/null +++ b/lib/ngx/ssl/clienthello.lua @@ -0,0 +1,232 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local base = require "resty.core.base" +base.allows_subsystem('http', 'stream') + + +local ffi = require "ffi" +local bit = require "bit" +local bor = bit.bor +local C = ffi.C +local ffi_str = ffi.string +local get_request = base.get_request +local error = error +local errmsg = base.get_errmsg_ptr() +local get_size_ptr = base.get_size_ptr +local FFI_OK = base.FFI_OK +local subsystem = ngx.config.subsystem +local ngx_phase = ngx.get_phase +local byte = string.byte +local lshift = bit.lshift +local table_insert = table.insert + + +local ngx_lua_ffi_ssl_get_client_hello_server_name +local ngx_lua_ffi_ssl_get_client_hello_ext +local ngx_lua_ffi_ssl_set_protocols + + +if subsystem == 'http' then + ffi.cdef[[ + int ngx_http_lua_ffi_ssl_get_client_hello_server_name(ngx_http_request_t *r, + const char **name, size_t *namelen, char **err); + + 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); + + int ngx_http_lua_ffi_ssl_set_protocols(ngx_http_request_t *r, + int protocols, char **err); + ]] + + ngx_lua_ffi_ssl_get_client_hello_server_name = + C.ngx_http_lua_ffi_ssl_get_client_hello_server_name + ngx_lua_ffi_ssl_get_client_hello_ext = + C.ngx_http_lua_ffi_ssl_get_client_hello_ext + ngx_lua_ffi_ssl_set_protocols = C.ngx_http_lua_ffi_ssl_set_protocols + +elseif subsystem == 'stream' then + ffi.cdef[[ + int ngx_stream_lua_ffi_ssl_get_client_hello_server_name( + ngx_stream_lua_request_t *r, const char **name, size_t *namelen, + char **err); + + int ngx_stream_lua_ffi_ssl_get_client_hello_ext( + ngx_stream_lua_request_t *r, unsigned int type, + const unsigned char **out, size_t *outlen, char **err); + + int ngx_stream_lua_ffi_ssl_set_protocols(ngx_stream_lua_request_t *r, + int protocols, char **err); + ]] + + ngx_lua_ffi_ssl_get_client_hello_server_name = + C.ngx_stream_lua_ffi_ssl_get_client_hello_server_name + ngx_lua_ffi_ssl_get_client_hello_ext = + C.ngx_stream_lua_ffi_ssl_get_client_hello_ext + ngx_lua_ffi_ssl_set_protocols = C.ngx_stream_lua_ffi_ssl_set_protocols +end + + +local _M = { version = base.version } + + +local ccharpp = ffi.new("const char*[1]") +local cucharpp = ffi.new("const unsigned char*[1]") + + +-- return server_name, err +function _M.get_client_hello_server_name() + local r = get_request() + if not r then + error("no request found") + end + + if ngx_phase() ~= "ssl_client_hello" then + error("API disabled in the current context") + end + + local sizep = get_size_ptr() + + local rc = ngx_lua_ffi_ssl_get_client_hello_server_name(r, ccharpp, sizep, + errmsg) + if rc == FFI_OK then + return ffi_str(ccharpp[0], sizep[0]) + end + + -- NGX_DECLINED: no sni extension + if rc == -5 then + return nil + end + + return nil, ffi_str(errmsg[0]) +end + + +-- return ext, err +function _M.get_client_hello_ext(ext_type) + local r = get_request() + if not r then + error("no request found") + end + + if ngx_phase() ~= "ssl_client_hello" then + error("API disabled in the current context") + end + + local sizep = get_size_ptr() + + local rc = ngx_lua_ffi_ssl_get_client_hello_ext(r, ext_type, cucharpp, + sizep, errmsg) + if rc == FFI_OK then + return ffi_str(cucharpp[0], sizep[0]) + end + + -- NGX_DECLINED: no extension + if rc == -5 then + return nil + end + + return nil, ffi_str(errmsg[0]) +end + +-- tls.handshake.extension.type supported_version +local supported_versions_type = 43 +local versions_map = { + [0x002] = "SSLv2", + [0x300] = "SSLv3", + [0x301] = "TLSv1", + [0x302] = "TLSv1.1", + [0x303] = "TLSv1.2", + [0x304] = "TLSv1.3", +} + +-- return types, err +function _M.get_supported_versions() + local r = get_request() + if not r then + error("no request found") + end + + if ngx_phase() ~= "ssl_client_hello" then + error("API disabled in the current context") + end + + local sizep = get_size_ptr() + + local rc = ngx_lua_ffi_ssl_get_client_hello_ext(r, supported_versions_type, + cucharpp, sizep, errmsg) + + if rc ~= FFI_OK then + -- NGX_DECLINED: no extension + if rc == -5 then + return nil + end + + return nil, ffi_str(errmsg[0]) + end + + local supported_versions_str = ffi_str(cucharpp[0], sizep[0]) + local remain_len = #supported_versions_str + if remain_len == 0 then + return nil + end + + local supported_versions_len = byte(supported_versions_str, 1) + remain_len = remain_len - 1 + + if remain_len ~= supported_versions_len then + return nil + end + local types = {} + while remain_len >= 2 do + local type_hi = byte(supported_versions_str, remain_len) + local type_lo = byte(supported_versions_str, remain_len + 1) + local type_id = lshift(type_hi, 8) + type_lo + if versions_map[type_id] ~= nil then + table_insert(types, versions_map[type_id]) + end + remain_len = remain_len - 2 + end + return types +end + + +local prot_map = { + ["SSLv2"] = 0x0002, + ["SSLv3"] = 0x0004, + ["TLSv1"] = 0x0008, + ["TLSv1.1"] = 0x0010, + ["TLSv1.2"] = 0x0020, + ["TLSv1.3"] = 0x0040 +} + + +-- return ok, err +function _M.set_protocols(protocols) + local r = get_request() + if not r then + error("no request found") + end + + if ngx_phase() ~= "ssl_client_hello" then + error("API disabled in the current context") + end + + local prots = 0 + for _, v in ipairs(protocols) do + if not prot_map[v] then + return nil, "invalid protocols failed" + end + prots = bor(prots, prot_map[v]) + end + + local rc = ngx_lua_ffi_ssl_set_protocols(r, prots, errmsg) + if rc == FFI_OK then + return true + end + + return nil, ffi_str(errmsg[0]) +end + +return _M diff --git a/lib/ngx/ssl/clienthello.md b/lib/ngx/ssl/clienthello.md new file mode 100644 index 000000000..be4a6782a --- /dev/null +++ b/lib/ngx/ssl/clienthello.md @@ -0,0 +1,273 @@ +Name +==== + +ngx.ssl.clienthello - Lua API for post-processing SSL client hello message for NGINX downstream SSL connections. + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Methods](#methods) + * [get_client_hello_server_name](#get_client_hello_server_name) + * [get_client_hello_ext](#get_client_hello_ext) + * [set_protocols](#set_protocols) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered experimental. + +Synopsis +======== + +```nginx +# nginx.conf + +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; + ... +} +``` + +Description +=========== + +This Lua module provides API functions for post-processing SSL client hello message for NGINX downstream connections. + +It must to be used in the contexts [ssl_client_hello_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_client_hello_by_lua_block). + +This Lua API 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 the custom client hello extension and do the corresponding handling in pure Lua. + +To load the `ngx.ssl.clienthello` module in Lua, just write + +```lua +local ssl_clt = require "ngx.ssl.clienthello" +``` + +[Back to TOC](#table-of-contents) + +Methods +======= + +get_client_hello_server_name +-------------- +**syntax:** *host, err = ssl_clt.get_client_hello_server_name()* + +**context:** *ssl_client_hello_by_lua** + +Returns the TLS SNI (Server Name Indication) name set by the client. + +Return `nil` when then the extension does not exist. + +In case of errors, it returns `nil` and a string describing the error. + +Note that the SNI name is gotten from the raw extensions of the client hello message associated with the current downstream SSL connection. + +So this function can only be called in the context of [ssl_client_hello_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_client_hello_by_lua_block). + +[Back to TOC](#table-of-contents) + +get_supported_versions +-------------- +**syntax:** *types, err = ssl_clt.get_supported_versions()* + +**context:** *ssl_client_hello_by_lua** + +Returns the table of ssl hello supported versions set by the client. + +Return `nil` when then the extension does not exist. + +In case of errors, it returns `nil` and a string describing the error. + +Note that the types is gotten from the raw extensions of the client hello message associated with the current downstream SSL connection. + +So this function can only be called in the context of [ssl_client_hello_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_client_hello_by_lua_block). + +[Back to TOC](#table-of-contents) + +get_client_hello_ext +---------------------- +**syntax:** *ext, err = ssl_clt.get_client_hello_ext(ext_type)* + +**context:** *ssl_client_hello_by_lua** + +Returns raw data of arbitrary SSL client hello extension including custom extensions. + +Returns `nil` if the specified extension type does not exist. + +In case of errors, it returns `nil` and a string describing the error. + +Note that the ext is gotten from the raw extensions of the client hello message associated with the current downstream SSL connection. + +So this function can only be called in the context of [ssl_client_hello_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_client_hello_by_lua_block). + +Example: + +Gets server name from raw extension data. The `0` in `ssl_clt.get_client_hello_ext(0)` denotes `TLSEXT_TYPE_server_name`, and the `0` in `byte(ext, 3) ~= 0` denotes `TLSEXT_NAMETYPE_host_name`. + +```nginx +# nginx.conf +server { + listen 443 ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local ssl_clt = require "ngx.ssl.clienthello" + local byte = string.byte + local ext = ssl_clt.get_client_hello_ext(0) + if not ext then + print("failed to get_client_hello_ext(0)") + ngx.exit(ngx.ERROR) + end + local total_len = string.len(ext) + if total_len <= 2 then + print("bad SSL Client Hello Extension") + ngx.exit(ngx.ERROR) + end + local len = byte(ext, 1) * 256 + byte(ext, 2) + if len + 2 ~= total_len then + print("bad SSL Client Hello Extension") + ngx.exit(ngx.ERROR) + end + if byte(ext, 3) ~= 0 then + print("bad SSL Client Hello Extension") + ngx.exit(ngx.ERROR) + end + if total_len <= 5 then + print("bad SSL Client Hello Extension") + ngx.exit(ngx.ERROR) + end + len = byte(ext, 4) * 256 + byte(ext, 5) + if len + 5 > total_len then + print("bad SSL Client Hello Extension") + ngx.exit(ngx.ERROR) + end + local name = string.sub(ext, 6, 6 + len -1) + + print("read SNI name from Lua: ", name) + } + ssl_certificate test.crt; + ssl_certificate_key test.key; +} +``` + +[Back to TOC](#table-of-contents) + +set_protocols +---------------------- +**syntax:** *ok, err = ssl_clt.set_protocols(protocols)* + +**context:** *ssl_client_hello_by_lua** + +Sets the SSL protocols supported by the current downstream SSL connection. + +Returns `true` on success, or a `nil` value and a string describing the error otherwise. + +Considering it is meaningless to set ssl protocols after the protocol is determined, +so this function may only be called in the context of [ssl_client_hello_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_client_hello_by_lua_block). + +Example: `ssl_clt.set_protocols({"TLSv1.1", "TLSv1.2", "TLSv1.3"})` + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Zhefeng Chen <chzf\_zju@163.com> (catbro666) + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2016-2017, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* the [ssl_client_hello_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_client_hello_by_lua_block) directive. +* the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) diff --git a/lib/ngx/ssl/session.lua b/lib/ngx/ssl/session.lua new file mode 100644 index 000000000..380a938e1 --- /dev/null +++ b/lib/ngx/ssl/session.lua @@ -0,0 +1,109 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local base = require "resty.core.base" +base.allows_subsystem('http') + + +local ffi = require "ffi" +local C = ffi.C +local ffi_str = ffi.string +local get_request = base.get_request +local error = error +local errmsg = base.get_errmsg_ptr() +local get_string_buf = base.get_string_buf +local FFI_ERROR = base.FFI_ERROR + + +ffi.cdef[[ +int ngx_http_lua_ffi_ssl_set_serialized_session(ngx_http_request_t *r, + const unsigned char *buf, int len, char **err); + +int ngx_http_lua_ffi_ssl_get_serialized_session(ngx_http_request_t *r, + char *buf, char **err); + +int ngx_http_lua_ffi_ssl_get_session_id(ngx_http_request_t *r, + char *buf, char **err); + +int ngx_http_lua_ffi_ssl_get_serialized_session_size(ngx_http_request_t *r, + char **err); + +int ngx_http_lua_ffi_ssl_get_session_id_size(ngx_http_request_t *r, + char **err); +]] + + +local _M = { version = base.version } + + +-- return session, err +function _M.get_serialized_session() + local r = get_request() + if not r then + error("no request found") + end + + local len = C.ngx_http_lua_ffi_ssl_get_serialized_session_size(r, errmsg) + + if len < 0 then + return nil, ffi_str(errmsg[0]) + end + + if len > 4096 then + return nil, "session too big to serialize" + end + local buf = get_string_buf(len) + + local rc = C.ngx_http_lua_ffi_ssl_get_serialized_session(r, buf, errmsg) + + if rc == FFI_ERROR then + return nil, ffi_str(errmsg[0]) + end + + return ffi_str(buf, len) +end + + +-- return session_id, err +function _M.get_session_id() + local r = get_request() + if not r then + error("no request found") + end + + local len = C.ngx_http_lua_ffi_ssl_get_session_id_size(r, errmsg) + + if len < 0 then + return nil, ffi_str(errmsg[0]) + end + + local buf = get_string_buf(len) + + local rc = C.ngx_http_lua_ffi_ssl_get_session_id(r, buf, errmsg) + + if rc == FFI_ERROR then + return nil, ffi_str(errmsg[0]) + end + + return ffi_str(buf, len) +end + + +-- return ok, err +function _M.set_serialized_session(sess) + local r = get_request() + if not r then + error("no request found") + end + + local rc = C.ngx_http_lua_ffi_ssl_set_serialized_session(r, sess, #sess, + errmsg) + if rc == FFI_ERROR then + return nil, ffi_str(errmsg[0]) + end + + return true +end + + +return _M diff --git a/lib/ngx/ssl/session.md b/lib/ngx/ssl/session.md new file mode 100644 index 000000000..176a447ad --- /dev/null +++ b/lib/ngx/ssl/session.md @@ -0,0 +1,277 @@ +Name +==== + +ngx.ssl.session - Lua API for manipulating SSL session data and IDs for NGINX downstream SSL connections. + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Methods](#methods) + * [get_session_id](#get_session_id) + * [get_serialized_session](#get_serialized_session) + * [set_serialized_session](#set_serialized_session) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Author](#author) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is production ready. + +Synopsis +======== + +```nginx +# nginx.conf + +# Note: you do not need the following line if you are using +# OpenResty 1.11.2.1+. +lua_package_path "/path/to/lua-resty-core/lib/?.lua;;"; + +ssl_session_fetch_by_lua_block { + local ssl_sess = require "ngx.ssl.session" + + local sess_id, err = ssl_sess.get_session_id() + if not sess_id then + ngx.log(ngx.ERR, "failed to get session ID: ", err) + -- considered a cache miss, and just return... + return + end + + -- the user is supposed to implement the my_lookup_ssl_session_by_id + -- Lua function used below. She can look up an external memcached + -- or redis cluster, for example. And she can also introduce a local + -- cache layer at the same time... + local sess, err = my_lookup_ssl_session_by_id(sess_id) + if not sess then + if err then + ngx.log(ngx.ERR, "failed to look up the session by ID ", + sess_id, ": ", err) + return + end + + -- cache miss...just return + return + end + + local ok, err = ssl_sess.set_serialized_session(sess) + if not ok then + ngx.log(ngx.ERR, "failed to set SSL session for ID ", sess_id, + ": ", err) + -- consider it as a cache miss... + return + end + + -- done here, SSL session successfully set and should resume accordingly... +} + +ssl_session_store_by_lua_block { + local ssl_sess = require "ngx.ssl.session" + + local sess_id, err = ssl_sess.get_session_id() + if not sess_id then + ngx.log(ngx.ERR, "failed to get session ID: ", err) + -- just give up + return + end + + local sess, err = ssl_sess.get_serialized_session() + if not sess then + ngx.log(ngx.ERR, "failed to get SSL session from the ", + "current connection: ", err) + -- just give up + return + end + + -- for the best performance, we should avoid creating a closure + -- dynamically here on the hot code path. Instead, we should + -- put this function in one of our own Lua module files. this + -- example is just for demonstration purposes... + local function save_it(premature, sess_id, sess) + -- the user is supposed to implement the + -- my_save_ssl_session_by_id Lua function used below. + -- She can save to an external memcached + -- or redis cluster, for example. And she can also introduce + -- a local cache layer at the same time... + local sess, err = my_save_ssl_session_by_id(sess_id, sess) + if not sess then + if err then + ngx.log(ngx.ERR, "failed to save the session by ID ", + sess_id, ": ", err) + return ngx.exit(ngx.ERROR) + end + + -- cache miss...just return + return + end + end + + -- create a 0-delay timer here... + local ok, err = ngx.timer.at(0, save_it, sess_id, sess) + if not ok then + ngx.log(ngx.ERR, "failed to create a 0-delay timer: ", err) + return + end +} + +server { + listen 443 ssl; + server_name test.com; + + # well, we could configure ssl_certificate_by_lua* here as well... + ssl_certificate /path/to/server-cert.pem; + ssl_certificate_key /path/to/server-priv-key.pem; +} +``` + +Description +=========== + +This Lua module provides API functions for manipulating SSL session data and IDs for NGINX +downstream connections. It is mostly for the contexts [ssl_session_fetch_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_session_fetch_by_lua_block) +and [ssl_session_store_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_session_store_by_lua_block). + +This Lua API can be used to implement distributed SSL session caching for downstream SSL connections, thus saving a lot of full SSL handshakes which are very expensive. + +To load the `ngx.ssl.session` module in Lua, just write + +```lua +local ssl_sess = require "ngx.ssl.session" +``` + +[Back to TOC](#table-of-contents) + +Methods +======= + +get_session_id +-------------- +**syntax:** *id, err = ssl_sess.get_session_id()* + +**context:** *ssl_session_fetch_by_lua*, ssl_session_store_by_lua** + +Fetches the SSL session ID associated with the current downstream SSL connection. +The ID is returned as a Lua string. + +In case of errors, it returns `nil` and a string describing the error. + +This API function is usually called in the contexts of +[ssl_session_store_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_session_store_by_lua_block) +and [ssl_session_fetch_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_session_fetch_by_lua_block). + +[Back to TOC](#table-of-contents) + +get_serialized_session +---------------------- +**syntax:** *session, err = ssl_sess.get_serialized_session()* + +**context:** *ssl_session_store_by_lua** + +Returns the serialized form of the SSL session data of the current SSL connection, in a Lua string. + +This session can be cached in [lua-resty-lrucache](https://github.com/openresty/lua-resty-lrucache), [lua_shared_dict](https://github.com/openresty/lua-nginx-module#lua_shared_dict), +and/or external data storage services like `memcached` and `redis`. The SSL session ID returned +by the [get_session_id](#get_session_id) function is usually used as the cache key. + +The returned SSL session data can later be loaded into other SSL connections using the same +session ID via the [set_serialized_session](#set_serialized_session) function. + +In case of errors, it returns `nil` and a string describing the error. + +This API function is usually called in the context of +[ssl_session_store_by_lua*](https://github.com/openresty/lua-nginx-module#ssl_session_store_by_lua_block) +where the SSL handshake has just completed. + +[Back to TOC](#table-of-contents) + +set_serialized_session +---------------------- +**syntax:** *ok, err = ssl_sess.set_serialized_session(session)* + +**context:** *ssl_session_fetch_by_lua** + +Sets the serialized SSL session provided as the argument to the current SSL connection. +If the SSL session is successfully set, the current SSL connection can resume the session +directly without going through the full SSL handshake process (which is very expensive in terms of CPU time). + +This API is usually used in the context of [ssl_session_fetch_by_lua*](https://github.com/openresty/lua-nginx-module#ssl_session_fetch_by_lua_block) +when a cache hit is found with the current SSL session ID. + +The serialized SSL session used as the argument should be originally returned by the +[get_serialized_session](#get_serialized_session) function. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Author +====== + +Yichun Zhang <agentzh@gmail.com> (agentzh), OpenResty Inc. + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2016-2017, by Yichun "agentzh" Zhang, OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* the [ssl_session_fetch_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_session_fetch_by_lua_block) directive. +* the [ssl_session_store_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_session_store_by_lua_block) directive. +* the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) diff --git a/lib/resty/core.lua b/lib/resty/core.lua new file mode 100644 index 000000000..e92084c09 --- /dev/null +++ b/lib/resty/core.lua @@ -0,0 +1,37 @@ +-- Copyright (C) Yichun Zhang (agentzh) + +local subsystem = ngx.config.subsystem + + +require "resty.core.var" +require "resty.core.worker" +require "resty.core.regex" +require "resty.core.shdict" +require "resty.core.time" +require "resty.core.hash" +require "resty.core.uri" +require "resty.core.exit" +require "resty.core.base64" +require "resty.core.request" + + +if subsystem == 'http' then + require "resty.core.response" + require "resty.core.phase" + require "resty.core.ndk" + require "resty.core.socket" + require "resty.core.coroutine" + require "resty.core.param" +end + + +require "resty.core.misc" +require "resty.core.ctx" + + +local base = require "resty.core.base" + + +return { + version = base.version +} diff --git a/lib/resty/core/base.lua b/lib/resty/core/base.lua new file mode 100644 index 000000000..540a4586a --- /dev/null +++ b/lib/resty/core/base.lua @@ -0,0 +1,269 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local ffi_new = ffi.new +local pcall = pcall +local error = error +local select = select +local ceil = math.ceil +local subsystem = ngx.config.subsystem + + +local str_buf_size = 4096 +local str_buf +local size_ptr +local FREE_LIST_REF = 0 + + +if subsystem == 'http' then + if not ngx.config + or not ngx.config.ngx_lua_version + or ngx.config.ngx_lua_version ~= 10025 + then + error("ngx_http_lua_module 0.10.25 required") + end + +elseif subsystem == 'stream' then + if not ngx.config + or not ngx.config.ngx_lua_version + or ngx.config.ngx_lua_version ~= 13 + then + error("ngx_stream_lua_module 0.0.13 required") + end + +else + error("ngx_http_lua_module 0.10.25 or " + .. "ngx_stream_lua_module 0.0.13 required") +end + + +if string.find(jit.version, " 2.0", 1, true) then + ngx.log(ngx.ALERT, "use of lua-resty-core with LuaJIT 2.0 is ", + "not recommended; use LuaJIT 2.1+ instead") +end + + +local ok, new_tab = pcall(require, "table.new") +if not ok then + new_tab = function (narr, nrec) return {} end +end + + +local clear_tab +ok, clear_tab = pcall(require, "table.clear") +if not ok then + local pairs = pairs + clear_tab = function (tab) + for k, _ in pairs(tab) do + tab[k] = nil + end + end +end + + +-- XXX for now LuaJIT 2.1 cannot compile require() +-- so we make the fast code path Lua only in our own +-- wrapper so that most of the require() calls in hot +-- Lua code paths can be JIT compiled. +do + local orig_require = require + local pkg_loaded = package.loaded + -- the key_sentinel is inserted into package.loaded before + -- the chunk is executed and replaced if the chunk completes normally. + local key_sentinel = pkg_loaded[...] + + local function my_require(name) + local mod = pkg_loaded[name] + if mod then + if mod == key_sentinel then + error("loop or previous error loading module '" .. name .. "'") + end + + return mod + end + return orig_require(name) + end + getfenv(0).require = my_require +end + + +if not pcall(ffi.typeof, "ngx_str_t") then + ffi.cdef[[ + typedef struct { + size_t len; + const unsigned char *data; + } ngx_str_t; + ]] +end + + +if subsystem == 'http' then + if not pcall(ffi.typeof, "ngx_http_request_t") then + ffi.cdef[[ + typedef struct ngx_http_request_s ngx_http_request_t; + ]] + end + + if not pcall(ffi.typeof, "ngx_http_lua_ffi_str_t") then + ffi.cdef[[ + typedef struct { + int len; + const unsigned char *data; + } ngx_http_lua_ffi_str_t; + ]] + end + +elseif subsystem == 'stream' then + if not pcall(ffi.typeof, "ngx_stream_lua_request_t") then + ffi.cdef[[ + typedef struct ngx_stream_lua_request_s ngx_stream_lua_request_t; + ]] + end + + if not pcall(ffi.typeof, "ngx_stream_lua_ffi_str_t") then + ffi.cdef[[ + typedef struct { + int len; + const unsigned char *data; + } ngx_stream_lua_ffi_str_t; + ]] + end + +else + error("unknown subsystem: " .. subsystem) +end + + +local c_buf_type = ffi.typeof("char[?]") + + +local _M = new_tab(0, 18) + + +_M.version = "0.1.27" +_M.new_tab = new_tab +_M.clear_tab = clear_tab + + +local errmsg + + +function _M.get_errmsg_ptr() + if not errmsg then + errmsg = ffi_new("char *[1]") + end + return errmsg +end + + +if not ngx then + error("no existing ngx. table found") +end + + +function _M.set_string_buf_size(size) + if size <= 0 then + return + end + if str_buf then + str_buf = nil + end + str_buf_size = ceil(size) +end + + +function _M.get_string_buf_size() + return str_buf_size +end + + +function _M.get_size_ptr() + if not size_ptr then + size_ptr = ffi_new("size_t[1]") + end + + return size_ptr +end + + +function _M.get_string_buf(size, must_alloc) + -- ngx.log(ngx.ERR, "str buf size: ", str_buf_size) + if size > str_buf_size or must_alloc then + local buf = ffi_new(c_buf_type, size) + return buf + end + + if not str_buf then + str_buf = ffi_new(c_buf_type, str_buf_size) + end + + return str_buf +end + + +function _M.ref_in_table(tb, key) + if key == nil then + return -1 + end + local ref = tb[FREE_LIST_REF] + if ref and ref ~= 0 then + tb[FREE_LIST_REF] = tb[ref] + + else + ref = #tb + 1 + end + tb[ref] = key + + -- print("ref key_id returned ", ref) + return ref +end + + +function _M.allows_subsystem(...) + local total = select("#", ...) + + for i = 1, total do + if select(i, ...) == subsystem then + return + end + end + + error("unsupported subsystem: " .. subsystem, 2) +end + + +_M.FFI_OK = 0 +_M.FFI_NO_REQ_CTX = -100 +_M.FFI_BAD_CONTEXT = -101 +_M.FFI_ERROR = -1 +_M.FFI_AGAIN = -2 +_M.FFI_BUSY = -3 +_M.FFI_DONE = -4 +_M.FFI_DECLINED = -5 +_M.FFI_ABORT = -6 + + +do + local exdata + + ok, exdata = pcall(require, "thread.exdata") + if ok and exdata then + function _M.get_request() + local r = exdata() + if r ~= nil then + return r + end + end + + else + local getfenv = getfenv + + function _M.get_request() + return getfenv(0).__ngx_req + end + end +end + + +return _M diff --git a/lib/resty/core/base64.lua b/lib/resty/core/base64.lua new file mode 100644 index 000000000..8a0e463b4 --- /dev/null +++ b/lib/resty/core/base64.lua @@ -0,0 +1,115 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_string = ffi.string +local ngx = ngx +local type = type +local error = error +local floor = math.floor +local tostring = tostring +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_encode_base64 +local ngx_lua_ffi_decode_base64 + + +if subsystem == "http" then + ffi.cdef[[ + size_t ngx_http_lua_ffi_encode_base64(const unsigned char *src, + size_t len, unsigned char *dst, + int no_padding); + + int ngx_http_lua_ffi_decode_base64(const unsigned char *src, + size_t len, unsigned char *dst, + size_t *dlen); + ]] + + ngx_lua_ffi_encode_base64 = C.ngx_http_lua_ffi_encode_base64 + ngx_lua_ffi_decode_base64 = C.ngx_http_lua_ffi_decode_base64 + +elseif subsystem == "stream" then + ffi.cdef[[ + size_t ngx_stream_lua_ffi_encode_base64(const unsigned char *src, + size_t len, unsigned char *dst, + int no_padding); + + int ngx_stream_lua_ffi_decode_base64(const unsigned char *src, + size_t len, unsigned char *dst, + size_t *dlen); + ]] + + ngx_lua_ffi_encode_base64 = C.ngx_stream_lua_ffi_encode_base64 + ngx_lua_ffi_decode_base64 = C.ngx_stream_lua_ffi_decode_base64 +end + + +local function base64_encoded_length(len, no_padding) + return no_padding and floor((len * 8 + 5) / 6) or + floor((len + 2) / 3) * 4 +end + + +ngx.encode_base64 = function (s, no_padding) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + + local slen = #s + local no_padding_bool = false; + local no_padding_int = 0; + + if no_padding then + if no_padding ~= true then + local typ = type(no_padding) + error("bad no_padding: boolean expected, got " .. typ, 2) + end + + no_padding_bool = true + no_padding_int = 1; + end + + local dlen = base64_encoded_length(slen, no_padding_bool) + local dst = get_string_buf(dlen) + local r_dlen = ngx_lua_ffi_encode_base64(s, slen, dst, no_padding_int) + -- if dlen ~= r_dlen then error("discrepancy in len") end + return ffi_string(dst, r_dlen) +end + + +local function base64_decoded_length(len) + return floor((len + 3) / 4) * 3 +end + + +ngx.decode_base64 = function (s) + if type(s) ~= 'string' then + error("string argument only", 2) + end + local slen = #s + local dlen = base64_decoded_length(slen) + -- print("dlen: ", tonumber(dlen)) + local dst = get_string_buf(dlen) + local pdlen = get_size_ptr() + local ok = ngx_lua_ffi_decode_base64(s, slen, dst, pdlen) + if ok == 0 then + return nil + end + return ffi_string(dst, pdlen[0]) +end + + +return { + version = base.version +} diff --git a/lib/resty/core/coroutine.lua b/lib/resty/core/coroutine.lua new file mode 100644 index 000000000..9c95b163e --- /dev/null +++ b/lib/resty/core/coroutine.lua @@ -0,0 +1,29 @@ +local base = require "resty.core.base" +local get_request = base.get_request + +do + local keys = {'create', 'yield', 'resume', 'status', 'wrap'} + local errmsg = base.get_errmsg_ptr() + local get_raw_phase = ngx.get_raw_phase + + for _, key in ipairs(keys) do + local std = coroutine['_' .. key] + local ours = coroutine['__' .. key] + coroutine[key] = function (...) + local r = get_request() + if r ~= nil then + local ctx = get_raw_phase(r, errmsg) + if ctx ~= 0x020 and ctx ~= 0x040 then + return ours(...) + end + end + return std(...) + end + end + + package.loaded.coroutine = coroutine +end + +return { + version = base.version +} diff --git a/lib/resty/core/ctx.lua b/lib/resty/core/ctx.lua new file mode 100644 index 000000000..3e558cae5 --- /dev/null +++ b/lib/resty/core/ctx.lua @@ -0,0 +1,147 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local debug = require "debug" +local base = require "resty.core.base" +local misc = require "resty.core.misc" + + +local C = ffi.C +local register_getter = misc.register_ngx_magic_key_getter +local register_setter = misc.register_ngx_magic_key_setter +local registry = debug.getregistry() +local new_tab = base.new_tab +local ref_in_table = base.ref_in_table +local get_request = base.get_request +local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX +local FFI_OK = base.FFI_OK +local error = error +local setmetatable = setmetatable +local type = type +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_get_ctx_ref +local ngx_lua_ffi_set_ctx_ref + + +if subsystem == "http" then + ffi.cdef[[ + int ngx_http_lua_ffi_get_ctx_ref(ngx_http_request_t *r, int *in_ssl_phase, + int *ssl_ctx_ref); + int ngx_http_lua_ffi_set_ctx_ref(ngx_http_request_t *r, int ref); + ]] + + ngx_lua_ffi_get_ctx_ref = C.ngx_http_lua_ffi_get_ctx_ref + ngx_lua_ffi_set_ctx_ref = C.ngx_http_lua_ffi_set_ctx_ref + +elseif subsystem == "stream" then + ffi.cdef[[ + int ngx_stream_lua_ffi_get_ctx_ref(ngx_stream_lua_request_t *r, + int *in_ssl_phase, int *ssl_ctx_ref); + int ngx_stream_lua_ffi_set_ctx_ref(ngx_stream_lua_request_t *r, int ref); + ]] + + ngx_lua_ffi_get_ctx_ref = C.ngx_stream_lua_ffi_get_ctx_ref + ngx_lua_ffi_set_ctx_ref = C.ngx_stream_lua_ffi_set_ctx_ref +end + + +local _M = { + _VERSION = base.version +} + + +-- use a new ctxs table to make LuaJIT JIT compiler happy to generate more +-- efficient machine code. +local ctxs = {} +registry.ngx_lua_ctx_tables = ctxs + + +local get_ctx_table +do + local in_ssl_phase = ffi.new("int[1]") + local ssl_ctx_ref = ffi.new("int[1]") + + function get_ctx_table(ctx) + local r = get_request() + + if not r then + error("no request found") + end + + local ctx_ref = ngx_lua_ffi_get_ctx_ref(r, in_ssl_phase, ssl_ctx_ref) + if ctx_ref == FFI_NO_REQ_CTX then + error("no request ctx found") + end + + if ctx_ref < 0 then + ctx_ref = ssl_ctx_ref[0] + if ctx_ref > 0 and ctxs[ctx_ref] then + if in_ssl_phase[0] ~= 0 then + return ctxs[ctx_ref] + end + + if not ctx then + ctx = new_tab(0, 4) + end + + ctx = setmetatable(ctx, ctxs[ctx_ref]) + + else + if in_ssl_phase[0] ~= 0 then + if not ctx then + ctx = new_tab(1, 4) + end + + -- to avoid creating another table, we assume the users + -- won't overwrite the `__index` key + ctx.__index = ctx + + elseif not ctx then + ctx = new_tab(0, 4) + end + end + + ctx_ref = ref_in_table(ctxs, ctx) + if ngx_lua_ffi_set_ctx_ref(r, ctx_ref) ~= FFI_OK then + return nil + end + return ctx + end + return ctxs[ctx_ref] + end +end +register_getter("ctx", get_ctx_table) +_M.get_ctx_table = get_ctx_table + + +local function set_ctx_table(ctx) + local ctx_type = type(ctx) + if ctx_type ~= "table" then + error("ctx should be a table while getting a " .. ctx_type) + end + + local r = get_request() + + if not r then + error("no request found") + end + + local ctx_ref = ngx_lua_ffi_get_ctx_ref(r, nil, nil) + if ctx_ref == FFI_NO_REQ_CTX then + error("no request ctx found") + end + + if ctx_ref < 0 then + ctx_ref = ref_in_table(ctxs, ctx) + ngx_lua_ffi_set_ctx_ref(r, ctx_ref) + return + end + ctxs[ctx_ref] = ctx +end +register_setter("ctx", set_ctx_table) + + +return _M diff --git a/lib/resty/core/exit.lua b/lib/resty/core/exit.lua new file mode 100644 index 000000000..30a7b613c --- /dev/null +++ b/lib/resty/core/exit.lua @@ -0,0 +1,66 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_string = ffi.string +local ngx = ngx +local error = error +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local get_request = base.get_request +local co_yield = coroutine._yield +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_exit + + +if subsystem == "http" then + ffi.cdef[[ + int ngx_http_lua_ffi_exit(ngx_http_request_t *r, int status, + unsigned char *err, size_t *errlen); + ]] + + ngx_lua_ffi_exit = C.ngx_http_lua_ffi_exit + +elseif subsystem == "stream" then + ffi.cdef[[ + int ngx_stream_lua_ffi_exit(ngx_stream_lua_request_t *r, int status, + unsigned char *err, size_t *errlen); + ]] + + ngx_lua_ffi_exit = C.ngx_stream_lua_ffi_exit +end + + +local ERR_BUF_SIZE = 128 +local FFI_DONE = base.FFI_DONE + + +ngx.exit = function (rc) + local err = get_string_buf(ERR_BUF_SIZE) + local errlen = get_size_ptr() + local r = get_request() + if r == nil then + error("no request found") + end + errlen[0] = ERR_BUF_SIZE + rc = ngx_lua_ffi_exit(r, rc, err, errlen) + if rc == 0 then + -- print("yielding...") + return co_yield() + end + if rc == FFI_DONE then + return + end + error(ffi_string(err, errlen[0]), 2) +end + + +return { + version = base.version +} diff --git a/lib/resty/core/hash.lua b/lib/resty/core/hash.lua new file mode 100644 index 000000000..062f3ff5b --- /dev/null +++ b/lib/resty/core/hash.lua @@ -0,0 +1,154 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_new = ffi.new +local ffi_string = ffi.string +local ngx = ngx +local type = type +local error = error +local tostring = tostring +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_md5 +local ngx_lua_ffi_md5_bin +local ngx_lua_ffi_sha1_bin +local ngx_lua_ffi_crc32_long +local ngx_lua_ffi_crc32_short + + +if subsystem == "http" then + ffi.cdef[[ + void ngx_http_lua_ffi_md5_bin(const unsigned char *src, size_t len, + unsigned char *dst); + + void ngx_http_lua_ffi_md5(const unsigned char *src, size_t len, + unsigned char *dst); + + int ngx_http_lua_ffi_sha1_bin(const unsigned char *src, size_t len, + unsigned char *dst); + + unsigned int ngx_http_lua_ffi_crc32_long(const unsigned char *src, + size_t len); + + unsigned int ngx_http_lua_ffi_crc32_short(const unsigned char *src, + size_t len); + ]] + + ngx_lua_ffi_md5 = C.ngx_http_lua_ffi_md5 + ngx_lua_ffi_md5_bin = C.ngx_http_lua_ffi_md5_bin + ngx_lua_ffi_sha1_bin = C.ngx_http_lua_ffi_sha1_bin + ngx_lua_ffi_crc32_short = C.ngx_http_lua_ffi_crc32_short + ngx_lua_ffi_crc32_long = C.ngx_http_lua_ffi_crc32_long + +elseif subsystem == "stream" then + ffi.cdef[[ + void ngx_stream_lua_ffi_md5_bin(const unsigned char *src, size_t len, + unsigned char *dst); + + void ngx_stream_lua_ffi_md5(const unsigned char *src, size_t len, + unsigned char *dst); + + int ngx_stream_lua_ffi_sha1_bin(const unsigned char *src, size_t len, + unsigned char *dst); + + unsigned int ngx_stream_lua_ffi_crc32_long(const unsigned char *src, + size_t len); + + unsigned int ngx_stream_lua_ffi_crc32_short(const unsigned char *src, + size_t len); + ]] + + ngx_lua_ffi_md5 = C.ngx_stream_lua_ffi_md5 + ngx_lua_ffi_md5_bin = C.ngx_stream_lua_ffi_md5_bin + ngx_lua_ffi_sha1_bin = C.ngx_stream_lua_ffi_sha1_bin + ngx_lua_ffi_crc32_short = C.ngx_stream_lua_ffi_crc32_short + ngx_lua_ffi_crc32_long = C.ngx_stream_lua_ffi_crc32_long +end + + +local MD5_DIGEST_LEN = 16 +local md5_buf = ffi_new("unsigned char[?]", MD5_DIGEST_LEN) + +ngx.md5_bin = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + ngx_lua_ffi_md5_bin(s, #s, md5_buf) + return ffi_string(md5_buf, MD5_DIGEST_LEN) +end + + +local MD5_HEX_DIGEST_LEN = MD5_DIGEST_LEN * 2 +local md5_hex_buf = ffi_new("unsigned char[?]", MD5_HEX_DIGEST_LEN) + +ngx.md5 = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + ngx_lua_ffi_md5(s, #s, md5_hex_buf) + return ffi_string(md5_hex_buf, MD5_HEX_DIGEST_LEN) +end + + +local SHA_DIGEST_LEN = 20 +local sha_buf = ffi_new("unsigned char[?]", SHA_DIGEST_LEN) + +ngx.sha1_bin = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + local ok = ngx_lua_ffi_sha1_bin(s, #s, sha_buf) + if ok == 0 then + error("SHA-1 support missing in Nginx") + end + return ffi_string(sha_buf, SHA_DIGEST_LEN) +end + + +ngx.crc32_short = function (s) + if type(s) ~= "string" then + if not s then + s = "" + else + s = tostring(s) + end + end + + return ngx_lua_ffi_crc32_short(s, #s) +end + + +ngx.crc32_long = function (s) + if type(s) ~= "string" then + if not s then + s = "" + else + s = tostring(s) + end + end + + return ngx_lua_ffi_crc32_long(s, #s) +end + + +return { + version = base.version +} diff --git a/lib/resty/core/misc.lua b/lib/resty/core/misc.lua new file mode 100644 index 000000000..4f4151d7b --- /dev/null +++ b/lib/resty/core/misc.lua @@ -0,0 +1,258 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local base = require "resty.core.base" +local ffi = require "ffi" +local os = require "os" + + +local C = ffi.C +local ffi_new = ffi.new +local ffi_str = ffi.string +local ngx = ngx +local type = type +local error = error +local rawget = rawget +local rawset = rawset +local tonumber = tonumber +local setmetatable = setmetatable +local FFI_OK = base.FFI_OK +local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX +local FFI_BAD_CONTEXT = base.FFI_BAD_CONTEXT +local new_tab = base.new_tab +local get_request = base.get_request +local get_size_ptr = base.get_size_ptr +local get_string_buf = base.get_string_buf +local get_string_buf_size = base.get_string_buf_size +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_get_resp_status +local ngx_lua_ffi_get_conf_env +local ngx_magic_key_getters +local ngx_magic_key_setters + + +local _M = new_tab(0, 3) +local ngx_mt = new_tab(0, 2) + + +if subsystem == "http" then + ngx_magic_key_getters = new_tab(0, 4) + ngx_magic_key_setters = new_tab(0, 2) + +elseif subsystem == "stream" then + ngx_magic_key_getters = new_tab(0, 2) + ngx_magic_key_setters = new_tab(0, 1) +end + + +local function register_getter(key, func) + ngx_magic_key_getters[key] = func +end +_M.register_ngx_magic_key_getter = register_getter + + +local function register_setter(key, func) + ngx_magic_key_setters[key] = func +end +_M.register_ngx_magic_key_setter = register_setter + + +ngx_mt.__index = function (tb, key) + local f = ngx_magic_key_getters[key] + if f then + return f() + end + return rawget(tb, key) +end + + +ngx_mt.__newindex = function (tb, key, ctx) + local f = ngx_magic_key_setters[key] + if f then + return f(ctx) + end + return rawset(tb, key, ctx) +end + + +setmetatable(ngx, ngx_mt) + + +if subsystem == "http" then + ffi.cdef[[ + int 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 r); + int ngx_http_lua_ffi_is_subrequest(ngx_http_request_t *r); + int ngx_http_lua_ffi_headers_sent(ngx_http_request_t *r); + int ngx_http_lua_ffi_get_conf_env(const unsigned char *name, + unsigned char **env_buf, + size_t *name_len); + int ngx_http_lua_ffi_req_is_internal(ngx_http_request_t *r); + ]] + + + ngx_lua_ffi_get_resp_status = C.ngx_http_lua_ffi_get_resp_status + ngx_lua_ffi_get_conf_env = C.ngx_http_lua_ffi_get_conf_env + + + -- ngx.status + + + local function set_status(status) + local r = get_request() + + if not r then + error("no request found") + end + + if type(status) ~= 'number' then + status = tonumber(status) + end + + local rc = C.ngx_http_lua_ffi_set_resp_status(r, status) + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + end + register_setter("status", set_status) + + + -- ngx.is_subrequest + + + local function is_subreq() + local r = get_request() + + if not r then + error("no request found") + end + + local rc = C.ngx_http_lua_ffi_is_subrequest(r) + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + return rc == 1 + end + register_getter("is_subrequest", is_subreq) + + + -- ngx.headers_sent + + + local function headers_sent() + local r = get_request() + + if not r then + error("no request found") + end + + local rc = C.ngx_http_lua_ffi_headers_sent(r) + + if rc == FFI_NO_REQ_CTX then + error("no request ctx found") + end + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + return rc == 1 + end + register_getter("headers_sent", headers_sent) + + + -- ngx.req.is_internal + + + function ngx.req.is_internal() + local r = get_request() + if not r then + error("no request found") + end + + local rc = C.ngx_http_lua_ffi_req_is_internal(r) + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context") + end + + return rc == 1 + end + +elseif subsystem == "stream" then + ffi.cdef[[ + int ngx_stream_lua_ffi_get_resp_status(ngx_stream_lua_request_t *r); + int ngx_stream_lua_ffi_get_conf_env(const unsigned char *name, + unsigned char **env_buf, + size_t *name_len); + ]] + + ngx_lua_ffi_get_resp_status = C.ngx_stream_lua_ffi_get_resp_status + ngx_lua_ffi_get_conf_env = C.ngx_stream_lua_ffi_get_conf_env +end + + +-- ngx.status + + +local function get_status() + local r = get_request() + + if not r then + error("no request found") + end + + local rc = ngx_lua_ffi_get_resp_status(r) + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + return rc +end +register_getter("status", get_status) + + +do + local _getenv = os.getenv + local env_ptr = ffi_new("unsigned char *[1]") + + os.getenv = function (name) + local r = get_request() + if r then + -- past init_by_lua* phase now + os.getenv = _getenv + env_ptr = nil + return os.getenv(name) + end + + local size = get_string_buf_size() + env_ptr[0] = get_string_buf(size) + local name_len_ptr = get_size_ptr() + + local rc = ngx_lua_ffi_get_conf_env(name, env_ptr, name_len_ptr) + if rc == FFI_OK then + return ffi_str(env_ptr[0] + name_len_ptr[0] + 1) + end + + -- FFI_DECLINED + + local value = _getenv(name) + if value ~= nil then + return value + end + + return nil + end +end + + +_M._VERSION = base.version + + +return _M diff --git a/lib/resty/core/ndk.lua b/lib/resty/core/ndk.lua new file mode 100644 index 000000000..6547fe53c --- /dev/null +++ b/lib/resty/core/ndk.lua @@ -0,0 +1,92 @@ +-- Copyright (C) by OpenResty Inc. + + +local ffi = require 'ffi' +local base = require "resty.core.base" +base.allows_subsystem('http') + + +local C = ffi.C +local ffi_cast = ffi.cast +local ffi_new = ffi.new +local ffi_str = ffi.string +local FFI_OK = base.FFI_OK +local new_tab = base.new_tab +local get_string_buf = base.get_string_buf +local get_request = base.get_request +local setmetatable = setmetatable +local type = type +local tostring = tostring +local error = error + + +local _M = { + version = base.version +} + + +ffi.cdef[[ +typedef void * ndk_set_var_value_pt; + +int ngx_http_lua_ffi_ndk_lookup_directive(const unsigned char *var_data, + size_t var_len, ndk_set_var_value_pt *func); +int ngx_http_lua_ffi_ndk_set_var_get(ngx_http_request_t *r, + ndk_set_var_value_pt func, const unsigned char *arg_data, size_t arg_len, + ngx_http_lua_ffi_str_t *value); +]] + + +local func_p = ffi_new("void*[1]") +local ffi_str_size = ffi.sizeof("ngx_http_lua_ffi_str_t") +local ffi_str_type = ffi.typeof("ngx_http_lua_ffi_str_t*") + + +local function ndk_set_var_get(self, var) + if type(var) ~= "string" then + var = tostring(var) + end + + if C.ngx_http_lua_ffi_ndk_lookup_directive(var, #var, func_p) ~= FFI_OK then + error('ndk.set_var: directive "' .. var + .. '" not found or does not use ndk_set_var_value', 2) + end + + local func = func_p[0] + + return function (arg) + local r = get_request() + if not r then + error("no request found") + end + + if type(arg) ~= "string" then + arg = tostring(arg) + end + + local buf = get_string_buf(ffi_str_size) + local value = ffi_cast(ffi_str_type, buf) + local rc = C.ngx_http_lua_ffi_ndk_set_var_get(r, func, arg, #arg, value) + if rc ~= FFI_OK then + error("calling directive " .. var .. " failed with code " .. rc, 2) + end + + return ffi_str(value.data, value.len) + end +end + + +local function ndk_set_var_set() + error("not allowed", 2) +end + + +if ndk then + local mt = new_tab(0, 2) + mt.__newindex = ndk_set_var_set + mt.__index = ndk_set_var_get + + ndk.set_var = setmetatable(new_tab(0, 0), mt) +end + + +return _M diff --git a/lib/resty/core/param.lua b/lib/resty/core/param.lua new file mode 100644 index 000000000..ef571372a --- /dev/null +++ b/lib/resty/core/param.lua @@ -0,0 +1,103 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" +require "resty.core.phase" -- for ngx.get_phase + + +local C = ffi.C +local ffi_str = ffi.string +local FFI_AGAIN = base.FFI_AGAIN +local FFI_OK = base.FFI_OK +local get_request = base.get_request +local get_string_buf = base.get_string_buf +local getmetatable = getmetatable +local ngx = ngx +local ngx_phase = ngx.get_phase + + +local _M = { + version = base.version +} + + +ffi.cdef[[ + typedef unsigned char u_char; + + void ngx_http_lua_ffi_get_setby_param(ngx_http_request_t *r, int idx, + u_char **data, size_t *len); + int ngx_http_lua_ffi_get_body_filter_param_eof(ngx_http_request_t *r); + int ngx_http_lua_ffi_get_body_filter_param_body(ngx_http_request_t *r, + u_char **data_p, size_t *len_p); + int ngx_http_lua_ffi_copy_body_filter_param_body(ngx_http_request_t *r, + u_char *data); +]] +local data_p = ffi.new("unsigned char*[1]") +local len_p = ffi.new("size_t[1]") + + +local function get_setby_param(r, idx) + C.ngx_http_lua_ffi_get_setby_param(r, idx, data_p, len_p) + if len_p[0] == 0 then + return nil + end + + return ffi_str(data_p[0], len_p[0]) +end + + +local function get_body_filter_param(r, idx) + if idx == 1 then + data_p[0] = nil + local rc = C.ngx_http_lua_ffi_get_body_filter_param_body(r, data_p, + len_p) + if rc == FFI_AGAIN then + local buf = get_string_buf(len_p[0]) + assert(C.ngx_http_lua_ffi_copy_body_filter_param_body(r, buf) + == FFI_OK) + return ffi_str(buf, len_p[0]) + end + + if len_p[0] == 0 then + return "" + end + + return ffi_str(data_p[0], len_p[0]) + + elseif idx == 2 then + local rc = C.ngx_http_lua_ffi_get_body_filter_param_eof(r) + return rc == 1 + + else + return nil + end +end + + +local function get_param(tb, idx) + local r = get_request() + if not r then + error("no request found") + end + + local phase = ngx_phase() + if phase == "set" then + return get_setby_param(r, idx) + end + + if phase == "body_filter" then + return get_body_filter_param(r, idx) + end + + error("API disabled in the current context") +end + + +do + local mt = getmetatable(ngx.arg) + mt.__index = get_param +end + + +return _M diff --git a/lib/resty/core/phase.lua b/lib/resty/core/phase.lua new file mode 100644 index 000000000..8bd2ce827 --- /dev/null +++ b/lib/resty/core/phase.lua @@ -0,0 +1,71 @@ +local ffi = require 'ffi' +local base = require "resty.core.base" + +local C = ffi.C +local FFI_ERROR = base.FFI_ERROR +local get_request = base.get_request +local error = error +local tostring = tostring + + +ffi.cdef[[ +int ngx_http_lua_ffi_get_phase(ngx_http_request_t *r, char **err) +]] + + +local errmsg = base.get_errmsg_ptr() +local context_names = { + [0x0001] = "set", + [0x0002] = "rewrite", + [0x0004] = "access", + [0x0008] = "content", + [0x0010] = "log", + [0x0020] = "header_filter", + [0x0040] = "body_filter", + [0x0080] = "timer", + [0x0100] = "init_worker", + [0x0200] = "balancer", + [0x0400] = "ssl_cert", + [0x0800] = "ssl_session_store", + [0x1000] = "ssl_session_fetch", + [0x2000] = "exit_worker", + [0x4000] = "ssl_client_hello", + [0x8000] = "server_rewrite", +} + + +function ngx.get_phase() + local r = get_request() + + -- if we have no request object, assume we are called from the "init" phase + if not r then + return "init" + end + + local context = C.ngx_http_lua_ffi_get_phase(r, errmsg) + if context == FFI_ERROR then -- NGX_ERROR + error(errmsg, 2) + end + + local phase = context_names[context] + if not phase then + error("unknown phase: " .. tostring(context)) + end + + return phase +end + + +function ngx.get_raw_phase(r) + local context = C.ngx_http_lua_ffi_get_phase(r, errmsg) + if context == FFI_ERROR then -- NGX_ERROR + error(errmsg, 2) + end + + return context +end + + +return { + version = base.version +} diff --git a/lib/resty/core/regex.lua b/lib/resty/core/regex.lua new file mode 100644 index 000000000..90fd835e9 --- /dev/null +++ b/lib/resty/core/regex.lua @@ -0,0 +1,1213 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" +local bit = require "bit" +local subsystem = ngx.config.subsystem +require "resty.core.time" -- for ngx.now used by resty.lrucache + +if subsystem == 'http' then + require "resty.core.phase" -- for ngx.get_phase +end + +local lrucache = require "resty.lrucache" + +local lrucache_get = lrucache.get +local lrucache_set = lrucache.set +local ffi_string = ffi.string +local ffi_gc = ffi.gc +local ffi_copy = ffi.copy +local ffi_cast = ffi.cast +local C = ffi.C +local bor = bit.bor +local band = bit.band +local lshift = bit.lshift +local sub = string.sub +local fmt = string.format +local byte = string.byte +local ngx = ngx +local type = type +local tostring = tostring +local error = error +local setmetatable = setmetatable +local tonumber = tonumber +local get_string_buf = base.get_string_buf +local get_string_buf_size = base.get_string_buf_size +local new_tab = base.new_tab +local ngx_phase = ngx.get_phase +local ngx_log = ngx.log +local ngx_NOTICE = ngx.NOTICE + + +local _M = { + version = base.version +} + + +ngx.re = new_tab(0, 5) + + +local pcre_ver_fn + +if subsystem == 'http' then + ffi.cdef[[ + const char *ngx_http_lua_ffi_pcre_version(void); + ]] + pcre_ver_fn = C.ngx_http_lua_ffi_pcre_version + +elseif subsystem == 'stream' then + ffi.cdef[[ + const char *ngx_stream_lua_ffi_pcre_version(void); + ]] + pcre_ver_fn = C.ngx_stream_lua_ffi_pcre_version + +else + error("unsupported subsystem: " .. tostring(subsystem)) +end + +local pcre_ver + +if not pcall(function() pcre_ver = ffi_string(pcre_ver_fn()) end) then + setmetatable(ngx.re, { + __index = function(_, key) + error("no support for 'ngx.re." .. key .. "': OpenResty was " .. + "compiled without PCRE support", 2) + end + }) + + _M.no_pcre = true + + return _M +end + + +local MAX_ERR_MSG_LEN = 128 + + +local FLAG_COMPILE_ONCE = 0x01 +local FLAG_DFA = 0x02 +local FLAG_JIT = 0x04 +local FLAG_DUPNAMES = 0x08 +local FLAG_NO_UTF8_CHECK = 0x10 + + +local PCRE_CASELESS = 0x0000001 +local PCRE_MULTILINE = 0x0000002 +local PCRE_DOTALL = 0x0000004 +local PCRE_EXTENDED = 0x0000008 +local PCRE_ANCHORED = 0x0000010 +local PCRE_UTF8 = 0x0000800 +local PCRE_DUPNAMES = 0x0080000 +local PCRE_JAVASCRIPT_COMPAT = 0x2000000 + + +local PCRE_ERROR_NOMATCH = -1 + + +local regex_match_cache +local regex_sub_func_cache = new_tab(0, 4) +local regex_sub_str_cache = new_tab(0, 4) +local max_regex_cache_size +local regex_cache_size = 0 +local script_engine +local ngx_lua_ffi_max_regex_cache_size +local ngx_lua_ffi_destroy_regex +local ngx_lua_ffi_compile_regex +local ngx_lua_ffi_exec_regex +local ngx_lua_ffi_create_script_engine +local ngx_lua_ffi_destroy_script_engine +local ngx_lua_ffi_init_script_engine +local ngx_lua_ffi_compile_replace_template +local ngx_lua_ffi_script_eval_len +local ngx_lua_ffi_script_eval_data + +-- PCRE 8.43 on macOS introduced the MAP_JIT option when creating the memory +-- region used to store JIT compiled code, which does not survive across +-- `fork()`, causing further usage of PCRE JIT compiler to segfault in worker +-- processes. +-- +-- This flag prevents any regex used in the init phase to be JIT compiled or +-- cached when running under macOS, even if the user requests so. Caching is +-- thus disabled to prevent further calls of same regex in worker to have poor +-- performance. +-- +-- TODO: improve this workaround when PCRE allows for unspecifying the MAP_JIT +-- option. +local no_jit_in_init + +if jit.os == "OSX" then + local maj, min = string.match(pcre_ver, "^(%d+)%.(%d+)") + if maj and min then + local pcre_ver_num = tonumber(maj .. min) + + if pcre_ver_num >= 843 then + no_jit_in_init = true + end + + else + -- assume this version is faulty as well + no_jit_in_init = true + end +end + + +if subsystem == 'http' then + ffi.cdef[[ + + typedef struct { + ngx_str_t value; + void *lengths; + void *values; + } ngx_http_lua_complex_value_t; + + typedef struct { + void *pool; + unsigned char *name_table; + int name_count; + int name_entry_size; + + int ncaptures; + int *captures; + + void *regex; + void *regex_sd; + + ngx_http_lua_complex_value_t *replace; + + const char *pattern; + } ngx_http_lua_regex_t; + + ngx_http_lua_regex_t * + ngx_http_lua_ffi_compile_regex(const unsigned char *pat, + size_t pat_len, int flags, + int pcre_opts, unsigned char *errstr, + size_t errstr_size); + + int ngx_http_lua_ffi_exec_regex(ngx_http_lua_regex_t *re, int flags, + const unsigned char *s, size_t len, int pos); + + void ngx_http_lua_ffi_destroy_regex(ngx_http_lua_regex_t *re); + + int ngx_http_lua_ffi_compile_replace_template(ngx_http_lua_regex_t *re, + const unsigned char + *replace_data, + size_t replace_len); + + struct ngx_http_lua_script_engine_s; + typedef struct ngx_http_lua_script_engine_s *ngx_http_lua_script_engine_t; + + ngx_http_lua_script_engine_t *ngx_http_lua_ffi_create_script_engine(void); + + void ngx_http_lua_ffi_init_script_engine(ngx_http_lua_script_engine_t *e, + const unsigned char *subj, + ngx_http_lua_regex_t *compiled, + int count); + + void ngx_http_lua_ffi_destroy_script_engine( + ngx_http_lua_script_engine_t *e); + + size_t ngx_http_lua_ffi_script_eval_len(ngx_http_lua_script_engine_t *e, + ngx_http_lua_complex_value_t *cv); + + size_t ngx_http_lua_ffi_script_eval_data(ngx_http_lua_script_engine_t *e, + ngx_http_lua_complex_value_t *cv, + unsigned char *dst); + + uint32_t ngx_http_lua_ffi_max_regex_cache_size(void); + ]] + + ngx_lua_ffi_max_regex_cache_size = C.ngx_http_lua_ffi_max_regex_cache_size + ngx_lua_ffi_destroy_regex = C.ngx_http_lua_ffi_destroy_regex + ngx_lua_ffi_compile_regex = C.ngx_http_lua_ffi_compile_regex + ngx_lua_ffi_exec_regex = C.ngx_http_lua_ffi_exec_regex + ngx_lua_ffi_create_script_engine = C.ngx_http_lua_ffi_create_script_engine + ngx_lua_ffi_init_script_engine = C.ngx_http_lua_ffi_init_script_engine + ngx_lua_ffi_destroy_script_engine = C.ngx_http_lua_ffi_destroy_script_engine + ngx_lua_ffi_compile_replace_template = + C.ngx_http_lua_ffi_compile_replace_template + ngx_lua_ffi_script_eval_len = C.ngx_http_lua_ffi_script_eval_len + ngx_lua_ffi_script_eval_data = C.ngx_http_lua_ffi_script_eval_data + +elseif subsystem == 'stream' then + ffi.cdef[[ + + typedef struct { + ngx_str_t value; + void *lengths; + void *values; + } ngx_stream_lua_complex_value_t; + + typedef struct { + void *pool; + unsigned char *name_table; + int name_count; + int name_entry_size; + + int ncaptures; + int *captures; + + void *regex; + void *regex_sd; + + ngx_stream_lua_complex_value_t *replace; + + const char *pattern; + } ngx_stream_lua_regex_t; + + ngx_stream_lua_regex_t * + ngx_stream_lua_ffi_compile_regex(const unsigned char *pat, + size_t pat_len, int flags, + int pcre_opts, unsigned char *errstr, + size_t errstr_size); + + int ngx_stream_lua_ffi_exec_regex(ngx_stream_lua_regex_t *re, int flags, + const unsigned char *s, size_t len, int pos); + + void ngx_stream_lua_ffi_destroy_regex(ngx_stream_lua_regex_t *re); + + int ngx_stream_lua_ffi_compile_replace_template(ngx_stream_lua_regex_t *re, + const unsigned char + *replace_data, + size_t replace_len); + + struct ngx_stream_lua_script_engine_s; + typedef struct ngx_stream_lua_script_engine_s + *ngx_stream_lua_script_engine_t; + + ngx_stream_lua_script_engine_t * + ngx_stream_lua_ffi_create_script_engine(void); + + void ngx_stream_lua_ffi_init_script_engine( + ngx_stream_lua_script_engine_t *e, const unsigned char *subj, + ngx_stream_lua_regex_t *compiled, int count); + + void ngx_stream_lua_ffi_destroy_script_engine( + ngx_stream_lua_script_engine_t *e); + + size_t ngx_stream_lua_ffi_script_eval_len( + ngx_stream_lua_script_engine_t *e, ngx_stream_lua_complex_value_t *cv); + + size_t ngx_stream_lua_ffi_script_eval_data( + ngx_stream_lua_script_engine_t *e, ngx_stream_lua_complex_value_t *cv, + unsigned char *dst); + + uint32_t ngx_stream_lua_ffi_max_regex_cache_size(void); + ]] + + ngx_lua_ffi_max_regex_cache_size = C.ngx_stream_lua_ffi_max_regex_cache_size + ngx_lua_ffi_destroy_regex = C.ngx_stream_lua_ffi_destroy_regex + ngx_lua_ffi_compile_regex = C.ngx_stream_lua_ffi_compile_regex + ngx_lua_ffi_exec_regex = C.ngx_stream_lua_ffi_exec_regex + ngx_lua_ffi_create_script_engine = C.ngx_stream_lua_ffi_create_script_engine + ngx_lua_ffi_init_script_engine = C.ngx_stream_lua_ffi_init_script_engine + ngx_lua_ffi_destroy_script_engine = + C.ngx_stream_lua_ffi_destroy_script_engine + ngx_lua_ffi_compile_replace_template = + C.ngx_stream_lua_ffi_compile_replace_template + ngx_lua_ffi_script_eval_len = C.ngx_stream_lua_ffi_script_eval_len + ngx_lua_ffi_script_eval_data = C.ngx_stream_lua_ffi_script_eval_data +end + + +local c_str_type = ffi.typeof("const char *") + +local cached_re_opts = new_tab(0, 4) + +local buf_grow_ratio = 2 + + +function _M.set_buf_grow_ratio(ratio) + buf_grow_ratio = ratio +end + + +local function get_max_regex_cache_size() + if max_regex_cache_size then + return max_regex_cache_size + end + max_regex_cache_size = ngx_lua_ffi_max_regex_cache_size() + return max_regex_cache_size +end + + +local regex_cache_is_empty = true + + +function _M.is_regex_cache_empty() + return regex_cache_is_empty +end + + +local function lrucache_set_wrapper(...) + regex_cache_is_empty = false + lrucache_set(...) +end + + +local parse_regex_opts = function (opts) + local t = cached_re_opts[opts] + if t then + return t[1], t[2] + end + + local flags = 0 + local pcre_opts = 0 + local len = #opts + + for i = 1, len do + local opt = byte(opts, i) + if opt == byte("o") then + flags = bor(flags, FLAG_COMPILE_ONCE) + + elseif opt == byte("j") then + flags = bor(flags, FLAG_JIT) + + elseif opt == byte("i") then + pcre_opts = bor(pcre_opts, PCRE_CASELESS) + + elseif opt == byte("s") then + pcre_opts = bor(pcre_opts, PCRE_DOTALL) + + elseif opt == byte("m") then + pcre_opts = bor(pcre_opts, PCRE_MULTILINE) + + elseif opt == byte("u") then + pcre_opts = bor(pcre_opts, PCRE_UTF8) + + elseif opt == byte("U") then + pcre_opts = bor(pcre_opts, PCRE_UTF8) + flags = bor(flags, FLAG_NO_UTF8_CHECK) + + elseif opt == byte("x") then + pcre_opts = bor(pcre_opts, PCRE_EXTENDED) + + elseif opt == byte("d") then + flags = bor(flags, FLAG_DFA) + + elseif opt == byte("a") then + pcre_opts = bor(pcre_opts, PCRE_ANCHORED) + + elseif opt == byte("D") then + pcre_opts = bor(pcre_opts, PCRE_DUPNAMES) + flags = bor(flags, FLAG_DUPNAMES) + + elseif opt == byte("J") then + pcre_opts = bor(pcre_opts, PCRE_JAVASCRIPT_COMPAT) + + else + error(fmt('unknown flag "%s" (flags "%s")', sub(opts, i, i), opts), + 3) + end + end + + cached_re_opts[opts] = {flags, pcre_opts} + return flags, pcre_opts +end + + +if no_jit_in_init then + local parse_regex_opts_ = parse_regex_opts + + parse_regex_opts = function (opts) + if ngx_phase() ~= "init" then + -- past init_by_lua* phase now + parse_regex_opts = parse_regex_opts_ + return parse_regex_opts(opts) + end + + local t = cached_re_opts[opts] + if t then + return t[1], t[2] + end + + local flags = 0 + local pcre_opts = 0 + local len = #opts + + for i = 1, len do + local opt = byte(opts, i) + if opt == byte("o") then + ngx_log(ngx_NOTICE, "regex compilation cache disabled in init ", + "phase under macOS") + + elseif opt == byte("j") then + ngx_log(ngx_NOTICE, "regex compilation disabled in init ", + "phase under macOS") + + elseif opt == byte("i") then + pcre_opts = bor(pcre_opts, PCRE_CASELESS) + + elseif opt == byte("s") then + pcre_opts = bor(pcre_opts, PCRE_DOTALL) + + elseif opt == byte("m") then + pcre_opts = bor(pcre_opts, PCRE_MULTILINE) + + elseif opt == byte("u") then + pcre_opts = bor(pcre_opts, PCRE_UTF8) + + elseif opt == byte("U") then + pcre_opts = bor(pcre_opts, PCRE_UTF8) + flags = bor(flags, FLAG_NO_UTF8_CHECK) + + elseif opt == byte("x") then + pcre_opts = bor(pcre_opts, PCRE_EXTENDED) + + elseif opt == byte("d") then + flags = bor(flags, FLAG_DFA) + + elseif opt == byte("a") then + pcre_opts = bor(pcre_opts, PCRE_ANCHORED) + + elseif opt == byte("D") then + pcre_opts = bor(pcre_opts, PCRE_DUPNAMES) + flags = bor(flags, FLAG_DUPNAMES) + + elseif opt == byte("J") then + pcre_opts = bor(pcre_opts, PCRE_JAVASCRIPT_COMPAT) + + else + error(fmt('unknown flag "%s" (flags "%s")', sub(opts, i, i), + opts), 3) + end + end + + cached_re_opts[opts] = {flags, pcre_opts} + return flags, pcre_opts + end +end + + +local function collect_named_captures(compiled, flags, res) + local name_count = compiled.name_count + local name_table = compiled.name_table + local entry_size = compiled.name_entry_size + + local ind = 0 + local dup_names = (band(flags, FLAG_DUPNAMES) ~= 0) + for i = 1, name_count do + local n = bor(lshift(name_table[ind], 8), name_table[ind + 1]) + -- ngx.say("n = ", n) + local name = ffi_string(name_table + ind + 2) + local cap = res[n] + if dup_names then + -- unmatched captures (false) are not collected + if cap then + local old = res[name] + if old then + old[#old + 1] = cap + else + res[name] = {cap} + end + end + else + res[name] = cap + end + + ind = ind + entry_size + end +end + + +local function collect_captures(compiled, rc, subj, flags, res) + local cap = compiled.captures + local ncap = compiled.ncaptures + local name_count = compiled.name_count + + if not res then + res = new_tab(ncap, name_count) + end + + local i = 0 + local n = 0 + while i <= ncap do + if i > rc then + res[i] = false + else + local from = cap[n] + if from >= 0 then + local to = cap[n + 1] + res[i] = sub(subj, from + 1, to) + else + res[i] = false + end + end + i = i + 1 + n = n + 2 + end + + if name_count > 0 then + collect_named_captures(compiled, flags, res) + end + + return res +end + + +_M.collect_captures = collect_captures + + +local function destroy_compiled_regex(compiled) + ngx_lua_ffi_destroy_regex(ffi_gc(compiled, nil)) +end + + +_M.destroy_compiled_regex = destroy_compiled_regex + + +local function re_match_compile(regex, opts) + local flags = 0 + local pcre_opts = 0 + + if opts then + flags, pcre_opts = parse_regex_opts(opts) + else + opts = "" + end + + local compiled, key + local compile_once = (band(flags, FLAG_COMPILE_ONCE) == 1) + + -- FIXME: better put this in the outer scope when fixing the ngx.re API's + -- compatibility in the init_by_lua* context. + if not regex_match_cache then + local sz = get_max_regex_cache_size() + if sz <= 0 then + compile_once = false + else + regex_match_cache = lrucache.new(sz) + end + end + + if compile_once then + key = regex .. '\0' .. opts + compiled = lrucache_get(regex_match_cache, key) + end + + -- compile the regex + + if compiled == nil then + -- print("compiled regex not found, compiling regex...") + local errbuf = get_string_buf(MAX_ERR_MSG_LEN) + + compiled = ngx_lua_ffi_compile_regex(regex, #regex, flags, + pcre_opts, errbuf, + MAX_ERR_MSG_LEN) + + if compiled == nil then + return nil, ffi_string(errbuf) + end + + ffi_gc(compiled, ngx_lua_ffi_destroy_regex) + + -- print("ncaptures: ", compiled.ncaptures) + + if compile_once then + -- print("inserting compiled regex into cache") + lrucache_set_wrapper(regex_match_cache, key, compiled) + end + end + + return compiled, compile_once, flags +end + + +_M.re_match_compile = re_match_compile + + +local function re_match_helper(subj, regex, opts, ctx, want_caps, res, nth) + -- we need to cast this to strings to avoid exceptions when they are + -- something else. + subj = tostring(subj) + + local compiled, compile_once, flags = re_match_compile(regex, opts) + if compiled == nil then + -- compiled_once holds the error string + if not want_caps then + return nil, nil, compile_once + end + return nil, compile_once + end + + -- exec the compiled regex + + local rc + do + local pos + if ctx then + pos = ctx.pos + if not pos or pos <= 0 then + pos = 0 + else + pos = pos - 1 + end + + else + pos = 0 + end + + rc = ngx_lua_ffi_exec_regex(compiled, flags, subj, #subj, pos) + end + + if rc == PCRE_ERROR_NOMATCH then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil + end + + if rc < 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + if not want_caps then + return nil, nil, "pcre_exec() failed: " .. rc + end + return nil, "pcre_exec() failed: " .. rc + end + + if rc == 0 then + if band(flags, FLAG_DFA) == 0 then + if not want_caps then + return nil, nil, "capture size too small" + end + return nil, "capture size too small" + end + + rc = 1 + end + + -- print("cap 0: ", compiled.captures[0]) + -- print("cap 1: ", compiled.captures[1]) + + if ctx then + ctx.pos = compiled.captures[1] + 1 + end + + if not want_caps then + if not nth or nth < 0 then + nth = 0 + end + + if nth > compiled.ncaptures then + return nil, nil, "nth out of bound" + end + + if nth >= rc then + return nil, nil + end + + local from = compiled.captures[nth * 2] + 1 + local to = compiled.captures[nth * 2 + 1] + + if from < 0 or to < 0 then + return nil, nil + end + + return from, to + end + + res = collect_captures(compiled, rc, subj, flags, res) + + if not compile_once then + destroy_compiled_regex(compiled) + end + + return res +end + + +function ngx.re.match(subj, regex, opts, ctx, res) + return re_match_helper(subj, regex, opts, ctx, true, res) +end + + +function ngx.re.find(subj, regex, opts, ctx, nth) + return re_match_helper(subj, regex, opts, ctx, false, nil, nth) +end + + +do + local function destroy_re_gmatch_iterator(iterator) + if not iterator._compile_once then + destroy_compiled_regex(iterator._compiled) + end + iterator._compiled = nil + iterator._pos = nil + iterator._subj = nil + end + + + local function iterate_re_gmatch(self) + local compiled = self._compiled + local subj = self._subj + local subj_len = self._subj_len + local flags = self._flags + local pos = self._pos + + if not pos then + -- The iterator is exhausted. + return nil + end + + local rc = ngx_lua_ffi_exec_regex(compiled, flags, subj, subj_len, pos) + + if rc == PCRE_ERROR_NOMATCH then + destroy_re_gmatch_iterator(self) + return nil + end + + if rc < 0 then + destroy_re_gmatch_iterator(self) + return nil, "pcre_exec() failed: " .. rc + end + + if rc == 0 then + if band(flags, FLAG_DFA) == 0 then + destroy_re_gmatch_iterator(self) + return nil, "capture size too small" + end + + rc = 1 + end + + local cp_pos = tonumber(compiled.captures[1]) + if cp_pos == compiled.captures[0] then + cp_pos = cp_pos + 1 + if cp_pos > subj_len then + local res = collect_captures(compiled, rc, subj, flags) + destroy_re_gmatch_iterator(self) + return res + end + end + self._pos = cp_pos + return collect_captures(compiled, rc, subj, flags) + end + + + local re_gmatch_iterator_mt = { __call = iterate_re_gmatch } + + function ngx.re.gmatch(subj, regex, opts) + subj = tostring(subj) + + local compiled, compile_once, flags = re_match_compile(regex, opts) + if compiled == nil then + -- compiled_once holds the error string + return nil, compile_once + end + + local re_gmatch_iterator = { + _compiled = compiled, + _compile_once = compile_once, + _subj = subj, + _subj_len = #subj, + _flags = flags, + _pos = 0, + } + + return setmetatable(re_gmatch_iterator, re_gmatch_iterator_mt) + end +end -- do + + +local function new_script_engine(subj, compiled, count) + if not script_engine then + script_engine = ngx_lua_ffi_create_script_engine() + if script_engine == nil then + return nil + end + ffi_gc(script_engine, ngx_lua_ffi_destroy_script_engine) + end + + ngx_lua_ffi_init_script_engine(script_engine, subj, compiled, count) + return script_engine +end + + +local function check_buf_size(buf, buf_size, pos, len, new_len, must_alloc) + if new_len > buf_size then + buf_size = buf_size * buf_grow_ratio + if buf_size < new_len then + buf_size = new_len + end + local new_buf = get_string_buf(buf_size, must_alloc) + ffi_copy(new_buf, buf, len) + buf = new_buf + pos = buf + len + end + return buf, buf_size, pos, new_len +end + + +_M.check_buf_size = check_buf_size + + +local function re_sub_compile(regex, opts, replace, func) + local flags = 0 + local pcre_opts = 0 + + if opts then + flags, pcre_opts = parse_regex_opts(opts) + else + opts = "" + end + + local compiled + local compile_once = (band(flags, FLAG_COMPILE_ONCE) == 1) + if compile_once then + if func then + local subcache = regex_sub_func_cache[opts] + if subcache then + -- print("cache hit!") + compiled = subcache[regex] + end + + else + local subcache = regex_sub_str_cache[opts] + if subcache then + local subsubcache = subcache[regex] + if subsubcache then + -- print("cache hit!") + compiled = subsubcache[replace] + end + end + end + end + + -- compile the regex + + if compiled == nil then + -- print("compiled regex not found, compiling regex...") + local errbuf = get_string_buf(MAX_ERR_MSG_LEN) + + compiled = ngx_lua_ffi_compile_regex(regex, #regex, flags, pcre_opts, + errbuf, MAX_ERR_MSG_LEN) + + if compiled == nil then + return nil, ffi_string(errbuf) + end + + ffi_gc(compiled, ngx_lua_ffi_destroy_regex) + + if func == nil then + local rc = + ngx_lua_ffi_compile_replace_template(compiled, replace, + #replace) + if rc ~= 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, "failed to compile the replacement template" + end + end + + -- print("ncaptures: ", compiled.ncaptures) + + if compile_once then + if regex_cache_size < get_max_regex_cache_size() then + -- print("inserting compiled regex into cache") + if func then + local subcache = regex_sub_func_cache[opts] + if not subcache then + regex_sub_func_cache[opts] = {[regex] = compiled} + + else + subcache[regex] = compiled + end + + else + local subcache = regex_sub_str_cache[opts] + if not subcache then + regex_sub_str_cache[opts] = + {[regex] = {[replace] = compiled}} + + else + local subsubcache = subcache[regex] + if not subsubcache then + subcache[regex] = {[replace] = compiled} + + else + subsubcache[replace] = compiled + end + end + end + + regex_cache_size = regex_cache_size + 1 + else + compile_once = false + end + end + end + + return compiled, compile_once, flags +end + + +_M.re_sub_compile = re_sub_compile + + +local function re_sub_func_helper(subj, regex, replace, opts, global) + local compiled, compile_once, flags = + re_sub_compile(regex, opts, nil, replace) + if not compiled then + -- error string is in compile_once + return nil, nil, compile_once + end + + -- exec the compiled regex + + subj = tostring(subj) + local subj_len = #subj + local count = 0 + local pos = 0 + local cp_pos = 0 + + local dst_buf_size = get_string_buf_size() + -- Note: we have to always allocate the string buffer because + -- the user might call whatever resty.core's API functions recursively + -- in the user callback function. + local dst_buf = get_string_buf(dst_buf_size, true) + local dst_pos = dst_buf + local dst_len = 0 + + while true do + local rc = ngx_lua_ffi_exec_regex(compiled, flags, subj, subj_len, pos) + if rc == PCRE_ERROR_NOMATCH then + break + end + + if rc < 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, "pcre_exec() failed: " .. rc + end + + if rc == 0 then + if band(flags, FLAG_DFA) == 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, "capture size too small" + end + + rc = 1 + end + + count = count + 1 + local prefix_len = compiled.captures[0] - cp_pos + + local res = collect_captures(compiled, rc, subj, flags) + + local piece = tostring(replace(res)) + local piece_len = #piece + + local new_dst_len = dst_len + prefix_len + piece_len + dst_buf, dst_buf_size, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + new_dst_len, true) + + if prefix_len > 0 then + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + prefix_len) + dst_pos = dst_pos + prefix_len + end + + if piece_len > 0 then + ffi_copy(dst_pos, piece, piece_len) + dst_pos = dst_pos + piece_len + end + + cp_pos = compiled.captures[1] + pos = cp_pos + if pos == compiled.captures[0] then + pos = pos + 1 + if pos > subj_len then + break + end + end + + if not global then + break + end + end + + if not compile_once then + destroy_compiled_regex(compiled) + end + + if count > 0 then + if cp_pos < subj_len then + local suffix_len = subj_len - cp_pos + + local new_dst_len = dst_len + suffix_len + local _ + dst_buf, _, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + new_dst_len, true) + + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + suffix_len) + end + return ffi_string(dst_buf, dst_len), count + end + + return subj, 0 +end + + +local function re_sub_str_helper(subj, regex, replace, opts, global) + local compiled, compile_once, flags = + re_sub_compile(regex, opts, replace, nil) + if not compiled then + -- error string is in compile_once + return nil, nil, compile_once + end + + -- exec the compiled regex + + subj = tostring(subj) + local subj_len = #subj + local count = 0 + local pos = 0 + local cp_pos = 0 + + local dst_buf_size = get_string_buf_size() + local dst_buf = get_string_buf(dst_buf_size) + local dst_pos = dst_buf + local dst_len = 0 + + while true do + local rc = ngx_lua_ffi_exec_regex(compiled, flags, subj, subj_len, pos) + if rc == PCRE_ERROR_NOMATCH then + break + end + + if rc < 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, "pcre_exec() failed: " .. rc + end + + if rc == 0 then + if band(flags, FLAG_DFA) == 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, "capture size too small" + end + + rc = 1 + end + + count = count + 1 + local prefix_len = compiled.captures[0] - cp_pos + + local cv = compiled.replace + if cv.lengths ~= nil then + local e = new_script_engine(subj, compiled, rc) + if e == nil then + return nil, nil, "failed to create script engine" + end + + local bit_len = ngx_lua_ffi_script_eval_len(e, cv) + local new_dst_len = dst_len + prefix_len + bit_len + dst_buf, dst_buf_size, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + new_dst_len) + + if prefix_len > 0 then + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + prefix_len) + dst_pos = dst_pos + prefix_len + end + + if bit_len > 0 then + ngx_lua_ffi_script_eval_data(e, cv, dst_pos) + dst_pos = dst_pos + bit_len + end + + else + local bit_len = cv.value.len + + dst_buf, dst_buf_size, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + dst_len + prefix_len + bit_len) + + if prefix_len > 0 then + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + prefix_len) + dst_pos = dst_pos + prefix_len + end + + if bit_len > 0 then + ffi_copy(dst_pos, cv.value.data, bit_len) + dst_pos = dst_pos + bit_len + end + end + + cp_pos = compiled.captures[1] + pos = cp_pos + if pos == compiled.captures[0] then + pos = pos + 1 + if pos > subj_len then + break + end + end + + if not global then + break + end + end + + if not compile_once then + destroy_compiled_regex(compiled) + end + + if count > 0 then + if cp_pos < subj_len then + local suffix_len = subj_len - cp_pos + + local new_dst_len = dst_len + suffix_len + local _ + dst_buf, _, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + new_dst_len) + + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + suffix_len) + end + return ffi_string(dst_buf, dst_len), count + end + + return subj, 0 +end + + +local function re_sub_helper(subj, regex, replace, opts, global) + local repl_type = type(replace) + if repl_type == "function" then + return re_sub_func_helper(subj, regex, replace, opts, global) + end + + if repl_type ~= "string" then + replace = tostring(replace) + end + + return re_sub_str_helper(subj, regex, replace, opts, global) +end + + +function ngx.re.sub(subj, regex, replace, opts) + return re_sub_helper(subj, regex, replace, opts, false) +end + + +function ngx.re.gsub(subj, regex, replace, opts) + return re_sub_helper(subj, regex, replace, opts, true) +end + + +return _M diff --git a/lib/resty/core/request.lua b/lib/resty/core/request.lua new file mode 100644 index 000000000..771948ece --- /dev/null +++ b/lib/resty/core/request.lua @@ -0,0 +1,462 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" +local utils = require "resty.core.utils" + + +local subsystem = ngx.config.subsystem +local FFI_BAD_CONTEXT = base.FFI_BAD_CONTEXT +local FFI_DECLINED = base.FFI_DECLINED +local FFI_OK = base.FFI_OK +local clear_tab = base.clear_tab +local new_tab = base.new_tab +local C = ffi.C +local ffi_cast = ffi.cast +local ffi_new = ffi.new +local ffi_str = ffi.string +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local setmetatable = setmetatable +local lower = string.lower +local find = string.find +local rawget = rawget +local ngx = ngx +local get_request = base.get_request +local type = type +local error = error +local tostring = tostring +local tonumber = tonumber +local str_replace_char = utils.str_replace_char + + +local _M = { + version = base.version +} + + +local ngx_lua_ffi_req_start_time + + +if subsystem == "stream" then + ffi.cdef[[ + double ngx_stream_lua_ffi_req_start_time(ngx_stream_lua_request_t *r); + ]] + + ngx_lua_ffi_req_start_time = C.ngx_stream_lua_ffi_req_start_time + +elseif subsystem == "http" then + ffi.cdef[[ + double ngx_http_lua_ffi_req_start_time(ngx_http_request_t *r); + ]] + + ngx_lua_ffi_req_start_time = C.ngx_http_lua_ffi_req_start_time +end + + +function ngx.req.start_time() + local r = get_request() + if not r then + error("no request found") + end + + return tonumber(ngx_lua_ffi_req_start_time(r)) +end + + +if subsystem == "stream" then + return _M +end + + +local errmsg = base.get_errmsg_ptr() +local ffi_str_type = ffi.typeof("ngx_http_lua_ffi_str_t*") +local ffi_str_size = ffi.sizeof("ngx_http_lua_ffi_str_t") + + +ffi.cdef[[ + typedef struct { + ngx_http_lua_ffi_str_t key; + ngx_http_lua_ffi_str_t value; + } ngx_http_lua_ffi_table_elt_t; + + int ngx_http_lua_ffi_req_get_headers_count(ngx_http_request_t *r, + int max, int *truncated); + + int ngx_http_lua_ffi_req_get_headers(ngx_http_request_t *r, + ngx_http_lua_ffi_table_elt_t *out, int count, int raw); + + int ngx_http_lua_ffi_req_get_uri_args_count(ngx_http_request_t *r, + int max, int *truncated); + + size_t ngx_http_lua_ffi_req_get_querystring_len(ngx_http_request_t *r); + + int ngx_http_lua_ffi_req_get_uri_args(ngx_http_request_t *r, + unsigned char *buf, ngx_http_lua_ffi_table_elt_t *out, int count); + + int 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, + unsigned char **name, size_t *len); + + int ngx_http_lua_ffi_req_set_method(ngx_http_request_t *r, int method); + + int ngx_http_lua_ffi_req_set_header(ngx_http_request_t *r, + const unsigned char *key, size_t key_len, const unsigned char *value, + size_t value_len, ngx_http_lua_ffi_str_t *mvals, size_t mvals_len, + int override, char **errmsg); +]] + + +local table_elt_type = ffi.typeof("ngx_http_lua_ffi_table_elt_t*") +local table_elt_size = ffi.sizeof("ngx_http_lua_ffi_table_elt_t") +local truncated = ffi.new("int[1]") + +local req_headers_mt = { + __index = function (tb, key) + key = lower(key) + local value = rawget(tb, key) + if value == nil and find(key, '_', 1, true) then + value = rawget(tb, (str_replace_char(key, '_', '-'))) + end + return value + end +} + + +function ngx.req.get_headers(max_headers, raw) + local r = get_request() + if not r then + error("no request found") + end + + if not max_headers then + max_headers = -1 + end + + if not raw then + raw = 0 + else + raw = 1 + end + + local n = C.ngx_http_lua_ffi_req_get_headers_count(r, max_headers, + truncated) + if n == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + if n == 0 then + local headers = {} + if raw == 0 then + headers = setmetatable(headers, req_headers_mt) + end + + return headers + end + + local raw_buf = get_string_buf(n * table_elt_size) + local buf = ffi_cast(table_elt_type, raw_buf) + + local rc = C.ngx_http_lua_ffi_req_get_headers(r, buf, n, raw) + if rc == 0 then + local headers = new_tab(0, n) + for i = 0, n - 1 do + local h = buf[i] + + local key = h.key + key = ffi_str(key.data, key.len) + + local value = h.value + value = ffi_str(value.data, value.len) + + local existing = headers[key] + if existing then + if type(existing) == "table" then + existing[#existing + 1] = value + else + headers[key] = {existing, value} + end + + else + headers[key] = value + end + end + + if raw == 0 then + headers = setmetatable(headers, req_headers_mt) + end + + if truncated[0] ~= 0 then + return headers, "truncated" + end + + return headers + end + + return nil +end + + +function ngx.req.get_uri_args(max_args, tab) + local r = get_request() + if not r then + error("no request found") + end + + if not max_args then + max_args = -1 + end + + if tab then + clear_tab(tab) + end + + local n = C.ngx_http_lua_ffi_req_get_uri_args_count(r, max_args, truncated) + if n == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + if n == 0 then + return tab or {} + end + + local args_len = C.ngx_http_lua_ffi_req_get_querystring_len(r) + + local strbuf = get_string_buf(args_len + n * table_elt_size) + local kvbuf = ffi_cast(table_elt_type, strbuf + args_len) + + local nargs = C.ngx_http_lua_ffi_req_get_uri_args(r, strbuf, kvbuf, n) + + local args = tab or new_tab(0, nargs) + for i = 0, nargs - 1 do + local arg = kvbuf[i] + + local key = arg.key + key = ffi_str(key.data, key.len) + + local value = arg.value + local len = value.len + if len == -1 then + value = true + else + value = ffi_str(value.data, len) + end + + local existing = args[key] + if existing then + if type(existing) == "table" then + existing[#existing + 1] = value + else + args[key] = {existing, value} + end + + else + args[key] = value + end + end + + if truncated[0] ~= 0 then + return args, "truncated" + end + + return args +end + + +do + local methods = { + [0x0002] = "GET", + [0x0004] = "HEAD", + [0x0008] = "POST", + [0x0010] = "PUT", + [0x0020] = "DELETE", + [0x0040] = "MKCOL", + [0x0080] = "COPY", + [0x0100] = "MOVE", + [0x0200] = "OPTIONS", + [0x0400] = "PROPFIND", + [0x0800] = "PROPPATCH", + [0x1000] = "LOCK", + [0x2000] = "UNLOCK", + [0x4000] = "PATCH", + [0x8000] = "TRACE", + } + + local namep = ffi_new("unsigned char *[1]") + + function ngx.req.get_method() + local r = get_request() + if not r then + error("no request found") + end + + do + local id = C.ngx_http_lua_ffi_req_get_method(r) + if id == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + local method = methods[id] + if method then + return method + end + end + + local sizep = get_size_ptr() + local rc = C.ngx_http_lua_ffi_req_get_method_name(r, namep, sizep) + if rc ~= 0 then + return nil + end + + return ffi_str(namep[0], sizep[0]) + end +end -- do + + +function ngx.req.set_method(method) + local r = get_request() + if not r then + error("no request found") + end + + if type(method) ~= "number" then + error("bad method number", 2) + end + + local rc = C.ngx_http_lua_ffi_req_set_method(r, method) + if rc == FFI_OK then + return + end + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + if rc == FFI_DECLINED then + error("unsupported HTTP method: " .. method, 2) + end + + error("unknown error: " .. rc) +end + + +do + local function set_req_header(name, value, override) + local r = get_request() + if not r then + error("no request found", 3) + end + + if name == nil then + error("bad 'name' argument: string expected, got nil", 3) + end + + if type(name) ~= "string" then + name = tostring(name) + end + + local rc + + if value == nil then + if not override then + error("bad 'value' argument: string or table expected, got nil", + 3) + end + + rc = C.ngx_http_lua_ffi_req_set_header(r, name, #name, nil, 0, nil, + 0, 1, errmsg) + + else + local sval, sval_len, mvals, mvals_len, buf + local value_type = type(value) + + if value_type == "table" then + mvals_len = #value + if mvals_len == 0 and not override then + error("bad 'value' argument: non-empty table expected", 3) + end + + buf = get_string_buf(ffi_str_size * mvals_len) + mvals = ffi_cast(ffi_str_type, buf) + + for i = 1, mvals_len do + local s = value[i] + if type(s) ~= "string" then + s = tostring(s) + value[i] = s + end + + local str = mvals[i - 1] + str.data = s + str.len = #s + end + + sval_len = 0 + + else + if value_type ~= "string" then + sval = tostring(value) + else + sval = value + end + + sval_len = #sval + mvals_len = 0 + end + + rc = C.ngx_http_lua_ffi_req_set_header(r, name, #name, sval, + sval_len, mvals, mvals_len, + override and 1 or 0, errmsg) + end + + if rc == FFI_OK or rc == FFI_DECLINED then + return + end + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 3) + end + + -- rc == FFI_ERROR + error(ffi_str(errmsg[0])) + end + + + _M.set_req_header = set_req_header + + + function ngx.req.set_header(name, value) + set_req_header(name, value, true) -- override + end +end -- do + + +function ngx.req.clear_header(name) + local r = get_request() + if not r then + error("no request found") + end + + if type(name) ~= "string" then + name = tostring(name) + end + + local rc = C.ngx_http_lua_ffi_req_set_header(r, name, #name, nil, 0, nil, 0, + 1, errmsg) + + if rc == FFI_OK or rc == FFI_DECLINED then + return + end + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + -- rc == FFI_ERROR + error(ffi_str(errmsg[0])) +end + + +return _M diff --git a/lib/resty/core/response.lua b/lib/resty/core/response.lua new file mode 100644 index 000000000..986de7461 --- /dev/null +++ b/lib/resty/core/response.lua @@ -0,0 +1,238 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_cast = ffi.cast +local ffi_str = ffi.string +local new_tab = base.new_tab +local FFI_BAD_CONTEXT = base.FFI_BAD_CONTEXT +local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX +local FFI_DECLINED = base.FFI_DECLINED +local get_string_buf = base.get_string_buf +local setmetatable = setmetatable +local type = type +local tostring = tostring +local get_request = base.get_request +local error = error +local ngx = ngx + + +local _M = { + version = base.version +} + + +local MAX_HEADER_VALUES = 100 +local errmsg = base.get_errmsg_ptr() +local ffi_str_type = ffi.typeof("ngx_http_lua_ffi_str_t*") +local ffi_str_size = ffi.sizeof("ngx_http_lua_ffi_str_t") + + +ffi.cdef[[ + int ngx_http_lua_ffi_set_resp_header(ngx_http_request_t *r, + const char *key_data, size_t key_len, int is_nil, + const char *sval, size_t sval_len, ngx_http_lua_ffi_str_t *mvals, + size_t mvals_len, int override, char **errmsg); + + int ngx_http_lua_ffi_get_resp_header(ngx_http_request_t *r, + const unsigned char *key, size_t key_len, + unsigned char *key_buf, ngx_http_lua_ffi_str_t *values, + int max_nvalues, char **errmsg); +]] + + +local ngx_lua_ffi_set_resp_header + +local MACOS = jit and jit.os == "OSX" + +if MACOS then + ffi.cdef[[ + 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; + + int ngx_http_lua_ffi_set_resp_header_macos( + ngx_http_lua_set_resp_header_params_t *p); + ]] + + local set_params = ffi.new("ngx_http_lua_set_resp_header_params_t") + + ngx_lua_ffi_set_resp_header = function(r, key, key_len, is_nil, + sval, sval_len, mvals, + mvals_len, override, err) + + set_params.r = r + set_params.key_data = key + set_params.key_len = key_len + set_params.is_nil = is_nil + set_params.sval = sval + set_params.sval_len = sval_len + set_params.mvals = mvals + set_params.mvals_len = mvals_len + set_params.override = override + set_params.errmsg = err + + return C.ngx_http_lua_ffi_set_resp_header_macos(set_params) + end + +else + ngx_lua_ffi_set_resp_header = function(r, key, key_len, is_nil, + sval, sval_len, mvals, + mvals_len, override, err) + + return C.ngx_http_lua_ffi_set_resp_header(r, key, key_len, is_nil, + sval, sval_len, mvals, + mvals_len, override, err) + end +end + + +local function set_resp_header(tb, key, value, no_override) + local r = get_request() + if not r then + error("no request found") + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local rc + if value == nil then + if no_override then + error("invalid header value", 3) + end + + rc = ngx_lua_ffi_set_resp_header(r, key, #key, true, nil, 0, nil, + 0, 1, errmsg) + else + local sval, sval_len, mvals, mvals_len, buf + + if type(value) == "table" then + mvals_len = #value + if mvals_len == 0 and no_override then + return + end + + buf = get_string_buf(ffi_str_size * mvals_len) + mvals = ffi_cast(ffi_str_type, buf) + for i = 1, mvals_len do + local s = value[i] + if type(s) ~= "string" then + s = tostring(s) + value[i] = s + end + local str = mvals[i - 1] + str.data = s + str.len = #s + end + + sval_len = 0 + + else + if type(value) ~= "string" then + sval = tostring(value) + else + sval = value + end + sval_len = #sval + + mvals_len = 0 + end + + local override_int = no_override and 0 or 1 + rc = ngx_lua_ffi_set_resp_header(r, key, #key, false, sval, + sval_len, mvals, mvals_len, + override_int, errmsg) + end + + if rc == 0 or rc == FFI_DECLINED then + return + end + + if rc == FFI_NO_REQ_CTX then + error("no request ctx found") + end + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + -- rc == FFI_ERROR + error(ffi_str(errmsg[0]), 2) +end + + +_M.set_resp_header = set_resp_header + + +local function get_resp_header(tb, key) + local r = get_request() + if not r then + error("no request found") + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + + local key_buf = get_string_buf(key_len + ffi_str_size * MAX_HEADER_VALUES) + local values = ffi_cast(ffi_str_type, key_buf + key_len) + local n = C.ngx_http_lua_ffi_get_resp_header(r, key, key_len, key_buf, + values, MAX_HEADER_VALUES, + errmsg) + + -- print("retval: ", n) + + if n == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + if n == 0 then + return nil + end + + if n == 1 then + local v = values[0] + return ffi_str(v.data, v.len) + end + + if n > 0 then + local ret = new_tab(n, 0) + for i = 1, n do + local v = values[i - 1] + ret[i] = ffi_str(v.data, v.len) + end + return ret + end + + -- n == FFI_ERROR + error(ffi_str(errmsg[0]), 2) +end + + +do + local mt = new_tab(0, 2) + mt.__newindex = set_resp_header + mt.__index = get_resp_header + + ngx.header = setmetatable(new_tab(0, 0), mt) +end + + +return _M diff --git a/lib/resty/core/shdict.lua b/lib/resty/core/shdict.lua new file mode 100644 index 000000000..8d60d160e --- /dev/null +++ b/lib/resty/core/shdict.lua @@ -0,0 +1,932 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" + + +local _M = { + version = base.version +} + +local ngx_shared = ngx.shared +if not ngx_shared then + return _M +end + + +local ffi_new = ffi.new +local ffi_str = ffi.string +local C = ffi.C +local get_string_buf = base.get_string_buf +local get_string_buf_size = base.get_string_buf_size +local get_size_ptr = base.get_size_ptr +local tonumber = tonumber +local tostring = tostring +local next = next +local type = type +local error = error +local getmetatable = getmetatable +local FFI_DECLINED = base.FFI_DECLINED +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_shdict_get +local ngx_lua_ffi_shdict_incr +local ngx_lua_ffi_shdict_store +local ngx_lua_ffi_shdict_flush_all +local ngx_lua_ffi_shdict_get_ttl +local ngx_lua_ffi_shdict_set_expire +local ngx_lua_ffi_shdict_capacity +local ngx_lua_ffi_shdict_free_space +local ngx_lua_ffi_shdict_udata_to_zone + + +if subsystem == 'http' then + ffi.cdef[[ +int ngx_http_lua_ffi_shdict_get(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); + +int ngx_http_lua_ffi_shdict_incr(void *zone, const unsigned char *key, + size_t key_len, double *value, char **err, int has_init, + double init, long init_ttl, int *forcible); + +int ngx_http_lua_ffi_shdict_store(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); + +int ngx_http_lua_ffi_shdict_flush_all(void *zone); + +long ngx_http_lua_ffi_shdict_get_ttl(void *zone, + const unsigned char *key, size_t key_len); + +int ngx_http_lua_ffi_shdict_set_expire(void *zone, + const unsigned char *key, size_t key_len, long exptime); + +size_t ngx_http_lua_ffi_shdict_capacity(void *zone); + +void *ngx_http_lua_ffi_shdict_udata_to_zone(void *zone_udata); + ]] + + ngx_lua_ffi_shdict_get = function(zone, key, key_len, value_type, + str_value_buf, value_len, + num_value, user_flags, + get_stale, is_stale, errmsg) + + return C.ngx_http_lua_ffi_shdict_get(zone, key, key_len, value_type, + str_value_buf, value_len, + num_value, user_flags, + get_stale, is_stale, errmsg) + end + + ngx_lua_ffi_shdict_incr = function(zone, key, + key_len, value, err, has_init, + init, init_ttl, forcible) + + return C.ngx_http_lua_ffi_shdict_incr(zone, key, + key_len, value, err, has_init, + init, init_ttl, forcible) + end + + ngx_lua_ffi_shdict_store = function(zone, op, + key, key_len, value_type, + str_value_buf, str_value_len, + num_value, exptime, user_flags, + errmsg, forcible) + + return C.ngx_http_lua_ffi_shdict_store(zone, op, + key, key_len, value_type, + str_value_buf, str_value_len, + num_value, exptime, user_flags, + errmsg, forcible) + end + + ngx_lua_ffi_shdict_flush_all = C.ngx_http_lua_ffi_shdict_flush_all + ngx_lua_ffi_shdict_get_ttl = C.ngx_http_lua_ffi_shdict_get_ttl + ngx_lua_ffi_shdict_set_expire = C.ngx_http_lua_ffi_shdict_set_expire + ngx_lua_ffi_shdict_capacity = C.ngx_http_lua_ffi_shdict_capacity + ngx_lua_ffi_shdict_udata_to_zone = + C.ngx_http_lua_ffi_shdict_udata_to_zone + + if not pcall(function () + return C.ngx_http_lua_ffi_shdict_free_space + end) + then + ffi.cdef[[ +size_t ngx_http_lua_ffi_shdict_free_space(void *zone); + ]] + end + + pcall(function () + ngx_lua_ffi_shdict_free_space = C.ngx_http_lua_ffi_shdict_free_space + end) + +elseif subsystem == 'stream' then + + ffi.cdef[[ +int ngx_stream_lua_ffi_shdict_get(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); + +int ngx_stream_lua_ffi_shdict_incr(void *zone, const unsigned char *key, + size_t key_len, double *value, char **err, int has_init, + double init, long init_ttl, int *forcible); + +int ngx_stream_lua_ffi_shdict_store(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); + +int ngx_stream_lua_ffi_shdict_flush_all(void *zone); + +long ngx_stream_lua_ffi_shdict_get_ttl(void *zone, + const unsigned char *key, size_t key_len); + +int ngx_stream_lua_ffi_shdict_set_expire(void *zone, + const unsigned char *key, size_t key_len, long exptime); + +size_t ngx_stream_lua_ffi_shdict_capacity(void *zone); + +void *ngx_stream_lua_ffi_shdict_udata_to_zone(void *zone_udata); + ]] + + ngx_lua_ffi_shdict_get = function(zone, key, key_len, value_type, + str_value_buf, value_len, + num_value, user_flags, + get_stale, is_stale, errmsg) + + return C.ngx_stream_lua_ffi_shdict_get(zone, key, key_len, value_type, + str_value_buf, value_len, + num_value, user_flags, + get_stale, is_stale, errmsg) + end + + ngx_lua_ffi_shdict_incr = function(zone, key, + key_len, value, err, has_init, + init, init_ttl, forcible) + + return C.ngx_stream_lua_ffi_shdict_incr(zone, key, + key_len, value, err, has_init, + init, init_ttl, forcible) + end + + ngx_lua_ffi_shdict_store = function(zone, op, + key, key_len, value_type, + str_value_buf, str_value_len, + num_value, exptime, user_flags, + errmsg, forcible) + + return C.ngx_stream_lua_ffi_shdict_store(zone, op, + key, key_len, value_type, + str_value_buf, str_value_len, + num_value, exptime, user_flags, + errmsg, forcible) + end + + ngx_lua_ffi_shdict_flush_all = C.ngx_stream_lua_ffi_shdict_flush_all + ngx_lua_ffi_shdict_get_ttl = C.ngx_stream_lua_ffi_shdict_get_ttl + ngx_lua_ffi_shdict_set_expire = C.ngx_stream_lua_ffi_shdict_set_expire + ngx_lua_ffi_shdict_capacity = C.ngx_stream_lua_ffi_shdict_capacity + ngx_lua_ffi_shdict_udata_to_zone = + C.ngx_stream_lua_ffi_shdict_udata_to_zone + + if not pcall(function () + return C.ngx_stream_lua_ffi_shdict_free_space + end) + then + ffi.cdef[[ +size_t ngx_stream_lua_ffi_shdict_free_space(void *zone); + ]] + end + + -- ngx_stream_lua is only compatible with NGINX >= 1.13.6, meaning it + -- cannot lack support for ngx_stream_lua_ffi_shdict_free_space. + ngx_lua_ffi_shdict_free_space = C.ngx_stream_lua_ffi_shdict_free_space + +else + error("unknown subsystem: " .. subsystem) +end + + +local MACOS = jit and jit.os == "OSX" + +if MACOS and subsystem == 'http' then + ffi.cdef[[ +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; + +int ngx_http_lua_ffi_shdict_get_macos( + ngx_http_lua_shdict_get_params_t *p); +int ngx_http_lua_ffi_shdict_store_macos( + ngx_http_lua_shdict_store_params_t *p); +int ngx_http_lua_ffi_shdict_incr_macos( + ngx_http_lua_shdict_incr_params_t *p); + ]] + + local get_params = ffi_new("ngx_http_lua_shdict_get_params_t") + local incr_params = ffi_new("ngx_http_lua_shdict_incr_params_t") + local store_params = ffi_new("ngx_http_lua_shdict_store_params_t") + + ngx_lua_ffi_shdict_get = function(zone, key, key_len, value_type, + str_value_buf, value_len, + num_value, user_flags, + get_stale, is_stale, errmsg) + + get_params.zone = zone + get_params.key = key + get_params.key_len = key_len + get_params.value_type = value_type + get_params.str_value_buf = str_value_buf + get_params.str_value_len = value_len + get_params.num_value = num_value + get_params.user_flags = user_flags + get_params.get_stale = get_stale + get_params.is_stale = is_stale + get_params.errmsg = errmsg + + return C.ngx_http_lua_ffi_shdict_get_macos(get_params) + end + + ngx_lua_ffi_shdict_incr = function(zone, key, + key_len, value, err, has_init, + init, init_ttl, forcible) + + incr_params.zone = zone + incr_params.key = key + incr_params.key_len = key_len + incr_params.num_value = value + incr_params.errmsg = err + incr_params.has_init = has_init + incr_params.init = init + incr_params.init_ttl = init_ttl + incr_params.forcible = forcible + + return C.ngx_http_lua_ffi_shdict_incr_macos(incr_params) + end + + ngx_lua_ffi_shdict_store = function(zone, op, + key, key_len, value_type, + str_value_buf, str_value_len, + num_value, exptime, user_flags, + errmsg, forcible) + + store_params.zone = zone + store_params.op = op + store_params.key = key + store_params.key_len = key_len + store_params.value_type = value_type + store_params.str_value_buf = str_value_buf + store_params.str_value_len = str_value_len + store_params.num_value = num_value + store_params.exptime = exptime + store_params.user_flags = user_flags + store_params.errmsg = errmsg + store_params.forcible = forcible + + return C.ngx_http_lua_ffi_shdict_store_macos(store_params) + end +end + +if MACOS and subsystem == 'stream' then + ffi.cdef[[ +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_stream_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_stream_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_stream_lua_shdict_incr_params_t; + +int ngx_stream_lua_ffi_shdict_get_macos( + ngx_stream_lua_shdict_get_params_t *p); +int ngx_stream_lua_ffi_shdict_store_macos( + ngx_stream_lua_shdict_store_params_t *p); +int ngx_stream_lua_ffi_shdict_incr_macos( + ngx_stream_lua_shdict_incr_params_t *p); + ]] + + local get_params = ffi_new("ngx_stream_lua_shdict_get_params_t") + local store_params = ffi_new("ngx_stream_lua_shdict_store_params_t") + local incr_params = ffi_new("ngx_stream_lua_shdict_incr_params_t") + + ngx_lua_ffi_shdict_get = function(zone, key, key_len, value_type, + str_value_buf, value_len, + num_value, user_flags, + get_stale, is_stale, errmsg) + + get_params.zone = zone + get_params.key = key + get_params.key_len = key_len + get_params.value_type = value_type + get_params.str_value_buf = str_value_buf + get_params.str_value_len = value_len + get_params.num_value = num_value + get_params.user_flags = user_flags + get_params.get_stale = get_stale + get_params.is_stale = is_stale + get_params.errmsg = errmsg + + return C.ngx_stream_lua_ffi_shdict_get_macos(get_params) + end + + ngx_lua_ffi_shdict_incr = function(zone, key, + key_len, value, err, has_init, + init, init_ttl, forcible) + + incr_params.zone = zone + incr_params.key = key + incr_params.key_len = key_len + incr_params.num_value = value + incr_params.errmsg = err + incr_params.has_init = has_init + incr_params.init = init + incr_params.init_ttl = init_ttl + incr_params.forcible = forcible + + return C.ngx_stream_lua_ffi_shdict_incr_macos(incr_params) + end + + ngx_lua_ffi_shdict_store = function(zone, op, + key, key_len, value_type, + str_value_buf, str_value_len, + num_value, exptime, user_flags, + errmsg, forcible) + + store_params.zone = zone + store_params.op = op + store_params.key = key + store_params.key_len = key_len + store_params.value_type = value_type + store_params.str_value_buf = str_value_buf + store_params.str_value_len = str_value_len + store_params.num_value = num_value + store_params.exptime = exptime + store_params.user_flags = user_flags + store_params.errmsg = errmsg + store_params.forcible = forcible + + return C.ngx_stream_lua_ffi_shdict_store_macos(store_params) + end +end + + +if not pcall(function () return C.free end) then + ffi.cdef[[ +void free(void *ptr); + ]] +end + + +local value_type = ffi_new("int[1]") +local user_flags = ffi_new("int[1]") +local num_value = ffi_new("double[1]") +local is_stale = ffi_new("int[1]") +local forcible = ffi_new("int[1]") +local str_value_buf = ffi_new("unsigned char *[1]") +local errmsg = base.get_errmsg_ptr() + + +local function check_zone(zone) + if not zone or type(zone) ~= "table" then + error("bad \"zone\" argument", 3) + end + + zone = zone[1] + if type(zone) ~= "userdata" then + error("bad \"zone\" argument", 3) + end + + zone = ngx_lua_ffi_shdict_udata_to_zone(zone) + if zone == nil then + error("bad \"zone\" argument", 3) + end + + return zone +end + + +local function shdict_store(zone, op, key, value, exptime, flags) + zone = check_zone(zone) + + if not exptime then + exptime = 0 + elseif exptime < 0 then + error('bad "exptime" argument', 2) + end + + if not flags then + flags = 0 + end + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + if key_len > 65535 then + return nil, "key too long" + end + + local str_val_buf + local str_val_len = 0 + local num_val = 0 + local valtyp = type(value) + + -- print("value type: ", valtyp) + -- print("exptime: ", exptime) + + if valtyp == "string" then + valtyp = 4 -- LUA_TSTRING + str_val_buf = value + str_val_len = #value + + elseif valtyp == "number" then + valtyp = 3 -- LUA_TNUMBER + num_val = value + + elseif value == nil then + valtyp = 0 -- LUA_TNIL + + elseif valtyp == "boolean" then + valtyp = 1 -- LUA_TBOOLEAN + num_val = value and 1 or 0 + + else + return nil, "bad value type" + end + + local rc = ngx_lua_ffi_shdict_store(zone, op, key, key_len, + valtyp, str_val_buf, + str_val_len, num_val, + exptime * 1000, flags, errmsg, + forcible) + + -- print("rc == ", rc) + + if rc == 0 then -- NGX_OK + return true, nil, forcible[0] == 1 + end + + -- NGX_DECLINED or NGX_ERROR + return false, ffi_str(errmsg[0]), forcible[0] == 1 +end + + +local function shdict_set(zone, key, value, exptime, flags) + return shdict_store(zone, 0, key, value, exptime, flags) +end + + +local function shdict_safe_set(zone, key, value, exptime, flags) + return shdict_store(zone, 0x0004, key, value, exptime, flags) +end + + +local function shdict_add(zone, key, value, exptime, flags) + return shdict_store(zone, 0x0001, key, value, exptime, flags) +end + + +local function shdict_safe_add(zone, key, value, exptime, flags) + return shdict_store(zone, 0x0005, key, value, exptime, flags) +end + + +local function shdict_replace(zone, key, value, exptime, flags) + return shdict_store(zone, 0x0002, key, value, exptime, flags) +end + + +local function shdict_delete(zone, key) + return shdict_set(zone, key, nil) +end + + +local function shdict_get(zone, key) + zone = check_zone(zone) + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + if key_len > 65535 then + return nil, "key too long" + end + + local size = get_string_buf_size() + local buf = get_string_buf(size) + str_value_buf[0] = buf + local value_len = get_size_ptr() + value_len[0] = size + + local rc = ngx_lua_ffi_shdict_get(zone, key, key_len, value_type, + str_value_buf, value_len, + num_value, user_flags, 0, + is_stale, errmsg) + if rc ~= 0 then + if errmsg[0] ~= nil then + return nil, ffi_str(errmsg[0]) + end + + error("failed to get the key") + end + + local typ = value_type[0] + + if typ == 0 then -- LUA_TNIL + return nil + end + + local flags = tonumber(user_flags[0]) + + local val + + if typ == 4 then -- LUA_TSTRING + if str_value_buf[0] ~= buf then + -- ngx.say("len: ", tonumber(value_len[0])) + buf = str_value_buf[0] + val = ffi_str(buf, value_len[0]) + C.free(buf) + else + val = ffi_str(buf, value_len[0]) + end + + elseif typ == 3 then -- LUA_TNUMBER + val = tonumber(num_value[0]) + + elseif typ == 1 then -- LUA_TBOOLEAN + val = (tonumber(buf[0]) ~= 0) + + else + error("unknown value type: " .. typ) + end + + if flags ~= 0 then + return val, flags + end + + return val +end + + +local function shdict_get_stale(zone, key) + zone = check_zone(zone) + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + if key_len > 65535 then + return nil, "key too long" + end + + local size = get_string_buf_size() + local buf = get_string_buf(size) + str_value_buf[0] = buf + local value_len = get_size_ptr() + value_len[0] = size + + local rc = ngx_lua_ffi_shdict_get(zone, key, key_len, value_type, + str_value_buf, value_len, + num_value, user_flags, 1, + is_stale, errmsg) + if rc ~= 0 then + if errmsg[0] ~= nil then + return nil, ffi_str(errmsg[0]) + end + + error("failed to get the key") + end + + local typ = value_type[0] + + if typ == 0 then -- LUA_TNIL + return nil + end + + local flags = tonumber(user_flags[0]) + local val + + if typ == 4 then -- LUA_TSTRING + if str_value_buf[0] ~= buf then + -- ngx.say("len: ", tonumber(value_len[0])) + buf = str_value_buf[0] + val = ffi_str(buf, value_len[0]) + C.free(buf) + else + val = ffi_str(buf, value_len[0]) + end + + elseif typ == 3 then -- LUA_TNUMBER + val = tonumber(num_value[0]) + + elseif typ == 1 then -- LUA_TBOOLEAN + val = (tonumber(buf[0]) ~= 0) + + else + error("unknown value type: " .. typ) + end + + if flags ~= 0 then + return val, flags, is_stale[0] == 1 + end + + return val, nil, is_stale[0] == 1 +end + + +local function shdict_incr(zone, key, value, init, init_ttl) + zone = check_zone(zone) + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + if key_len > 65535 then + return nil, "key too long" + end + + if type(value) ~= "number" then + value = tonumber(value) + end + num_value[0] = value + + if init then + local typ = type(init) + if typ ~= "number" then + init = tonumber(init) + + if not init then + error("bad init arg: number expected, got " .. typ, 2) + end + end + end + + if init_ttl ~= nil then + local typ = type(init_ttl) + if typ ~= "number" then + init_ttl = tonumber(init_ttl) + + if not init_ttl then + error("bad init_ttl arg: number expected, got " .. typ, 2) + end + end + + if init_ttl < 0 then + error('bad "init_ttl" argument', 2) + end + + if not init then + error('must provide "init" when providing "init_ttl"', 2) + end + + else + init_ttl = 0 + end + + local rc = ngx_lua_ffi_shdict_incr(zone, key, key_len, num_value, + errmsg, init and 1 or 0, + init or 0, init_ttl * 1000, + forcible) + if rc ~= 0 then -- ~= NGX_OK + return nil, ffi_str(errmsg[0]) + end + + if not init then + return tonumber(num_value[0]) + end + + return tonumber(num_value[0]), nil, forcible[0] == 1 +end + + +local function shdict_flush_all(zone) + zone = check_zone(zone) + + ngx_lua_ffi_shdict_flush_all(zone) +end + + +local function shdict_ttl(zone, key) + zone = check_zone(zone) + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + + if key_len > 65535 then + return nil, "key too long" + end + + local rc = ngx_lua_ffi_shdict_get_ttl(zone, key, key_len) + + if rc == FFI_DECLINED then + return nil, "not found" + end + + return tonumber(rc) / 1000 +end + + +local function shdict_expire(zone, key, exptime) + zone = check_zone(zone) + + if not exptime then + error('bad "exptime" argument', 2) + end + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + + if key_len > 65535 then + return nil, "key too long" + end + + local rc = ngx_lua_ffi_shdict_set_expire(zone, key, key_len, + exptime * 1000) + + if rc == FFI_DECLINED then + return nil, "not found" + end + + -- NGINX_OK/FFI_OK + + return true +end + + +local function shdict_capacity(zone) + zone = check_zone(zone) + + return tonumber(ngx_lua_ffi_shdict_capacity(zone)) +end + + +local shdict_free_space +if ngx_lua_ffi_shdict_free_space then + shdict_free_space = function (zone) + zone = check_zone(zone) + + return tonumber(ngx_lua_ffi_shdict_free_space(zone)) + end + +else + shdict_free_space = function () + error("'shm:free_space()' not supported in NGINX < 1.11.7", 2) + end +end + + +local _, dict = next(ngx_shared, nil) +if dict then + local mt = getmetatable(dict) + if mt then + mt = mt.__index + if mt then + mt.get = shdict_get + mt.get_stale = shdict_get_stale + mt.incr = shdict_incr + mt.set = shdict_set + mt.safe_set = shdict_safe_set + mt.add = shdict_add + mt.safe_add = shdict_safe_add + mt.replace = shdict_replace + mt.delete = shdict_delete + mt.flush_all = shdict_flush_all + mt.ttl = shdict_ttl + mt.expire = shdict_expire + mt.capacity = shdict_capacity + mt.free_space = shdict_free_space + end + end +end + + +return _M diff --git a/lib/resty/core/socket.lua b/lib/resty/core/socket.lua new file mode 100644 index 000000000..ede5b663f --- /dev/null +++ b/lib/resty/core/socket.lua @@ -0,0 +1,272 @@ +local base = require "resty.core.base" +base.allows_subsystem("http") +local debug = require "debug" +local ffi = require "ffi" + + +local error = error +local assert = assert +local tonumber = tonumber +local tostring = tostring +local type = type +local select = select +local registry = debug.getregistry() + +local C = ffi.C +local ffi_new = ffi.new +local ffi_str = ffi.string +local ffi_gc = ffi.gc + +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local get_request = base.get_request + +local co_yield = coroutine._yield + + +local option_index = { + ["keepalive"] = 1, + ["reuseaddr"] = 2, + ["tcp-nodelay"] = 3, + ["sndbuf"] = 4, + ["rcvbuf"] = 5, +} + + +ffi.cdef[[ +typedef struct ngx_http_lua_socket_tcp_upstream_s + ngx_http_lua_socket_tcp_upstream_t; + +int +ngx_http_lua_ffi_socket_tcp_getoption(ngx_http_lua_socket_tcp_upstream_t *u, + int opt, int *val, unsigned char *err, size_t *errlen); + +int +ngx_http_lua_ffi_socket_tcp_setoption(ngx_http_lua_socket_tcp_upstream_t *u, + int opt, int val, unsigned char *err, size_t *errlen); + +int +ngx_http_lua_ffi_socket_tcp_sslhandshake(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, void *sess, + int enable_session_reuse, ngx_str_t *server_name, int verify, + int ocsp_status_req, void *chain, void *pkey, char **errmsg); + +int +ngx_http_lua_ffi_socket_tcp_get_sslhandshake_result(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, void **sess, char **errmsg, + int *openssl_error_code); + +void +ngx_http_lua_ffi_ssl_free_session(void *sess); +]] + + +local output_value_buf = ffi_new("int[1]") +local ERR_BUF_SIZE = 4096 + +local FFI_OK = base.FFI_OK +local FFI_ERROR = base.FFI_ERROR +local FFI_DONE = base.FFI_DONE +local FFI_AGAIN = base.FFI_AGAIN +local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX + +local SOCKET_CTX_INDEX = 1 +local SOCKET_CLIENT_CERT_INDEX = 6 +local SOCKET_CLIENT_PKEY_INDEX = 7 + + +local function get_tcp_socket(cosocket) + local tcp_socket = cosocket[SOCKET_CTX_INDEX] + if not tcp_socket then + error("socket is never created nor connected") + end + + return tcp_socket +end + + +local function getoption(cosocket, option) + local tcp_socket = get_tcp_socket(cosocket) + + if option == nil then + return nil, 'missing the "option" argument' + end + + if option_index[option] == nil then + return nil, "unsupported option " .. tostring(option) + end + + local err = get_string_buf(ERR_BUF_SIZE) + local errlen = get_size_ptr() + errlen[0] = ERR_BUF_SIZE + + local rc = C.ngx_http_lua_ffi_socket_tcp_getoption(tcp_socket, + option_index[option], + output_value_buf, + err, + errlen) + if rc ~= FFI_OK then + return nil, ffi_str(err, errlen[0]) + end + + return tonumber(output_value_buf[0]) +end + + +local function setoption(cosocket, option, value) + local tcp_socket = get_tcp_socket(cosocket) + + if option == nil then + return nil, 'missing the "option" argument' + end + + if value == nil then + return nil, 'missing the "value" argument' + end + + if option_index[option] == nil then + return nil, "unsupported option " .. tostring(option) + end + + local err = get_string_buf(ERR_BUF_SIZE) + local errlen = get_size_ptr() + errlen[0] = ERR_BUF_SIZE + + local rc = C.ngx_http_lua_ffi_socket_tcp_setoption(tcp_socket, + option_index[option], + value, + err, + errlen) + if rc ~= FFI_OK then + return nil, ffi_str(err, errlen[0]) + end + + return true +end + + +local errmsg = base.get_errmsg_ptr() +local session_ptr = ffi_new("void *[1]") +local server_name_str = ffi_new("ngx_str_t[1]") +local openssl_error_code = ffi_new("int[1]") + + +local function setclientcert(cosocket, cert, pkey) + if not cert and not pkey then + cosocket[SOCKET_CLIENT_CERT_INDEX] = nil + cosocket[SOCKET_CLIENT_PKEY_INDEX] = nil + return true + end + + if not cert or not pkey then + return nil, + "client certificate must be supplied with corresponding " .. + "private key" + end + + if type(cert) ~= "cdata" then + return nil, "bad cert arg: cdata expected, got " .. type(cert) + end + + if type(pkey) ~= "cdata" then + return nil, "bad pkey arg: cdata expected, got " .. type(pkey) + end + + cosocket[SOCKET_CLIENT_CERT_INDEX] = cert + cosocket[SOCKET_CLIENT_PKEY_INDEX] = pkey + + return true +end + + +local function sslhandshake(cosocket, reused_session, server_name, ssl_verify, + send_status_req, ...) + + local n = select("#", ...) + if not cosocket or n > 0 then + error("ngx.socket sslhandshake: expecting 1 ~ 5 arguments " .. + "(including the object), but seen " .. (cosocket and 5 + n or 0)) + end + + local r = get_request() + if not r then + error("no request found", 2) + end + + session_ptr[0] = type(reused_session) == "cdata" and reused_session or nil + + if server_name then + server_name_str[0].data = server_name + server_name_str[0].len = #server_name + + else + server_name_str[0].data = nil + server_name_str[0].len = 0 + end + + local u = get_tcp_socket(cosocket) + + local rc = C.ngx_http_lua_ffi_socket_tcp_sslhandshake(r, u, + session_ptr[0], + reused_session ~= false, + server_name_str, + ssl_verify and 1 or 0, + send_status_req and 1 or 0, + cosocket[SOCKET_CLIENT_CERT_INDEX], + cosocket[SOCKET_CLIENT_PKEY_INDEX], + errmsg) + + if rc == FFI_NO_REQ_CTX then + error("no request ctx found", 2) + end + + while true do + if rc == FFI_ERROR then + if openssl_error_code[0] ~= 0 then + return nil, openssl_error_code[0] .. ": " .. ffi_str(errmsg[0]) + end + + return nil, ffi_str(errmsg[0]) + end + + if rc == FFI_DONE then + return reused_session + end + + if rc == FFI_OK then + if reused_session == false then + return true + end + + rc = C.ngx_http_lua_ffi_socket_tcp_get_sslhandshake_result(r, u, + session_ptr, errmsg, openssl_error_code) + + assert(rc == FFI_OK) + + if session_ptr[0] == nil then + return session_ptr[0] + end + + return ffi_gc(session_ptr[0], C.ngx_http_lua_ffi_ssl_free_session) + end + + assert(rc == FFI_AGAIN) + + co_yield() + + rc = C.ngx_http_lua_ffi_socket_tcp_get_sslhandshake_result(r, u, + session_ptr, errmsg, openssl_error_code) + end +end + + +do + local method_table = registry.__tcp_cosocket_mt + method_table.getoption = getoption + method_table.setoption = setoption + method_table.setclientcert = setclientcert + method_table.sslhandshake = sslhandshake +end + + +return { version = base.version } diff --git a/lib/resty/core/time.lua b/lib/resty/core/time.lua new file mode 100644 index 000000000..140cb4e21 --- /dev/null +++ b/lib/resty/core/time.lua @@ -0,0 +1,183 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" + + +local error = error +local tonumber = tonumber +local type = type +local C = ffi.C +local ffi_new = ffi.new +local ffi_str = ffi.string +local time_val = ffi_new("long[1]") +local get_string_buf = base.get_string_buf +local ngx = ngx +local FFI_ERROR = base.FFI_ERROR +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_now +local ngx_lua_ffi_time +local ngx_lua_ffi_monotonic_msec +local ngx_lua_ffi_today +local ngx_lua_ffi_localtime +local ngx_lua_ffi_utctime +local ngx_lua_ffi_update_time + + +if subsystem == 'http' then + ffi.cdef[[ +double ngx_http_lua_ffi_now(void); +long ngx_http_lua_ffi_time(void); +long ngx_http_lua_ffi_monotonic_msec(void); +void ngx_http_lua_ffi_today(unsigned char *buf); +void ngx_http_lua_ffi_localtime(unsigned char *buf); +void ngx_http_lua_ffi_utctime(unsigned char *buf); +void ngx_http_lua_ffi_update_time(void); +int ngx_http_lua_ffi_cookie_time(unsigned char *buf, long t); +void ngx_http_lua_ffi_http_time(unsigned char *buf, long t); +void ngx_http_lua_ffi_parse_http_time(const unsigned char *str, size_t len, + long *time); + ]] + + ngx_lua_ffi_now = C.ngx_http_lua_ffi_now + ngx_lua_ffi_time = C.ngx_http_lua_ffi_time + ngx_lua_ffi_monotonic_msec = C.ngx_http_lua_ffi_monotonic_msec + ngx_lua_ffi_today = C.ngx_http_lua_ffi_today + ngx_lua_ffi_localtime = C.ngx_http_lua_ffi_localtime + ngx_lua_ffi_utctime = C.ngx_http_lua_ffi_utctime + ngx_lua_ffi_update_time = C.ngx_http_lua_ffi_update_time + +elseif subsystem == 'stream' then + ffi.cdef[[ +double ngx_stream_lua_ffi_now(void); +long ngx_stream_lua_ffi_time(void); +long ngx_stream_lua_ffi_monotonic_msec(void); +void ngx_stream_lua_ffi_today(unsigned char *buf); +void ngx_stream_lua_ffi_localtime(unsigned char *buf); +void ngx_stream_lua_ffi_utctime(unsigned char *buf); +void ngx_stream_lua_ffi_update_time(void); + ]] + + ngx_lua_ffi_now = C.ngx_stream_lua_ffi_now + ngx_lua_ffi_time = C.ngx_stream_lua_ffi_time + ngx_lua_ffi_monotonic_msec = C.ngx_stream_lua_ffi_monotonic_msec + ngx_lua_ffi_today = C.ngx_stream_lua_ffi_today + ngx_lua_ffi_localtime = C.ngx_stream_lua_ffi_localtime + ngx_lua_ffi_utctime = C.ngx_stream_lua_ffi_utctime + ngx_lua_ffi_update_time = C.ngx_stream_lua_ffi_update_time +end + + +function ngx.now() + local now = tonumber(ngx_lua_ffi_now()) + return now +end + + +function ngx.time() + local time = tonumber(ngx_lua_ffi_time()) + return time +end + + +local function monotonic_msec() + local msec = tonumber(ngx_lua_ffi_monotonic_msec()) + return msec +end + + +local function monotonic_time() + local msec = tonumber(ngx_lua_ffi_monotonic_msec()) + local time = msec / 1000 + + return time +end + + +function ngx.update_time() + ngx_lua_ffi_update_time() +end + + +function ngx.today() + -- the format of today is 2010-11-19 + local today_buf_size = 10 + local buf = get_string_buf(today_buf_size) + ngx_lua_ffi_today(buf) + return ffi_str(buf, today_buf_size) +end + + +function ngx.localtime() + -- the format of localtime is 2010-11-19 20:56:31 + local localtime_buf_size = 19 + local buf = get_string_buf(localtime_buf_size) + ngx_lua_ffi_localtime(buf) + return ffi_str(buf, localtime_buf_size) +end + + +function ngx.utctime() + -- the format of utctime is 2010-11-19 20:56:31 + local utctime_buf_size = 19 + local buf = get_string_buf(utctime_buf_size) + ngx_lua_ffi_utctime(buf) + return ffi_str(buf, utctime_buf_size) +end + + +if subsystem == 'http' then + +function ngx.cookie_time(sec) + if type(sec) ~= "number" then + error("number argument only", 2) + end + + -- the format of cookie time is Mon, 28-Sep-2038 06:00:00 GMT + -- or Mon, 28-Sep-18 06:00:00 GMT + local cookie_time_buf_size = 29 + local buf = get_string_buf(cookie_time_buf_size) + local used_size = C.ngx_http_lua_ffi_cookie_time(buf, sec) + return ffi_str(buf, used_size) +end + + +function ngx.http_time(sec) + if type(sec) ~= "number" then + error("number argument only", 2) + end + + -- the format of http time is Mon, 28 Sep 1970 06:00:00 GMT + local http_time_buf_size = 29 + local buf = get_string_buf(http_time_buf_size) + C.ngx_http_lua_ffi_http_time(buf, sec) + return ffi_str(buf, http_time_buf_size) +end + + +function ngx.parse_http_time(time_str) + if type(time_str) ~= "string" then + error("string argument only", 2) + end + + C.ngx_http_lua_ffi_parse_http_time(time_str, #time_str, time_val) + + local res = time_val[0] + if res == FFI_ERROR then + return nil + end + + local time = tonumber(res) + return time +end + +end + +return { + version = base.version, + monotonic_msec = monotonic_msec, + monotonic_time = monotonic_time +} diff --git a/lib/resty/core/time.md b/lib/resty/core/time.md new file mode 100644 index 000000000..7fa6191f9 --- /dev/null +++ b/lib/resty/core/time.md @@ -0,0 +1,145 @@ +Name +==== + +`resty.core.time` - utility functions for time operations. + +Table of Contents +================= + +* [Name](#name) +* [Status](#status) +* [Synopsis](#synopsis) +* [Description](#description) +* [Methods](#methods) + * [monotonic_msec](#monotonic_msec) + * [monotonic_time](#monotonic_time) +* [Community](#community) + * [English Mailing List](#english-mailing-list) + * [Chinese Mailing List](#chinese-mailing-list) +* [Bugs and Patches](#bugs-and-patches) +* [Copyright and License](#copyright-and-license) +* [See Also](#see-also) + +Status +====== + +This Lua module is currently considered production ready. + +Synopsis +======== + +```nginx +location = /t { + content_by_lua_block { + local time = require "resty.core.time" + ngx.say(time.monotonic_time()) + ngx.say(time.monotonic_msec()) + } +} +``` + +The value get by `"resty.core.time".monotonic_time` should equal to the value from /proc/uptime. + +[Back to TOC](#table-of-contents) + +Description +=========== + +This module provides utility functions for the time operations. + +[Back to TOC](#table-of-contents) + +Methods +======= + +monotonic_msec +-------------- + +**syntax:** *monotonic_msec()* + +Returns the elapsed time in microseconds from the machine boot for the current time stamp from the Nginx cached time (no syscall involved unlike Lua's date library). + +```lua +local cur_msec = require "resty.core.time".monotonic_msec +ngx.say(cur_msec()) + +``` + +This api was first introduced in lua-resty-core v0.1.25. + +[Back to TOC](#table-of-contents) + +monotonic_time +-------------- + +**syntax:** *monotonic_time()* + +Returns a floating-point number for the elapsed time in seconds (including milliseconds as the decimal part) from the machine boot for the current time stamp from the Nginx cached time (no syscall involved unlike Lua's date library). + +```lua +local cur_time = require "resty.core.time".monotonic_time +ngx.say(cur_time()) + +``` + +This api was first introduced in lua-resty-core v0.1.25. + +[Back to TOC](#table-of-contents) + +Community +========= + +[Back to TOC](#table-of-contents) + +English Mailing List +-------------------- + +The [openresty-en](https://groups.google.com/group/openresty-en) mailing list +is for English speakers. + +[Back to TOC](#table-of-contents) + +Chinese Mailing List +-------------------- + +The [openresty](https://groups.google.com/group/openresty) mailing list is for +Chinese speakers. + +[Back to TOC](#table-of-contents) + +Bugs and Patches +================ + +Please report bugs or submit patches by + +1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-core/issues), +1. or posting to the [OpenResty community](#community). + +[Back to TOC](#table-of-contents) + +Copyright and License +===================== + +This module is licensed under the BSD license. + +Copyright (C) 2018, by OpenResty Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[Back to TOC](#table-of-contents) + +See Also +======== +* the [lua-resty-core](https://github.com/openresty/lua-resty-core) library. +* the ngx_lua module: https://github.com/openresty/lua-nginx-module +* OpenResty: https://openresty.org + +[Back to TOC](#table-of-contents) diff --git a/lib/resty/core/uri.lua b/lib/resty/core/uri.lua new file mode 100644 index 000000000..96b1ab4c4 --- /dev/null +++ b/lib/resty/core/uri.lua @@ -0,0 +1,115 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_string = ffi.string +local ngx = ngx +local type = type +local error = error +local tostring = tostring +local get_string_buf = base.get_string_buf +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_escape_uri +local ngx_lua_ffi_unescape_uri +local ngx_lua_ffi_uri_escaped_length + +local NGX_ESCAPE_URI = 0 +local NGX_ESCAPE_URI_COMPONENT = 2 +local NGX_ESCAPE_MAIL_AUTH = 6 + + +if subsystem == "http" then + ffi.cdef[[ + size_t ngx_http_lua_ffi_uri_escaped_length(const unsigned char *src, + size_t len, int type); + + void ngx_http_lua_ffi_escape_uri(const unsigned char *src, size_t len, + unsigned char *dst, int type); + + size_t ngx_http_lua_ffi_unescape_uri(const unsigned char *src, + size_t len, unsigned char *dst); + ]] + + ngx_lua_ffi_escape_uri = C.ngx_http_lua_ffi_escape_uri + ngx_lua_ffi_unescape_uri = C.ngx_http_lua_ffi_unescape_uri + ngx_lua_ffi_uri_escaped_length = C.ngx_http_lua_ffi_uri_escaped_length + +elseif subsystem == "stream" then + ffi.cdef[[ + size_t ngx_stream_lua_ffi_uri_escaped_length(const unsigned char *src, + size_t len, int type); + + void ngx_stream_lua_ffi_escape_uri(const unsigned char *src, size_t len, + unsigned char *dst, int type); + + size_t ngx_stream_lua_ffi_unescape_uri(const unsigned char *src, + size_t len, unsigned char *dst); + ]] + + ngx_lua_ffi_escape_uri = C.ngx_stream_lua_ffi_escape_uri + ngx_lua_ffi_unescape_uri = C.ngx_stream_lua_ffi_unescape_uri + ngx_lua_ffi_uri_escaped_length = C.ngx_stream_lua_ffi_uri_escaped_length +end + + +ngx.escape_uri = function (s, esc_type) + if type(s) ~= 'string' then + if not s then + s = '' + + else + s = tostring(s) + end + end + + if esc_type == nil then + esc_type = NGX_ESCAPE_URI_COMPONENT + + else + if type(esc_type) ~= 'number' then + error("\"type\" is not a number", 3) + end + + if esc_type < NGX_ESCAPE_URI or esc_type > NGX_ESCAPE_MAIL_AUTH then + error("\"type\" " .. esc_type .. " out of range", 3) + end + end + + local slen = #s + local dlen = ngx_lua_ffi_uri_escaped_length(s, slen, esc_type) + + -- print("dlen: ", tonumber(dlen)) + if dlen == slen then + return s + end + local dst = get_string_buf(dlen) + ngx_lua_ffi_escape_uri(s, slen, dst, esc_type) + return ffi_string(dst, dlen) +end + + +ngx.unescape_uri = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + local slen = #s + local dlen = slen + local dst = get_string_buf(dlen) + dlen = ngx_lua_ffi_unescape_uri(s, slen, dst) + return ffi_string(dst, dlen) +end + + +return { + version = base.version, +} diff --git a/lib/resty/core/utils.lua b/lib/resty/core/utils.lua new file mode 100644 index 000000000..fda074aa0 --- /dev/null +++ b/lib/resty/core/utils.lua @@ -0,0 +1,46 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_str = ffi.string +local ffi_copy = ffi.copy +local byte = string.byte +local str_find = string.find +local get_string_buf = base.get_string_buf +local subsystem = ngx.config.subsystem + + +local _M = { + version = base.version +} + + +if subsystem == "http" then + ffi.cdef[[ + void ngx_http_lua_ffi_str_replace_char(unsigned char *buf, size_t len, + const unsigned char find, const unsigned char replace); + ]] + + + function _M.str_replace_char(str, find, replace) + if not str_find(str, find, nil, true) then + return str + end + + local len = #str + local buf = get_string_buf(len) + ffi_copy(buf, str, len) + + C.ngx_http_lua_ffi_str_replace_char(buf, len, byte(find), + byte(replace)) + + return ffi_str(buf, len) + end +end + + +return _M diff --git a/lib/resty/core/var.lua b/lib/resty/core/var.lua new file mode 100644 index 000000000..ea9c7635a --- /dev/null +++ b/lib/resty/core/var.lua @@ -0,0 +1,160 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_new = ffi.new +local ffi_str = ffi.string +local type = type +local error = error +local tostring = tostring +local setmetatable = setmetatable +local get_request = base.get_request +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local new_tab = base.new_tab +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_var_get +local ngx_lua_ffi_var_set + + +local ERR_BUF_SIZE = 256 + + +ngx.var = new_tab(0, 0) + + +if subsystem == "http" then + ffi.cdef[[ + int ngx_http_lua_ffi_var_get(ngx_http_request_t *r, + const char *name_data, size_t name_len, char *lowcase_buf, + int capture_id, char **value, size_t *value_len, char **err); + + int ngx_http_lua_ffi_var_set(ngx_http_request_t *r, + const unsigned char *name_data, size_t name_len, + unsigned char *lowcase_buf, const unsigned char *value, + size_t value_len, unsigned char *errbuf, size_t *errlen); + ]] + + ngx_lua_ffi_var_get = C.ngx_http_lua_ffi_var_get + ngx_lua_ffi_var_set = C.ngx_http_lua_ffi_var_set + +elseif subsystem == "stream" then + ffi.cdef[[ + int ngx_stream_lua_ffi_var_get(ngx_stream_lua_request_t *r, + const char *name_data, size_t name_len, char *lowcase_buf, + int capture_id, char **value, size_t *value_len, char **err); + + int ngx_stream_lua_ffi_var_set(ngx_stream_lua_request_t *r, + const unsigned char *name_data, size_t name_len, + unsigned char *lowcase_buf, const unsigned char *value, + size_t value_len, unsigned char *errbuf, size_t *errlen); + ]] + + ngx_lua_ffi_var_get = C.ngx_stream_lua_ffi_var_get + ngx_lua_ffi_var_set = C.ngx_stream_lua_ffi_var_set +end + + +local value_ptr = ffi_new("unsigned char *[1]") +local errmsg = base.get_errmsg_ptr() + + +local function var_get(self, name) + local r = get_request() + if not r then + error("no request found") + end + + local value_len = get_size_ptr() + local rc + if type(name) == "number" then + rc = ngx_lua_ffi_var_get(r, nil, 0, nil, name, value_ptr, value_len, + errmsg) + + else + if type(name) ~= "string" then + error("bad variable name", 2) + end + + local name_len = #name + local lowcase_buf = get_string_buf(name_len) + + rc = ngx_lua_ffi_var_get(r, name, name_len, lowcase_buf, 0, value_ptr, + value_len, errmsg) + end + + -- ngx.log(ngx.WARN, "rc = ", rc) + + if rc == 0 then -- NGX_OK + return ffi_str(value_ptr[0], value_len[0]) + end + + if rc == -5 then -- NGX_DECLINED + return nil + end + + if rc == -1 then -- NGX_ERROR + error(ffi_str(errmsg[0]), 2) + end +end + + +local function var_set(self, name, value) + local r = get_request() + if not r then + error("no request found") + end + + if type(name) ~= "string" then + error("bad variable name", 2) + end + local name_len = #name + + local errlen = get_size_ptr() + errlen[0] = ERR_BUF_SIZE + local lowcase_buf = get_string_buf(name_len + ERR_BUF_SIZE) + + local value_len + if value == nil then + value_len = 0 + else + if type(value) ~= 'string' then + value = tostring(value) + end + value_len = #value + end + + local errbuf = lowcase_buf + name_len + local rc = ngx_lua_ffi_var_set(r, name, name_len, lowcase_buf, value, + value_len, errbuf, errlen) + + -- ngx.log(ngx.WARN, "rc = ", rc) + + if rc == 0 then -- NGX_OK + return + end + + if rc == -1 then -- NGX_ERROR + error(ffi_str(errbuf, errlen[0]), 2) + end +end + + +do + local mt = new_tab(0, 2) + mt.__index = var_get + mt.__newindex = var_set + + setmetatable(ngx.var, mt) +end + + +return { + version = base.version +} diff --git a/lib/resty/core/worker.lua b/lib/resty/core/worker.lua new file mode 100644 index 000000000..934070d8b --- /dev/null +++ b/lib/resty/core/worker.lua @@ -0,0 +1,137 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local jit = require "jit" +local base = require "resty.core.base" +local ffi_cast = ffi.cast + + +local C = ffi.C +local new_tab = base.new_tab +local subsystem = ngx.config.subsystem +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr + + +local ngx_lua_ffi_worker_id +local ngx_lua_ffi_worker_pid +local ngx_lua_ffi_worker_pids +local ngx_lua_ffi_worker_count +local ngx_lua_ffi_worker_exiting +local ffi_intp_type = ffi.typeof("int *") +local ffi_int_size = ffi.sizeof("int") + + +local is_not_windows = jit.os ~= "Windows" + +if is_not_windows then + ngx.worker = new_tab(0, 5) + +else + ngx.worker = new_tab(0, 4) +end + + +if subsystem == "http" then + ffi.cdef[[ + int ngx_http_lua_ffi_worker_id(void); + int ngx_http_lua_ffi_worker_pid(void); + int ngx_http_lua_ffi_worker_count(void); + int ngx_http_lua_ffi_worker_exiting(void); + ]] + + ngx_lua_ffi_worker_id = C.ngx_http_lua_ffi_worker_id + ngx_lua_ffi_worker_pid = C.ngx_http_lua_ffi_worker_pid + ngx_lua_ffi_worker_count = C.ngx_http_lua_ffi_worker_count + ngx_lua_ffi_worker_exiting = C.ngx_http_lua_ffi_worker_exiting + +elseif subsystem == "stream" then + ffi.cdef[[ + int ngx_stream_lua_ffi_worker_id(void); + int ngx_stream_lua_ffi_worker_pid(void); + int ngx_stream_lua_ffi_worker_count(void); + int ngx_stream_lua_ffi_worker_exiting(void); + ]] + + ngx_lua_ffi_worker_id = C.ngx_stream_lua_ffi_worker_id + ngx_lua_ffi_worker_pid = C.ngx_stream_lua_ffi_worker_pid + ngx_lua_ffi_worker_count = C.ngx_stream_lua_ffi_worker_count + ngx_lua_ffi_worker_exiting = C.ngx_stream_lua_ffi_worker_exiting + +end + + +function ngx.worker.exiting() + return ngx_lua_ffi_worker_exiting() ~= 0 +end + + +function ngx.worker.pid() + return ngx_lua_ffi_worker_pid() +end + + +if is_not_windows then + if subsystem == "http" then + ffi.cdef[[ + int ngx_http_lua_ffi_worker_pids(int *pids, size_t *pids_len); + ]] + + ngx_lua_ffi_worker_pids = C.ngx_http_lua_ffi_worker_pids + + elseif subsystem == "stream" then + ffi.cdef[[ + int ngx_stream_lua_ffi_worker_pids(int *pids, size_t *pids_len); + ]] + + ngx_lua_ffi_worker_pids = C.ngx_stream_lua_ffi_worker_pids + end + + + function ngx.worker.pids() + if ngx.get_phase() == "init" or ngx.get_phase() == "init_worker" then + return nil, "API disabled in the current context" + end + + local pids = {} + local size_ptr = get_size_ptr() + -- the old and the new workers coexist during reloading + local worker_cnt = ngx.worker.count() * 4 + if worker_cnt == 0 then + return pids + end + + size_ptr[0] = worker_cnt + local pids_ptr = get_string_buf(worker_cnt * ffi_int_size) + local intp_buf = ffi_cast(ffi_intp_type, pids_ptr) + local res = ngx_lua_ffi_worker_pids(intp_buf, size_ptr) + + if res == 0 then + for i = 1, tonumber(size_ptr[0]) do + pids[i] = intp_buf[i - 1] + end + end + + return pids + end +end + +function ngx.worker.id() + local id = ngx_lua_ffi_worker_id() + if id < 0 then + return nil + end + + return id +end + + +function ngx.worker.count() + return ngx_lua_ffi_worker_count() +end + + +return { + _VERSION = base.version +} diff --git a/t/TestCore.pm b/t/TestCore.pm new file mode 100644 index 000000000..9f6b844a7 --- /dev/null +++ b/t/TestCore.pm @@ -0,0 +1,51 @@ +package t::TestCore; + +use Test::Nginx::Socket::Lua -Base; +use Cwd qw(cwd); + +$ENV{TEST_NGINX_HOTLOOP} ||= 10; +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; + +our $pwd = cwd(); + +our $lua_package_path = './lib/?.lua;./t/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;'; + +our $init_by_lua_block = <<_EOC_; + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on("b", "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + jit.opt.start("hotloop=$ENV{TEST_NGINX_HOTLOOP}") + -- jit.off() +_EOC_ + +our $HttpConfig = <<_EOC_; + lua_package_path '$lua_package_path'; + + init_by_lua_block { + $t::TestCore::init_by_lua_block + } +_EOC_ + +our @EXPORT = qw( + $pwd + $lua_package_path + $init_by_lua_block + $HttpConfig +); + +add_block_preprocessor(sub { + my $block = shift; + + if (!defined $block->http_config) { + $block->set_value("http_config", $HttpConfig); + } +}); + +1; diff --git a/t/TestCore/Stream.pm b/t/TestCore/Stream.pm new file mode 100644 index 000000000..8348a567f --- /dev/null +++ b/t/TestCore/Stream.pm @@ -0,0 +1,50 @@ +package t::TestCore::Stream; + +use Test::Nginx::Socket::Lua::Stream -Base; +use Cwd qw(cwd); + +$ENV{TEST_NGINX_HOTLOOP} ||= 10; + +our $pwd = cwd(); + +our $lua_package_path = './lib/?.lua;../lua-resty-lrucache/lib/?.lua;;'; + +our $init_by_lua_block = <<_EOC_; + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on("b", "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + jit.opt.start("hotloop=$ENV{TEST_NGINX_HOTLOOP}") + -- jit.off() +_EOC_ + +our $StreamConfig = <<_EOC_; + lua_package_path '$lua_package_path'; + + init_by_lua_block { + $t::TestCore::Stream::init_by_lua_block + } +_EOC_ + +our @EXPORT = qw( + $pwd + $lua_package_path + $init_by_lua_block + $StreamConfig +); + +add_block_preprocessor(sub { + my $block = shift; + + if (!defined $block->stream_config) { + $block->set_value("stream_config", $StreamConfig); + } +}); + +1; diff --git a/t/balancer-timeout.t b/t/balancer-timeout.t new file mode 100644 index 000000000..d5cdfe11b --- /dev/null +++ b/t/balancer-timeout.t @@ -0,0 +1,392 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +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 (defined $ENV{MOCKEAGAIN} && $ENV{MOCKEAGAIN} eq 'r') { + $ENV{MOCKEAGAIN} = 'rw'; + + } else { + $ENV{MOCKEAGAIN} = 'w'; + } + + $ENV{TEST_NGINX_EVENT_TYPE} = 'poll'; + $ENV{TEST_NGINX_POSTPONE_OUTPUT} = 1; +} + +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_on(); +#workers(2); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4); + +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = $t::TestCore::lua_package_path; + +#worker_connections(1024); +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: set_timeouts +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(1.234, 5.678, 7.689)) + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + location = /back { + echo "fake origin"; + } +--- request + GET /t +--- response_body +fake origin +--- grep_error_log eval: qr/event timer add: \d+: (?:1234|5678|7689):/ +--- grep_error_log_out eval +qr/\Aevent timer add: \d+: 1234: +event timer add: \d+: 5678: +event timer add: \d+: 7689: +\z/ +--- no_error_log +[warn] + + + +=== TEST 2: set_timeouts (nil connect timeout) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + proxy_connect_timeout 1234ms; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(nil, 5.678, 7.689)) + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + location = /back { + echo "fake origin"; + } +--- request + GET /t +--- response_body +fake origin +--- grep_error_log eval: qr/event timer add: \d+: (?:1234|5678|7689):/ +--- grep_error_log_out eval +qr/\Aevent timer add: \d+: 1234: +event timer add: \d+: 5678: +event timer add: \d+: 7689: +\z/ +--- no_error_log +[warn] + + + +=== TEST 3: set_timeouts (nil send timeout) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + proxy_send_timeout 5678ms; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(1.234, nil, 7.689)) + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + location = /back { + echo "fake origin"; + } +--- request + GET /t +--- response_body +fake origin +--- grep_error_log eval: qr/event timer add: \d+: (?:1234|5678|7689):/ +--- grep_error_log_out eval +qr/\Aevent timer add: \d+: 1234: +event timer add: \d+: 5678: +event timer add: \d+: 7689: +\z/ +--- no_error_log +[warn] + + + +=== TEST 4: set_timeouts (nil read timeout) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + proxy_read_timeout 7689ms; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(1.234, 5.678, nil)) + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + location = /back { + echo "fake origin"; + } +--- request + GET /t +--- response_body +fake origin +--- grep_error_log eval: qr/event timer add: \d+: (?:1234|5678|7689):/ +--- grep_error_log_out eval +qr/\Aevent timer add: \d+: 1234: +event timer add: \d+: 5678: +event timer add: \d+: 7689: +\z/ +--- no_error_log +[warn] + + + +=== TEST 5: set connect timeout to 0 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + assert(b.set_timeouts(0, 1.234, 5.678)) + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + location = /back { + echo "fake origin"; + } +--- request + GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log eval +qr/\[error\] .*? balancer_by_lua\(nginx.conf:\d+\):4: bad connect timeout/ +--- no_error_log +[warn] + + + +=== TEST 6: set connect timeout to -1 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + assert(b.set_timeouts(-1, 1.234, 5.678)) + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + location = /back { + echo "fake origin"; + } +--- request + GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log eval +qr/\[error\] .*? balancer_by_lua\(nginx.conf:\d+\):4: bad connect timeout/ +--- no_error_log +[warn] + + + +=== TEST 7: set send timeout to 0 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + assert(b.set_timeouts(1.234, 0, 5.678)) + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + location = /back { + echo "fake origin"; + } +--- request + GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log eval +qr/\[error\] .*? balancer_by_lua\(nginx.conf:\d+\):4: bad send timeout/ +--- no_error_log +[warn] + + + +=== TEST 8: set send timeout to -1 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + assert(b.set_timeouts(1.234, -1, 5.678)) + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + location = /back { + echo "fake origin"; + } +--- request + GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log eval +qr/\[error\] .*? balancer_by_lua\(nginx.conf:\d+\):4: bad send timeout/ +--- no_error_log +[warn] + + + +=== TEST 9: set read timeout to -1 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + assert(b.set_timeouts(1.234, 5.678, -1)) + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + location = /back { + echo "fake origin"; + } +--- request + GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log eval +qr/\[error\] .*? balancer_by_lua\(nginx.conf:\d+\):4: bad read timeout/ +--- no_error_log +[warn] + + + +=== TEST 10: set_timeouts called in a wrong context +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + +--- config + + location = /t { + content_by_lua_block { + local balancer = require "ngx.balancer" + local ok, err = balancer.set_timeouts(1, 1, 1) + if not ok then + ngx.say("failed to call: ", err) + return + end + ngx.say("unexpected success!") + } + } + +--- request +GET /t +--- response_body +failed to call: no upstream found +--- no_error_log +[error] +[alert] + + + +=== TEST 11: set_timeouts called with a non-numerical parameter +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local balancer = require "ngx.balancer" + local ok, err = balancer.set_timeouts("1.234", 1, 1) + if not ok then + ngx.log(ngx.ERR, "failed to call: ", err) + end + } + } + +--- config + location = /t { + proxy_pass http://backend; + } +--- request +GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log eval +qr/\[error\] .*? bad connect timeout/ +--- no_error_log +[alert] diff --git a/t/balancer.t b/t/balancer.t new file mode 100644 index 000000000..3e9fb2f73 --- /dev/null +++ b/t/balancer.t @@ -0,0 +1,884 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_on(); +#workers(2); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4 + 6); + +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::lua_package_path"; + +#worker_connections(1024); +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: set current peer (separate addr and port) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- error_log eval +[ +'[lua] balancer_by_lua(nginx.conf:29):2: hello from balancer by lua! while connecting to upstream,', +qr{connect\(\) failed .*?, upstream: "http://127\.0\.0\.3:12345/t"}, +] +--- no_error_log +[warn] + + + +=== TEST 2: set current peer & next upstream (3 tries) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + proxy_next_upstream_tries 10; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- grep_error_log eval: qr{connect\(\) failed .*, upstream: "http://.*?"} +--- grep_error_log_out eval +qr#^(?:connect\(\) failed .*?, upstream: "http://127.0.0.3:12345/t"\n){3}$# +--- no_error_log +[warn] + + + +=== TEST 3: set current peer & next upstream (no retries) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- grep_error_log eval: qr{connect\(\) failed .*, upstream: "http://.*?"} +--- grep_error_log_out eval +qr#^(?:connect\(\) failed .*?, upstream: "http://127.0.0.3:12345/t"\n){1}$# +--- no_error_log +[warn] + + + +=== TEST 4: set current peer & next upstream (3 tries exceeding the limit) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + proxy_next_upstream_tries 2; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- grep_error_log eval: qr{connect\(\) failed .*, upstream: "http://.*?"} +--- grep_error_log_out eval +qr#^(?:connect\(\) failed .*?, upstream: "http://127.0.0.3:12345/t"\n){2}$# +--- error_log +set more tries: reduced tries due to limit + + + +=== TEST 5: get last peer failure status (404) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + proxy_next_upstream_tries 10; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + + local state, status = b.get_last_failure() + print("last peer failure: ", state, " ", status) + + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + + location = /back { + return 404; + } +--- request + GET /t +--- response_body_like: 404 Not Found +--- error_code: 404 +--- grep_error_log eval: qr{last peer failure: \S+ \S+} +--- grep_error_log_out +last peer failure: nil nil +last peer failure: next 404 +last peer failure: next 404 + +--- no_error_log +[warn] + + + +=== TEST 6: get last peer failure status (500) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + proxy_next_upstream_tries 10; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + + local state, status = b.get_last_failure() + print("last peer failure: ", state, " ", status) + + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + + location = /back { + return 500; + } +--- request + GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- grep_error_log eval: qr{last peer failure: \S+ \S+} +--- grep_error_log_out +last peer failure: nil nil +last peer failure: failed 500 +last peer failure: failed 500 + +--- no_error_log +[warn] + + + +=== TEST 7: get last peer failure status (503) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + proxy_next_upstream_tries 10; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + + local state, status = b.get_last_failure() + print("last peer failure: ", state, " ", status) + + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + + location = /back { + return 503; + } +--- request + GET /t +--- response_body_like: 503 Service Temporarily Unavailable +--- error_code: 503 +--- grep_error_log eval: qr{last peer failure: \S+ \S+} +--- grep_error_log_out eval +qr{\Alast peer failure: nil nil +last peer failure: failed 50[23] +last peer failure: failed 50[23] +\z} + +--- no_error_log +[warn] + + + +=== TEST 8: get last peer failure status (connect failed) +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + proxy_next_upstream_tries 10; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + + local state, status = b.get_last_failure() + print("last peer failure: ", state, " ", status) + + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- config + location = /t { + proxy_pass http://backend/back; + } + + location = /back { + return 404; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- grep_error_log eval: qr{last peer failure: \S+ \S+} +--- grep_error_log_out +last peer failure: nil nil +last peer failure: failed 502 +last peer failure: failed 502 + +--- no_error_log +[warn] + + + +=== TEST 9: set current peer (port embedded in addr) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + assert(b.set_current_peer("127.0.0.3:12345")) + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- error_log eval +[ +'[lua] balancer_by_lua(nginx.conf:29):2: hello from balancer by lua! while connecting to upstream,', +qr{connect\(\) failed .*?, upstream: "http://127\.0\.0\.3:12345/t"}, +] +--- no_error_log +[warn] + + + +=== TEST 10: keepalive before balancer +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + keepalive 10; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + assert(b.set_current_peer("127.0.0.3:12345")) + } + } +--- config + location = /t { + proxy_pass http://backend; + } +--- request + GET /t +--- response_body_like: 502 Bad Gateway +--- grep_error_log eval: qr/load balancing method redefined in/ +--- grep_error_log_out eval +[ +"load balancing method redefined in +", +"", +] +--- error_code: 502 +--- error_log eval +[ +'[lua] balancer_by_lua(nginx.conf:30):2: hello from balancer by lua! while connecting to upstream,', +qr{connect\(\) failed .*?, upstream: "http://127\.0\.0\.3:12345/t"}, +] +--- no_error_log +[crit] + + + +=== TEST 11: keepalive after balancer +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + } + keepalive 1; + } +--- config + location = /t { + content_by_lua_block { + local res0 = ngx.location.capture("/tt") + local res1 = ngx.location.capture("/tt") + local res2 = ngx.location.capture("/tt") + + if res2.status == ngx.HTTP_OK then + ngx.print(res2.body) + end + } + } + + location = /tt { + proxy_pass http://backend/back; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + + location = /back { + echo "hello keepalive!"; + } +--- request + GET /t +--- response_body +hello keepalive! +--- error_code: 200 +--- grep_error_log eval: qr{\S+ keepalive peer:.*?connection} +--- grep_error_log_out eval +["free keepalive peer: saving connection +get keepalive peer: using connection +free keepalive peer: saving connection +get keepalive peer: using connection +free keepalive peer: saving connection +", +"get keepalive peer: using connection +free keepalive peer: saving connection +get keepalive peer: using connection +free keepalive peer: saving connection +get keepalive peer: using connection +free keepalive peer: saving connection +", +] +--- no_error_log +[warn] + + + +=== TEST 12: set_current_peer called in a wrong context +--- wait: 0.2 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 127.0.0.1:$TEST_NGINX_SERVER_PORT; + balancer_by_lua_block { + print("hello from balancer by lua!") + } + } + +--- config + + location = /fake { + echo ok; + } + + location = /t { + proxy_pass http://backend/fake; + + log_by_lua_block { + local balancer = require "ngx.balancer" + local ok, err = balancer.set_current_peer("127.0.0.1", 1234) + if not ok then + ngx.log(ngx.ERR, "failed to call: ", err) + return + end + ngx.log(ngx.ALERT, "unexpected success") + } + } + +--- request +GET /t +--- response_body +ok +--- error_log eval +qr/\[error\] .*? log_by_lua.*? failed to call: API disabled in the current context/ +--- no_error_log +[alert] + + + +=== TEST 13: get_last_failure called in a wrong context +--- wait: 0.2 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 127.0.0.1:$TEST_NGINX_SERVER_PORT; + balancer_by_lua_block { + print("hello from balancer by lua!") + } + } + +--- config + + location = /fake { + echo ok; + } + + location = /t { + proxy_pass http://backend/fake; + + log_by_lua_block { + local balancer = require "ngx.balancer" + local state, status, err = balancer.get_last_failure() + if not state and err then + ngx.log(ngx.ERR, "failed to call: ", err) + return + end + ngx.log(ngx.ALERT, "unexpected success") + } + } + +--- request +GET /t +--- response_body +ok +--- error_log eval +qr/\[error\] .*? log_by_lua.*? failed to call: API disabled in the current context/ +--- no_error_log +[alert] + + + +=== TEST 14: set_more_tries called in a wrong context +--- wait: 0.2 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 127.0.0.1:$TEST_NGINX_SERVER_PORT; + balancer_by_lua_block { + print("hello from balancer by lua!") + } + } + +--- config + + location = /fake { + echo ok; + } + + location = /t { + proxy_pass http://backend/fake; + + log_by_lua_block { + local balancer = require "ngx.balancer" + local ok, err = balancer.set_more_tries(1) + if not ok then + ngx.log(ngx.ERR, "failed to call: ", err) + return + end + ngx.log(ngx.ALERT, "unexpected success") + } + } + +--- request +GET /t +--- response_body +ok +--- error_log eval +qr/\[error\] .*? log_by_lua.*? failed to call: API disabled in the current context/ +--- no_error_log +[alert] + + + +=== TEST 15: hot loop when proxy_upstream_next error is hit and keepalive is used. +github issue openresty/lua-nginx-module#693 +--- skip_nginx: 4: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + print("hello from balancer by lua!") + assert(b.set_current_peer("127.0.0.1", $TEST_NGINX_SERVER_PORT)) + } + keepalive 1; + } +--- config + location /t { + rewrite ^/t(.*) $1 break; + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + + location = /back { + return 200; + } + + location = /main { + echo_location /t/back; + echo_location /t/bad; + } + + location = /bad { + content_by_lua_block { + ngx.exit(444) + } + } +--- request + GET /main +--- no_error_log +[alert] +--- ignore_response +--- grep_error_log eval: qr{hello from balancer by lua!} +--- grep_error_log_out +hello from balancer by lua! +hello from balancer by lua! +hello from balancer by lua! +--- error_log eval +qr/\[error] .*? upstream prematurely closed connection while reading response header from upstream/ + + + +=== TEST 16: https (keepalive) +--- skip_nginx: 5: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + print("hello from balancer by lua!") + assert(b.set_current_peer("127.0.0.1", $TEST_NGINX_RAND_PORT_1)) + } + keepalive 1; + } + + server { + listen $TEST_NGINX_RAND_PORT_1 ssl; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location = /back { + return 200 "ok"; + } + } +--- config + location /t { + proxy_pass https://backend/back; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + +--- request + GET /t +--- no_error_log +[alert] +[error] +--- response_body chomp +ok +--- grep_error_log eval: qr{hello from balancer by lua!} +--- grep_error_log_out +hello from balancer by lua! +--- no_check_leak + + + +=== TEST 17: https (no keepalive) +--- skip_nginx: 5: < 1.7.5 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + balancer_by_lua_block { + local b = require "ngx.balancer" + print("hello from balancer by lua!") + assert(b.set_current_peer("127.0.0.1", $TEST_NGINX_RAND_PORT_2)) + } + } + + server { + listen $TEST_NGINX_RAND_PORT_2 ssl; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location = /back { + return 200 "ok"; + } + } +--- config + location /t { + proxy_pass https://backend/back; + proxy_http_version 1.1; + proxy_set_header Connection ""; + } + +--- request + GET /t +--- no_error_log +[alert] +[error] +--- response_body chomp +ok +--- grep_error_log eval: qr{hello from balancer by lua!} +--- grep_error_log_out +hello from balancer by lua! +--- no_check_leak + + + +=== TEST 18: test ngx.var.upstream_addr after using more than one set_current_peer +--- wait: 0.2 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + proxy_next_upstream_tries 3; + + upstream backend { + server 127.0.0.1:$TEST_NGINX_SERVER_PORT; + balancer_by_lua_block { + local balancer = require "ngx.balancer" + if ngx.ctx.tries == nil then + balancer.set_more_tries(1) + ngx.ctx.tries = 1 + balancer.set_current_peer("127.0.0.3", 12345) + else + balancer.set_current_peer("127.0.0.3", 12346) + end + } + } + +--- config + + location = /t { + proxy_pass http://backend; + log_by_lua_block { + ngx.log(ngx.INFO, "ngx.var.upstream_addr is " .. ngx.var.upstream_addr) + } + } + +--- request +GET /t +--- response_body_like: 502 Bad Gateway +--- error_code: 502 +--- error_log eval +qr/log_by_lua\(nginx.conf:\d+\):\d+: ngx.var.upstream_addr is 127.0.0.3:12345, 127.0.0.3:12346/ +--- no_error_log +[alert] + + + +=== TEST 19: recreate upstream module requests with header change +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1; + + balancer_by_lua_block { + print("here") + local b = require "ngx.balancer" + + if ngx.ctx.balancer_run then + assert(b.set_current_peer("127.0.0.1", tonumber(ngx.var.server_port))) + ngx.var.test = "second" + assert(b.recreate_request()) + + else + ngx.ctx.balancer_run = true + assert(b.set_current_peer("127.0.0.3", 12345)) + assert(b.set_more_tries(1)) + end + } + } +--- config + location = /t { + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_404; + proxy_next_upstream_tries 2; + + set $test "first"; + + proxy_set_header X-Test $test; + proxy_pass http://backend/upstream; + } + + location = /upstream { + return 200 "value is: $http_x_test"; + } +--- request +GET /t +--- response_body: value is: second +--- error_log +connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1 +--- no_error_log +[warn] +[crit] diff --git a/t/cert/chain/chain-bad0.pem b/t/cert/chain/chain-bad0.pem new file mode 100644 index 000000000..555cd276c --- /dev/null +++ b/t/cert/chain/chain-bad0.pem @@ -0,0 +1,42 @@ +-----BEGIN CERTIFICATE----- +MIICijCCAfOgAwIBAgICEAQwDQYJKoZIhvcNAQEFBQAwTTELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEVMBMGA1UE +AwwMU2lnbmluZy1DQS0yMCAXDTE0MDkyMDA1Mjc0NloYDzIxMTQwODI3MDUyNzQ2 +WjBJMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UECgwJ +T3BlblJlc3R5MREwDwYDVQQDDAh0ZXN0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOB +jQAwgYkCgYEA2FOsrFw/+YColkuwWNtaht7KMALZGcj2FMVAyUHru3rR4fmWO1TV +6L+sUFpJEdqZYETgJWhANnz2zrScuVjW6udEmGPronL46Wm0Sk1ohkHKZ1hh5nDo +CP6twnVZJA7wLxpwg4yjd2ToTdXFKGKpU9GhIvU2Q6dGAKqXVHLUckcCAwEAAaN7 +MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYBAD=BAD=BAD= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICdjCCAd+gAwIBAgICEAIwDQYJKoZIhvcNAQEFBQAwYDELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEjAQ +BgNVBAoMCU9wZW5SZXN0eTEQMA4GA1UEAwwHUm9vdCBDQTAgFw0xNDA5MjAwNTA5 +MDVaGA8yMTE0MDgyNzA1MDkwNVowTTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh +bGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEVMBMGA1UEAwwMU2lnbmluZy1D +QS0xMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5mj22Md22ivGfYSV5cPbq +S2oPDHLqRfxNUc/1cYiUnPkEQJn9LRcVOt5fcEoGeRP7gUmt2llEEoF0ndgZPk7o +xwDu+ZaBer8J5oiw47LoyuNyI+SGg0HKs0nA9XaK17X8oxIbKwu0VxAkl0C+yxfn +xd6TG1mU/zQ/zU0UdgkO8wIDAQABo1AwTjAdBgNVHQ4EFgQUEleOLJvKyY34iLFN +7qZt85nDr+EwHwYDVR0jBBgwFoAUVmXJi2VVJy6rFPAmRr27nqErQVgwDAYDVR0T +BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQAe+28+Er1FEVlS1WD/fHOeMs52+gu2 +Slho25KkoNJjJCecasVs+oTUtYCTsHmPM8YGmUmBmfRSur3/bvVpP2XgWVHOFmYv +ObUx/xgqpI4Ud3uiLFRL8KUsgxLE1RxKX3sxJu1jutWD4rUdw/M0oLrd7ofucHGu +G8WXmwimnK3Awg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICYzCCAcygAwIBAgICEAMwDQYJKoZIhvcNAQEFBQAwTTELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEVMBMGA1UE +AwwMU2lnbmluZy1DQS0xMCAXDTE0MDkyMDA1MjUwNFoYDzIxMTQwODI3MDUyNTA0 +WjBNMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UECgwJ +T3BlblJlc3R5MRUwEwYDVQQDDAxTaWduaW5nLUNBLTIwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBAKTQrhaoj50s7hL1DF4pZZvMm2dvQCTXRP/U3o3UNhzhNyvf +/2k1bQtPrpoW56nGJNOOpMMvJdjzZnOOhI6cpsf5zoy3nWAmhUyP9EMXr52UGvUh +exwrnO7+Ssptx8/uKgIoH24TlIU/UKMDGL1s+bWdN7knYSl10zl3XoNBqowhAgMB +AAGjUDBOMB0GA1UdDgQWBBQ5d3ejTpKL4iUgcmQ1CnqHqFip+DAfBgNVHSMEGDAW +gBQSV44sm8rJjfiIsU3upm3zmcOv4TAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB +BQUAA4GBADtLtjFRcprvQmBemGBx1yZKRvEOHwi+5htf4v0oVI2xxQlvBMtp3Dle +Z+CRnxCUvDWQSmX+WL3pnRjwssQsbgUApGNZaoXPDig6rTQcHowIz6x5GOYrFkmc +CwlmUClTeASePSdAxAxy1ozWsZz18viMnAsN4Uub7MllDB7+JweW +-----END CERTIFICATE----- diff --git a/t/cert/chain/chain-bad2.pem b/t/cert/chain/chain-bad2.pem new file mode 100644 index 000000000..264f89564 --- /dev/null +++ b/t/cert/chain/chain-bad2.pem @@ -0,0 +1,39 @@ +-----BEGIN CERTIFICATE----- +MIICijCCAfOgAwIBAgICEAQwDQYJKoZIhvcNAQEFBQAwTTELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEVMBMGA1UE +AwwMU2lnbmluZy1DQS0yMCAXDTE0MDkyMDA1Mjc0NloYDzIxMTQwODI3MDUyNzQ2 +WjBJMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UECgwJ +T3BlblJlc3R5MREwDwYDVQQDDAh0ZXN0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOB +jQAwgYkCgYEA2FOsrFw/+YColkuwWNtaht7KMALZGcj2FMVAyUHru3rR4fmWO1TV +6L+sUFpJEdqZYETgJWhANnz2zrScuVjW6udEmGPronL46Wm0Sk1ohkHKZ1hh5nDo +CP6twnVZJA7wLxpwg4yjd2ToTdXFKGKpU9GhIvU2Q6dGAKqXVHLUckcCAwEAAaN7 +MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQg +Q2VydGlmaWNhdGUwHQYDVR0OBBYEFB/bwNk8S3eomqwzH3twxM+6yAfdMB8GA1Ud +IwQYMBaAFDl3d6NOkoviJSByZDUKeoeoWKn4MA0GCSqGSIb3DQEBBQUAA4GBAB7N +g2ax2+pcN368MURScgOum0QgLK0AIKXcz53IyI/fzyQmnEOD9NL/69nkfSXPH7iq +Y1gDudpSQvj+LnHMj94mNM3aXHo7ZAcYJ6FhtlgylhCX8n8AxERDt53iMWlPwpXF +ozLRwADG71i5D+YIOg3JwBT3JoxDE1Ubk3Fyx60v +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICdjCCAd+gAwIBAgICEAIwDQYJKoZIhvcNAQEFBQAwYDELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEjAQ +BgNVBAoMCU9wZW5SZXN0eTEQMA4GA1UEAwwHUm9vdCBDQTAgFw0xNDA5MjAwNTA5 +MDVaGA8yMTE0MDgyNzA1MDkwNVowTTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh +bGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEVMBMGA1UEAwwMU2lnbmluZy1D +QS0xMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5mj22Md22ivGfYSV5cPbq +S2oPDHLqRfxNUc/1cYiUnPkEQJn9LRcVOt5fcEoGeRP7gUmt2llEEoF0ndgZPk7o +xwDu+ZaBer8J5oiw47LoyuNyI+SGg0HKs0nA9XaK17X8oxIbKwu0VxAkl0C+yxfn +xd6TG1mU/zQ/zU0UdgkO8wIDAQABo1AwTjAdBgNVHQ4EFgQUEleOLJvKyY34iLFN +7qZt85nDr+EwHwYDVR0jBBgwFoAUVmXJi2VVJy6rFPAmRr27nqErQVgwDAYDVR0T +BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQAe+28+Er1FEVlS1WD/fHOeMs52+gu2 +Slho25KkoNJjJCecasVs+oTUtYCTsHmPM8YGmUmBmfRSur3/bvVpP2XgWVHOFmYv +ObUx/xgqpI4Ud3uiLFRL8KUsgxLE1RxKX3sxJu1jutWD4rUdw/M0oLrd7ofucHGu +G8WXmwimnK3Awg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICYzCCAcygAwIBAgICEAMwDQYJKoZIhvcNAQEFBQAwTTELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEVMBMGA1UE +AwwMU2lnbmluZy1DQS0xMCAXDTE0MDkyMDA1MjUwNFoYDzIxMTQwODI3MDUyNTA0 +WjBNMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UECgwJ +BAD=BAD=BAD +-----END CERTIFICATE----- diff --git a/t/cert/chain/chain.der b/t/cert/chain/chain.der new file mode 100644 index 000000000..ee6d9bad2 Binary files /dev/null and b/t/cert/chain/chain.der differ diff --git a/t/cert/chain/chain.pem b/t/cert/chain/chain.pem new file mode 100644 index 000000000..21b704f2f --- /dev/null +++ b/t/cert/chain/chain.pem @@ -0,0 +1,172 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4100 (0x1004) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, O=OpenResty, CN=Signing-CA-2 + Validity + Not Before: Sep 20 05:27:46 2014 GMT + Not After : Aug 27 05:27:46 2114 GMT + Subject: C=US, ST=California, O=OpenResty, CN=test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:d8:53:ac:ac:5c:3f:f9:80:a8:96:4b:b0:58:db: + 5a:86:de:ca:30:02:d9:19:c8:f6:14:c5:40:c9:41: + eb:bb:7a:d1:e1:f9:96:3b:54:d5:e8:bf:ac:50:5a: + 49:11:da:99:60:44:e0:25:68:40:36:7c:f6:ce:b4: + 9c:b9:58:d6:ea:e7:44:98:63:eb:a2:72:f8:e9:69: + b4:4a:4d:68:86:41:ca:67:58:61:e6:70:e8:08:fe: + ad:c2:75:59:24:0e:f0:2f:1a:70:83:8c:a3:77:64: + e8:4d:d5:c5:28:62:a9:53:d1:a1:22:f5:36:43:a7: + 46:00:aa:97:54:72:d4:72:47 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 1F:DB:C0:D9:3C:4B:77:A8:9A:AC:33:1F:7B:70:C4:CF:BA:C8:07:DD + X509v3 Authority Key Identifier: + keyid:39:77:77:A3:4E:92:8B:E2:25:20:72:64:35:0A:7A:87:A8:58:A9:F8 + + Signature Algorithm: sha1WithRSAEncryption + 1e:cd:83:66:b1:db:ea:5c:37:7e:bc:31:44:52:72:03:ae:9b: + 44:20:2c:ad:00:20:a5:dc:cf:9d:c8:c8:8f:df:cf:24:26:9c: + 43:83:f4:d2:ff:eb:d9:e4:7d:25:cf:1f:b8:aa:63:58:03:b9: + da:52:42:f8:fe:2e:71:cc:8f:de:26:34:cd:da:5c:7a:3b:64: + 07:18:27:a1:61:b6:58:32:96:10:97:f2:7f:00:c4:44:43:b7: + 9d:e2:31:69:4f:c2:95:c5:a3:32:d1:c0:00:c6:ef:58:b9:0f: + e6:08:3a:0d:c9:c0:14:f7:26:8c:43:13:55:1b:93:71:72:c7: + ad:2f +-----BEGIN CERTIFICATE----- +MIICijCCAfOgAwIBAgICEAQwDQYJKoZIhvcNAQEFBQAwTTELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEVMBMGA1UE +AwwMU2lnbmluZy1DQS0yMCAXDTE0MDkyMDA1Mjc0NloYDzIxMTQwODI3MDUyNzQ2 +WjBJMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UECgwJ +T3BlblJlc3R5MREwDwYDVQQDDAh0ZXN0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOB +jQAwgYkCgYEA2FOsrFw/+YColkuwWNtaht7KMALZGcj2FMVAyUHru3rR4fmWO1TV +6L+sUFpJEdqZYETgJWhANnz2zrScuVjW6udEmGPronL46Wm0Sk1ohkHKZ1hh5nDo +CP6twnVZJA7wLxpwg4yjd2ToTdXFKGKpU9GhIvU2Q6dGAKqXVHLUckcCAwEAAaN7 +MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQg +Q2VydGlmaWNhdGUwHQYDVR0OBBYEFB/bwNk8S3eomqwzH3twxM+6yAfdMB8GA1Ud +IwQYMBaAFDl3d6NOkoviJSByZDUKeoeoWKn4MA0GCSqGSIb3DQEBBQUAA4GBAB7N +g2ax2+pcN368MURScgOum0QgLK0AIKXcz53IyI/fzyQmnEOD9NL/69nkfSXPH7iq +Y1gDudpSQvj+LnHMj94mNM3aXHo7ZAcYJ6FhtlgylhCX8n8AxERDt53iMWlPwpXF +ozLRwADG71i5D+YIOg3JwBT3JoxDE1Ubk3Fyx60v +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4098 (0x1002) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=San Francisco, O=OpenResty, CN=Root CA + Validity + Not Before: Sep 20 05:09:05 2014 GMT + Not After : Aug 27 05:09:05 2114 GMT + Subject: C=US, ST=California, O=OpenResty, CN=Signing-CA-1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:b9:9a:3d:b6:31:dd:b6:8a:f1:9f:61:25:79:70: + f6:ea:4b:6a:0f:0c:72:ea:45:fc:4d:51:cf:f5:71: + 88:94:9c:f9:04:40:99:fd:2d:17:15:3a:de:5f:70: + 4a:06:79:13:fb:81:49:ad:da:59:44:12:81:74:9d: + d8:19:3e:4e:e8:c7:00:ee:f9:96:81:7a:bf:09:e6: + 88:b0:e3:b2:e8:ca:e3:72:23:e4:86:83:41:ca:b3: + 49:c0:f5:76:8a:d7:b5:fc:a3:12:1b:2b:0b:b4:57: + 10:24:97:40:be:cb:17:e7:c5:de:93:1b:59:94:ff: + 34:3f:cd:4d:14:76:09:0e:f3 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 12:57:8E:2C:9B:CA:C9:8D:F8:88:B1:4D:EE:A6:6D:F3:99:C3:AF:E1 + X509v3 Authority Key Identifier: + keyid:56:65:C9:8B:65:55:27:2E:AB:14:F0:26:46:BD:BB:9E:A1:2B:41:58 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 1e:fb:6f:3e:12:bd:45:11:59:52:d5:60:ff:7c:73:9e:32:ce: + 76:fa:0b:b6:4a:58:68:db:92:a4:a0:d2:63:24:27:9c:6a:c5: + 6c:fa:84:d4:b5:80:93:b0:79:8f:33:c6:06:99:49:81:99:f4: + 52:ba:bd:ff:6e:f5:69:3f:65:e0:59:51:ce:16:66:2f:39:b5: + 31:ff:18:2a:a4:8e:14:77:7b:a2:2c:54:4b:f0:a5:2c:83:12: + c4:d5:1c:4a:5f:7b:31:26:ed:63:ba:d5:83:e2:b5:1d:c3:f3: + 34:a0:ba:dd:ee:87:ee:70:71:ae:1b:c5:97:9b:08:a6:9c:ad: + c0:c2 +-----BEGIN CERTIFICATE----- +MIICdjCCAd+gAwIBAgICEAIwDQYJKoZIhvcNAQEFBQAwYDELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEjAQ +BgNVBAoMCU9wZW5SZXN0eTEQMA4GA1UEAwwHUm9vdCBDQTAgFw0xNDA5MjAwNTA5 +MDVaGA8yMTE0MDgyNzA1MDkwNVowTTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh +bGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEVMBMGA1UEAwwMU2lnbmluZy1D +QS0xMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5mj22Md22ivGfYSV5cPbq +S2oPDHLqRfxNUc/1cYiUnPkEQJn9LRcVOt5fcEoGeRP7gUmt2llEEoF0ndgZPk7o +xwDu+ZaBer8J5oiw47LoyuNyI+SGg0HKs0nA9XaK17X8oxIbKwu0VxAkl0C+yxfn +xd6TG1mU/zQ/zU0UdgkO8wIDAQABo1AwTjAdBgNVHQ4EFgQUEleOLJvKyY34iLFN +7qZt85nDr+EwHwYDVR0jBBgwFoAUVmXJi2VVJy6rFPAmRr27nqErQVgwDAYDVR0T +BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQAe+28+Er1FEVlS1WD/fHOeMs52+gu2 +Slho25KkoNJjJCecasVs+oTUtYCTsHmPM8YGmUmBmfRSur3/bvVpP2XgWVHOFmYv +ObUx/xgqpI4Ud3uiLFRL8KUsgxLE1RxKX3sxJu1jutWD4rUdw/M0oLrd7ofucHGu +G8WXmwimnK3Awg== +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4099 (0x1003) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, O=OpenResty, CN=Signing-CA-1 + Validity + Not Before: Sep 20 05:25:04 2014 GMT + Not After : Aug 27 05:25:04 2114 GMT + Subject: C=US, ST=California, O=OpenResty, CN=Signing-CA-2 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:a4:d0:ae:16:a8:8f:9d:2c:ee:12:f5:0c:5e:29: + 65:9b:cc:9b:67:6f:40:24:d7:44:ff:d4:de:8d:d4: + 36:1c:e1:37:2b:df:ff:69:35:6d:0b:4f:ae:9a:16: + e7:a9:c6:24:d3:8e:a4:c3:2f:25:d8:f3:66:73:8e: + 84:8e:9c:a6:c7:f9:ce:8c:b7:9d:60:26:85:4c:8f: + f4:43:17:af:9d:94:1a:f5:21:7b:1c:2b:9c:ee:fe: + 4a:ca:6d:c7:cf:ee:2a:02:28:1f:6e:13:94:85:3f: + 50:a3:03:18:bd:6c:f9:b5:9d:37:b9:27:61:29:75: + d3:39:77:5e:83:41:aa:8c:21 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 39:77:77:A3:4E:92:8B:E2:25:20:72:64:35:0A:7A:87:A8:58:A9:F8 + X509v3 Authority Key Identifier: + keyid:12:57:8E:2C:9B:CA:C9:8D:F8:88:B1:4D:EE:A6:6D:F3:99:C3:AF:E1 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 3b:4b:b6:31:51:72:9a:ef:42:60:5e:98:60:71:d7:26:4a:46: + f1:0e:1f:08:be:e6:1b:5f:e2:fd:28:54:8d:b1:c5:09:6f:04: + cb:69:dc:39:5e:67:e0:91:9f:10:94:bc:35:90:4a:65:fe:58: + bd:e9:9d:18:f0:b2:c4:2c:6e:05:00:a4:63:59:6a:85:cf:0e: + 28:3a:ad:34:1c:1e:8c:08:cf:ac:79:18:e6:2b:16:49:9c:0b: + 09:66:50:29:53:78:04:9e:3d:27:40:c4:0c:72:d6:8c:d6:b1: + 9c:f5:f2:f8:8c:9c:0b:0d:e1:4b:9b:ec:c9:65:0c:1e:fe:27: + 07:96 +-----BEGIN CERTIFICATE----- +MIICYzCCAcygAwIBAgICEAMwDQYJKoZIhvcNAQEFBQAwTTELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEVMBMGA1UE +AwwMU2lnbmluZy1DQS0xMCAXDTE0MDkyMDA1MjUwNFoYDzIxMTQwODI3MDUyNTA0 +WjBNMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UECgwJ +T3BlblJlc3R5MRUwEwYDVQQDDAxTaWduaW5nLUNBLTIwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBAKTQrhaoj50s7hL1DF4pZZvMm2dvQCTXRP/U3o3UNhzhNyvf +/2k1bQtPrpoW56nGJNOOpMMvJdjzZnOOhI6cpsf5zoy3nWAmhUyP9EMXr52UGvUh +exwrnO7+Ssptx8/uKgIoH24TlIU/UKMDGL1s+bWdN7knYSl10zl3XoNBqowhAgMB +AAGjUDBOMB0GA1UdDgQWBBQ5d3ejTpKL4iUgcmQ1CnqHqFip+DAfBgNVHSMEGDAW +gBQSV44sm8rJjfiIsU3upm3zmcOv4TAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB +BQUAA4GBADtLtjFRcprvQmBemGBx1yZKRvEOHwi+5htf4v0oVI2xxQlvBMtp3Dle +Z+CRnxCUvDWQSmX+WL3pnRjwssQsbgUApGNZaoXPDig6rTQcHowIz6x5GOYrFkmc +CwlmUClTeASePSdAxAxy1ozWsZz18viMnAsN4Uub7MllDB7+JweW +-----END CERTIFICATE----- diff --git a/t/cert/chain/root-ca.crt b/t/cert/chain/root-ca.crt new file mode 100644 index 000000000..d2f3c8fa0 --- /dev/null +++ b/t/cert/chain/root-ca.crt @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIJAK3s1yAQ5tdfMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp +c2NvMRIwEAYDVQQKDAlPcGVuUmVzdHkxEDAOBgNVBAMMB1Jvb3QgQ0EwIBcNMTQw +OTIwMDM1NTU0WhgPMjExNDA4MjcwMzU1NTRaMGAxCzAJBgNVBAYTAlVTMRMwEQYD +VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRIwEAYDVQQK +DAlPcGVuUmVzdHkxEDAOBgNVBAMMB1Jvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBAN7CcpCjiafBdl1KaExRcuutAF0/eq4/ht7L4/i0nPDzikscFJ/O +aVyH3UpUF/KMq+72vom2bEbUeRROr1rL/JRe9raGlQtvdovHZt6f4c3/Coihtupp +9BXYrBCU4P+Bxai5gtTXGFvLC2a72qKcXDNeH+NxpIaemfPxSvemCYUXAgMBAAGj +UDBOMB0GA1UdDgQWBBRWZcmLZVUnLqsU8CZGvbueoStBWDAfBgNVHSMEGDAWgBRW +ZcmLZVUnLqsU8CZGvbueoStBWDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A4GBAGjMH6qkY+61311DERFhDuYzMSSZjH53qzFseq/chlIMGjrgJIMy6rl7T0AU +2hjvW+FOyhf5NqRrAQDTTuLbtXZ/ygiUformE8lR/SNRY/DVj1yarQkWUC5UpqOs +GWG1VW9DHQAMFVkYwPO3XKeTXpEFOxPLHtXBYcVemCT4zo42 +-----END CERTIFICATE----- diff --git a/t/cert/chain/test-com-bad.key.pem b/t/cert/chain/test-com-bad.key.pem new file mode 100644 index 000000000..0b9a6551a --- /dev/null +++ b/t/cert/chain/test-com-bad.key.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDYU6ysXD/5gKiWS7BY21qG3sowAtkZyPYUxUDJQeu7etHh+ZY7 +VNXov6xQWkkR2plgROAlaEA2fPbOtJy5WNbq50SYY+uicvjpabRKTWiGQcpnWGHm +cOgI/q3CdVkkDvAvGnCDjKN3ZOhN1cUoYqlT0aEi9TZDp0YAqpdUctRyRwIDAQAB +AoGBAIl/5elIWYGFPaMKSPSxuECxq2II7WVuTru1BRDnTabE0lMICW185tohuqz4 +NimbAJIoNTCRqv73Pwjz1AobZb6Nm7TDaahhstak6IlTYKcjXVBuM/UU4G13Kz/f +hNVblv2cCn9CkeTNOvPZjYJXw/c4XlHasjDMMh8S83Q9095BAkEA+6oPzEiSsdo5 +BAD=BAD=BAD= +-----END RSA PRIVATE KEY----- diff --git a/t/cert/chain/test-com.key.der b/t/cert/chain/test-com.key.der new file mode 100644 index 000000000..3a19bbc15 Binary files /dev/null and b/t/cert/chain/test-com.key.der differ diff --git a/t/cert/chain/test-com.key.pem b/t/cert/chain/test-com.key.pem new file mode 100644 index 000000000..883ea796a --- /dev/null +++ b/t/cert/chain/test-com.key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDYU6ysXD/5gKiWS7BY21qG3sowAtkZyPYUxUDJQeu7etHh+ZY7 +VNXov6xQWkkR2plgROAlaEA2fPbOtJy5WNbq50SYY+uicvjpabRKTWiGQcpnWGHm +cOgI/q3CdVkkDvAvGnCDjKN3ZOhN1cUoYqlT0aEi9TZDp0YAqpdUctRyRwIDAQAB +AoGBAIl/5elIWYGFPaMKSPSxuECxq2II7WVuTru1BRDnTabE0lMICW185tohuqz4 +NimbAJIoNTCRqv73Pwjz1AobZb6Nm7TDaahhstak6IlTYKcjXVBuM/UU4G13Kz/f +hNVblv2cCn9CkeTNOvPZjYJXw/c4XlHasjDMMh8S83Q9095BAkEA+6oPzEiSsdo5 +RX9D0EV+Uv4ID08johKbcZdGbsp+mo+PQ9CYOlE67QcKf8J4Hp2SFmq7mpTvvS7F +tA/a2WwJswJBANwNwsJre3QPJmJCBAGsIrPrw9rFKLiT0/ajyhT7kKfG4Rw9t55S +lY9VPFOxAJF9lDo4QiFUHi/8Htvd0B78wx0CQFh5cRRgbzIXhgrosu6Ff+Otayf2 +qpBP+lX02M4aYmf0EGnG672U0SKDVy2TMKeSvckjvNCbi6z2xIqJCGdnlAECQFTh ++f6E91oNfgDo9iKvA7PjfeklpE+OtnStOYZeg640SSFbrTilIovnlR2zaUS17DeI ++/lfOUXJOx4UsfNCDQECQD7nndBJDJeSggFSJKcZ0RI59NVG8eGRSX7/3ycbq6+t +guGI7WBvhDH4jNNL8jhuE+XuJuhhzOwP85872AFgIgw= +-----END RSA PRIVATE KEY----- diff --git a/t/cert/ocsp/chain.pem b/t/cert/ocsp/chain.pem new file mode 100644 index 000000000..4743a3602 --- /dev/null +++ b/t/cert/ocsp/chain.pem @@ -0,0 +1,183 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4 (0x4) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:c7:bd:50:99:71:46:af:93:22:85:ab:74:8b:5b: + 19:74:af:3e:ad:d2:e1:17:3e:cb:5b:36:9c:8a:38: + bd:1b:47:2d:8b:92:55:1d:fe:a6:72:92:78:00:de: + 30:cb:a3:10:b5:92:aa:b8:e0:7b:44:9a:f5:99:89: + 36:f4:84:20:81:e3:5c:76:00:9d:76:e7:b9:41:ab: + 74:b6:14:9f:b2:94:b3:b6:48:a8:92:dc:09:e3:3d: + 04:e3:5f:0f:5b:50:ad:0c:59:3a:88:06:39:2d:34: + a6:52:2f:58:6f:53:1b:df:9f:98:ea:82:8d:52:60: + b1:ef:6b:e9:f5:ad:29:87:45 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 67:DF:28:25:D1:F8:83:36:28:EE:DB:41:63:E4:E0:3A:32:0D:EA:30 + X509v3 Authority Key Identifier: + keyid:B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + DirName:/C=US/ST=California/L=Default City/O=OpenResty/CN=signing-ca-1 + serial:03 + + Authority Information Access: + OCSP - URI:http://127.0.0.1:8888/ocsp?foo=1 + + Signature Algorithm: sha1WithRSAEncryption + 37:29:3f:ed:d9:47:9a:51:36:a3:5b:00:85:66:de:51:4d:48: + 2d:f8:bc:f1:5e:b4:fd:30:48:f0:25:ee:77:57:9c:f1:4b:0a: + 4f:7e:96:1a:f8:48:76:23:46:8d:d6:f2:5e:1e:08:52:12:53: + 08:07:9f:75:db:77:22:2e:7e:89:c2:2c:66:85:6b:df:e9:77: + ca:23:6d:9a:af:87:8a:8c:27:37:1e:9e:55:92:8e:8a:a9:93: + 24:41:a8:96:01:c0:65:93:8e:3d:7a:6c:bf:ed:c8:2a:f8:26: + cc:00:17:b7:27:ca:85:6c:2e:d5:2a:0a:8d:f3:88:e8:26:48: + e3:e8 +-----BEGIN CERTIFICATE----- +MIIDaTCCAtKgAwIBAgIBBDANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMjAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowYDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MREwDwYDVQQDEwh0ZXN0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAx71QmXFGr5Mihat0i1sZdK8+rdLhFz7LWzaciji9G0cti5JVHf6mcpJ4 +AN4wy6MQtZKquOB7RJr1mYk29IQggeNcdgCddue5Qat0thSfspSztkioktwJ4z0E +418PW1CtDFk6iAY5LTSmUi9Yb1Mb35+Y6oKNUmCx72vp9a0ph0UCAwEAAaOCASsw +ggEnMAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVk +IENlcnRpZmljYXRlMB0GA1UdDgQWBBRn3ygl0fiDNiju20Fj5OA6Mg3qMDCBjgYD +VR0jBIGGMIGDgBSzC/V9URZRfig3w6IPHS8QwFGjs6FopGYwZDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTES +MBAGA1UEChMJT3BlblJlc3R5MRUwEwYDVQQDEwxzaWduaW5nLWNhLTGCAQMwPAYI +KwYBBQUHAQEEMDAuMCwGCCsGAQUFBzABhiBodHRwOi8vMTI3LjAuMC4xOjg4ODgv +b2NzcD9mb289MTANBgkqhkiG9w0BAQUFAAOBgQA3KT/t2UeaUTajWwCFZt5RTUgt ++LzxXrT9MEjwJe53V5zxSwpPfpYa+Eh2I0aN1vJeHghSElMIB59123ciLn6Jwixm +hWvf6XfKI22ar4eKjCc3Hp5Vko6KqZMkQaiWAcBlk449emy/7cgq+CbMABe3J8qF +bC7VKgqN84joJkjj6A== +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3 (0x3) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-1 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:d3:24:1c:92:a5:bb:00:d9:b1:fb:2b:1d:7a:32: + a1:6c:49:eb:3c:2d:29:80:d6:65:8b:17:3a:f0:4b: + dc:0c:57:fb:d5:31:68:a5:e4:54:86:55:f9:1b:a8: + d7:7d:32:01:3b:cf:5c:38:2b:f5:bc:d3:8b:c8:b6: + ab:76:65:32:e6:4b:d5:e4:fd:d1:92:c8:33:6a:74: + f3:c7:ec:97:c3:c7:9f:e4:d5:55:75:b8:bd:39:ec: + 2d:1f:c6:54:c8:2b:2d:17:e0:05:77:28:44:f7:dd: + e1:6e:f0:59:05:51:f5:b9:b4:fe:be:ad:40:a6:d5: + 9a:c1:64:e0:9b:dd:67:e5:f1 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + X509v3 Authority Key Identifier: + keyid:D2:30:71:56:50:A6:BC:21:C5:A1:A1:AB:11:A7:08:5B:EB:3A:A4:27 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 0c:61:c0:c7:11:c2:f0:39:f0:76:9d:4f:43:d4:90:54:1f:26: + 3d:54:3d:77:5f:c0:b3:4a:c2:1b:b6:18:d2:12:8d:24:4d:76: + f5:07:0b:14:3e:17:2d:42:ee:85:30:db:e3:4d:81:67:59:97: + 0a:b3:bb:c5:27:ea:69:c6:ee:99:5c:44:36:53:3e:c4:47:68: + f8:fe:c6:53:38:fb:e7:9a:0c:3c:6c:78:93:29:d2:49:7d:29: + d0:61:6e:81:9b:d6:ec:1a:e2:3e:62:62:41:bc:6d:4d:33:91: + 76:20:5e:32:70:08:3e:24:72:fe:b1:8a:83:57:04:19:b5:cb: + 99:b7 +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMTAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowZDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MRUwEwYDVQQDEwxzaWduaW5nLWNhLTIwgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBANMkHJKluwDZsfsrHXoyoWxJ6zwtKYDWZYsXOvBL3AxX+9UxaKXk +VIZV+Ruo130yATvPXDgr9bzTi8i2q3ZlMuZL1eT90ZLIM2p088fsl8PHn+TVVXW4 +vTnsLR/GVMgrLRfgBXcoRPfd4W7wWQVR9bm0/r6tQKbVmsFk4JvdZ+XxAgMBAAGj +UDBOMB0GA1UdDgQWBBSzC/V9URZRfig3w6IPHS8QwFGjszAfBgNVHSMEGDAWgBTS +MHFWUKa8IcWhoasRpwhb6zqkJzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A4GBAAxhwMcRwvA58HadT0PUkFQfJj1UPXdfwLNKwhu2GNISjSRNdvUHCxQ+Fy1C +7oUw2+NNgWdZlwqzu8Un6mnG7plcRDZTPsRHaPj+xlM4++eaDDxseJMp0kl9KdBh +boGb1uwa4j5iYkG8bU0zkXYgXjJwCD4kcv6xioNXBBm1y5m3 +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=root-ca + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:a0:3e:1a:4f:6c:b9:3d:ab:0f:02:de:da:82:92: + ee:a2:69:88:80:ed:f2:b6:98:bc:c6:ee:d3:47:82: + 4a:e7:d3:7f:55:68:5c:6d:9e:aa:ba:59:e3:5b:7f: + 32:4f:79:44:4a:4f:13:e4:2e:3f:1f:98:10:a4:72: + d5:f0:e7:44:8e:d4:a7:b9:fb:54:be:b6:fa:f7:dc: + 9c:29:93:d4:9f:a1:5b:18:6e:68:93:91:1b:8c:a0: + 4f:02:52:e9:9d:e8:98:f3:fd:67:da:78:4b:4f:d8: + 2d:90:83:5c:0b:e5:fe:48:27:e4:ec:bb:99:26:06: + 8e:34:fe:93:e4:d2:fc:97:57 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + D2:30:71:56:50:A6:BC:21:C5:A1:A1:AB:11:A7:08:5B:EB:3A:A4:27 + X509v3 Authority Key Identifier: + keyid:1D:2F:09:60:EB:E4:EA:B5:0B:52:A9:5C:5E:09:2B:DD:34:70:CF:BA + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + a6:16:2f:fc:13:67:5e:ce:0e:79:cb:b0:91:52:9b:9e:b5:9f: + e1:fa:7d:78:f4:2a:93:f3:94:62:45:17:87:b9:0a:59:b9:a3: + a9:75:51:ca:f0:04:6c:01:d1:3a:a9:dd:66:7d:27:7b:1e:4f: + 48:3a:25:ea:a5:01:32:fc:87:4b:08:da:f8:f5:62:88:e8:b9: + 94:c7:cb:ee:33:08:ab:2f:52:f4:4a:14:4f:ac:2d:a2:f8:de: + c9:6f:95:b7:91:23:b9:ec:95:90:de:86:21:f5:6f:1b:cf:13: + 47:77:78:dd:7a:16:e9:8b:cc:df:3d:45:8a:76:af:15:d1:9a: + 37:a2 +-----BEGIN CERTIFICATE----- +MIICizCCAfSgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxEDAOBgNVBAMTB3Jvb3QtY2EwIBcNMTQxMDE2MDMyNzA5 +WhgPMjExNDA5MjIwMzI3MDlaMGQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxp +Zm9ybmlhMRUwEwYDVQQHEwxEZWZhdWx0IENpdHkxEjAQBgNVBAoTCU9wZW5SZXN0 +eTEVMBMGA1UEAxMMc2lnbmluZy1jYS0xMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCgPhpPbLk9qw8C3tqCku6iaYiA7fK2mLzG7tNHgkrn039VaFxtnqq6WeNb +fzJPeURKTxPkLj8fmBCkctXw50SO1Ke5+1S+tvr33Jwpk9SfoVsYbmiTkRuMoE8C +Uumd6Jjz/WfaeEtP2C2Qg1wL5f5IJ+Tsu5kmBo40/pPk0vyXVwIDAQABo1AwTjAd +BgNVHQ4EFgQU0jBxVlCmvCHFoaGrEacIW+s6pCcwHwYDVR0jBBgwFoAUHS8JYOvk +6rULUqlcXgkr3TRwz7owDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCm +Fi/8E2dezg55y7CRUpuetZ/h+n149CqT85RiRReHuQpZuaOpdVHK8ARsAdE6qd1m +fSd7Hk9IOiXqpQEy/IdLCNr49WKI6LmUx8vuMwirL1L0ShRPrC2i+N7Jb5W3kSO5 +7JWQ3oYh9W8bzxNHd3jdehbpi8zfPUWKdq8V0Zo3og== +-----END CERTIFICATE----- diff --git a/t/cert/ocsp/ocsp-req.der b/t/cert/ocsp/ocsp-req.der new file mode 100644 index 000000000..f125311ac Binary files /dev/null and b/t/cert/ocsp/ocsp-req.der differ diff --git a/t/cert/ocsp/ocsp-resp-no-certs.der b/t/cert/ocsp/ocsp-resp-no-certs.der new file mode 100644 index 000000000..01a45cf9f Binary files /dev/null and b/t/cert/ocsp/ocsp-resp-no-certs.der differ diff --git a/t/cert/ocsp/ocsp-resp-signed-by-orphaned-no-certs.der b/t/cert/ocsp/ocsp-resp-signed-by-orphaned-no-certs.der new file mode 100644 index 000000000..61c775f51 Binary files /dev/null and b/t/cert/ocsp/ocsp-resp-signed-by-orphaned-no-certs.der differ diff --git a/t/cert/ocsp/ocsp-resp-signed-by-orphaned.der b/t/cert/ocsp/ocsp-resp-signed-by-orphaned.der new file mode 100644 index 000000000..506dbd2d9 Binary files /dev/null and b/t/cert/ocsp/ocsp-resp-signed-by-orphaned.der differ diff --git a/t/cert/ocsp/ocsp-resp.der b/t/cert/ocsp/ocsp-resp.der new file mode 100644 index 000000000..1fe910f4f Binary files /dev/null and b/t/cert/ocsp/ocsp-resp.der differ diff --git a/t/cert/ocsp/revoked-chain.pem b/t/cert/ocsp/revoked-chain.pem new file mode 100644 index 000000000..3f98b7c2f --- /dev/null +++ b/t/cert/ocsp/revoked-chain.pem @@ -0,0 +1,183 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 8 (0x8) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=revoked-test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:ca:50:23:9a:59:70:ea:00:47:ff:72:05:29:9b: + 5d:6d:4b:73:37:a4:ff:38:20:4b:5b:ac:1f:3b:34: + f5:12:f8:8b:0e:02:bc:bd:14:34:39:6f:7d:5b:1f: + d4:15:e7:64:2e:65:fb:b1:a8:aa:f6:96:d3:e6:2b: + 00:0e:f3:8a:ef:99:ab:3e:e6:5d:eb:6d:a6:4a:d0: + aa:ff:a9:d6:9a:41:f0:66:22:0a:38:9c:28:4f:1f: + 0d:cf:a2:79:96:f9:fc:3d:1e:83:70:f5:97:6e:07: + cf:a2:17:87:0d:2a:41:19:3a:44:96:89:e7:0d:cb: + 88:20:86:e1:de:08:8b:0d:db + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + FB:98:2B:56:90:69:E1:B4:2B:C2:DB:25:7C:13:87:D5:D7:BC:70:B6 + X509v3 Authority Key Identifier: + keyid:B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + DirName:/C=US/ST=California/L=Default City/O=OpenResty/CN=signing-ca-1 + serial:03 + + Authority Information Access: + OCSP - URI:http://127.0.0.1:8888/ocsp?foo=1 + + Signature Algorithm: sha1WithRSAEncryption + 43:77:33:e9:cc:b1:42:35:94:0a:57:a5:dd:94:21:c0:cc:42: + 04:81:bd:b2:ac:4d:10:68:f3:fe:33:0a:8e:b9:3e:e9:f2:44: + aa:1c:e7:3e:e8:e0:57:40:41:ef:4a:b1:32:b0:f2:75:7c:aa: + 77:d2:64:9d:ba:a1:12:ea:f9:83:31:ba:9f:83:58:1c:38:e9: + d0:a6:dd:04:72:85:d1:2d:c7:3b:b2:71:ef:e4:f6:57:0c:6a: + b6:fc:e5:13:2d:be:a6:c1:f4:4b:4d:c8:69:cc:7c:2e:25:c1: + 8e:80:9e:19:c3:17:b2:21:a7:af:e8:2f:f1:d4:bb:8c:a3:39: + be:49 +-----BEGIN CERTIFICATE----- +MIIDcTCCAtqgAwIBAgIBCDANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMjAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowaDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MRkwFwYDVQQDExByZXZva2VkLXRlc3QuY29tMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDKUCOaWXDqAEf/cgUpm11tS3M3pP84IEtbrB87NPUS+IsO +Ary9FDQ5b31bH9QV52QuZfuxqKr2ltPmKwAO84rvmas+5l3rbaZK0Kr/qdaaQfBm +Igo4nChPHw3PonmW+fw9HoNw9ZduB8+iF4cNKkEZOkSWiecNy4gghuHeCIsN2wID +AQABo4IBKzCCAScwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBH +ZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFPuYK1aQaeG0K8LbJXwTh9XX +vHC2MIGOBgNVHSMEgYYwgYOAFLML9X1RFlF+KDfDog8dLxDAUaOzoWikZjBkMQsw +CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVs +dCBDaXR5MRIwEAYDVQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2Et +MYIBAzA8BggrBgEFBQcBAQQwMC4wLAYIKwYBBQUHMAGGIGh0dHA6Ly8xMjcuMC4w +LjE6ODg4OC9vY3NwP2Zvbz0xMA0GCSqGSIb3DQEBBQUAA4GBAEN3M+nMsUI1lApX +pd2UIcDMQgSBvbKsTRBo8/4zCo65PunyRKoc5z7o4FdAQe9KsTKw8nV8qnfSZJ26 +oRLq+YMxup+DWBw46dCm3QRyhdEtxzuyce/k9lcMarb85RMtvqbB9EtNyGnMfC4l +wY6AnhnDF7Ihp6/oL/HUu4yjOb5J +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3 (0x3) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-1 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:d3:24:1c:92:a5:bb:00:d9:b1:fb:2b:1d:7a:32: + a1:6c:49:eb:3c:2d:29:80:d6:65:8b:17:3a:f0:4b: + dc:0c:57:fb:d5:31:68:a5:e4:54:86:55:f9:1b:a8: + d7:7d:32:01:3b:cf:5c:38:2b:f5:bc:d3:8b:c8:b6: + ab:76:65:32:e6:4b:d5:e4:fd:d1:92:c8:33:6a:74: + f3:c7:ec:97:c3:c7:9f:e4:d5:55:75:b8:bd:39:ec: + 2d:1f:c6:54:c8:2b:2d:17:e0:05:77:28:44:f7:dd: + e1:6e:f0:59:05:51:f5:b9:b4:fe:be:ad:40:a6:d5: + 9a:c1:64:e0:9b:dd:67:e5:f1 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + X509v3 Authority Key Identifier: + keyid:D2:30:71:56:50:A6:BC:21:C5:A1:A1:AB:11:A7:08:5B:EB:3A:A4:27 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 0c:61:c0:c7:11:c2:f0:39:f0:76:9d:4f:43:d4:90:54:1f:26: + 3d:54:3d:77:5f:c0:b3:4a:c2:1b:b6:18:d2:12:8d:24:4d:76: + f5:07:0b:14:3e:17:2d:42:ee:85:30:db:e3:4d:81:67:59:97: + 0a:b3:bb:c5:27:ea:69:c6:ee:99:5c:44:36:53:3e:c4:47:68: + f8:fe:c6:53:38:fb:e7:9a:0c:3c:6c:78:93:29:d2:49:7d:29: + d0:61:6e:81:9b:d6:ec:1a:e2:3e:62:62:41:bc:6d:4d:33:91: + 76:20:5e:32:70:08:3e:24:72:fe:b1:8a:83:57:04:19:b5:cb: + 99:b7 +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMTAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowZDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MRUwEwYDVQQDEwxzaWduaW5nLWNhLTIwgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBANMkHJKluwDZsfsrHXoyoWxJ6zwtKYDWZYsXOvBL3AxX+9UxaKXk +VIZV+Ruo130yATvPXDgr9bzTi8i2q3ZlMuZL1eT90ZLIM2p088fsl8PHn+TVVXW4 +vTnsLR/GVMgrLRfgBXcoRPfd4W7wWQVR9bm0/r6tQKbVmsFk4JvdZ+XxAgMBAAGj +UDBOMB0GA1UdDgQWBBSzC/V9URZRfig3w6IPHS8QwFGjszAfBgNVHSMEGDAWgBTS +MHFWUKa8IcWhoasRpwhb6zqkJzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A4GBAAxhwMcRwvA58HadT0PUkFQfJj1UPXdfwLNKwhu2GNISjSRNdvUHCxQ+Fy1C +7oUw2+NNgWdZlwqzu8Un6mnG7plcRDZTPsRHaPj+xlM4++eaDDxseJMp0kl9KdBh +boGb1uwa4j5iYkG8bU0zkXYgXjJwCD4kcv6xioNXBBm1y5m3 +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=root-ca + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:a0:3e:1a:4f:6c:b9:3d:ab:0f:02:de:da:82:92: + ee:a2:69:88:80:ed:f2:b6:98:bc:c6:ee:d3:47:82: + 4a:e7:d3:7f:55:68:5c:6d:9e:aa:ba:59:e3:5b:7f: + 32:4f:79:44:4a:4f:13:e4:2e:3f:1f:98:10:a4:72: + d5:f0:e7:44:8e:d4:a7:b9:fb:54:be:b6:fa:f7:dc: + 9c:29:93:d4:9f:a1:5b:18:6e:68:93:91:1b:8c:a0: + 4f:02:52:e9:9d:e8:98:f3:fd:67:da:78:4b:4f:d8: + 2d:90:83:5c:0b:e5:fe:48:27:e4:ec:bb:99:26:06: + 8e:34:fe:93:e4:d2:fc:97:57 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + D2:30:71:56:50:A6:BC:21:C5:A1:A1:AB:11:A7:08:5B:EB:3A:A4:27 + X509v3 Authority Key Identifier: + keyid:1D:2F:09:60:EB:E4:EA:B5:0B:52:A9:5C:5E:09:2B:DD:34:70:CF:BA + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + a6:16:2f:fc:13:67:5e:ce:0e:79:cb:b0:91:52:9b:9e:b5:9f: + e1:fa:7d:78:f4:2a:93:f3:94:62:45:17:87:b9:0a:59:b9:a3: + a9:75:51:ca:f0:04:6c:01:d1:3a:a9:dd:66:7d:27:7b:1e:4f: + 48:3a:25:ea:a5:01:32:fc:87:4b:08:da:f8:f5:62:88:e8:b9: + 94:c7:cb:ee:33:08:ab:2f:52:f4:4a:14:4f:ac:2d:a2:f8:de: + c9:6f:95:b7:91:23:b9:ec:95:90:de:86:21:f5:6f:1b:cf:13: + 47:77:78:dd:7a:16:e9:8b:cc:df:3d:45:8a:76:af:15:d1:9a: + 37:a2 +-----BEGIN CERTIFICATE----- +MIICizCCAfSgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxEDAOBgNVBAMTB3Jvb3QtY2EwIBcNMTQxMDE2MDMyNzA5 +WhgPMjExNDA5MjIwMzI3MDlaMGQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxp +Zm9ybmlhMRUwEwYDVQQHEwxEZWZhdWx0IENpdHkxEjAQBgNVBAoTCU9wZW5SZXN0 +eTEVMBMGA1UEAxMMc2lnbmluZy1jYS0xMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCgPhpPbLk9qw8C3tqCku6iaYiA7fK2mLzG7tNHgkrn039VaFxtnqq6WeNb +fzJPeURKTxPkLj8fmBCkctXw50SO1Ke5+1S+tvr33Jwpk9SfoVsYbmiTkRuMoE8C +Uumd6Jjz/WfaeEtP2C2Qg1wL5f5IJ+Tsu5kmBo40/pPk0vyXVwIDAQABo1AwTjAd +BgNVHQ4EFgQU0jBxVlCmvCHFoaGrEacIW+s6pCcwHwYDVR0jBBgwFoAUHS8JYOvk +6rULUqlcXgkr3TRwz7owDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCm +Fi/8E2dezg55y7CRUpuetZ/h+n149CqT85RiRReHuQpZuaOpdVHK8ARsAdE6qd1m +fSd7Hk9IOiXqpQEy/IdLCNr49WKI6LmUx8vuMwirL1L0ShRPrC2i+N7Jb5W3kSO5 +7JWQ3oYh9W8bzxNHd3jdehbpi8zfPUWKdq8V0Zo3og== +-----END CERTIFICATE----- diff --git a/t/cert/ocsp/revoked-ocsp-resp.der b/t/cert/ocsp/revoked-ocsp-resp.der new file mode 100644 index 000000000..71d41a77f Binary files /dev/null and b/t/cert/ocsp/revoked-ocsp-resp.der differ diff --git a/t/cert/ocsp/test-com.crt b/t/cert/ocsp/test-com.crt new file mode 100644 index 000000000..d34cb6b9e --- /dev/null +++ b/t/cert/ocsp/test-com.crt @@ -0,0 +1,69 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4 (0x4) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:c7:bd:50:99:71:46:af:93:22:85:ab:74:8b:5b: + 19:74:af:3e:ad:d2:e1:17:3e:cb:5b:36:9c:8a:38: + bd:1b:47:2d:8b:92:55:1d:fe:a6:72:92:78:00:de: + 30:cb:a3:10:b5:92:aa:b8:e0:7b:44:9a:f5:99:89: + 36:f4:84:20:81:e3:5c:76:00:9d:76:e7:b9:41:ab: + 74:b6:14:9f:b2:94:b3:b6:48:a8:92:dc:09:e3:3d: + 04:e3:5f:0f:5b:50:ad:0c:59:3a:88:06:39:2d:34: + a6:52:2f:58:6f:53:1b:df:9f:98:ea:82:8d:52:60: + b1:ef:6b:e9:f5:ad:29:87:45 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 67:DF:28:25:D1:F8:83:36:28:EE:DB:41:63:E4:E0:3A:32:0D:EA:30 + X509v3 Authority Key Identifier: + keyid:B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + DirName:/C=US/ST=California/L=Default City/O=OpenResty/CN=signing-ca-1 + serial:03 + + Authority Information Access: + OCSP - URI:http://127.0.0.1:8888/ocsp?foo=1 + + Signature Algorithm: sha1WithRSAEncryption + 37:29:3f:ed:d9:47:9a:51:36:a3:5b:00:85:66:de:51:4d:48: + 2d:f8:bc:f1:5e:b4:fd:30:48:f0:25:ee:77:57:9c:f1:4b:0a: + 4f:7e:96:1a:f8:48:76:23:46:8d:d6:f2:5e:1e:08:52:12:53: + 08:07:9f:75:db:77:22:2e:7e:89:c2:2c:66:85:6b:df:e9:77: + ca:23:6d:9a:af:87:8a:8c:27:37:1e:9e:55:92:8e:8a:a9:93: + 24:41:a8:96:01:c0:65:93:8e:3d:7a:6c:bf:ed:c8:2a:f8:26: + cc:00:17:b7:27:ca:85:6c:2e:d5:2a:0a:8d:f3:88:e8:26:48: + e3:e8 +-----BEGIN CERTIFICATE----- +MIIDaTCCAtKgAwIBAgIBBDANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMjAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowYDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MREwDwYDVQQDEwh0ZXN0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAx71QmXFGr5Mihat0i1sZdK8+rdLhFz7LWzaciji9G0cti5JVHf6mcpJ4 +AN4wy6MQtZKquOB7RJr1mYk29IQggeNcdgCddue5Qat0thSfspSztkioktwJ4z0E +418PW1CtDFk6iAY5LTSmUi9Yb1Mb35+Y6oKNUmCx72vp9a0ph0UCAwEAAaOCASsw +ggEnMAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVk +IENlcnRpZmljYXRlMB0GA1UdDgQWBBRn3ygl0fiDNiju20Fj5OA6Mg3qMDCBjgYD +VR0jBIGGMIGDgBSzC/V9URZRfig3w6IPHS8QwFGjs6FopGYwZDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTES +MBAGA1UEChMJT3BlblJlc3R5MRUwEwYDVQQDEwxzaWduaW5nLWNhLTGCAQMwPAYI +KwYBBQUHAQEEMDAuMCwGCCsGAQUFBzABhiBodHRwOi8vMTI3LjAuMC4xOjg4ODgv +b2NzcD9mb289MTANBgkqhkiG9w0BAQUFAAOBgQA3KT/t2UeaUTajWwCFZt5RTUgt ++LzxXrT9MEjwJe53V5zxSwpPfpYa+Eh2I0aN1vJeHghSElMIB59123ciLn6Jwixm +hWvf6XfKI22ar4eKjCc3Hp5Vko6KqZMkQaiWAcBlk449emy/7cgq+CbMABe3J8qF +bC7VKgqN84joJkjj6A== +-----END CERTIFICATE----- diff --git a/t/cert/ocsp/wrong-issuer-order-chain.pem b/t/cert/ocsp/wrong-issuer-order-chain.pem new file mode 100644 index 000000000..098e862ba --- /dev/null +++ b/t/cert/ocsp/wrong-issuer-order-chain.pem @@ -0,0 +1,183 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4 (0x4) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:c7:bd:50:99:71:46:af:93:22:85:ab:74:8b:5b: + 19:74:af:3e:ad:d2:e1:17:3e:cb:5b:36:9c:8a:38: + bd:1b:47:2d:8b:92:55:1d:fe:a6:72:92:78:00:de: + 30:cb:a3:10:b5:92:aa:b8:e0:7b:44:9a:f5:99:89: + 36:f4:84:20:81:e3:5c:76:00:9d:76:e7:b9:41:ab: + 74:b6:14:9f:b2:94:b3:b6:48:a8:92:dc:09:e3:3d: + 04:e3:5f:0f:5b:50:ad:0c:59:3a:88:06:39:2d:34: + a6:52:2f:58:6f:53:1b:df:9f:98:ea:82:8d:52:60: + b1:ef:6b:e9:f5:ad:29:87:45 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 67:DF:28:25:D1:F8:83:36:28:EE:DB:41:63:E4:E0:3A:32:0D:EA:30 + X509v3 Authority Key Identifier: + keyid:B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + DirName:/C=US/ST=California/L=Default City/O=OpenResty/CN=signing-ca-1 + serial:03 + + Authority Information Access: + OCSP - URI:http://127.0.0.1:8888/ocsp?foo=1 + + Signature Algorithm: sha1WithRSAEncryption + 37:29:3f:ed:d9:47:9a:51:36:a3:5b:00:85:66:de:51:4d:48: + 2d:f8:bc:f1:5e:b4:fd:30:48:f0:25:ee:77:57:9c:f1:4b:0a: + 4f:7e:96:1a:f8:48:76:23:46:8d:d6:f2:5e:1e:08:52:12:53: + 08:07:9f:75:db:77:22:2e:7e:89:c2:2c:66:85:6b:df:e9:77: + ca:23:6d:9a:af:87:8a:8c:27:37:1e:9e:55:92:8e:8a:a9:93: + 24:41:a8:96:01:c0:65:93:8e:3d:7a:6c:bf:ed:c8:2a:f8:26: + cc:00:17:b7:27:ca:85:6c:2e:d5:2a:0a:8d:f3:88:e8:26:48: + e3:e8 +-----BEGIN CERTIFICATE----- +MIIDaTCCAtKgAwIBAgIBBDANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMjAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowYDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MREwDwYDVQQDEwh0ZXN0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAx71QmXFGr5Mihat0i1sZdK8+rdLhFz7LWzaciji9G0cti5JVHf6mcpJ4 +AN4wy6MQtZKquOB7RJr1mYk29IQggeNcdgCddue5Qat0thSfspSztkioktwJ4z0E +418PW1CtDFk6iAY5LTSmUi9Yb1Mb35+Y6oKNUmCx72vp9a0ph0UCAwEAAaOCASsw +ggEnMAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVk +IENlcnRpZmljYXRlMB0GA1UdDgQWBBRn3ygl0fiDNiju20Fj5OA6Mg3qMDCBjgYD +VR0jBIGGMIGDgBSzC/V9URZRfig3w6IPHS8QwFGjs6FopGYwZDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTES +MBAGA1UEChMJT3BlblJlc3R5MRUwEwYDVQQDEwxzaWduaW5nLWNhLTGCAQMwPAYI +KwYBBQUHAQEEMDAuMCwGCCsGAQUFBzABhiBodHRwOi8vMTI3LjAuMC4xOjg4ODgv +b2NzcD9mb289MTANBgkqhkiG9w0BAQUFAAOBgQA3KT/t2UeaUTajWwCFZt5RTUgt ++LzxXrT9MEjwJe53V5zxSwpPfpYa+Eh2I0aN1vJeHghSElMIB59123ciLn6Jwixm +hWvf6XfKI22ar4eKjCc3Hp5Vko6KqZMkQaiWAcBlk449emy/7cgq+CbMABe3J8qF +bC7VKgqN84joJkjj6A== +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=root-ca + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:a0:3e:1a:4f:6c:b9:3d:ab:0f:02:de:da:82:92: + ee:a2:69:88:80:ed:f2:b6:98:bc:c6:ee:d3:47:82: + 4a:e7:d3:7f:55:68:5c:6d:9e:aa:ba:59:e3:5b:7f: + 32:4f:79:44:4a:4f:13:e4:2e:3f:1f:98:10:a4:72: + d5:f0:e7:44:8e:d4:a7:b9:fb:54:be:b6:fa:f7:dc: + 9c:29:93:d4:9f:a1:5b:18:6e:68:93:91:1b:8c:a0: + 4f:02:52:e9:9d:e8:98:f3:fd:67:da:78:4b:4f:d8: + 2d:90:83:5c:0b:e5:fe:48:27:e4:ec:bb:99:26:06: + 8e:34:fe:93:e4:d2:fc:97:57 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + D2:30:71:56:50:A6:BC:21:C5:A1:A1:AB:11:A7:08:5B:EB:3A:A4:27 + X509v3 Authority Key Identifier: + keyid:1D:2F:09:60:EB:E4:EA:B5:0B:52:A9:5C:5E:09:2B:DD:34:70:CF:BA + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + a6:16:2f:fc:13:67:5e:ce:0e:79:cb:b0:91:52:9b:9e:b5:9f: + e1:fa:7d:78:f4:2a:93:f3:94:62:45:17:87:b9:0a:59:b9:a3: + a9:75:51:ca:f0:04:6c:01:d1:3a:a9:dd:66:7d:27:7b:1e:4f: + 48:3a:25:ea:a5:01:32:fc:87:4b:08:da:f8:f5:62:88:e8:b9: + 94:c7:cb:ee:33:08:ab:2f:52:f4:4a:14:4f:ac:2d:a2:f8:de: + c9:6f:95:b7:91:23:b9:ec:95:90:de:86:21:f5:6f:1b:cf:13: + 47:77:78:dd:7a:16:e9:8b:cc:df:3d:45:8a:76:af:15:d1:9a: + 37:a2 +-----BEGIN CERTIFICATE----- +MIICizCCAfSgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxEDAOBgNVBAMTB3Jvb3QtY2EwIBcNMTQxMDE2MDMyNzA5 +WhgPMjExNDA5MjIwMzI3MDlaMGQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxp +Zm9ybmlhMRUwEwYDVQQHEwxEZWZhdWx0IENpdHkxEjAQBgNVBAoTCU9wZW5SZXN0 +eTEVMBMGA1UEAxMMc2lnbmluZy1jYS0xMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCgPhpPbLk9qw8C3tqCku6iaYiA7fK2mLzG7tNHgkrn039VaFxtnqq6WeNb +fzJPeURKTxPkLj8fmBCkctXw50SO1Ke5+1S+tvr33Jwpk9SfoVsYbmiTkRuMoE8C +Uumd6Jjz/WfaeEtP2C2Qg1wL5f5IJ+Tsu5kmBo40/pPk0vyXVwIDAQABo1AwTjAd +BgNVHQ4EFgQU0jBxVlCmvCHFoaGrEacIW+s6pCcwHwYDVR0jBBgwFoAUHS8JYOvk +6rULUqlcXgkr3TRwz7owDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCm +Fi/8E2dezg55y7CRUpuetZ/h+n149CqT85RiRReHuQpZuaOpdVHK8ARsAdE6qd1m +fSd7Hk9IOiXqpQEy/IdLCNr49WKI6LmUx8vuMwirL1L0ShRPrC2i+N7Jb5W3kSO5 +7JWQ3oYh9W8bzxNHd3jdehbpi8zfPUWKdq8V0Zo3og== +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3 (0x3) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-1 + Validity + Not Before: Oct 16 03:27:09 2014 GMT + Not After : Sep 22 03:27:09 2114 GMT + Subject: C=US, ST=California, L=Default City, O=OpenResty, CN=signing-ca-2 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:d3:24:1c:92:a5:bb:00:d9:b1:fb:2b:1d:7a:32: + a1:6c:49:eb:3c:2d:29:80:d6:65:8b:17:3a:f0:4b: + dc:0c:57:fb:d5:31:68:a5:e4:54:86:55:f9:1b:a8: + d7:7d:32:01:3b:cf:5c:38:2b:f5:bc:d3:8b:c8:b6: + ab:76:65:32:e6:4b:d5:e4:fd:d1:92:c8:33:6a:74: + f3:c7:ec:97:c3:c7:9f:e4:d5:55:75:b8:bd:39:ec: + 2d:1f:c6:54:c8:2b:2d:17:e0:05:77:28:44:f7:dd: + e1:6e:f0:59:05:51:f5:b9:b4:fe:be:ad:40:a6:d5: + 9a:c1:64:e0:9b:dd:67:e5:f1 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + B3:0B:F5:7D:51:16:51:7E:28:37:C3:A2:0F:1D:2F:10:C0:51:A3:B3 + X509v3 Authority Key Identifier: + keyid:D2:30:71:56:50:A6:BC:21:C5:A1:A1:AB:11:A7:08:5B:EB:3A:A4:27 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 0c:61:c0:c7:11:c2:f0:39:f0:76:9d:4f:43:d4:90:54:1f:26: + 3d:54:3d:77:5f:c0:b3:4a:c2:1b:b6:18:d2:12:8d:24:4d:76: + f5:07:0b:14:3e:17:2d:42:ee:85:30:db:e3:4d:81:67:59:97: + 0a:b3:bb:c5:27:ea:69:c6:ee:99:5c:44:36:53:3e:c4:47:68: + f8:fe:c6:53:38:fb:e7:9a:0c:3c:6c:78:93:29:d2:49:7d:29: + d0:61:6e:81:9b:d6:ec:1a:e2:3e:62:62:41:bc:6d:4d:33:91: + 76:20:5e:32:70:08:3e:24:72:fe:b1:8a:83:57:04:19:b5:cb: + 99:b7 +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJVUzET +MBEGA1UECBMKQ2FsaWZvcm5pYTEVMBMGA1UEBxMMRGVmYXVsdCBDaXR5MRIwEAYD +VQQKEwlPcGVuUmVzdHkxFTATBgNVBAMTDHNpZ25pbmctY2EtMTAgFw0xNDEwMTYw +MzI3MDlaGA8yMTE0MDkyMjAzMjcwOVowZDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +CkNhbGlmb3JuaWExFTATBgNVBAcTDERlZmF1bHQgQ2l0eTESMBAGA1UEChMJT3Bl +blJlc3R5MRUwEwYDVQQDEwxzaWduaW5nLWNhLTIwgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBANMkHJKluwDZsfsrHXoyoWxJ6zwtKYDWZYsXOvBL3AxX+9UxaKXk +VIZV+Ruo130yATvPXDgr9bzTi8i2q3ZlMuZL1eT90ZLIM2p088fsl8PHn+TVVXW4 +vTnsLR/GVMgrLRfgBXcoRPfd4W7wWQVR9bm0/r6tQKbVmsFk4JvdZ+XxAgMBAAGj +UDBOMB0GA1UdDgQWBBSzC/V9URZRfig3w6IPHS8QwFGjszAfBgNVHSMEGDAWgBTS +MHFWUKa8IcWhoasRpwhb6zqkJzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A4GBAAxhwMcRwvA58HadT0PUkFQfJj1UPXdfwLNKwhu2GNISjSRNdvUHCxQ+Fy1C +7oUw2+NNgWdZlwqzu8Un6mnG7plcRDZTPsRHaPj+xlM4++eaDDxseJMp0kl9KdBh +boGb1uwa4j5iYkG8bU0zkXYgXjJwCD4kcv6xioNXBBm1y5m3 +-----END CERTIFICATE----- diff --git a/t/cert/test.crt b/t/cert/test.crt new file mode 100644 index 000000000..bfcc567ce --- /dev/null +++ b/t/cert/test.crt @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIGETCCA/mgAwIBAgIUE3pqyVuRQL+qGuSFAUCLq4g7pt4wDQYJKoZIhvcNAQEL +BQAwgZcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH +DA1TYW4gRnJhbmNpc2NvMRIwEAYDVQQKDAlPcGVuUmVzdHkxEjAQBgNVBAsMCU9w +ZW5SZXN0eTERMA8GA1UEAwwIdGVzdC5jb20xIDAeBgkqhkiG9w0BCQEWEWFnZW50 +emhAZ21haWwuY29tMB4XDTIyMDUyOTA2MTk1N1oXDTMyMDUyNjA2MTk1N1owgZcx +CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4g +RnJhbmNpc2NvMRIwEAYDVQQKDAlPcGVuUmVzdHkxEjAQBgNVBAsMCU9wZW5SZXN0 +eTERMA8GA1UEAwwIdGVzdC5jb20xIDAeBgkqhkiG9w0BCQEWEWFnZW50emhAZ21h +aWwuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyLzMbnMBcxYB +2W0uEqPKo2lOJdUQTnakipVLqRvZIJv7NkZgU76pxdFwoSxPpvJcpJ4rsosBZvhV +dkGoKmuVfIFU0lYcdaccq88aT7E9XfTXiiyB2tkT6jS6wr+QxDj7KW47zdUBUT9O +6ClNyY2o1gZldElTG0Bwk4j2sAkXuWGmyncTOJ4ge3mWVksAQYbL5pwfdfyqgDmK +B4nLJHBkorLbF7nm7pK2HzQCtaEUJpQKpJdCULcOHrydjVAwHUQsZAb9XXjQWPTb +A0BSplbgMSI6saT9uA2RjLBzpYKj8J1rWGadWteSyQAf6XooQrquTPuR+OWF6t/m +2vkTcJlh1ukPPAPZBvlAQX9VSLWk5fmAQZA5BxYXNVWcMGVNO7UtilRmjqK1nCmv +oyDXHzpE5RZPBZH4ecOqTscUgmS72ItPGWMtEtCQYbzWyMAa1cpCvK40YRa4814r +XgffWgWJQfqyVvRjzpWIUiqwjUX3/p3W3pxX/GNHOv2ZH/pebcODOl7EFxzv5eQc +z9vW5+RfiCSzs5bGG7qw58dMFROeAJbwR6t2o6GRd0HfgTTwSDcrrpcdLP/PaL+v +twnrNa9r7rIwXnDWxYo3KiGpqEfG2WwW+lJsUzZOi9eI9kYPyvFmNFLugZbHMi+h +ICCb8AQB2thON5X4N7FtP5GVfMR9vIkCAwEAAaNTMFEwHQYDVR0OBBYEFDEy866N +WPHPTJKeL/96VYoczkINMB8GA1UdIwQYMBaAFDEy866NWPHPTJKeL/96VYoczkIN +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAHJgyS6/OyRqzqwd ++6DnntGV+MTo9WBNvNs/fekJghnrr85oG330GasYENQLi0wF20to7FMan2U9kwgV +cbhDwe70JD0jg8htYof/uXOMBVWT4iZ+eXn60mP3iLsSutwt/drXBBzbxMYbUC12 +CIiadkV8aMPIN6oGnF7TLF4AvBqYYp2qAVGXr/ZQm3L7NPB0jkSktSe6obnaq1tO +ug18ImhzAqkn1UGnLRiTADOba5HuKtovwWtLblNBODdnv1E7IK1A6jpqwiYjlbU5 +4v9ZzFzEJw+GqYHkTRmJGCA2Uw7HNEUFeno1BTp19Ce9fvrkofTWYmLp/NAvEp6E +aFnBdCjY4tWzI2Iig7IjDIM7F6igGODybeN9ijD7oSyEDtNE7ECLSuXhgekiQJ8c +NYALgbbNPxWx8zJcNiYy1NDYdIjO5vYFOpbn+rQObKVCX/X+Al9fT126ciTT1xAF +fZtkGPpp3Wjgws9UDSzetvWHYt0Rs70m351LFPHyR4tQLHoDFqnA2buG/mSvKZ9U +to0JQ/8QPRIYv0FUJcF0+/xQRYrIqvmjiCpfL2hwJyRViq7f8z0/tmMoLxFlPo/k +GC+gvh3WwTB622h9JEqS48lllcYZIQWOX2mbFtUtNFdzUtSRmZMa8RmcYr5So2OH +9QLIIjC6ntPFjWq+uLJAJ8uazRt7 +-----END CERTIFICATE----- diff --git a/t/cert/test.crt.der b/t/cert/test.crt.der new file mode 100644 index 000000000..936635fd6 Binary files /dev/null and b/t/cert/test.crt.der differ diff --git a/t/cert/test.key b/t/cert/test.key new file mode 100644 index 000000000..0c07e066f --- /dev/null +++ b/t/cert/test.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDIvMxucwFzFgHZ +bS4So8qjaU4l1RBOdqSKlUupG9kgm/s2RmBTvqnF0XChLE+m8lykniuyiwFm+FV2 +Qagqa5V8gVTSVhx1pxyrzxpPsT1d9NeKLIHa2RPqNLrCv5DEOPspbjvN1QFRP07o +KU3JjajWBmV0SVMbQHCTiPawCRe5YabKdxM4niB7eZZWSwBBhsvmnB91/KqAOYoH +icskcGSistsXuebukrYfNAK1oRQmlAqkl0JQtw4evJ2NUDAdRCxkBv1deNBY9NsD +QFKmVuAxIjqxpP24DZGMsHOlgqPwnWtYZp1a15LJAB/peihCuq5M+5H45YXq3+ba ++RNwmWHW6Q88A9kG+UBBf1VItaTl+YBBkDkHFhc1VZwwZU07tS2KVGaOorWcKa+j +INcfOkTlFk8Fkfh5w6pOxxSCZLvYi08ZYy0S0JBhvNbIwBrVykK8rjRhFrjzXite +B99aBYlB+rJW9GPOlYhSKrCNRff+ndbenFf8Y0c6/Zkf+l5tw4M6XsQXHO/l5BzP +29bn5F+IJLOzlsYburDnx0wVE54AlvBHq3ajoZF3Qd+BNPBINyuulx0s/89ov6+3 +Ces1r2vusjBecNbFijcqIamoR8bZbBb6UmxTNk6L14j2Rg/K8WY0Uu6BlscyL6Eg +IJvwBAHa2E43lfg3sW0/kZV8xH28iQIDAQABAoICABwZgax8YNmRXRTomah2USlq +1kupdazmIsZbe8niYhSUgSfp1hYi/HT6in+lSkkeaCWLFqbZmoqlfKEfM8EsajKR +kCQZdcZqbDMIvLAnKWX7nihzboIKHSWN2A7m7gbpyw7TpX98r8CF0i/hiEgMknPT +VWRf10hbTub4J0AhJbcHmmeBH6mvSPC/5nGR8ik6C1TuyeCkS+HDLDU97rfdG9lC +nDTICzGeS+w2RaLTN5Tm6E599gSCe3GGCa/8Z5/RKT2fVNw+yzuImxfrayZpxtxZ +5El1xSZ8j8FX+fhTP0uxXZN0WdabkqqcX9s5BGXC6B9Sn+5tgr+MNC626ye58N36 +vhASbLhnZxZ1MxbVNdvQjl9A5mj/Tv/TM/syjFkElFwXBFz5MXgYRTu5TnoJpDyp +wMqNodTSbe8UuULtBIHYyuLEsrPWjne/ape8CqSQKgfnI91tveAMlAQA+yGRufSw +fx1gDrBIk3EtDGuelAIiW1ZimZoTj51HW7Mpfq9PXbuVO7i+zzSDFP/zyto2hITq +UeiTwKYpj8AgF39HiudAyJVYKDjKgATeSN1ziTvXBvBxe4JJ+7wovjl+R5ClBUEk +cNrn3FNVrgPjmJ6X7+42U2upQ2WIo8dT6PuE408nh2FR/VuabEqqwmmDEUW19+U+ +yZWfhDMv6Y/l6f75UBHhAoIBAQD++61+BBEwqTPfGH5EUxxeeaXXto8zW/N3aBql +f77iI3/XuOhve4WfL8v5xAXPfPIBGGBlG5RQ0Quxoo5fE6RPKt+5vaaOpMhLVf4L +sAQfLM9M9JK84BbWI+Z1o2s1knxlTlK0GZ/pbqg6J1YFfFAL/3PKi98EAGw9Dpj9 +u3GnSjF/dbtwwUiTS0Rv/8FV4bFxsSoul+N3xeyi26UWG9WIvuAzuLJfvqV2fb/l +o3LKpeXyD+SFatRmPe0JWIi2o0ZQpF8hhJXg1UYo2RGWf/bbl3SnivIMb/hNs8LB +17DNKu1DvgZoJSyocFmKAShZvbbirQYRtHo7SI2hAaOt6T81AoIBAQDJib1HIp/V +N59IIFOru6+kc9Vkhm8FDf3iyQfIL9Y0uVI4WrdKUeIH+pEKU7CMCT0WSgCxENe2 +TT+pOF3/T398eEmGAr79CtgtSzzuDvBlpD2aEfhjBPUE7g6tNuUFjgFJmzdXHT7u +99mWag4l30hfIAFXLu9ofm05nw0CRxlotYUiTCBU892ZxpO2/nDNJUjAU1MOvSa7 +MM7VbETui9teHgjZa+AadHZs2OqnWVo+g3RcKl3PyAdcOWobVQknRrpN9SlQkf/V +GYRhY2tQCpstKrEDQPWsNGmwjmT3uPdK8t5SXZZBji282XtCa+C+tB34AZ8YuU/Z +BH+Akez5zw6FAoIBAQCLK17kGuAvCQsQx1OTgzFGt2q3NCMwyw01rRJuJi1PTETo +vznOL0MdQX85Ua5CM1X7Fwz14nmvKooRaEIAzr2toB8AR+zyiinwRH0mb+mwAksb +G5pDkKOmOW394zYOxWcz++3T8vB+/jC/nNysnc8q3UCb2n/ctUZehOsoAfjkb/BY +OzAVOMmd60TtRFCHyWmKPkJhr/EtXE/uC6gtSv/fZR8F29cvvuScqcHlWrK6vJWm +6tm1oDtRmpcXtMTZuoAUX8K0jqMnVgC3JtMcq7dW33GCSKoX870429Z+6nTLZpSd +lsf0a+XWAYw9cKhPYubBDeL0IudcGBuFN1nZACfJAoIBABpg/vdKnuUHjL+iC5GU +1V6PEsU/m1RsCmkeqvgW2tC32P0rUoZVxWIJ9+YEIj2SD/7U3NZQQAvKfKSnjhYW +z7b4/5ac0WbJfpYfHPCD4A9Nugpqg7piMbfdeOpPHxblCWIbANlUKKKaqk43v3ZR +jWV2CPbiW8+vjJhYKxm7OKYt7CkbEbhM2xp/lWIEV7tiP+18eoiZVXJ25vukWjlm +8OWWxM3Aguqzh7Sjh8MzvM4l4psVqIXDxsLZePvu223aohQGHMxA791yo5Mjsi4d +1UXKKrUkUYOisJq9aJXMDgIvW84oFbyq4W2wgaOl/xq29J07iRlxV/Qt1Ip9jyj7 +YwkCggEBANFod5wwlOzTyAlRdZAPvyBWa0i+SLWecwr93rGQF3UuZwzaviY7U0tp +BoeFZAxiwns3SM6SJYIhce8Ku0PO1rp4FXvOopnIVO9BFXk1E5YUholQHHbERsse +pQahtlZ26ZAyRQjMQI9KKmU6pNFiF87MCnsz70BQajKo2+5NfZpnjaQzT6uyJeRQ +Iy38QlOk0221UsUU/bjahDlxmuHVdcnl6gi+4SYrI45wJSiBTiCJrVmxScg2D3IA +EZ3pVKOam0a3Mroqe0uMdY892cIYSAKOrZcxg5ZXWKDwlcsXgVuGDSvrU8LUPqn1 +jxLWsmKbkVA5hFln8GPxs6EqfqOJhkM= +-----END PRIVATE KEY----- diff --git a/t/cert/test.key.der b/t/cert/test.key.der new file mode 100644 index 000000000..76df7016b Binary files /dev/null and b/t/cert/test.key.der differ diff --git a/t/cert/test2.crt b/t/cert/test2.crt new file mode 100644 index 000000000..b75e130ee --- /dev/null +++ b/t/cert/test2.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIUBwRM0hlOTRGtIMvLy56P3TZc6jIwDQYJKoZIhvcNAQEL +BQAwgYYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH +DA1TYW4gRnJhbmNpc2NvMRIwEAYDVQQKDAlPcGVuUmVzdHkxEjAQBgNVBAMMCXRl +c3QyLmNvbTEiMCAGCSqGSIb3DQEJARYTb3BlbnJlc3R5QGdtYWlsLmNvbTAeFw0y +MjA1MjkwNjEzMjVaFw0zMjA1MjYwNjEzMjVaMIGGMQswCQYDVQQGEwJVUzETMBEG +A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzESMBAGA1UE +CgwJT3BlblJlc3R5MRIwEAYDVQQDDAl0ZXN0Mi5jb20xIjAgBgkqhkiG9w0BCQEW +E29wZW5yZXN0eUBnbWFpbC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQDCOlzMo5qPtb5wjXHqy1mjm2rMAPeiz0wrDRXC0+9l/f/6KYBfJoaAuLau +P6kCojDNLOOHAjmbjeoG+bVGoLVNAGhFaThKQepmBPFhjtT0+7syGq6H12Wb9mmn +hMy65gS+7dsUU74HUjlHuYyOnXHmrJH/7sGzG6DXX6t2x0Ts49gVWzFr1TINMjP0 +opupvpokbr+EEzIP4NE5isPQnMeIt2HhzTMN+wHBalnHIVq0v03M911l4XpzHtP+ +MetDMraHJdv8Hlh5qeyrDkn7N5zE05cnSu6AK3vMaxsDbRJ92Akv6bLMqTqSnR3j +16Qwqs369cU+zl7wQJ7+J1Gh5DhOe9Vns+bLbBJC5XZz3qcFxUXXRResuBYPQNht +JwgT7wq+XNMkd7TzDQzfWx4G7zz+3ogiNK6k02e0Uafuizi4nyMpwuu9SwvfYan3 +QBL7ddjuMGUHG9oOepLmiKt8ST/plduc+67oXu1oeGnaEthpQ9Aong0/JhDvvnsX +Pe4tZQwzFK1m/fxF034l3fy+gmGfnRC4k2thUotfp+BReDAZ5Wwg6FO7eaowuQbH +Qk8Pt36yd0fWwAgX3kmhxC91QOYt29Aya4gTuJT4ajueDVRzB3bCxX0t7Awz/Ohh +36XiLHZp7Zo1eJtG7oOXRJdiKCglxOk9WiXTIaDXunXDGRl+DQIDAQABo1MwUTAd +BgNVHQ4EFgQUwIug2OC092u/dZFClmac5Sbo4ukwHwYDVR0jBBgwFoAUwIug2OC0 +92u/dZFClmac5Sbo4ukwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AgEAhnWpbQ31DbMzbSiY7iKM8OsUAqPzjvGObB6hzF+i8/q0GfQm11V+aKFTWu3p +5aJua0hCPxq3F6q3Wj+F42pcRkoOmcGMMkGVOFeT1TRaC1WwTvhYjwFke4jqOi+5 +kcfLWO3bkADm73AuW/PJ6WgQzrfOvd+uvVX6B0NKumgNYrhyDfwb4tZ/fOLHSA9W +NNd/93yo2OhH0vOMbAcDGdOpBR7Sx7c+BeI57kORIlSwCQmaSeIIOLs69VsMH/Yo +DiNWfbj7pPRhdCSEmd59wyPKUcBJHfEE9z54NIb6ETTKrmtwYLPNKdgW6dqHe8UK +bsjXiiw0Y3nHhrv7Fh+aqy470mn890K0+CPTh9Gi+Qwkfyt7tJr4uCdDzLPoP6up +FlQaobMtOFuCMNZs+B1UZm2jcj5j/PjY19PZu8XAHOFJmY66oqK6FvZHrq/26K29 +p6BXPMUVhxFoXUX2EmAtey919+GY9KncMuhobn+5If/6mug3QRNNt/csw30NvBOZ +6Lqc920e/lgoFPQ7RnxI5PLNdTFLTqM8c9lMt6rqcff7cK9aonfWPd3s1pyWTFhx +tSBH95qNcu+kgu+iVLeFZjqyTW5lLyqZLPjnx8cS4HZtAUeM73RjFcDVokqGxAgc +qzatPoPNGjLtG1JoMsXjaMuc0ed3BkjXpXH24z8o0g7ppgQ= +-----END CERTIFICATE----- diff --git a/t/cert/test2.key b/t/cert/test2.key new file mode 100644 index 000000000..029d02e42 --- /dev/null +++ b/t/cert/test2.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQDCOlzMo5qPtb5w +jXHqy1mjm2rMAPeiz0wrDRXC0+9l/f/6KYBfJoaAuLauP6kCojDNLOOHAjmbjeoG ++bVGoLVNAGhFaThKQepmBPFhjtT0+7syGq6H12Wb9mmnhMy65gS+7dsUU74HUjlH +uYyOnXHmrJH/7sGzG6DXX6t2x0Ts49gVWzFr1TINMjP0opupvpokbr+EEzIP4NE5 +isPQnMeIt2HhzTMN+wHBalnHIVq0v03M911l4XpzHtP+MetDMraHJdv8Hlh5qeyr +Dkn7N5zE05cnSu6AK3vMaxsDbRJ92Akv6bLMqTqSnR3j16Qwqs369cU+zl7wQJ7+ +J1Gh5DhOe9Vns+bLbBJC5XZz3qcFxUXXRResuBYPQNhtJwgT7wq+XNMkd7TzDQzf +Wx4G7zz+3ogiNK6k02e0Uafuizi4nyMpwuu9SwvfYan3QBL7ddjuMGUHG9oOepLm +iKt8ST/plduc+67oXu1oeGnaEthpQ9Aong0/JhDvvnsXPe4tZQwzFK1m/fxF034l +3fy+gmGfnRC4k2thUotfp+BReDAZ5Wwg6FO7eaowuQbHQk8Pt36yd0fWwAgX3kmh +xC91QOYt29Aya4gTuJT4ajueDVRzB3bCxX0t7Awz/Ohh36XiLHZp7Zo1eJtG7oOX +RJdiKCglxOk9WiXTIaDXunXDGRl+DQIDAQABAoICAF1s/V5iA+LEpUo2f2lVT1Tn +WUOv424pEJZUwPbqQuloeEy40crzGRepwhWnAYEGyzqmMPusElHEvaGFU7EZdJwJ +ah6ZMj5l3n4cOo+WyNDelXjQZMtqzLFsof22X1Q3eGjuuMbaUTnsTyk1E2s2SKeQ +stnM2tdwnrl6h1unFnxsTHBZYThhYKYAVAWEgiPwTuzfaLf7E8WXeoyXNwGNqF0W ++LpInhpHdty2b2Ddpmfy7VV+Vyq+fswJNCe9k1DLgAkaKOrpHd3H4tOVF/kDEyGK +NdbssDRutinqCyBmwtJNrH2jiCHx9P65HLuP6qaOQm5I8gv38KrYKqJN/3JD+ODQ +dn8yVXjcCRzq8vylFLJfVEY0hB3ZWJAgDxY8GFax7foyaJ6889qgMOU4xBdotF7c +hKNsH+ClHSuJ5h4SNAV7/mRcJrYDFJ9imuY4PGFMZMQYSZig5dLawTe52+wsSuZ4 +kYxDXk76ZLr/MsbijrNOdze3Mny6EPxJGlv04XNNfIrsN9PXQGLpUwaJSJMGnFTI +TgAlcLATVMLusU2xf/Ra8eCk8Xydk0ExdB4TnMD0TTM/6bPJYIwwqrjee1ao4V0E +eISaoC8QHDi1XIFav2amzeyfHQI8uV4OQwV1rVLgeBY8TtQ0Mk6oU3AZ9z8Hvb1V +szdpOicXdI2lC9qpFiIBAoIBAQDxYMCvRDTkYcebG2sSi7ViUd3NWuBYFjlNfHED +/Rz6mUDud9r6TC3nqCPuugGt9P7fz3KktCoSh/x6iYsUP7iSK2V6Gg/kD1wtjIdY +9OW8u2cHyyGPshgZBgFJ4jE1wvzHQApQYrbdLdSmlwnvXT+tEG57vEa1ep2DSNaJ +hXlvpPMJAghNtc0wvcTZ8Aas8HfuEthKrb22O2zo0J2QObBZv2gcA0djW0ZbEEGg +BVNzmfaZFrLqSIP+7SD9KTgyRz1pAmeIgv//szYweHNVGeVbPgG2W4HiaV7c4+aF +3dmaikSZgz6Qily74HcVMrTzcgw2v2uM3IkTvWEX1PrixPdhAoIBAQDN/mqXtqtO +mbQBbn6+QgnVNRc/lnLv2QnzfuK93omZGFrl+COaCdM+T93PQkTiEydCjOYgwv3x +6KVMyrZLVKuYIHcdwP7z+Xi3wDQkCO0D8JmJGFBU+tAUJHEgF7DxEDa+PGxEPXWe +Ta4ZXpqQH96bssfzyFB2+PQcSIcRyWdnhJyVgXdRfMxMbYxH6g5X1bIGOAzCF1hp +b9QN3Rc/2UKCfw3IoFl1Yzo8yXi9hLhkIb2tjpBmt9V6itgVHL12HLLgvzs4egkI +aBHtMIxWcrzq+XtG3ARgugkRlhLhDR7nZOV2nFYXFNJ2tHfg2q4PBkrFm5EPLpXj +shzOZzlok0ItAoIBAGVo7ssz7rzcz828sTlzdNs/5d33BjY78As78wdn+hrW9E68 +EEYEZ2ziWWZcw4PgYIyLTXEhOGPcuhiJXOM++j5++P1Zob+BLIr+dYbMLREj8t91 +Wj5S5ojs2vG9lEswBp93ql2ne4hlTuhGoKfuF2iQdLqLmXF5eF/F5EyWxTRAqtLo +BbEQZorJz8B4dXWG+fwN8s09PW0oflM6AlirxAmQvx61yfJWULLFm6ytUChLUS10 +OerwWHRsD+YMwU1uXMCXgAP8da1qD6RBlsL0REYXQQTqYSFMf8xx+1HWqSuiV7vf +RhPX7aoJXj6LtTZqffMqbPcbWIPwlrA6jMVlV0ECggEAHqfDAyrrGXpkQGZKNFQt +lG4fNJZWKn15LqRuZ+UrQv5N4LVpzu2xYy+Nid+J0r8Y8512Td/W3N5LYz8zm8hQ +9QW31FS5XGN+5JGU8NvnMdPndXCJ2+urdaPqteTwrx9DllH1pr4it9lFlH7wr4we +m7siaJQh7WKlKWRdvXbkjZI9nz4yHI1e9ezDmJwrYETsBmLm+ydwP9iljR6e7CCP +9k2kJnw+c+q9avhsoH+U1Un0KVTzBmZLPb5V4+ZwB5jDwhCTZpc6quaZ7FtyNxdC +KBHSl7v8ZsyottZHnvhN5g+s2lbvtOWjYIkA4hSJHLFKBVheYQ0Ev2rtQIY6E+b+ +TQKCAQA+jl6f5wFSGBFjnrQu5V6E3tcI6fhkk2b4KGRBLyp27gIdqnw6FCZLsJ+g +qUmJd8lz3E4UR/VXy2lSWcKbmDmO1l+v2fZZu227hbZp8YTsPqt1JhnrroBP3Pdj +d+Zj+ZJpw5MbLb+8Wsx41fsAy8k1kZX82Vigbj2HEE/O7keflwBfa455ue9IGIFY +1SRNNJPB4RZ/9T/NM+MsTJZapsp6Si/nkncluSKeBnE1LfHoJi2eVW77qPIOGOPy +L3iaDoc8MmhMx2EFzSDlzwdB9Jg8cXxF4XxjadYc6JOtzUEkCqR0XS8KOgO2eMU0 +uWdAG+HmOwioRij8CIrb/9veL2jl +-----END PRIVATE KEY----- diff --git a/t/cert/test_passphrase.crt b/t/cert/test_passphrase.crt new file mode 100644 index 000000000..f4d516ba9 --- /dev/null +++ b/t/cert/test_passphrase.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICozCCAgwCCQDEutRdSs3vZjANBgkqhkiG9w0BAQUFADCBlDELMAkGA1UEBhMC +Q04xEjAQBgNVBAgMCUd1YW5nZG9uZzERMA8GA1UEBwwIU2hlblpoZW4xEjAQBgNV +BAoMCU9wZW5SZXN0eTESMBAGA1UECwwJT3BlblJlc3R5MREwDwYDVQQDDAh0ZXN0 +LmNvbTEjMCEGCSqGSIb3DQEJARYUZ3VhbmdsaW5sdkBnbWFpbC5jb20wIBcNMTYw +NDI4MTQ0MzI4WhgPMjE1MTAzMjcxNDQzMjhaMIGUMQswCQYDVQQGEwJDTjESMBAG +A1UECAwJR3Vhbmdkb25nMREwDwYDVQQHDAhTaGVuWmhlbjESMBAGA1UECgwJT3Bl +blJlc3R5MRIwEAYDVQQLDAlPcGVuUmVzdHkxETAPBgNVBAMMCHRlc3QuY29tMSMw +IQYJKoZIhvcNAQkBFhRndWFuZ2xpbmx2QGdtYWlsLmNvbTCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEA2KZ+HdH9R2tarxD8PKqu5EYq2BNGlFRg1xJmrw0XZBRM +UP/VPb+sIeioooz36uhiXfQjExlpBCA/0zNAN+HbFyqpPPTf1qLGrj/dqeE4MJaN +Bwzxiv3fZnENT65u2qbiFWIY+ATNHgA20d50nxNNjPTzLbkx/nYXL92r4kuAGk0C +AwEAATANBgkqhkiG9w0BAQUFAAOBgQCfMo0qbcs3kwl1tcNBO5hCcUUJRzyv041V +ff/nZ/JPIMo/LSZd12K82G/dLRN7uRT9nzqtm+JRkHALHWWWFKi6bdg1vcdOTWqC +08bCkJHQoXJQQLvvA6gNvnR+0b7L4CrCmrcyYgKDLXVGNP9Wv/PqSWWbxsmqngkA +Mvy6CVytFw== +-----END CERTIFICATE----- diff --git a/t/cert/test_passphrase.key b/t/cert/test_passphrase.key new file mode 100644 index 000000000..b8fc97634 --- /dev/null +++ b/t/cert/test_passphrase.key @@ -0,0 +1,18 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,679ACC8E69ACAA92 + +Ssrjp3VU4somCNPiXkWqcudDnvnwbyj/Q0pS07at3lXKbhQSgI1Tzhg9Pm3BXXj5 +mkLdeGG5ocrj1Q9dhtmZgZeHHQIiynZBhjBu1Y+HPef8jXOWLrCOi8EKiWkJ2qG3 +V1KFM/95CcDt0mRLykUXEL3IpUst05SFb9XwiLokB7ypeu3NhgNUHjL6G+ubB4ri +TOUjCW4pEoNHjdC22IiqSncwCVhluYSGhr6ktHKehZMhYIXmL1wmSLdhTlsPXCQl +xvYILQ2vJcKIR1BkeYYPD/OQC6zCZlXIErzfgeZiz2+NTudKYpb9VmsQKsO+R8L7 +tZ/fNaR0vk8bbimMHgStAV4acVsC/7WxsqOjMJ8VTq1iqhYPl6N7kRdR3H3kSSOm +cN9T3SrOHDVaHbnWgToaOE4mKFjvFSLIOcWgus0iOHWXmY+SLG+Ndag3oVB6R9oB +cAHX19mq99+GhzA8IV4I0En2UCKQhnGPvkM+9mcCDxhRETlwncDjlMGOHpQ65J9r +eReVPIpnDkvHxPGTtsR3ZHTdWTZb+C0W2N3QIlJKrOzxFmfoj++yG3tMX42aDY0g +DVkrXgcKobiWN0AVrJNAwfG7uObKSCFYgz/0RRMCO4cjXRW99nxdjVDZhyc6R0Te +jzuF04okkOLNb25n2hP+yIULrn+6Nv/uHtFI0j0n3hOzcKh//dNbACSAKgkHni9g +JKDFJXgLJxf+Wc3So0DF9gYMKJJ+WbcdVT9gkC7RyQHlC90Pn7kNXzHr0ZawUsNI +ZxSkL4dMhYAfA4lUBJbOkwbSurv97LinOSRffpM0Nmf7VNw/Ue15eg== +-----END RSA PRIVATE KEY----- diff --git a/t/count.t b/t/count.t new file mode 100644 index 000000000..9d7cc6c95 --- /dev/null +++ b/t/count.t @@ -0,0 +1,45 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: module size +--- config + location = /re { + access_log off; + content_by_lua_block { + local base = require "resty.core.base" + local n = 0 + for _, _ in pairs(base) do + n = n + 1 + end + ngx.say("base size: ", n) + } + } +--- request +GET /re + +--- stap2 +global c +probe process("$LIBLUA_PATH").function("rehashtab") { + c++ + printf("rehash: %d\n", c) +} + +--- response_body +base size: 20 +--- no_error_log +[error] diff --git a/t/ctx.t b/t/ctx.t new file mode 100644 index 000000000..9a00a8265 --- /dev/null +++ b/t/ctx.t @@ -0,0 +1,1044 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; +use Cwd qw(cwd); + +#worker_connections(1014); +#master_process_enabled(1); +log_level('debug'); + +#repeat_each(120); +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 13); + +#no_diff(); +#no_long_string(); + +my $pwd = cwd(); + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + + $http_config .= <<_EOC_; + + lua_package_path "$pwd/lib/?.lua;\$prefix/html/?.lua;../lua-resty-lrucache/lib/?.lua;;"; + init_by_lua_block { + local verbose = false + if verbose then + local dump = require "jit.dump" + dump.on(nil, "$Test::Nginx::Util::ErrLogFile") + else + local v = require "jit.v" + v.on("$Test::Nginx::Util::ErrLogFile") + end + + require "resty.core" + -- jit.off() + } +_EOC_ + + $block->set_value("http_config", $http_config); +}); + +check_accum_error_log(); + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +run_tests(); + +__DATA__ + +=== TEST 1: get ngx.ctx +--- config + location = /t { + content_by_lua_block { + for i = 1, 100 do + ngx.ctx.foo = i + end + ngx.say("ctx.foo = ", ngx.ctx.foo) + } + } +--- request +GET /t +--- response_body +ctx.foo = 100 +--- no_error_log +[error] + -- NYI: + bad argument +--- error_log eval +qr/\[TRACE\s+\d+\s+content_by_lua\(nginx\.conf:\d+\):2 loop\]/ + + + +=== TEST 2: set ngx.ctx +--- config + location = /t { + content_by_lua_block { + for i = 1, 100 do + ngx.ctx = {foo = i} + end + ngx.say("ctx.foo = ", ngx.ctx.foo) + } + } +--- request +GET /t +--- response_body +ctx.foo = 100 +--- no_error_log +[error] + -- NYI: + bad argument +--- error_log eval +qr/\[TRACE\s+\d+\s+content_by_lua\(nginx\.conf:\d+\):2 loop\]/ + + + +=== TEST 3: ngx.ctx in 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 { + ngx.ctx.answer = 42 + ngx.log(ngx.WARN, "ngx.ctx.answer = ", ngx.ctx.answer) + + ngx.ctx.count = 0 + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + content_by_lua_block { + ngx.say(ngx.ctx.answer) + ngx.ctx.count = ngx.ctx.count + 1 + ngx.say(ngx.ctx.count) + } + } + } +--- config + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + 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 + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + for i = 1, 2 do + local req = "GET /foo HTTP/1.1\r\nHost: test.com\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + local body_seen = false + while true do + local line, err = sock:receive() + if not line then + break + end + + if body_seen then + if line == "0" then + assert(sock:receive()) + break + end + local line, err = sock:receive(line) + ngx.print("received: ", line) + assert(sock:receive()) + + elseif line == "" then + body_seen = true + end + end + end + + sock:close() + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +received: 42 +received: 1 +received: 42 +received: 1 +--- error_log +ngx.ctx.answer = 42 +--- grep_error_log eval +qr/lua release ngx.ctx at ref \d+/ +--- grep_error_log_out eval +["lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +", +"lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +"] +--- no_error_log +[error] + + + +=== TEST 4: ngx.ctx in ssl_certificate_by_lua (share objects) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + ngx.ctx.req = { count = 0 } + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + content_by_lua_block { + ngx.ctx.req.count = ngx.ctx.req.count + 1 + ngx.say(ngx.ctx.req.count) + } + } + } +--- config + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + 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 + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + for i = 1, 2 do + local req = "GET /foo HTTP/1.1\r\nHost: test.com\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + local body_seen = false + while true do + local line, err = sock:receive() + if not line then + break + end + + if body_seen then + if line == "0" then + assert(sock:receive()) + break + end + local line, err = sock:receive(line) + ngx.print("received: ", line) + assert(sock:receive()) + + elseif line == "" then + body_seen = true + end + end + end + + sock:close() + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +received: 1 +received: 2 +--- no_error_log +[error] + + + +=== TEST 5: ngx.ctx in ssl_certificate_by_lua (release ctx when client aborted) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + ssl.clear_certs() + ngx.ctx.answer = 42 + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + return 200 "ok"; + } + } +--- config + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + 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 + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + end + + sock:close() + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +failed to do SSL handshake: handshake failed +--- grep_error_log eval +qr/lua release ngx.ctx at ref \d+/ +--- grep_error_log_out eval +["lua release ngx.ctx at ref 1 +", +"lua release ngx.ctx at ref 1 +lua release ngx.ctx at ref 1 +"] + + + +=== TEST 6: ngx.ctx in ssl_session_store_by_lua +--- http_config + ssl_session_store_by_lua_block { + ngx.ctx.answer = 42 + ngx.log(ngx.WARN, "ngx.ctx.answer = ", ngx.ctx.answer) + + ngx.ctx.count = 0 + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_session_tickets off; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + content_by_lua_block { + ngx.say(ngx.ctx.answer) + ngx.ctx.count = ngx.ctx.count + 1 + ngx.say(ngx.ctx.count) + } + } + } +--- config + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + 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 + + 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 + + for i = 1, 2 do + local req = "GET /foo HTTP/1.1\r\nHost: test.com\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + local body_seen = false + while true do + local line, err = sock:receive() + if not line then + break + end + + if body_seen then + if line == "0" then + assert(sock:receive()) + break + end + local line, err = sock:receive(line) + ngx.print("received: ", line) + assert(sock:receive()) + + elseif line == "" then + body_seen = true + end + end + end + + package.loaded.session = sess + sock:close() + end -- do + } + } +--- request +GET /t +--- response_body +received: 42 +received: 1 +received: 42 +received: 1 +--- error_log +ngx.ctx.answer = 42 +--- grep_error_log eval +qr/lua release ngx.ctx at ref \d+/ +--- grep_error_log_out eval +["lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +", +"lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +"] +--- no_error_log +[error] + + + +=== TEST 7: ngx.ctx in ssl_session_store_by_lua (release ctx when client aborted) +--- http_config + ssl_session_store_by_lua_block { + ngx.ctx.answer = 42 + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_session_tickets off; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + return 200 "ok"; + } + } +--- config + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + 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 + + 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("closed") + sock:close() + end -- do + } + } +--- request +GET /t +--- response_body +closed +--- grep_error_log eval +qr/lua release ngx.ctx at ref \d+/ +--- grep_error_log_out eval +["lua release ngx.ctx at ref 1 +", +"lua release ngx.ctx at ref 1 +lua release ngx.ctx at ref 1 +"] +--- no_error_log +[error] + + + +=== TEST 8: ngx.ctx in ssl_session_fetch_by_lua +--- http_config + ssl_session_fetch_by_lua_block { + ngx.ctx.answer = 42 + ngx.ctx.count = 0 + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_session_tickets off; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + content_by_lua_block { + if package.loaded.session then + ngx.say(ngx.ctx.answer) + ngx.ctx.count = ngx.ctx.count + 1 + ngx.say(ngx.ctx.count) + end + } + } + } +--- config + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + 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 + + 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 + + for i = 1, 2 do + local req = "GET /foo HTTP/1.1\r\nHost: test.com\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + local body_seen = false + while true do + local line, err = sock:receive() + if not line then + break + end + + if body_seen then + if line == "0" then + assert(sock:receive()) + break + end + local line, err = sock:receive(line) + ngx.log(ngx.WARN, "received: ", line) + assert(sock:receive()) + + elseif line == "" then + body_seen = true + end + end + end + + package.loaded.session = sess + sock:close() + end -- do + } + } +--- request +GET /t +--- grep_error_log eval +qr/(received: \w+|lua release ngx.ctx at ref \d+)/ +--- grep_error_log_out eval +["lua release ngx.ctx at ref 1 +", +"lua release ngx.ctx at ref 1 +lua release ngx.ctx at ref 2 +received: 42 +received: 1 +lua release ngx.ctx at ref 2 +received: 42 +received: 1 +lua release ngx.ctx at ref 1 +"] +--- no_error_log +[error] + + + +=== TEST 9: ngx.ctx in ssl_session_fetch_by_lua (release ctx when client aborted) +--- http_config + ssl_session_fetch_by_lua_block { + ngx.ctx.answer = 42 + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_session_tickets off; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + return 200 "ok"; + } + } +--- config + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + 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 + + 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 + ngx.say("closed") + sock:close() + end -- do + } + } +--- request +GET /t +--- response_body +closed +--- grep_error_log eval +qr/lua release ngx.ctx at ref \d+/ +--- grep_error_log_out eval +["lua release ngx.ctx at ref 1 +", +"lua release ngx.ctx at ref 1 +lua release ngx.ctx at ref 1 +"] +--- no_error_log +[error] + + + +=== TEST 10: ngx.ctx in ssl* and other phases +--- http_config + ssl_session_store_by_lua_block { + ngx.ctx.count = ngx.ctx.count and (ngx.ctx.count + 1) or 1 + } + + ssl_session_fetch_by_lua_block { + ngx.ctx.count = ngx.ctx.count and (ngx.ctx.count + 10) or 10 + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_session_tickets off; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + ssl_certificate_by_lua_block { + ngx.ctx.count = ngx.ctx.count and (ngx.ctx.count + 100) or 100 + } + server_tokens off; + location /foo { + content_by_lua_block { + ngx.ctx.count = ngx.ctx.count + 1 + ngx.say(ngx.ctx.count) + } + } + } +--- config + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + 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 + + 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 + + for i = 1, 2 do + local req = "GET /foo HTTP/1.1\r\nHost: test.com\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + local body_seen = false + while true do + local line, err = sock:receive() + if not line then + break + end + + if body_seen then + if line == "0" then + assert(sock:receive()) + break + end + local line, err = sock:receive(line) + ngx.log(ngx.WARN, "received: ", line) + assert(sock:receive()) + + elseif line == "" then + body_seen = true + end + end + end + + package.loaded.session = sess + sock:close() + end -- do + } + } +--- request +GET /t +--- grep_error_log eval +qr/(received: \w+|lua release ngx.ctx at ref \d+)/ +--- grep_error_log_out eval +["lua release ngx.ctx at ref 2 +received: 112 +lua release ngx.ctx at ref 2 +received: 112 +lua release ngx.ctx at ref 1 +", +"lua release ngx.ctx at ref 2 +received: 112 +lua release ngx.ctx at ref 2 +received: 112 +lua release ngx.ctx at ref 1 +lua release ngx.ctx at ref 2 +received: 112 +lua release ngx.ctx at ref 2 +received: 112 +lua release ngx.ctx at ref 1 +"] +--- no_error_log +[error] + + + +=== TEST 11: overwrite values will only take affect in the current http request +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + ngx.ctx.answer = 0 + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo1 { + content_by_lua_block { + ngx.say(ngx.ctx.answer) + ngx.ctx.answer = 42 + } + } + location /foo2 { + content_by_lua_block { + ngx.say(ngx.ctx.answer) + } + } + } +--- config + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + 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 + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + for i = 1, 2 do + local req = "GET /foo" .. i .. " HTTP/1.1\r\nHost: test.com\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + local body_seen = false + while true do + local line, err = sock:receive() + if not line then + break + end + + if body_seen then + if line == "0" then + assert(sock:receive()) + break + end + local line, err = sock:receive(line) + ngx.print("received: ", line) + assert(sock:receive()) + + elseif line == "" then + body_seen = true + end + end + end + + sock:close() + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +received: 0 +received: 0 + + + +=== TEST 12: prohibit setting ngx.ctx to non-table value +--- config + location = /t { + content_by_lua_block { + local ok, err = pcall(function() + ngx.ctx = nil + end) + if not ok then + ngx.say(err) + end + } + } +--- request +GET /t +--- response_body_like +ctx should be a table while getting a nil +--- no_error_log +[error] + + + +=== TEST 13: get_ctx_table +--- config + location = /t { + content_by_lua_block { + local get_ctx_table = require "resty.core.ctx" .get_ctx_table + + local reused_ctx = {} + local ctx = get_ctx_table(reused_ctx) + if ctx == reused_ctx then + ngx.say("reused") + end + + local ctx2 = get_ctx_table() + if ctx2 == reused_ctx then + ngx.say("reused again") + end + } + } +--- request +GET /t +--- response_body +reused +reused again +--- no_error_log +[error] + + + +=== TEST 14: ngx.ctx in ssl_client_hello_by_lua +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + ngx.ctx.answer = 42 + ngx.log(ngx.WARN, "ngx.ctx.answer = ", ngx.ctx.answer) + + ngx.ctx.count = 0 + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + content_by_lua_block { + ngx.say(ngx.ctx.answer) + ngx.ctx.count = ngx.ctx.count + 1 + ngx.say(ngx.ctx.count) + } + } + } +--- config + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + 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 + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + for i = 1, 2 do + local req = "GET /foo HTTP/1.1\r\nHost: test.com\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + local body_seen = false + while true do + local line, err = sock:receive() + if not line then + break + end + + if body_seen then + if line == "0" then + assert(sock:receive()) + break + end + local line, err = sock:receive(line) + ngx.print("received: ", line) + assert(sock:receive()) + + elseif line == "" then + body_seen = true + end + end + end + + sock:close() + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +received: 42 +received: 1 +received: 42 +received: 1 +--- error_log +ngx.ctx.answer = 42 +--- grep_error_log eval +qr/lua release ngx.ctx at ref \d+/ +--- grep_error_log_out eval +["lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +", +"lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +"] +--- no_error_log +[error] diff --git a/t/decode-base64.t b/t/decode-base64.t new file mode 100644 index 000000000..974246c60 --- /dev/null +++ b/t/decode-base64.t @@ -0,0 +1,225 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5 - 2); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: string +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.decode_base64("aGVsbG8=") + end + ngx.say(s) + } + } +--- request +GET /base64 +--- response_body +hello +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 2: set base64 (nil) +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.decode_base64("") + end + ngx.say(s) + } + } +--- request +GET /base64 +--- response_body eval: "\n" +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 3: set base64 (number) +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.decode_base64("My4xNA==") + end + ngx.say(s) + } + } +--- request +GET /base64 +--- response_body +3.14 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 4: set base64 (boolean) +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.decode_base64("dHJ1ZQ==") + end + ngx.say(s) + } + } +--- request +GET /base64 +--- response_body +true +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 5: string (buf size just smaller than 4096) +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.decode_base64(string.rep("a", 5460)) + end + if not s then + ngx.say("bad base64 string") + else + ngx.say(string.len(s)) + end + } + } +--- request +GET /base64 +--- response_body +4095 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 6: string (buf size just a bit bigger than 4096) +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.decode_base64(string.rep("a", 5462)) + end + if not s then + ngx.say("bad base64 string") + else + ngx.say(string.len(s)) + end + } + } +--- request +GET /base64 +--- response_body +4096 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 7: decode_base64url +--- config + location = /t { + content_by_lua_block { + local enc = require("ngx.base64") + + local function to_hex(str) + return (str:gsub('.', function(c) + return string.format('%02x', string.byte(c)) + end)) + end + + -- RFC 4648 test vectors + ngx.say("decode_base64url(\"\") = \"", enc.decode_base64url(""), "\"") + ngx.say("decode_base64url(\"Zg\") = \"", enc.decode_base64url("Zg"), "\"") + ngx.say("decode_base64url(\"Zm8\") = \"", enc.decode_base64url("Zm8"), "\"") + ngx.say("decode_base64url(\"Zm9v\") = \"", enc.decode_base64url("Zm9v"), "\"") + ngx.say("decode_base64url(\"Zm9vYg\") = \"", enc.decode_base64url("Zm9vYg"), "\"") + ngx.say("decode_base64url(\"Zm9vYmE\") = \"", enc.decode_base64url("Zm9vYmE"), "\"") + ngx.say("decode_base64url(\"Zm9vYmFy\") = \"", enc.decode_base64url("Zm9vYmFy"), "\"") + ngx.say("decode_base64url(\"_w\") = \"\\x", to_hex(enc.decode_base64url("_w")), "\"") + + ngx.say("decode_base64url(\"YQBi\") = \"\\x", to_hex(enc.decode_base64url("YQBi")), "\"") + } + } +--- request +GET /t +--- response_body +decode_base64url("") = "" +decode_base64url("Zg") = "f" +decode_base64url("Zm8") = "fo" +decode_base64url("Zm9v") = "foo" +decode_base64url("Zm9vYg") = "foob" +decode_base64url("Zm9vYmE") = "fooba" +decode_base64url("Zm9vYmFy") = "foobar" +decode_base64url("_w") = "\xff" +decode_base64url("YQBi") = "\x610062" +--- no_error_log +[error] +[crit] + + + +=== TEST 8: decode_base64url with invalid input +--- config + location = /t { + content_by_lua_block { + local enc = require("ngx.base64") + + local res, err = enc.decode_base64url(" ") + + ngx.say("decode_base64url returned: ", res, ", ", err) + } + } +--- request +GET /t +--- response_body +decode_base64url returned: nil, invalid input +--- no_error_log +[error] + -- NYI: diff --git a/t/encode-base64.t b/t/encode-base64.t new file mode 100644 index 000000000..3dc16c287 --- /dev/null +++ b/t/encode-base64.t @@ -0,0 +1,236 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5 - 1); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: set base64 (string) +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.encode_base64("hello") + end + ngx.say(s) + } + } +--- request +GET /base64 +--- response_body +aGVsbG8= +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 2: set base64 (nil) +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.encode_base64(nil) + end + ngx.say(s) + } + } +--- request +GET /base64 +--- response_body eval: "\n" +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 3: set base64 (number) +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.encode_base64(3.14) + end + ngx.say(s) + } + } +--- request +GET /base64 +--- response_body +My4xNA== +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 4: set base64 (boolean) +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.encode_base64(true) + end + ngx.say(s) + } + } +--- request +GET /base64 +--- response_body +dHJ1ZQ== +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 5: set base64 (buf is a little larger than 4096) +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.encode_base64(string.rep("a", 3073)) + end + ngx.say(string.len(s)) + } + } +--- request +GET /base64 +--- response_body +4100 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 6: set base64 (buf is just 4096) +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.encode_base64(string.rep("a", 3071)) + end + ngx.say(string.len(s)) + } + } +--- request +GET /base64 +--- response_body +4096 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 7: set base64 (number) without padding (explicitly specified) +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 200 do + s = ngx.encode_base64(3.14, true) + end + ngx.say(s) + } + } +--- request +GET /base64 +--- response_body +My4xNA +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 8: set base64 (number) with padding (explicitly specified) +--- config + location = /base64 { + content_by_lua_block { + local s + for i = 1, 200 do + s = ngx.encode_base64(3.14, false) + end + ngx.say(s) + } + } +--- request +GET /base64 +--- response_body +My4xNA== +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 9: encode_base64url +--- config + location = /t { + content_by_lua_block { + local enc = require("ngx.base64") + + -- RFC 4648 test vectors + ngx.say("encode_base64url(\"\") = \"", enc.encode_base64url(""), "\"") + ngx.say("encode_base64url(\"f\") = \"", enc.encode_base64url("f"), "\"") + ngx.say("encode_base64url(\"fo\") = \"", enc.encode_base64url("fo"), "\"") + ngx.say("encode_base64url(\"foo\") = \"", enc.encode_base64url("foo"), "\"") + ngx.say("encode_base64url(\"foob\") = \"", enc.encode_base64url("foob"), "\"") + ngx.say("encode_base64url(\"fooba\") = \"", enc.encode_base64url("fooba"), "\"") + ngx.say("encode_base64url(\"foobar\") = \"", enc.encode_base64url("foobar"), "\"") + ngx.say("encode_base64url(\"\\xff\") = \"", enc.encode_base64url("\xff"), "\"") + + ngx.say("encode_base64url(\"a\\0b\") = \"", enc.encode_base64url("a\0b"), "\"") + } + } +--- request +GET /t +--- response_body +encode_base64url("") = "" +encode_base64url("f") = "Zg" +encode_base64url("fo") = "Zm8" +encode_base64url("foo") = "Zm9v" +encode_base64url("foob") = "Zm9vYg" +encode_base64url("fooba") = "Zm9vYmE" +encode_base64url("foobar") = "Zm9vYmFy" +encode_base64url("\xff") = "_w" +encode_base64url("a\0b") = "YQBi" +--- no_error_log +[error] + -- NYI: diff --git a/t/errlog-raw-log.t b/t/errlog-raw-log.t new file mode 100644 index 000000000..84eebe91b --- /dev/null +++ b/t/errlog-raw-log.t @@ -0,0 +1,218 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +log_level('error'); + +repeat_each(1); + +plan tests => repeat_each() * (blocks() * 2 + 5); + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + my $init_by_lua_block = $block->init_by_lua_block || ''; + + $http_config .= <<_EOC_; + lua_package_path '$t::TestCore::lua_package_path'; + init_by_lua_block { + $t::TestCore::init_by_lua_block + $init_by_lua_block + } +_EOC_ + + $block->set_value("http_config", $http_config); +}); + +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: errlog.raw_log with bad log level (ngx.ERROR, -1) +--- config + location /log { + content_by_lua_block { + local errlog = require "ngx.errlog" + + errlog.raw_log(ngx.ERROR, "hello, log") + ngx.say("done") + } + } +--- request +GET /log +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +bad log level + + + +=== TEST 2: errlog.raw_log with bad levels (9) +--- config + location /log { + content_by_lua_block { + local errlog = require "ngx.errlog" + + errlog.raw_log(9, "hello, log") + ngx.say("done") + } + } +--- request +GET /log +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +bad log level + + + +=== TEST 3: errlog.raw_log with bad log message +--- config + location /log { + content_by_lua_block { + local errlog = require "ngx.errlog" + + errlog.raw_log(ngx.ERR, 123) + ngx.say("done") + } + } +--- request +GET /log +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +bad argument #2 to 'raw_log' (must be a string) + + + +=== TEST 4: errlog.raw_log test log-level ERR +--- config + location /log { + content_by_lua_block { + local errlog = require "ngx.errlog" + + errlog.raw_log(ngx.ERR, "hello world") + } + } +--- request +GET /log +--- error_log eval +qr/\[error\] \S+: \S+ hello world/ + + + +=== TEST 5: errlog.raw_log JITs +--- init_by_lua_block + -- local verbose = true + local verbose = false + local outfile = errlog_file + -- local outfile = "/tmp/v.log" + if verbose then + local dump = require "jit.dump" + dump.on(nil, outfile) + else + local v = require "jit.v" + v.on(outfile) + end + + require "resty.core" + -- jit.opt.start("hotloop=1") + -- jit.opt.start("loopunroll=1000000") + -- jit.off() +--- config + location /log { + content_by_lua_block { + local errlog = require "ngx.errlog" + + for i = 1, 100 do + errlog.raw_log(ngx.ERR, "hello world") + end + } + } +--- request +GET /log +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):4 loop\]/ + + + +=== TEST 6: errlog.raw_log in init_by_lua +--- init_by_lua_block + local errlog = require "ngx.errlog" + errlog.raw_log(ngx.ERR, "hello world from init_by_lua") +--- config + location /t { + return 200; + } +--- request +GET /t +--- grep_error_log chop +hello world from init_by_lua +--- grep_error_log_out eval +["hello world from init_by_lua\n", ""] + + + +=== TEST 7: errlog.raw_log in init_worker_by_lua +--- http_config + init_worker_by_lua_block { + local errlog = require "ngx.errlog" + errlog.raw_log(ngx.ERR, "hello world from init_worker_by_lua") + } +--- config + location /t { + return 200; + } +--- request +GET /t +--- grep_error_log chop +hello world from init_worker_by_lua +--- grep_error_log_out eval +["hello world from init_worker_by_lua\n", ""] + + + +=== TEST 8: errlog.raw_log with \0 in the log message +--- config + location /log { + content_by_lua_block { + local errlog = require "ngx.errlog" + errlog.raw_log(ngx.ERR, "hello\0world") + ngx.say("ok") + } + } +--- request +GET /log +--- response_body +ok +--- error_log eval +"hello\0world, client: " + + + +=== TEST 9: errlog.raw_log is captured by errlog.get_logs() +--- http_config + lua_capture_error_log 4k; +--- config + location /log { + content_by_lua_block { + local errlog = require "ngx.errlog" + errlog.raw_log(ngx.ERR, "hello from raw_log()") + + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + + ngx.say("log lines: ", #res / 3) + } + } +--- request +GET /log +--- response_body +log lines: 1 +--- error_log eval +qr/\[error\] .*? hello from raw_log\(\)/ +--- skip_nginx: 3: <1.11.2 diff --git a/t/errlog.t b/t/errlog.t new file mode 100644 index 000000000..80107e9ea --- /dev/null +++ b/t/errlog.t @@ -0,0 +1,1253 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +log_level('error'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 2 + 15); + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + my $init_by_lua_block = $block->init_by_lua_block || ''; + + $http_config .= <<_EOC_; + lua_package_path '$t::TestCore::lua_package_path'; + init_by_lua_block { + $t::TestCore::init_by_lua_block + $init_by_lua_block + } +_EOC_ + + $block->set_value("http_config", $http_config); +}); + +#no_diff(); +no_long_string(); +#check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- http_config + lua_capture_error_log 4m; +--- config + location /t { + access_by_lua_block { + ngx.log(ngx.ERR, "enter 1") + ngx.log(ngx.ERR, "enter 11") + + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.say("log lines:", #res / 3) + } + } +--- request +GET /t +--- response_body +log lines:2 +--- grep_error_log eval +qr/enter \d+/ +--- grep_error_log_out eval +[ +"enter 1 +enter 11 +", +"enter 1 +enter 11 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 2: overflow captured error logs +--- http_config + lua_capture_error_log 4k; +--- config + location /t { + access_by_lua_block { + ngx.log(ngx.ERR, "enter 1") + ngx.log(ngx.ERR, "enter 22" .. string.rep("a", 4096)) + + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.say("log lines:", #res / 3) + } + } +--- request +GET /t +--- response_body +log lines:1 +--- grep_error_log eval +qr/enter \d+/ +--- grep_error_log_out eval +[ +"enter 1 +enter 22 +", +"enter 1 +enter 22 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 3: 404 error (not found) +--- http_config + lua_capture_error_log 4m; +--- config + log_by_lua_block { + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.log(ngx.ERR, "capture log line:", #res / 3) + } +--- request +GET /t +--- error_code: 404 +--- grep_error_log eval +qr/capture log line:\d+|No such file or directory/ +--- grep_error_log_out eval +[ +qr/^No such file or directory +capture log line:1 +$/, +qr/^No such file or directory +capture log line:2 +$/ +] +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 4: 500 error +--- http_config + lua_capture_error_log 4m; +--- config + location /t { + content_by_lua_block { + local t = {}/4 + } + } + log_by_lua_block { + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.log(ngx.ERR, "capture log line:", #res / 3) + } +--- request +GET /t +--- error_code: 500 +--- grep_error_log eval +qr/capture log line:\d+|attempt to perform arithmetic on a table value/ +--- grep_error_log_out eval +[ +qr/^attempt to perform arithmetic on a table value +capture log line:1 +$/, +qr/^attempt to perform arithmetic on a table value +capture log line:2 +$/ +] +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 5: no error log +--- http_config + lua_capture_error_log 4m; +--- config + location /t { + echo "hello"; + } + log_by_lua_block { + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.log(ngx.ERR, "capture log line:", #res / 3) + } +--- request +GET /t +--- response_body +hello +--- grep_error_log eval +qr/capture log line:\d+/ +--- grep_error_log_out eval +[ +qr/^capture log line:0 +$/, +qr/^capture log line:1 +$/ +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 6: customize the log path +--- http_config + lua_capture_error_log 4m; + error_log logs/error_http.log error; +--- config + location /t { + error_log logs/error.log error; + access_by_lua_block { + ngx.log(ngx.ERR, "enter access /t") + } + echo "hello"; + } + log_by_lua_block { + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.log(ngx.ERR, "capture log line:", #res / 3) + + } +--- request +GET /t +--- response_body +hello +--- grep_error_log eval +qr/capture log line:\d+|enter access/ +--- grep_error_log_out eval +[ +qr/^enter access +capture log line:1 +$/, +qr/^enter access +capture log line:2 +$/ +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 7: invalid size (< 4k) +--- http_config + lua_capture_error_log 3k; +--- config + location /t { + echo "hello"; + } +--- must_die +--- error_log +invalid capture error log size "3k", minimum size is 4096 +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 8: invalid size (no argu) +--- http_config + lua_capture_error_log; +--- config + location /t { + echo "hello"; + } +--- must_die +--- error_log +invalid number of arguments in "lua_capture_error_log" directive +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 9: without directive + ngx.errlog +--- config + location /t { + access_by_lua_block { + ngx.log(ngx.ERR, "enter 1") + + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.say("log lines:", #res / 3) + } + } +--- request +GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +directive "lua_capture_error_log" is not set +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 10: without directive + ngx.set_filter_level +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.ERR) + if not status then + error(err) + end + } + } +--- request +GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +directive "lua_capture_error_log" is not set +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 11: filter log by level(ngx.INFO) +--- http_config + lua_capture_error_log 4m; +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.INFO) + if not status then + error(err) + end + + ngx.log(ngx.INFO, "-->1") + ngx.log(ngx.WARN, "-->2") + ngx.log(ngx.ERR, "-->3") + } + content_by_lua_block { + local errlog = require "ngx.errlog" + local res = errlog.get_logs() + ngx.say("log lines:", #res / 3) + } + } +--- log_level: info +--- request +GET /t +--- response_body +log lines:3 +--- grep_error_log eval +qr/-->\d+/ +--- grep_error_log_out eval +[ +"-->1 +-->2 +-->3 +", +"-->1 +-->2 +-->3 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 12: filter log by level(ngx.WARN) +--- http_config + lua_capture_error_log 4m; +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + ngx.log(ngx.INFO, "-->1") + ngx.log(ngx.WARN, "-->2") + ngx.log(ngx.ERR, "-->3") + } + content_by_lua_block { + local errlog = require "ngx.errlog" + local res = errlog.get_logs() + ngx.say("log lines:", #res / 3) + } + } +--- log_level: info +--- request +GET /t +--- response_body +log lines:2 +--- grep_error_log eval +qr/-->\d+/ +--- grep_error_log_out eval +[ +"-->1 +-->2 +-->3 +", +"-->1 +-->2 +-->3 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 13: filter log by level(ngx.CRIT) +--- http_config + lua_capture_error_log 4m; +--- log_level: info +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.CRIT) + if not status then + error(err) + end + + ngx.log(ngx.INFO, "-->1") + ngx.log(ngx.WARN, "-->2") + ngx.log(ngx.ERR, "-->3") + } + content_by_lua_block { + local errlog = require "ngx.errlog" + local res = errlog.get_logs() + ngx.say("log lines:", #res / 3) + } + } +--- request +GET /t +--- response_body +log lines:0 +--- grep_error_log eval +qr/-->\d+/ +--- grep_error_log_out eval +[ +"-->1 +-->2 +-->3 +", +"-->1 +-->2 +-->3 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 14: set max count and reuse table +--- http_config + lua_capture_error_log 4m; +--- config + location /t { + access_by_lua_block { + tab_clear = require "table.clear" + ngx.log(ngx.ERR, "enter 1") + ngx.log(ngx.ERR, "enter 22") + ngx.log(ngx.ERR, "enter 333") + + local errlog = require "ngx.errlog" + local res = {} + local err + res, err = errlog.get_logs(2, res) + if not res then + error("FAILED " .. err) + end + ngx.say("log lines:", #res / 3) + + tab_clear(res) + res, err = errlog.get_logs(2, res) + if not res then + error("FAILED " .. err) + end + ngx.say("log lines:", #res / 3) + } + } +--- request +GET /t +--- response_body +log lines:2 +log lines:1 +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 15: wrong argument +--- http_config + lua_capture_error_log 4m; +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level() + if not status then + error(err) + end + } + } +--- request +GET /t +--- error_code: 500 +--- response_body_like: 500 +--- grep_error_log eval +qr/missing \"level\" argument/ +--- grep_error_log_out eval +[ +"missing \"level\" argument +", +"missing \"level\" argument +", +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 16: check the captured error log body +--- http_config + lua_capture_error_log 4m; +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + ngx.log(ngx.INFO, "-->1") + ngx.log(ngx.WARN, "-->2") + ngx.log(ngx.ERR, "-->3") + } + + content_by_lua_block { + local errlog = require "ngx.errlog" + local res = errlog.get_logs() + for i = 1, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + } + } +--- log_level: info +--- request +GET /t +--- response_body_like +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: -->2, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: -->3, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +--- grep_error_log eval +qr/-->\d+/ +--- grep_error_log_out eval +[ +"-->1 +-->2 +-->3 +", +"-->1 +-->2 +-->3 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 17: flood the capturing buffer (4k) +--- http_config + lua_capture_error_log 4k; +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + for i = 1, 100 do + ngx.log(ngx.INFO, "--> ", i) + ngx.log(ngx.WARN, "--> ", i) + ngx.log(ngx.ERR, "--> ", i) + end + } + + content_by_lua_block { + local errlog = require "ngx.errlog" + local res = errlog.get_logs(1000) + ngx.say("log lines: #", #res / 3) + + -- first 3 logs + for i = 1, 3 * 3, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + + -- last 3 logs + for i = #res - 8, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + } + } +--- log_level: info +--- request +GET /t +--- response_body_like chomp +\A(?:log lines: #21 +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 90, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 91, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 99, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 100, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 100, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +|log lines: #20 +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 91, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 91, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 92, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 99, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 100, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 100, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +)\z +--- skip_nginx: 2: <1.11.2 +--- wait: 0.1 + + + +=== TEST 18: flood the capturing buffer (5k) +--- http_config + lua_capture_error_log 5k; +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + for i = 1, 100 do + ngx.log(ngx.INFO, "--> ", i) + ngx.log(ngx.WARN, "--> ", i) + ngx.log(ngx.ERR, "--> ", i) + end + } + + content_by_lua_block { + local errlog = require "ngx.errlog" + local res = errlog.get_logs(1000) + ngx.say("log lines: #", #res / 3) + + -- first 3 logs + for i = 1, 3 * 3, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + + -- last 3 logs + for i = #res - 8, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + } + } +--- log_level: info +--- request +GET /t +--- response_body_like chomp +\Alog lines: #26 +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 88, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 88, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 89, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 99, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 100, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> 100, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +\z +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 19: fetch a few and generate a few, then fetch again (overflown again) +--- http_config + lua_capture_error_log 5k; +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + for i = 1, 100 do + ngx.log(ngx.INFO, "--> ", i) + ngx.log(ngx.WARN, "--> ", i) + ngx.log(ngx.ERR, "--> ", i) + end + } + + content_by_lua_block { + local errlog = require "ngx.errlog" + + local res = errlog.get_logs(3) + ngx.say("msg count: ", #res / 3) + + -- first 3 logs + for i = 1, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + + ngx.log(ngx.ERR, "--> 101") + ngx.log(ngx.ERR, "--> 102") + ngx.log(ngx.ERR, "--> 103") + ngx.log(ngx.ERR, "--> 104") + + local res = errlog.get_logs(3) + ngx.say("msg count: ", #res / 3) + + -- first 3 logs + for i = 1, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + + local res = errlog.get_logs(1000) + -- last 3 logs + for i = #res - 8, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + } + } +--- log_level: info +--- request +GET /t +--- response_body_like chomp +\Amsg count: 3 +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: --> 88, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: --> 88, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: --> 89, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +msg count: 3 +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: --> 90, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: --> 90, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: --> 91, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 102, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 103, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 104, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +\z +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 20: fetch a few and generate a few, then fetch again (not overflown again) +--- http_config + lua_capture_error_log 5k; +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + for i = 1, 100 do + ngx.log(ngx.INFO, "--> ", i) + ngx.log(ngx.WARN, "--> ", i) + ngx.log(ngx.ERR, "--> ", i) + end + } + + content_by_lua_block { + local errlog = require "ngx.errlog" + + local res = errlog.get_logs(3) + ngx.say("msg count: ", #res / 3) + + -- first 3 logs + for i = 1, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + + ngx.log(ngx.ERR, "howdy, something new!") + ngx.log(ngx.ERR, "howdy, something even newer!") + + local res = errlog.get_logs(3) + ngx.say("msg count: ", #res / 3) + + -- first 3 logs + for i = 1, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + + local res = errlog.get_logs(1000) + -- last 3 logs + for i = #res - 8, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + } + } +--- log_level: info +--- request +GET /t +--- response_body_like chomp +\Amsg count: 3 +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: --> 88, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: --> 88, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: --> 89, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +msg count: 3 +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: --> 89, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: --> 90, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: --> 90, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: --> 100, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: howdy, something new!, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: howdy, something even newer!, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +\z +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 21: multi-line error log +--- http_config + lua_capture_error_log 4k; +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + ngx.log(ngx.ERR, "-->\n", "new line") + } + + content_by_lua_block { + local errlog = require "ngx.errlog" + local res = errlog.get_logs() + ngx.say("log lines: #", #res / 3) + + for i = 1, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + } + } +--- log_level: info +--- request +GET /t +--- response_body_like chomp +\Alog lines: #1 +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> +new line, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +\z +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 22: user-supplied Lua table to hold the result (get one log + no log) +--- http_config + lua_capture_error_log 4k; +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + ngx.log(ngx.ERR, "-->\n", "new line") + } + + content_by_lua_block { + local errlog = require "ngx.errlog" + local t = {} + + for i = 1, 2 do + local res = errlog.get_logs(10, t) + ngx.say("maybe log lines: #", #res / 3) + for j = 1, #res, 3 do + local level, msg = res[j], res[j + 2] + if not level then + break + end + ngx.say("log level:", level) + ngx.say("log body:", msg) + end + ngx.say("end") + end + } + } +--- log_level: info +--- request +GET /t +--- response_body_like chomp +\Amaybe log lines: #1 +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*access_by_lua\(nginx.conf:\d+\):\d+: --> +new line, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +end +maybe log lines: #1 +end +\z +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 23: the system default filter level is "debug" +--- config + location /t { + content_by_lua_block { + local errlog = require "ngx.errlog" + ngx.print('Is "debug" the system default filter level? ', + errlog.get_sys_filter_level() == ngx.DEBUG) + } + } +--- log_level: debug +--- request +GET /t +--- response_body chomp +Is "debug" the system default filter level? true + + + +=== TEST 24: the system default filter level is "emerg" +--- config + location /t { + content_by_lua_block { + local errlog = require "ngx.errlog" + ngx.print('Is "emerg" the system default filter level? ', + errlog.get_sys_filter_level() == ngx.EMERG) + } + } +--- log_level: emerg +--- request +GET /t +--- response_body chomp +Is "emerg" the system default filter level? true + + + +=== TEST 25: get system default filter level during Nginx starts (init) +--- init_by_lua_block + local errlog = require "ngx.errlog" + package.loaded.log_level = errlog.get_sys_filter_level() + +--- config + location /t { + content_by_lua_block { + local log_level = package.loaded.log_level + + if log_level >= ngx.WARN then + ngx.log(ngx.WARN, "log a warning event") + else + ngx.log(ngx.WARN, "do not log another warning event") + end + } + } +--- log_level: warn +--- request +GET /t +--- error_log +log a warning event +--- no_error_log +do not log another warning event + + + +=== TEST 26: get system default filter level during Nginx worker starts (init worker) +--- http_config + init_worker_by_lua_block { + local errlog = require "ngx.errlog" + package.loaded.log_level = errlog.get_sys_filter_level() + } +--- config + location /t { + content_by_lua_block { + local log_level = package.loaded.log_level + + if log_level >= ngx.WARN then + ngx.log(ngx.WARN, "log a warning event") + else + ngx.log(ngx.WARN, "do not log another warning event") + end + } + } +--- log_level: warn +--- request +GET /t +--- error_log +log a warning event +--- no_error_log +do not log another warning event + + + +=== TEST 27: sanity (with log time) +--- http_config + lua_capture_error_log 4m; +--- config + location /t { + access_by_lua_block { + ngx.log(ngx.ERR, "enter 1") + ngx.log(ngx.ERR, "enter 11") + + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs(nil, nil, {fetch_time = true}) + if not res then + error("FAILED " .. err) + end + ngx.say("log lines:", #res / 3) + } + } +--- request +GET /t +--- response_body +log lines:2 +--- grep_error_log eval +qr/enter \d+/ +--- grep_error_log_out eval +[ +"enter 1 +enter 11 +", +"enter 1 +enter 11 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 28: log time eq ngx.now +--- http_config + lua_capture_error_log 4m; +--- config + location /t { + access_by_lua_block { + local now = ngx.now() + ngx.log(ngx.CRIT, "enter 1") + ngx.log(ngx.ERR, "enter 11") + + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs(nil, nil, {fetch_time = true}) + if not res then + error("FAILED " .. err) + end + ngx.say("log lines: ", #res / 3) + + for i = 1, #res, 3 do + ngx.say("log level: ", res[i]) + ngx.say("log time: ", res[i + 1]) + ngx.say("log body: ", res[i + 2]) + ngx.say("same with now: ", res[i + 1] == now) + end + } + } +--- request +GET /t +--- response_body_like chomp +\Alog lines: 2 +log level: 3 +log time: \d+(?:\.\d+)? +log body: \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[crit\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: enter 1, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +same with now: true +log level: 4 +log time: \d{10}(?:\.\d+)? +log body: \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?access_by_lua\(nginx.conf:\d+\):\d+: enter 11, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +same with now: true +--- grep_error_log eval +qr/enter \d+/ +--- grep_error_log_out eval +[ +"enter 1 +enter 11 +", +"enter 1 +enter 11 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 29: ringbuf overflow bug +--- http_config + lua_capture_error_log 4k; +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local msg = string.rep("*", 10) + + for i = 1, 2 do + ngx.log(ngx.ERR, msg .. i) + end + } + + content_by_lua_block { + local errlog = require "ngx.errlog" + local msg = string.rep("*", 10) + + for i = 1, 40 do + local res = errlog.get_logs(1) + if res and #res then + ngx.log(ngx.ERR, msg .. i) + end + end + + local res = errlog.get_logs() + for i = 1, #res, 3 do + ngx.say("log level: ", res[i]) + ngx.say("log time: ", res[i + 1]) + ngx.say("log body: ", res[i + 2]) + end + } + } +--- log_level: info +--- request +GET /t +--- response_body_like chomp +log level: 4 +log time: \d+(?:\.\d+)? +log body: \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: \*\*\*\*\*\*\*\*\*\*39, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +log level: 4 +log time: \d{10}(?:\.\d+)? +log body: \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: \*\*\*\*\*\*\*\*\*\*40, client: 127.0.0.1, server: localhost, request: "GET /t HTTP/1.1", host: "localhost" +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 30: ringbuf sentinel bug1 +--- http_config + lua_capture_error_log 4k; +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local msg = string.rep("a", 20) + local bigmsg = string.rep("A", 3000) + + for i = 1, 10 do + ngx.log(ngx.ERR, msg) + end + ngx.log(ngx.ERR, bigmsg) + ngx.log(ngx.ERR, msg) + } + + content_by_lua_block { + local errlog = require "ngx.errlog" + + local res = errlog.get_logs(2) + ngx.say("log lines: #", #res / 3) + + for i = 1, #res, 3 do + ngx.say(string.gsub(res[i + 2], "^.*([Aa][Aa][Aa]).*$", "%1"), "") + end + } + } +--- log_level: info +--- request +GET /t +--- response_body +log lines: #2 +AAA +aaa +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 31: ringbuf sentinel bug2 +--- http_config + lua_capture_error_log 4k; +--- config + location /t { + access_by_lua_block { + local errlog = require "ngx.errlog" + local msg = string.rep("a", 20) + + for i = 1, 20 do + ngx.log(ngx.ERR, msg) + end + } + + content_by_lua_block { + local errlog = require "ngx.errlog" + local msg = string.rep("a", 20) + + local res = errlog.get_logs(18) + ngx.say("log lines: #", #res / 3) + ngx.flush(true) + + for i = 1, 18 do + ngx.log(ngx.ERR, msg) + end + + local bigmsg = string.rep("A", 2000) + ngx.log(ngx.ERR, bigmsg) + + local res = errlog.get_logs() + ngx.say("log lines: #", #res / 3) + ngx.flush(true) + } + } +--- log_level: info +--- request +GET /t +--- response_body +log lines: #18 +log lines: #8 +--- skip_nginx: 2: <1.11.2 diff --git a/t/exit.t b/t/exit.t new file mode 100644 index 000000000..fc8d77927 --- /dev/null +++ b/t/exit.t @@ -0,0 +1,171 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +log_level('warn'); + +repeat_each(120); +#repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + my $init_by_lua_block = $block->init_by_lua_block || ''; + + $http_config .= <<_EOC_; + lua_package_path '\$prefix/html/?.lua;$t::TestCore::lua_package_path'; + init_by_lua_block { + $t::TestCore::init_by_lua_block + $init_by_lua_block + } +_EOC_ + + $block->set_value("http_config", $http_config); +}); + +#no_diff(); +#no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- config + location = /t { + content_by_lua_block { + ngx.exit(403) + } + } +--- request +GET /t +--- response_body_like: 403 Forbidden +--- error_code: 403 +--- no_error_log eval +["[error]", +qr/ -- NYI: (?!FastFunc coroutine.yield)/, +" bad argument"] + + + +=== TEST 2: call ngx.exit() from a custom lua module +--- config + location = /t { + content_by_lua_block { + local foo = require "foo" + foo.go() + } + } +--- user_files +>>> foo.lua +local exit = ngx.exit + +local function go() + exit(403) + return +end + +return { go = go } +--- request +GET /t +--- response_body_like: 403 Forbidden +--- error_code: 403 +--- no_error_log eval +["[error]", +qr/ -- NYI: (?!FastFunc coroutine.yield)/, +" bad argument"] + + + +=== TEST 3: accepts NGX_OK +--- config + location = /t { + content_by_lua_block { + ngx.exit(ngx.OK) + } + } +--- request +GET /t +--- response_body +--- no_error_log eval +["[error]", +qr/ -- NYI: (?!FastFunc coroutine.yield)/, +" bad argument"] + + + +=== TEST 4: accepts NGX_ERROR +--- config + location = /t { + content_by_lua_block { + ngx.exit(ngx.ERROR) + } + } +--- request +GET /t +--- error_code: +--- response_body +--- no_error_log eval +["[error]", +qr/ -- NYI: (?!FastFunc coroutine.yield)/, +" bad argument"] + + + +=== TEST 5: accepts NGX_DECLINED +--- config + location = /t { + content_by_lua_block { + ngx.exit(ngx.DECLINED) + } + } +--- request +GET /t +--- error_code: +--- response_body +--- no_error_log eval +["[error]", +qr/ -- NYI: (?!FastFunc coroutine.yield)/, +" bad argument"] + + + +=== TEST 6: refuses NGX_AGAIN +--- config + location = /t { + content_by_lua_block { + ngx.exit(ngx.AGAIN) + } + } +--- request +GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log eval +qr/\[error\] .*? bad argument to 'ngx.exit': does not accept NGX_AGAIN or NGX_DONE/ +--- no_error_log eval +qr/ -- NYI: (?!FastFunc coroutine.yield)/ +[crit] + + + +=== TEST 7: refuses NGX_DONE +--- config + location = /t { + content_by_lua_block { + ngx.exit(ngx.DONE) + } + } +--- request +GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log eval +qr/\[error\] .*? bad argument to 'ngx.exit': does not accept NGX_AGAIN or NGX_DONE/ +--- no_error_log eval +qr/ -- NYI: (?!FastFunc coroutine.yield)/ +[crit] diff --git a/t/lib/helper.lua b/t/lib/helper.lua new file mode 100644 index 000000000..d2aef2ffe --- /dev/null +++ b/t/lib/helper.lua @@ -0,0 +1,57 @@ +local _M = {} + + +local run_lua_with_graceful_shutdown +do + local function set_up_ngx_conf(dir, code) + local conf = [[ + error_log stderr error; + master_process off; + daemon off; + events { + worker_connections 64; + } + http { + init_worker_by_lua_block { + ngx.timer.at(0, function () + ]] .. code .. [[ + require("ngx.process").signal_graceful_exit() + end) + } + } + ]] + + assert(os.execute("mkdir -p " .. dir .. "/logs")) + + local conf_file = 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 + + local function get_ngx_bin_path() + local ffi = require "ffi" + ffi.cdef[[char **ngx_argv;]] + return ffi.string(ffi.C.ngx_argv[0]) + end + + function run_lua_with_graceful_shutdown(dir, code) + local ngx_pipe = require "ngx.pipe" + local conf_file = set_up_ngx_conf(dir, code) + local nginx = get_ngx_bin_path() + + local cmd = nginx .. " -p " .. dir .. " -c " .. conf_file + return ngx_pipe.spawn(cmd) + end +end +_M.run_lua_with_graceful_shutdown = run_lua_with_graceful_shutdown + + +return _M diff --git a/t/master-pid-single-process.t b/t/master-pid-single-process.t new file mode 100644 index 000000000..3b858fa9a --- /dev/null +++ b/t/master-pid-single-process.t @@ -0,0 +1,57 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +#master_on(); +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- config + location = /t { + content_by_lua_block { + local process = require "ngx.process" + + local v + local get_pid = process.get_master_pid + for i = 1, 400 do + v = get_pid() + end + + local f = assert(io.open(ngx.config.prefix() .. "/logs/nginx.pid", "r")) + local str = assert(f:read("*l")) + local expected = str + if tostring(v) == expected then + ngx.say("ok") + else + ngx.say("expected: ", expected) + end + f:close() + ngx.say("got: ", v, " (", type(v), ")") + } + } +--- request +GET /t +--- response_body_like chop +\Aok +got: \d+ \(number\) +\z +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):\d loop\]/ +--- no_error_log +[error] + -- NYI: + stitch +--- skip_nginx: 6: < 1.13.8 diff --git a/t/master-pid.t b/t/master-pid.t new file mode 100644 index 000000000..e71eeb532 --- /dev/null +++ b/t/master-pid.t @@ -0,0 +1,57 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +master_on(); +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- config + location = /t { + content_by_lua_block { + local process = require "ngx.process" + + local v + local get_pid = process.get_master_pid + for i = 1, 400 do + v = get_pid() + end + + local f = assert(io.open(ngx.config.prefix() .. "/logs/nginx.pid", "r")) + local str = assert(f:read("*l")) + local expected = str + if tostring(v) == expected then + ngx.say("ok") + else + ngx.say("expected: ", expected) + end + f:close() + ngx.say("got: ", v, " (", type(v), ")") + } + } +--- request +GET /t +--- response_body_like chop +\Aok +got: \d+ \(number\) +\z +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):\d loop\]/ +--- no_error_log +[error] + -- NYI: + stitch +--- skip_nginx: 6: < 1.13.8 diff --git a/t/md5-bin.t b/t/md5-bin.t new file mode 100644 index 000000000..7414c85da --- /dev/null +++ b/t/md5-bin.t @@ -0,0 +1,104 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: set md5_bin (string) +--- config + location = /md5_bin { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.md5_bin("hello") + end + ngx.say(string.len(s)) + } + } +--- request +GET /md5_bin +--- response_body +16 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 2: set md5_bin (nil) +--- config + location = /md5_bin { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.md5_bin(nil) + end + ngx.say(string.len(s)) + } + } +--- request +GET /md5_bin +--- response_body +16 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 3: set md5_bin (number) +--- config + location = /md5_bin { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.md5_bin(3.14) + end + ngx.say(string.len(s)) + } + } +--- request +GET /md5_bin +--- response_body +16 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 4: set md5_bin (boolean) +--- config + location = /md5_bin { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.md5_bin(true) + end + ngx.say(string.len(s)) + } + } +--- request +GET /md5_bin +--- response_body +16 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] diff --git a/t/md5.t b/t/md5.t new file mode 100644 index 000000000..3f8b79267 --- /dev/null +++ b/t/md5.t @@ -0,0 +1,104 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: set md5 hello +--- config + location = /md5 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.md5("hello") + end + ngx.say(s) + } + } +--- request +GET /md5 +--- response_body +5d41402abc4b2a76b9719d911017c592 +--- error_log eval +qr/\[TRACE\s+1 content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 2: nil string to ngx.md5 +--- config + location = /md5 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.md5(nil) + end + ngx.say(s) + } + } +--- request +GET /md5 +--- response_body +d41d8cd98f00b204e9800998ecf8427e +--- error_log eval +qr/\[TRACE\s+1 content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 3: empty string to ngx.md5 +--- config + location /md5 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.md5("") + end + ngx.say(s) + } + } +--- request +GET /md5 +--- response_body +d41d8cd98f00b204e9800998ecf8427e +--- error_log eval +qr/\[TRACE\s+1 content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 4: number to ngx.md5 +--- config + location /md5 { + content_by_lua_block { + local s + for i = 1, 100 do + s = ngx.md5(3.14) + end + ngx.say(s) + } + } +--- request +GET /md5 +--- response_body +4beed3b9c4a886067de0e3a094246f78 +--- error_log eval +qr/\[TRACE\s+1 content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] diff --git a/t/misc.t b/t/misc.t new file mode 100644 index 000000000..f45436240 --- /dev/null +++ b/t/misc.t @@ -0,0 +1,224 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +log_level('warn'); + +#repeat_each(120); +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.is_subrequest +--- config + location = /t { + return 201; + header_filter_by_lua_block { + local rc + for i = 1, 100 do + rc = ngx.is_subrequest + end + ngx.log(ngx.WARN, "is subrequest: ", rc) + } + } +--- request +GET /t +--- response_body +--- error_code: 201 +--- no_error_log +[error] + bad argument +--- error_log eval +["is subrequest: false,", +qr/\[TRACE\s+\d+\s+header_filter_by_lua\(nginx.conf:58\):3 loop\]/ +] + + + +=== TEST 2: ngx.headers_sent (false) +--- config + location = /t { + content_by_lua_block { + local rc + for i = 1, 100 do + rc = ngx.headers_sent + end + ngx.say("headers sent: ", rc) + } + } +--- request +GET /t +--- response_body +headers sent: false +--- no_error_log +[error] + -- NYI: + bad argument +--- error_log eval +qr/\[TRACE\s+\d+\s+content_by_lua\(nginx\.conf:\d+\):3 loop\]/ + + + +=== TEST 3: ngx.headers_sent (true) +--- config + location = /t { + content_by_lua_block { + ngx.send_headers() + local rc + for i = 1, 100 do + rc = ngx.headers_sent + end + ngx.say("headers sent: ", rc) + } + } +--- request +GET /t +--- response_body +headers sent: true +--- no_error_log +[error] + -- NYI: + bad argument +--- error_log eval +qr/\[TRACE\s+\d+\s+content_by_lua\(nginx\.conf:\d+\):4 loop\]/ + + + +=== TEST 4: base.check_subsystem +--- config + location = /t { + content_by_lua_block { + local base = require "resty.core.base" + base.allows_subsystem('http', 'stream') + base.allows_subsystem('http') + + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +ok +--- no_error_log +[error] +[crit] + -- NYI: + bad argument + + + +=== TEST 5: base.check_subsystem with non-http subsystem +--- config + location = /t { + content_by_lua_block { + local base = require "resty.core.base" + base.allows_subsystem('stream') + + ngx.say("ok") + } + } +--- request +GET /t +--- error_code: 500 +--- no_error_log +[alert] +[crit] + -- NYI: + bad argument +--- error_log +unsupported subsystem: http + + + +=== TEST 6: not internal request +--- http_config eval: $::HttpConfig +--- config + location /test { + rewrite ^/test$ /lua last; + } + location /lua { + content_by_lua_block { + if ngx.req.is_internal() then + ngx.say("internal") + else + ngx.say("not internal") + end + } + } +--- request +GET /lua +--- response_body +not internal +--- no_error_log +[error] +[alert] +[crit] + -- NYI: + + + +=== TEST 7: internal request +--- http_config eval: $::HttpConfig +--- config + location /test { + rewrite ^/test$ /lua last; + } + location /lua { + content_by_lua_block { + if ngx.req.is_internal() then + ngx.say("internal") + else + ngx.say("not internal") + end + } + } +--- request +GET /test +--- response_body +internal +--- no_error_log +[error] +[alert] +[crit] + -- NYI: + + + +=== TEST 8: bad context +--- http_config eval: $::HttpConfig +--- config + location /lua { + content_by_lua_block { + local function test() + local ok, err = pcall(ngx.req.is_internal) + package.loaded.bad_context = {ok, err} + end + + ngx.timer.at(0, test) + ngx.sleep(0.02) + + local ctx = package.loaded.bad_context + ngx.say(ctx[1]) + local i = string.find(ctx[2], "API disabled in the current context") + ngx.say(i > 1) + } + } +--- request +GET /lua +--- response_body +false +true +--- no_error_log +[error] +[alert] +[crit] + -- NYI: diff --git a/t/ndk.t b/t/ndk.t new file mode 100644 index 000000000..a2eaf6c2e --- /dev/null +++ b/t/ndk.t @@ -0,0 +1,262 @@ +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5 + 4); + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + + $http_config .= <<_EOC_; + $t::TestCore::HttpConfig +_EOC_ + + $block->set_value("http_config", $http_config); + + if (!defined $block->error_log) { + my $no_error_log = <<_EOC_; +[error] +[alert] +[emerg] +-- NYI: +stitch +_EOC_ + + $block->set_value("no_error_log", $no_error_log); + } + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } +}); + +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- config + location /t { + content_by_lua_block { + local s = ndk.set_var.set_escape_uri(" :") + local r = ndk.set_var.set_unescape_uri("a%20b") + ngx.say(s) + ngx.say(r) + + local set_escape_uri = ndk.set_var.set_escape_uri + local set_unescape_uri = ndk.set_var.set_unescape_uri + ngx.say(set_escape_uri(" :")) + ngx.say(set_unescape_uri("a%20b")) + + local res + for i = 1, $TEST_NGINX_HOTLOOP * 10 do + res = set_escape_uri(" :") + end + + for i = 1, $TEST_NGINX_HOTLOOP * 10 do + res = set_unescape_uri("a%20b") + end + } + } +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):13 loop\]/, +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):17 loop\]/ +--- no_error_log +[error] +[alert] +[emerg] +-- NYI: +stitch +--- response_body +%20%3A +a b +%20%3A +a b + + + +=== TEST 2: directive not found +--- config + location /t { + content_by_lua_block { + local s = ndk.set_var.set_escape_uri_blah_blah(" :") + ngx.say(s) + } + } +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +ndk.set_var: directive "set_escape_uri_blah_blah" not found or does not use ndk_set_var_value + + + +=== TEST 3: ndk.set_var initialize ngx_http_variable_value_t variable properly +--- config + location /t { + content_by_lua_block { + local version = '2011.10.13+0000' + local e_version = ndk.set_var.set_encode_base32(version) + local s_version= ndk.set_var.set_quote_sql_str(version) + ngx.say(e_version) + ngx.say(s_version) + } + } +--- response_body +68o32c9e64o2sc9j5co30c1g +'2011.10.13+0000' + + + +=== TEST 4: set directive not allowed +--- config + location /t { + content_by_lua_block { + ndk.set_var.set_escape_uri = "hack it" + } + } +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +not allowed + + + +=== TEST 5: call directive failed +--- config + location /t { + content_by_lua_block { + ndk.set_var.set_decode_hex('a') + } + } +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +calling directive set_decode_hex failed with code -1 + + + +=== TEST 6: convert directive type to string +--- config + location /t { + content_by_lua_block { + ndk.set_var[1]() + } + } +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +ndk.set_var: directive "1" not found or does not use ndk_set_var_value + + + +=== TEST 7: convert directive argument to string +--- config + location /t { + content_by_lua_block { + local s = ndk.set_var.set_escape_uri(1) + ngx.say(s) + } + } +--- response_body +1 + + + +=== TEST 8: call in set_by_lua +--- config + location /t { + set_by_lua_block $s { + return ndk.set_var.set_escape_uri(" :") + } + echo $s; + } +--- response_body +%20%3A + + + +=== TEST 9: call in timer +--- config + location /t { + content_by_lua_block { + ngx.timer.at(0, function() + local s = ndk.set_var.set_escape_uri(" :") + ngx.log(ngx.WARN, "s = ", s) + end) + ngx.sleep(0.01) + } + } +--- error_log +s = %20%3A +--- no_error_log +[error] + + + +=== TEST 10: call in header_filter_by_lua +--- config + location /t { + content_by_lua_block { + ngx.send_headers() + ngx.say(package.loaded.s) + } + header_filter_by_lua_block { + package.loaded.s = ndk.set_var.set_escape_uri(" :") + } + } +--- response_body +%20%3A + + + +=== TEST 11: call in log_by_lua +--- config + location /t { + echo ok; + log_by_lua_block { + local s = ndk.set_var.set_escape_uri(" :") + ngx.log(ngx.WARN, "s = ", s) + } + } +--- response_body +ok +--- error_log +s = %20%3A +--- no_error_log +[error] + + + +=== TEST 12: call in init_worker_by_lua +--- http_config + init_worker_by_lua_block { + package.loaded.s = ndk.set_var.set_escape_uri(" :") + } +--- config + location /t { + content_by_lua_block { + ngx.say(package.loaded.s) + } + } +--- response_body +%20%3A + + + +=== TEST 13: cache the function in init_worker_by_lua and call in other phases +--- http_config + init_worker_by_lua_block { + package.loaded.set_escape_uri = ndk.set_var.set_escape_uri + } +--- config + location /t { + content_by_lua_block { + ngx.say(package.loaded.set_escape_uri(" :")) + } + } +--- response_body +%20%3A diff --git a/t/ngx-req.t b/t/ngx-req.t new file mode 100644 index 000000000..0487c98fc --- /dev/null +++ b/t/ngx-req.t @@ -0,0 +1,317 @@ +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +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"); + } +}); + +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx_req.add_header (jitted) +--- config + location = /t { + content_by_lua_block { + local ngx_req = require "ngx.req" + + for i = 1, $TEST_NGINX_HOTLOOP * 20 do + ngx_req.add_header("Foo", "bar") + end + } + } +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + + + +=== TEST 2: ngx_req.add_header (single value) +--- config + location = /t { + content_by_lua_block { + local ngx_req = require "ngx.req" + + ngx.req.set_header("Foo", "bar") + ngx_req.add_header("Foo", "baz") + ngx_req.add_header("Foo", 2) + + ngx.say("Foo: ", table.concat(ngx.req.get_headers()["Foo"], ", ")) + } + } +--- response_body +Foo: bar, baz, 2 + + + +=== TEST 3: ngx_req.add_header (empty single value) +--- config + location = /t { + content_by_lua_block { + local ngx_req = require "ngx.req" + + ngx.req.set_header("Foo", "bar") + ngx_req.add_header("Foo", "") + + ngx.say("Foo: [", table.concat(ngx.req.get_headers()["Foo"], ", "), ']') + } + } +--- response_body +Foo: [bar, ] + + + +=== TEST 4: ngx_req.add_header (non-string single value) +--- config + location = /t { + content_by_lua_block { + local ngx_req = require "ngx.req" + + ngx.req.set_header("Foo", "bar") + ngx_req.add_header("Foo", 123) + + ngx.say("Foo: [", table.concat(ngx.req.get_headers()["Foo"], ", "), ']') + } + } +--- response_body +Foo: [bar, 123] + + + +=== TEST 5: ngx_req.add_header (non-string header name) +--- config + location = /t { + content_by_lua_block { + local ngx_req = require "ngx.req" + + ngx_req.add_header(123, 456) + ngx_req.add_header(123, 789) + + ngx.say("123: [", table.concat(ngx.req.get_headers()[123], ", "), ']') + } + } +--- response_body +123: [456, 789] + + + +=== TEST 6: ngx_req.add_header (multiple values) +--- config + location = /t { + content_by_lua_block { + local ngx_req = require "ngx.req" + + ngx.req.set_header("Foo", "bar") + ngx_req.add_header("Foo", { "baz", 123 }) + + ngx.say("Foo: ", table.concat(ngx.req.get_headers()["Foo"], ", ")) + } + } +--- response_body +Foo: bar, baz, 123 + + + +=== TEST 7: ngx_req.add_header (override builtin header) +--- config + location = /t { + content_by_lua_block { + local ngx_req = require "ngx.req" + + ngx_req.add_header("User-Agent", "Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0") + + ngx.say("UA: ", ngx.var.http_user_agent) + } + } +--- response_body +UA: Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0 + + + +=== TEST 8: ngx_req.add_header (added header is inherited by subrequests) +--- config + location = /sub { + content_by_lua_block { + ngx.say("Foo: ", table.concat(ngx.req.get_headers()["Foo"], ", ")) + } + } + + location = /t { + content_by_lua_block { + local ngx_req = require "ngx.req" + + ngx.req.set_header("Foo", "bar") + ngx_req.add_header("Foo", {"baz", 2}) + + local res = ngx.location.capture("/sub") + ngx.print(res.body) + } + } +--- response_body +Foo: bar, baz, 2 + + + +=== TEST 9: ngx_req.add_header (invalid context) +--- http_config + init_worker_by_lua_block { + local ngx_req = require "ngx.req" + + ngx_req.add_header("Foo", "baz") + } +--- config + location /t { + return 200; + } +--- response_body +--- error_log +API disabled in the current context + + + +=== TEST 10: ngx_req.add_header (header names edge-cases) +--- config + location = /t { + content_by_lua_block { + local ngx_req = require "ngx.req" + + local function check_invalid_header_name(header_name) + local ok, err = pcall(ngx_req.add_header, header_name, "bar") + if not ok then + ngx.say(err) + else + ngx.say("ok") + end + end + + check_invalid_header_name() + check_invalid_header_name("") + check_invalid_header_name({}) + } + } +--- response_body +bad 'name' argument: string expected, got nil +ok +ok + + + +=== TEST 11: ngx_req.add_header (invalid header values) +--- config + location = /t { + content_by_lua_block { + local ngx_req = require "ngx.req" + + local function check_invalid_header_value(...) + local ok, err = pcall(ngx_req.add_header, "Foo", ...) + if not ok then + ngx.say(err) + else + ngx.say("ok") + end + end + + check_invalid_header_value() + check_invalid_header_value(nil) + check_invalid_header_value({}) + } + } +--- response_body +bad 'value' argument: string or table expected, got nil +bad 'value' argument: string or table expected, got nil +bad 'value' argument: non-empty table expected + + + +=== TEST 12: ngx_req.add_header (header name with control characters) +--- config + location /bar { + access_by_lua_block { + local ngx_req = require "ngx.req" + ngx_req.add_header("header\r\nabc", "value") + } + proxy_pass http://127.0.0.1:$server_port/foo; + } + + location = /foo { + echo $echo_client_request_headers; + } +--- request +GET /bar +--- response_body_like chomp +\bheader%0D%0Aabc: value\r\n + + + +=== TEST 13: ngx_req.add_header (header value with control characters) +--- config + location /bar { + access_by_lua_block { + local ngx_req = require "ngx.req" + ngx_req.add_header("header", "value\r\nabc") + } + proxy_pass http://127.0.0.1:$server_port/foo; + } + + location = /foo { + echo $echo_client_request_headers; + } +--- request +GET /bar +--- response_body_like chomp +\bheader: value%0D%0Aabc\r\n + + + +=== TEST 14: ngx_req.add_header (header name with Chinese characters) +--- config + location /bar { + access_by_lua_block { + local ngx_req = require "ngx.req" + ngx_req.add_header("header中文", "value") + } + proxy_pass http://127.0.0.1:$server_port/foo; + } + + location = /foo { + echo $echo_client_request_headers; + } +--- request +GET /bar +--- response_body_like chomp +\bheader%E4%B8%AD%E6%96%87: value + + + +=== TEST 15: ngx_req.add_header (header value with Chinese characters) +--- config + location /bar { + access_by_lua_block { + local ngx_req = require "ngx.req" + ngx_req.add_header("header", "value中文") + } + proxy_pass http://127.0.0.1:$server_port/foo; + } + + location = /foo { + echo $echo_client_request_headers; + } +--- request +GET /bar +--- response_body_like chomp +\bheader: value中文 diff --git a/t/ngx-resp.t b/t/ngx-resp.t new file mode 100644 index 000000000..f50d59959 --- /dev/null +++ b/t/ngx-resp.t @@ -0,0 +1,186 @@ +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 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"); + } +}); + +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.resp.add_header (single value) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ngx_resp = require "ngx.resp" + ngx_resp.add_header("Foo", "bar") + ngx_resp.add_header("Foo", 2) + ngx.say("Foo: ", table.concat(ngx.header["Foo"], ", ")) + } + } +--- response_body +Foo: bar, 2 + + + +=== TEST 2: ngx.resp.add_header (nil) +--- config + location = /t { + content_by_lua_block { + local ngx_resp = require "ngx.resp" + local ok, err = pcall(ngx_resp.add_header, "Foo") + if not ok then + ngx.say(err) + else + ngx.say('ok') + end + } + } +--- response_body +invalid header value + + + +=== TEST 3: ngx.resp.add_header (multi-value) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ngx_resp = require "ngx.resp" + ngx_resp.add_header('Foo', {'bar', 'baz'}) + local v = ngx.header["Foo"] + ngx.say("Foo: ", table.concat(ngx.header["Foo"], ", ")) + } + } +--- response_body +Foo: bar, baz + + + +=== TEST 4: ngx.resp.add_header (append header) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ngx_resp = require "ngx.resp" + ngx.header["fruit"] = "apple" + ngx_resp.add_header("fruit", "banana") + ngx_resp.add_header("fruit", "cherry") + ngx.say("fruit: ", table.concat(ngx.header["fruit"], ", ")) + } + } +--- response_body +fruit: apple, banana, cherry + + + +=== TEST 5: ngx.resp.add_header (override builtin header) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ngx_resp = require "ngx.resp" + ngx_resp.add_header("Date", "now") + ngx.say("Date: ", ngx.header["Date"]) + } + } +--- response_body +Date: now + + + +=== TEST 6: ngx.resp.add_header (empty table) +--- config + location = /t { + content_by_lua_block { + local ngx_resp = require "ngx.resp" + ngx.header["Foo"] = "aaa" + local ok, err = pcall(ngx_resp.add_header, "Foo", {}) + if not ok then + ngx.say(err) + else + ngx.say("Foo: ", ngx.header["Foo"]) + end + } + } +--- response_body +Foo: aaa + + + +=== TEST 7: ngx.resp.add_header (header name with control characters) +--- config + location = /t { + content_by_lua_block { + local ngx_resp = require "ngx.resp" + ngx_resp.add_header("head\r\n", "value") + ngx.say("OK") + } + } +--- response_body +OK +--- response_headers +head%0D%0A: value + + + +=== TEST 8: ngx.resp.add_header (header value with control characters) +--- config + location = /t { + content_by_lua_block { + local ngx_resp = require "ngx.resp" + ngx_resp.add_header("head", "value\r\n") + ngx.say("OK") + } + } +--- response_body +OK +--- response_headers +head: value%0D%0A + + + +=== TEST 9: ngx.resp.add_header (header name with Chinese characters) +--- config + location = /t { + content_by_lua_block { + local ngx_resp = require "ngx.resp" + ngx_resp.add_header("head中文", "value") + ngx.say("OK") + } + } +--- response_body +OK +--- response_headers +head%E4%B8%AD%E6%96%87: value + + + +=== TEST 10: ngx.resp.add_header (header value with Chinese characters) +--- config + location = /t { + content_by_lua_block { + local ngx_resp = require "ngx.resp" + ngx_resp.add_header("head", "value中文") + ngx.say("OK") + } + } +--- response_body +OK +--- response_headers +head: value中文 diff --git a/t/ocsp.t b/t/ocsp.t new file mode 100644 index 000000000..23a51017c --- /dev/null +++ b/t/ocsp.t @@ -0,0 +1,1566 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(10140); +#workers(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6 + 13); + +no_long_string(); +#no_diff(); + +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::lua_package_path"; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +run_tests(); + +__DATA__ + +=== TEST 1: get OCSP responder (good case) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + -- specify the max length explicitly here, since string buf size may be too short + local url, err = ocsp.get_ocsp_responder_from_der_chain(cert_data, 128) + if not url then + ngx.log(ngx.ERR, "failed to get OCSP responder: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP url found: ", url) + } + 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(3000) + + local err + 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 + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +OCSP url found: http://127.0.0.1:8888/ocsp?foo=1, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 2: get OCSP responder (not found) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/chain/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local url, err = ocsp.get_ocsp_responder_from_der_chain(cert_data) + if not url then + if err then + ngx.log(ngx.ERR, "failed to get OCSP responder: ", err) + else + ngx.log(ngx.WARN, "OCSP responder not found") + end + return + end + + ngx.log(ngx.WARN, "OCSP url found: ", url) + } + 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(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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +OCSP responder not found + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 3: get OCSP responder (no issuer cert at all) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/test-com.crt")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local url, err = ocsp.get_ocsp_responder_from_der_chain(cert_data) + if not url then + if err then + ngx.log(ngx.ERR, "failed to get OCSP responder: ", err) + else + ngx.log(ngx.WARN, "OCSP responder not found") + end + return + end + + ngx.log(ngx.WARN, "OCSP url found: ", url) + } + 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(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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +failed to get OCSP responder: no issuer certificate in chain + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 4: get OCSP responder (issuer cert not next to the leaf cert) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/wrong-issuer-order-chain.pem")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local url, err = ocsp.get_ocsp_responder_from_der_chain(cert_data) + if not url then + if err then + ngx.log(ngx.ERR, "failed to get OCSP responder: ", err) + else + ngx.log(ngx.WARN, "OCSP responder not found") + end + return + end + + ngx.log(ngx.WARN, "OCSP url found: ", url) + } + 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(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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +failed to get OCSP responder: issuer certificate not next to leaf + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 5: get OCSP responder (truncated) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local url, err = ocsp.get_ocsp_responder_from_der_chain(cert_data, 6) + if not url then + if err then + ngx.log(ngx.ERR, "failed to get OCSP responder: ", err) + else + ngx.log(ngx.WARN, "OCSP responder not found") + end + return + end + + if err then + ngx.log(ngx.WARN, "still get an error: ", err) + end + + ngx.log(ngx.WARN, "OCSP url found: ", url) + } + 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(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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +OCSP url found: http:/, +still get an error: truncated + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 6: create OCSP request (good) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + -- specify the max length explicitly here, since string buf size may be too short + local req, err = ocsp.create_ocsp_request(cert_data, 128) + if not req then + ngx.log(ngx.ERR, "failed to create OCSP request: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP request created with length ", #req) + + local f = assert(io.open("t/cert/ocsp/ocsp-req.der", "r")) + local expected = assert(f:read("*a")) + f:close() + if req ~= expected then + ngx.log(ngx.ERR, "ocsp responder: got unexpected OCSP request") + 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; + lua_ssl_verify_depth 3; + + location /t { + 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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +OCSP request created with length 68 + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 7: create OCSP request (buffer too small) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local req, err = ocsp.create_ocsp_request(cert_data, 67) + if not req then + ngx.log(ngx.ERR, "failed to create OCSP request: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP request created with length ", #req) + local bytes = {string.byte(req, 1, #req)} + for i, byte in ipairs(bytes) do + bytes[i] = string.format("%02x", byte) + end + ngx.log(ngx.WARN, "OCSP request content: ", table.concat(bytes, " ")) + } + 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(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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +failed to create OCSP request: output buffer too small: 68 > 67 + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 8: create OCSP request (empty string cert chain) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local cert_data = "" + local req, err = ocsp.create_ocsp_request(cert_data, 67) + if not req then + ngx.log(ngx.ERR, "failed to create OCSP request: ", err) + return ngx.exit(ngx.ERROR) + end + + ngx.log(ngx.WARN, "OCSP request created with length ", #req) + local bytes = {string.byte(req, 1, #req)} + for i, byte in ipairs(bytes) do + bytes[i] = string.format("%02x", byte) + end + ngx.log(ngx.WARN, "OCSP request content: ", table.concat(bytes, " ")) + } + 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(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) + 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 +lua ssl server name: "test.com" +failed to create OCSP request: d2i_X509_bio() failed + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 9: create OCSP request (no issuer cert in the chain) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/test-com.crt")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local req, err = ocsp.create_ocsp_request(cert_data, 67) + if not req then + ngx.log(ngx.ERR, "failed to create OCSP request: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP request created with length ", #req) + local bytes = {string.byte(req, 1, #req)} + for i, byte in ipairs(bytes) do + bytes[i] = string.format("%02x", byte) + end + ngx.log(ngx.WARN, "OCSP request content: ", table.concat(bytes, " ")) + } + 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(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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +failed to create OCSP request: no issuer certificate in chain + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 10: validate good OCSP response +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local f = assert(io.open("t/cert/ocsp/ocsp-resp.der")) + local resp = f:read("*a") + f:close() + + local ok, err = ocsp.validate_ocsp_response(resp, cert_data) + if not ok then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP response validation ok") + } + 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(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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +OCSP response validation ok + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 11: fail to validate OCSP response - no issuer cert +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/test-com.crt")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local f = assert(io.open("t/cert/ocsp/ocsp-resp.der")) + local resp = f:read("*a") + f:close() + + -- specify the max length explicitly here, since string buf size may be too short + local req, err = ocsp.validate_ocsp_response(resp, cert_data, 128) + if not req then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP response validation ok") + } + 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(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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +failed to validate OCSP response: no issuer certificate in chain + +--- no_error_log +OCSP response validation ok +[alert] +[emerg] + + + +=== TEST 12: validate good OCSP response - no certs in response +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local f = assert(io.open("t/cert/ocsp/ocsp-resp-no-certs.der")) + local resp = f:read("*a") + f:close() + + local req, err = ocsp.validate_ocsp_response(resp, cert_data) + if not req then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP response validation ok") + } + 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(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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +OCSP response validation ok + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 13: validate OCSP response - OCSP response signed by an unknown cert and the OCSP response contains the unknown cert + +FIXME: we should complain in this case. + +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local f = assert(io.open("t/cert/ocsp/ocsp-resp-signed-by-orphaned.der")) + local resp = f:read("*a") + f:close() + + local req, err = ocsp.validate_ocsp_response(resp, cert_data) + if not req then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP response validation ok") + } + 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(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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +OCSP response validation ok + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 14: fail to validate OCSP response - OCSP response signed by an unknown cert and the OCSP response does not contain the unknown cert + +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local f = assert(io.open("t/cert/ocsp/ocsp-resp-signed-by-orphaned-no-certs.der")) + local resp = f:read("*a") + f:close() + + -- specify the max length explicitly here, since string buf size may be too short + local req, err = ocsp.validate_ocsp_response(resp, cert_data, 128) + if not req then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return + end + + ngx.log(ngx.WARN, "OCSP response validation ok") + } + 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(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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata + +--- error_log +lua ssl server name: "test.com" +failed to validate OCSP response: OCSP_basic_verify() failed + +--- no_error_log +OCSP response validation ok +[alert] +[emerg] + + + +=== TEST 15: fail to validate OCSP response - OCSP response returns revoked status +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/revoked-chain.pem")) + local cert_data = f:read("*a") + f:close() + + local err + cert_data, err = ssl.cert_pem_to_der(cert_data) + if not cert_data then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return ngx.exit(ngx.ERROR) + end + + local f = assert(io.open("t/cert/ocsp/revoked-ocsp-resp.der")) + local resp = f:read("*a") + f:close() + + -- specify the max length explicitly here, since string buf size may be too short + local req, err = ocsp.validate_ocsp_response(resp, cert_data, 128) + if not req then + ngx.log(ngx.ERR, "failed to validate OCSP response: ", err) + return ngx.exit(ngx.ERROR) + end + + ngx.log(ngx.WARN, "OCSP response validation ok") + } + 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(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) + 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 +lua ssl server name: "test.com" +failed to validate OCSP response: certificate status "revoked" in the OCSP response + +--- no_error_log +OCSP response validation ok +[alert] +[emerg] + + + +=== TEST 16: good status req from client +FIXME: check the OCSP staple actually received by the ssl client +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/ocsp-resp.der")) + local resp = assert(f:read("*a")) + f:close() + + print("resp len: ", #resp) + + local ok, err = ocsp.set_ocsp_status_resp(resp) + if not ok then + ngx.log(ngx.ERR, "failed to set ocsp status resp: ", err) + return + end + ngx.log(ngx.WARN, "ocsp status resp set ok: ", err) + } + 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(3000) + + 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 + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", true, true) + 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: cdata + +--- error_log +lua ssl server name: "test.com" +ocsp status resp set ok: nil, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 17: no status req from client +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local ocsp = require "ngx.ocsp" + + local f = assert(io.open("t/cert/ocsp/ocsp-resp.der")) + local resp = assert(f:read("*a")) + f:close() + + print("resp len: ", #resp) + + local ok, err = ocsp.set_ocsp_status_resp(resp) + if not ok then + ngx.log(ngx.ERR, "failed to set ocsp status resp: ", err) + return + end + ngx.log(ngx.WARN, "ocsp status resp set ok: ", err) + } + 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(3000) + + 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 + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", 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: cdata + +--- error_log +lua ssl server name: "test.com" +ocsp status resp set ok: no status req, + +--- no_error_log +[error] +[alert] +[emerg] diff --git a/t/os-getenv-hup.t b/t/os-getenv-hup.t new file mode 100644 index 000000000..8ff811989 --- /dev/null +++ b/t/os-getenv-hup.t @@ -0,0 +1,170 @@ +# 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 { + undef $ENV{TEST_NGINX_USE_STAP}; + } +} + +use lib '.'; +use t::TestCore $SkipReason ? (skip_all => $SkipReason) : (); + +plan tests => repeat_each() * (blocks() * 4); + +add_block_preprocessor(sub { + my $block = shift; + + if (!defined $block->error_log) { + $block->set_value("error_log", + qr/\[notice\] .*? \(SIGHUP\) received/); + } + + if (!defined $block->no_error_log) { + $block->set_value("no_error_log", "[error]"); + } + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } +}); + +no_shuffle(); +use_hup(); + +$ENV{TEST_NGINX_BAR} = 'old'; +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::lua_package_path"; + +run_tests(); + +__DATA__ + +=== TEST 1: env directive explicit value is visible within init_by_lua* +--- main_config +env FOO=old; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- config +location /t { + content_by_lua_block { + ngx.say(package.loaded.foo) + } +} +--- response_body +old +--- error_log +[notice] + + + +=== TEST 2: HUP reload changes env value (1/3) +--- main_config +env FOO=new; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- config +location /t { + content_by_lua_block { + ngx.say(package.loaded.foo) + } +} +--- response_body +new + + + +=== TEST 3: HUP reload changes env value (2/3) +--- main_config +env FOO=; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- config +location /t { + content_by_lua_block { + ngx.say(package.loaded.foo) + } +} +--- response_body_like chomp +\s + + + +=== TEST 4: HUP reload changes env value (3/3) +--- main_config +env FOO; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- config +location /t { + content_by_lua_block { + ngx.say(package.loaded.foo) + } +} +--- response_body +nil + + + +=== TEST 5: HUP reload changes visible environment variable (1/2) +--- main_config +env TEST_NGINX_BAR; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.test_nginx_bar = os.getenv("TEST_NGINX_BAR") + } +--- config +location /t { + content_by_lua_block { + ngx.say(package.loaded.test_nginx_bar) + } +} +--- response_body +old + + + +=== TEST 6: HUP reload changes visible environment variable (2/2) +--- main_config +env TEST_NGINX_BAR=new; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.test_nginx_bar = os.getenv("TEST_NGINX_BAR") + } +--- config +location /t { + content_by_lua_block { + ngx.say(package.loaded.test_nginx_bar) + } +} +--- response_body +new diff --git a/t/os-getenv.t b/t/os-getenv.t new file mode 100644 index 000000000..21f72d92b --- /dev/null +++ b/t/os-getenv.t @@ -0,0 +1,252 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +plan tests => repeat_each() * (blocks() * 3 + 1); + +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"); + } +}); + +$ENV{TEST_NGINX_BAR} = 'world'; +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::lua_package_path"; + +run_tests(); + +__DATA__ + +=== TEST 1: env directive explicit value is visible within init_by_lua* +--- main_config +env FOO=hello; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- config +location /t { + content_by_lua_block { + ngx.say(package.loaded.foo, "\n", os.getenv("FOO")) + } +} +--- response_body +hello +hello + + + +=== TEST 2: env directive explicit value is visible within init_by_lua* with lua_shared_dict +--- main_config +env FOO=hello; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + lua_shared_dict dogs 24k; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- config +location /t { + content_by_lua_block { + ngx.say(package.loaded.foo, "\n", os.getenv("FOO")) + } +} +--- response_body +hello +hello + + + +=== TEST 3: env directive explicit value is case-sensitive within init_by_lua* +--- main_config +env FOO=hello; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("foo") + } +--- config +location /t { + content_by_lua_block { + ngx.say(package.loaded.foo, "\n", os.getenv("foo")) + } +} +--- response_body +nil +nil + + + +=== TEST 4: env directives with no value are ignored +--- main_config +env FOO; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- config +location /t { + content_by_lua_block { + ngx.say(package.loaded.foo, "\n", os.getenv("FOO")) + } +} +--- response_body +nil +nil + + + +=== TEST 5: env is visible from environment +--- main_config +env TEST_NGINX_BAR; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("TEST_NGINX_BAR") + } +--- config +location /t { + content_by_lua_block { + ngx.say(package.loaded.foo, "\n", os.getenv("TEST_NGINX_BAR")) + } +} +--- response_body +world +world + + + +=== TEST 6: env explicit set vs environment set +--- main_config +env TEST_NGINX_BAR=goodbye; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("TEST_NGINX_BAR") + } +--- config +location /t { + content_by_lua_block { + ngx.say(package.loaded.foo, "\n", os.getenv("TEST_NGINX_BAR")) + } +} +--- response_body +goodbye +goodbye + + + +=== TEST 7: env directive with empty value +--- main_config +env FOO=; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- config +location /t { + content_by_lua_block { + ngx.say("in init: ", package.loaded.foo, "\n", + "in content: ", os.getenv("FOO")) + } +} +--- response_body_like +in init:\s+ +in content:\s+ + + + +=== TEST 8: os.getenv() overwrite is reverted in worker phases +--- main_config +env FOO=hello; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + package.loaded.init_os_getenv = os.getenv + } +--- config +location /t { + content_by_lua_block { + ngx.say("FOO=", os.getenv("FOO")) + + if os.getenv ~= package.loaded.init_os_getenv then + ngx.say("os.getenv() overwrite was reverted") + + else + ngx.say("os.getenv() overwrite was not reverted") + end + } +} +--- response_body +FOO=hello +os.getenv() overwrite was reverted + + + +=== TEST 9: os.getenv() can be localized after loading resty.core +--- main_config +env FOO=hello; +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + do + local getenv = os.getenv + + package.loaded.f = function () + ngx.log(ngx.NOTICE, "FOO: ", getenv("FOO")) + end + end + + require "resty.core" + + package.loaded.f() + + package.loaded.is_os_getenv = os.getenv == package.loaded.os_getenv + } +--- config +location /t { + content_by_lua_block { + package.loaded.f() + package.loaded.f() + + if os.getenv ~= package.loaded.init_os_getenv then + ngx.say("os.getenv() overwrite was reverted") + + else + ngx.say("os.getenv() overwrite was not reverted") + end + } +} +--- response_body +os.getenv() overwrite was reverted +--- grep_error_log eval +qr/FOO: [a-z]+/ +--- grep_error_log_out +FOO: hello +FOO: hello +FOO: hello diff --git a/t/param.t b/t/param.t new file mode 100644 index 000000000..75266631e --- /dev/null +++ b/t/param.t @@ -0,0 +1,66 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4 + 1); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.arg getter in set_by_lua +--- config + location = /t { + # set_by_lua_block doesn't support arguments + set_by_lua $res ' + local arg = ngx.arg + local val + for i = 1, 30 do + val = arg[1] + arg[2] + end + return val + ' $arg_a $arg_b; + echo $res; + } +--- request +GET /t?a=1&b=2 +--- response_body +3 +--- error_log eval +qr/\[TRACE\s+\d+ set_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: (?!return to lower frame) + + + +=== TEST 2: ngx.arg getter in body_filter_by_lua +--- config + location = /t { + echo hello; + body_filter_by_lua_block { + local arg = ngx.arg + local eof + local body = "" + for i = 1, 30 do + body = body .. arg[1] + eof = arg[2] + end + } + } +--- request +GET /t +--- error_log eval +qr/\[TRACE\s+\d+ body_filter_by_lua\(nginx\.conf:\d+\):5 loop\]/ +--- no_error_log +[error] + -- NYI: (?!return to lower frame) diff --git a/t/phase.t b/t/phase.t new file mode 100644 index 000000000..a0e1a32c4 --- /dev/null +++ b/t/phase.t @@ -0,0 +1,33 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: get_phase +--- config + location /lua { + content_by_lua_block { + local phase + for i = 1, 100 do + phase = ngx.get_phase() + end + ngx.say(phase) + } + } +--- request +GET /lua +--- response_body +content +--- no_error_log +[error] + -- NYI: +--- error_log eval +qr/\[TRACE\s+\d+\s+content_by_lua\(nginx\.conf:\d+\):3 loop\]/ diff --git a/t/pipe-cpu-affinity.t b/t/pipe-cpu-affinity.t new file mode 100644 index 000000000..7b909425b --- /dev/null +++ b/t/pipe-cpu-affinity.t @@ -0,0 +1,80 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 2); + +add_block_preprocessor(sub { + my $block = shift; + + if (!defined $block->error_log) { + $block->set_value("no_error_log", "[error]\n[alert]\n[emerg]"); + } + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } +}); + +env_to_nginx("PATH"); +master_on(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: reset the cpu affinity in the sub-process +--- main_config +worker_cpu_affinity 0001; +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local nproc, err = ngx_pipe.spawn({"nproc"}) + if not nproc then + ngx.say(err) + return + end + + local ncpu, err = nproc:stdout_read_line() + if not ncpu then + ngx.say(err) + return + end + + if tonumber(ncpu) < 2 then + -- when ncpu is 1, this test is a smoking test. + -- We could not ensure the affinity is reset, but we could + -- ensure no error occurs. + ngx.say("ok") + return + end + + local proc, err = ngx_pipe.spawn({"sleep", 3600}) + if not proc then + ngx.say(err) + return + end + + -- ensure affinity is already reset before running taskset + ngx.sleep(0.1) + + local taskset, err = ngx_pipe.spawn({"taskset", "-pc", proc:pid()}) + if not taskset then + ngx.say(err) + return + end + + local report, err = taskset:stdout_read_line() + if not report then + ngx.say(err) + return + end + + ngx.say(report) + } + } +--- response_body_like +(ok|pid \d+'s current affinity list: 0[-,]\d+) diff --git a/t/pipe-multi-workers.t b/t/pipe-multi-workers.t new file mode 100644 index 000000000..02f344c98 --- /dev/null +++ b/t/pipe-multi-workers.t @@ -0,0 +1,44 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +workers(2); +master_on(); + +repeat_each(1); + +plan tests => repeat_each() * (blocks() * 2); + +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.worker.count +--- http_config + init_worker_by_lua_block { + ngx.timer.at(0, function() + local ngx_pipe = require "ngx.pipe" + + local function check_error(...) + local data, err = pcall(...) + if not data then + ngx.log(ngx.ERR, err) + end + end + + check_error(ngx_pipe.spawn, {"ls"}) + end) + } +--- config + listen $TEST_NGINX_RAND_PORT_1 reuseport; + location = /t { + content_by_lua_block { + ngx.sleep(0.01) -- ensure timer is fired + ngx.say("ok") + } + } +--- request +GET /t +--- no_error_log +failed (9: Bad file descriptor) diff --git a/t/pipe-stderr.t b/t/pipe-stderr.t new file mode 100644 index 000000000..5d54c2fcc --- /dev/null +++ b/t/pipe-stderr.t @@ -0,0 +1,833 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; +use Cwd qw(abs_path realpath cwd); +use File::Basename; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 5); + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + my $init_by_lua_block = $block->init_by_lua_block || ''; + + $http_config .= <<_EOC_; + lua_package_path '$t::TestCore::lua_package_path'; + init_by_lua_block { + $t::TestCore::init_by_lua_block + $init_by_lua_block + } +_EOC_ + + $block->set_value("http_config", $http_config); + + if (!defined $block->error_log) { + $block->set_value("no_error_log", "[error]"); + } + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } +}); + +$ENV{TEST_NGINX_CERT_DIR} ||= dirname(realpath(abs_path(__FILE__))); +my $port = server_port; +if ($port < 65535) { + $port++; +} else { + $port--; +} +$ENV{TEST_NGINX_SERVER_SSL_PORT} = $port; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +env_to_nginx("PATH"); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: read stderr, pattern is read line +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", ">&2 echo hello world"}) + + local data, err = proc:stderr_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello world + + + +=== TEST 2: read stderr, pattern is read bytes +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", ">&2 echo hello world"}) + + local data, err = proc:stderr_read_bytes(5) + if not data then + ngx.say(err) + else + ngx.say(data) + end + + data, err = proc:stderr_read_bytes(6) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello + world + + + +=== TEST 3: read stderr, bytes length is zero +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", ">&2 echo hello world"}) + + local data, err = proc:stderr_read_bytes(0) + if not data then + ngx.say(err) + else + ngx.say("data:", data) + end + } + } +--- response_body +data: + + + +=== TEST 4: read stderr, bytes length is less than zero +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", ">&2 echo hello world"}) + + local ok, err = pcall(proc.stderr_read_bytes, proc, -1) + if not ok then + ngx.say(err) + end + } + } +--- response_body +bad len argument + + + +=== TEST 5: read stderr, bytes length is more than data +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", ">&2 echo hello world"}) + + local data, err = proc:stderr_read_bytes(20) + if not data then + ngx.say(err) + else + ngx.say("data:", data) + end + } + } +--- response_body +closed + + + +=== TEST 6: read stderr, pattern is read all +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", ">&2 echo -n hello && sleep 0.05 && >&2 echo -n world"}) + + local data, err = proc:stderr_read_all() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +helloworld + + + +=== TEST 7: read stderr, pattern is read any +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", ">&2 echo -n hello && sleep 0.05 && >&2 echo -n world"}) + + local data, err = proc:stderr_read_any(1024) + if not data then + ngx.say(err) + else + ngx.say(data) + end + + data, err = proc:stderr_read_any(1024) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello +world + + + +=== TEST 8: read stderr, pattern is read any, with limited, max <= 0 +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", ">&2 echo -n hello && sleep 0.05 && >&2 echo -n world"}) + + local ok, err = pcall(proc.stderr_read_any, proc, 0) + if not ok then + ngx.say(err) + end + } + } +--- response_body +bad max argument + + + +=== TEST 9: read stderr, without yield +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", ">&2 echo hello world"}) + + ngx.sleep(0.05) + local data, err = proc:stderr_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello world + + + +=== TEST 10: read stderr, without yield, pattern is read bytes +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", ">&2 echo hello world"}) + + ngx.sleep(0.05) + local data, err = proc:stderr_read_bytes(7) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello w + + + +=== TEST 11: read stderr, without yield, pattern is read all +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", ">&2 echo hello && >&2 echo world"}) + + ngx.sleep(0.05) + local data, err = proc:stderr_read_all() + if not data then + ngx.say(err) + else + ngx.print(data) + end + } + } +--- response_body +hello +world + + + +=== TEST 12: read stderr, without yield, pattern is read any +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", ">&2 echo -n hello && sleep 0.01 && >&2 echo -n world"}) + + ngx.sleep(0.05) + local data, err = proc:stderr_read_any(1024) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +helloworld + + + +=== TEST 13: read stderr, mix read pattern and stdout/stderr +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local script = [[ + echo -n hello + >&2 echo world + >&2 echo -n more + sleep 0.1 + >&2 echo -n da + sleep 0.1 + >&2 echo ta + echo more + >&2 echo -n data + ]] + local proc = ngx_pipe.spawn({"sh", "-c", script}) + + local function check_call(proc, func, ...) + local data, err = func(proc, ...) + if not data then + ngx.say(err) + ngx.exit(ngx.OK) + end + ngx.say(data) + end + + ngx.sleep(0.05) + ngx.say("reading any") + check_call(proc, proc.stderr_read_any, 1024) + + ngx.say("reading 3") + check_call(proc, proc.stderr_read_bytes, 3) + + ngx.say("reading line") + check_call(proc, proc.stderr_read_line) + + ngx.say("reading all") + check_call(proc, proc.stderr_read_all) + } + } +--- response_body +reading any +world +more +reading 3 +dat +reading line +a +reading all +data + + + +=== TEST 14: read stderr, timeout +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sleep", "10s"}) + proc:set_timeouts(nil, 4000, 100) + + local ok, err = proc:stderr_read_line() + if not ok then + ngx.say(err) + else + ngx.say("ok") + end + } + } +--- response_body +timeout +--- no_error_log +[error] +--- error_log +lua pipe add timer for reading: 100(ms) + + + +=== TEST 15: read stderr, aborted by uthread kill +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "sleep 0.1 && >&2 echo hello"}) + + local function read() + proc:stderr_read_line() + ngx.log(ngx.ERR, "can't reach here") + end + + local th = ngx.thread.spawn(read) + ngx.thread.kill(th) + + local data, err = proc:stderr_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello +--- no_error_log +[error] +--- error_log +lua pipe read process: +lua pipe proc read stderr cleanup + + + +=== TEST 16: more than one coroutines read stderr of a process +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "sleep 0.1 && >&2 echo hello && >&2 echo world"}) + + local function read() + local data, err = proc:stderr_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + end + + local th1 = ngx.thread.spawn(read) + local th2 = ngx.thread.spawn(read) + ngx.thread.wait(th1) + ngx.thread.wait(th2) + ngx.thread.spawn(read) + } + } +--- response_body +pipe busy reading +hello +world + + + +=== TEST 17: read stderr while read stdout in other request +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + package.loaded.proc = ngx_pipe.spawn({"sh", "-c", [[ + echo hello + >&2 echo world + sleep 0.1 + >&2 echo more + echo -n da + sleep 0.1 + echo ta + echo -n more + >&2 echo -n data + ]]}) + local res1, res2 = ngx.location.capture_multi{{"/req1"}, {"/req2"}} + ngx.say("stderr:") + ngx.print(res1.body) + ngx.say("stdout:") + ngx.print(res2.body) + } + } + + location = /req1 { + content_by_lua_block { + while true do + local data, err = package.loaded.proc:stderr_read_any(1024) + if data then + ngx.print(data) + else + if err ~= 'closed' then + ngx.say(err) + end + break + end + end + ngx.say('') + } + } + + location = /req2 { + content_by_lua_block { + while true do + local data, err = package.loaded.proc:stdout_read_any(1024) + if data then + ngx.print(data) + else + if err ~= 'closed' then + ngx.say(err) + end + break + end + end + ngx.say('') + } + } +--- response_body +stderr: +world +more +data +stdout: +hello +data +more + + + +=== TEST 18: read stderr while read stdout in other request, individual error +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + package.loaded.proc = ngx_pipe.spawn({"sleep", 0.5}) + package.loaded.proc:set_timeouts(nil, 100) + local res1, res2 = ngx.location.capture_multi{{"/req1"}, {"/req2"}} + ngx.say("stderr:") + ngx.print(res1.body) + ngx.say("stdout:") + ngx.print(res2.body) + } + } + + location = /req1 { + content_by_lua_block { + local data, err = package.loaded.proc:stderr_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } + + location = /req2 { + content_by_lua_block { + local data, err = package.loaded.proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +stderr: +closed +stdout: +timeout + + + +=== TEST 19: read stderr while read stdout in other request, individual result +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + package.loaded.proc = ngx_pipe.spawn({"sh", "-c", ">&2 echo hello"}) + local res1, res2 = ngx.location.capture_multi{{"/req1"}, {"/req2"}} + ngx.say("stderr:") + ngx.print(res1.body) + ngx.say("stdout:") + ngx.print(res2.body) + } + } + + location = /req1 { + content_by_lua_block { + local data, err = package.loaded.proc:stderr_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } + + location = /req2 { + content_by_lua_block { + local data, err = package.loaded.proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +stderr: +hello +stdout: +closed + + + +=== TEST 20: read stdout as stderr, mix read pattern and stdout/stderr, merge_stderr is true +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local script = [[ + echo hello + sleep 0.1 + >&2 echo world + >&2 echo more + sleep 0.1 + >&2 echo -n da + sleep 0.1 + >&2 echo ta + echo more + >&2 echo -n data + ]] + + local proc = ngx_pipe.spawn({"sh", "-c", script}, {merge_stderr = true}) + ngx.say("reading stdout all") + local data, err = proc:stdout_read_all() + if not data then + ngx.say(err) + ngx.exit(ngx.OK) + end + ngx.say(data) + + proc = ngx_pipe.spawn({"sh", "-c", script}, {merge_stderr = true}) + ngx.say("reading any") + local i = 1 + while true do + local data, err = proc:stdout_read_any(1024) + + i = i + 1 + if data then + ngx.print(data) + else + if err ~= 'closed' then + ngx.say(err) + end + break + end + end + ngx.say('') + + } + } +--- error_log eval +qr/lua pipe spawn process:[0-9A-F]+ pid:\d+ merge_stderr:1 buffer_size:4096/ +--- no_error_log +[error] +--- response_body +reading stdout all +hello +world +more +data +more +data +reading any +hello +world +more +data +more +data + + + +=== TEST 21: read stderr, merge_stderr is true +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local args = {"sh", "-c", ">&2 echo hello world"} + local proc = ngx_pipe.spawn(args, {merge_stderr = true}) + + local data, err = proc:stderr_read_all() + if not data then + ngx.say(err) + else + ngx.say(data) + end + + local data, err = proc:stderr_read_any(1024) + if not data then + ngx.say(err) + else + ngx.say(data) + end + + local data, err = proc:stderr_read_bytes(1) + if not data then + ngx.say(err) + else + ngx.say(data) + end + + local data, err = proc:stderr_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +merged to stdout +merged to stdout +merged to stdout +merged to stdout + + + +=== TEST 22: read stdout as stderr, aborted by uthread kill, merge_stderr is true +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local args = {"sh", "-c", "sleep 0.1 && >&2 echo hello && echo world"} + local proc = ngx_pipe.spawn(args, {merge_stderr = true}) + + local function read() + proc:stdout_read_line() + ngx.log(ngx.ERR, "can't reach here") + end + + local th = ngx.thread.spawn(read) + ngx.thread.kill(th) + + local data, err = proc:stdout_read_line() + if not data then + ngx.say("err: ", err) + else + ngx.say(data) + end + + local data, err = proc:stdout_read_line() + if not data then + ngx.say("err: ", err) + else + ngx.say(data) + end + } + } +--- response_body +hello +world + + + +=== TEST 23: read stderr, aborted by uthread kill, with graceful shutdown +--- user_files +>>> a.lua +local ngx_pipe = require "ngx.pipe" +local proc = ngx_pipe.spawn({"bash"}) + +local function func() + proc:stderr_read_line() + ngx.log(ngx.ERR, "can't reach here") +end + +local th = ngx.thread.spawn(func) +ngx.thread.kill(th) + +local data, err = proc:kill(9) -- SIGKILL +if not data then + io.stdout:write("proc:kill(9) err: ", err) +else + io.stdout:write("ok") +end + +--- config + location = /t { + content_by_lua_block { + local helper = require "helper" + local f = io.open("$TEST_NGINX_HTML_DIR/a.lua") + local code = f:read("*a") + local proc = helper.run_lua_with_graceful_shutdown("$TEST_NGINX_HTML_DIR", code) + proc:set_timeouts(300, 300, 300, 300) + + local data, err = proc:stdout_read_all() + if not data then + ngx.say("stdout err: ", err) + else + ngx.say("stdout: ", data) + end + + local data, err = proc:stderr_read_any(4096) + if not data then + ngx.say("stderr err: ", err) + else + ngx.say("stderr: ", data) + end + } + } +--- response_body +stdout: ok +stderr err: closed +--- no_error_log +[error] + + + +=== TEST 24: spawn process with stderr_read_timeout option +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sleep", "10s"}, { + stderr_read_timeout = 100 + }) + + local data, err = proc:stderr_read_line() + if not data then + ngx.say("stderr err: ", err) + else + ngx.say("stderr: ", data) + end + } + } +--- response_body +stderr err: timeout +--- error_log +lua pipe add timer for reading: 100(ms) +--- no_error_log +[error] diff --git a/t/pipe-stdin.t b/t/pipe-stdin.t new file mode 100644 index 000000000..6f205673a --- /dev/null +++ b/t/pipe-stdin.t @@ -0,0 +1,562 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 10); + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + my $init_by_lua_block = $block->init_by_lua_block || ''; + + $http_config .= <<_EOC_; + lua_package_path '$t::TestCore::lua_package_path'; + init_by_lua_block { + $t::TestCore::init_by_lua_block + $init_by_lua_block + } +_EOC_ + + $block->set_value("http_config", $http_config); + + if (!defined $block->error_log) { + $block->set_value("no_error_log", "[error]"); + } + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } +}); + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +env_to_nginx("PATH"); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: write process +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({'tee'}) + + local function write(...) + local bytes, err = proc:write(...) + if not bytes then + ngx.say(err) + return + end + ngx.say(bytes) + end + + write('') + write('hello') + write(' world') + + local data, err = proc:stdout_read_any(1024) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +0 +5 +6 +hello world + + + +=== TEST 2: write process, bad pipe +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({'echo', 'a'}) + + ngx.sleep(0.1) + local bytes, err = proc:write("test") + if not bytes then + ngx.say(err) + return + end + ngx.say(bytes) + } + } +--- response_body +closed +--- error_log eval +qr/lua pipe write data error pipe:[0-9A-F]+ \(\d+: Broken pipe\)/ + + + +=== TEST 3: write process after waiting +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"echo", "hello world"}) + + local ok, err = proc:wait() + if not ok then + ngx.say("wait failed: ", err) + return + end + + local data, err = proc:write("a") + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +closed + + + +=== TEST 4: write process, timeout +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", 1}) + if not proc then + ngx.say(err) + return + end + + local data = ("1234"):rep(2048) + proc:set_timeouts(100) + local total = 0 + local step = #data + + while true do + local data, err = proc:write(data) + if not data then + ngx.say(err) + break + end + + total = total + step + if total > 64 * step then + break + end + end + + ngx.log(ngx.WARN, "total write before timeout:", total) + } + } +--- response_body +timeout +--- no_error_log +[error] +--- error_log +lua pipe add timer for writing: 100(ms) +lua pipe write yielding + + + +=== TEST 5: write process, yield and write again +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"tee"}) + if not proc then + ngx.say(err) + return + end + + local data = ("1234"):rep(2048) + proc:set_timeouts(100) + local total = 0 + local step = #data + + while true do + local data, err = proc:write(data) + if not data then + ngx.say(err) + break + end + + total = total + step + if total > 64 * step then + break + end + end + ngx.log(ngx.WARN, "total write before timeout:", total) + + local function drain() + local data, err = proc:stdout_read_bytes(#data / 2) + if not data then + ngx.log(ngx.ERR, "drain failed: ", err) + end + end + + ngx.thread.spawn(function() + drain() + ngx.sleep(0.1) + drain() + end) + + proc:set_timeouts(400) + local bytes, err = proc:write(data) + if not bytes then + ngx.say(err) + else + ngx.say(bytes) + end + } + } +--- response_body +timeout +8192 +--- no_error_log +[error] +--- error_log +lua pipe write yielding + + + +=== TEST 6: more than one coroutines write +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", 1}) + if not proc then + ngx.say(err) + return + end + + local data = ("1234"):rep(2048) + proc:set_timeouts(100) + local total = 0 + local step = #data + + -- make writers blocked later + while true do + local data, err = proc:write(data) + if not data then + ngx.say(err) + break + end + + total = total + step + if total > 64 * step then + break + end + end + + local function write() + local data, err = proc:write(data) + if not data then + ngx.say(err) + else + ngx.say(data) + end + end + + local th1 = ngx.thread.spawn(write) + local th2 = ngx.thread.spawn(write) + ngx.thread.wait(th1) + ngx.thread.wait(th2) + ngx.thread.spawn(write) + } + } +--- response_body +timeout +pipe busy writing +timeout +timeout +--- no_error_log +[error] +--- error_log +lua pipe add timer for writing: 100(ms) +lua pipe write yielding + + + +=== TEST 7: write process, aborted by uthread kill +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", 1}) + if not proc then + ngx.say(err) + return + end + + local data = ("1234"):rep(2048) + proc:set_timeouts(100) + local total = 0 + local step = #data + + -- make writers blocked later + while true do + local data, err = proc:write(data) + if not data then + ngx.say(err) + break + end + + total = total + step + if total > 64 * step then + break + end + end + + local function write() + proc:write(data) + ngx.log(ngx.ERR, "can't reach here") + end + + local th = ngx.thread.spawn(write) + ngx.thread.kill(th) + + local data, err = proc:write(data) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +timeout +timeout +--- no_error_log +[error] +--- error_log +lua pipe add timer for writing: 100(ms) +lua pipe write yielding +lua pipe proc write cleanup + + + +=== TEST 8: write and read process +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + package.loaded.proc = ngx_pipe.spawn({'tee'}) + local res1, res2 = ngx.location.capture_multi{{"/req1"}, {"/req2"}} + ngx.print(res1.body) + ngx.print(res2.body) + } + } + + location = /req1 { + content_by_lua_block { + local proc = package.loaded.proc + local data, err = proc:stdout_read_any(1024) + if not data then + ngx.say(err) + end + + data, err = proc:write(data) + if not data then + ngx.say(err) + end + } + } + + location = /req2 { + content_by_lua_block { + local proc = package.loaded.proc + local data, err = proc:write("payload") + if not data then + ngx.say(err) + end + + ngx.sleep(0.2) -- yield to let other req read and write the data + data, err = proc:stdout_read_any(1024) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +payload + + + +=== TEST 9: write process, support table, number and boolean arguments +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({'tee'}) + + local function write(...) + local bytes, err = proc:write(...) + if not bytes then + ngx.say(err) + return + end + ngx.say(bytes) + end + + write(10) + write({"hello", " ", "world"}) + + local data, err = proc:stdout_read_any(1024) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +2 +11 +10hello world + + + +=== TEST 10: write process, throw error if bad argument is written +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({'tee'}) + + local function write(...) + local ok, err = pcall(proc.write, proc, ...) + if not ok then + ngx.say(err) + return + end + end + + write(true) + write(nil) + write(ngx.null) + } + } +--- response_body +bad data arg: string, number, or table expected, got boolean +bad data arg: string, number, or table expected, got nil +bad data arg: string, number, or table expected, got userdata + + + +=== TEST 11: write process, aborted by uthread kill, with graceful shutdown +--- user_files +>>> a.lua +local ngx_pipe = require "ngx.pipe" +local proc = ngx_pipe.spawn({"bash"}) + +-- make writers blocked later +local data = ("1234"):rep(2048) +proc:set_timeouts(300) +local total = 0 +local step = #data +while true do + local data, err = proc:write(data) + if not data then + ngx.log(ngx.ERR, "proc write error: ", err) + break + end + + total = total + step + if total > 64 * step then + break + end +end + +local function func() + proc:write("blah blah") +end + +local th = ngx.thread.spawn(func) +ngx.thread.kill(th) + +local data, err = proc:kill(9) +if not data then + io.stdout:write("proc:kill(9) err: ", err) +else + io.stdout:write("ok") +end + +--- config + location = /t { + content_by_lua_block { + local helper = require "helper" + local spawn = helper.run_lua_with_graceful_shutdown + local f = io.open("$TEST_NGINX_HTML_DIR/a.lua") + local code = f:read("*a") + local proc = spawn("$TEST_NGINX_HTML_DIR", code) + proc:set_timeouts(nil, 1000, 1000) + + local data, err = proc:stdout_read_all() + if not data then + ngx.say("stdout err: ", err) + else + ngx.say("stdout: ", data) + end + + local data, err = proc:stderr_read_any(4096) + if not data then + ngx.say("stderr err: ", err) + else + ngx.say("stderr: ", data) + end + } + } +--- response_body +stdout: ok +stderr err: closed +--- no_error_log +[error] + + + +=== TEST 12: spawn process with write_timeout option +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", 1}, { + write_timeout = 100 + }) + + local data = ("1234"):rep(2048) + local total = 0 + local step = #data + + while true do + local data, err = proc:write(data) + if not data then + ngx.say(err) + break + end + + total = total + step + if total > 64 * step then + break + end + end + } + } +--- response_body +timeout +--- error_log +lua pipe add timer for writing: 100(ms) +lua pipe write yielding +--- no_error_log +[error] diff --git a/t/pipe-stdout.t b/t/pipe-stdout.t new file mode 100644 index 000000000..4d140b67a --- /dev/null +++ b/t/pipe-stdout.t @@ -0,0 +1,1029 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; +use Cwd qw(abs_path realpath cwd); +use File::Basename; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 6); + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + my $init_by_lua_block = $block->init_by_lua_block || ''; + + $http_config .= <<_EOC_; + lua_package_path '$t::TestCore::lua_package_path'; + init_by_lua_block { + $t::TestCore::init_by_lua_block + $init_by_lua_block + } +_EOC_ + + $block->set_value("http_config", $http_config); + + if (!defined $block->error_log) { + $block->set_value("no_error_log", "[error]"); + } + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } +}); + +$ENV{TEST_NGINX_CERT_DIR} ||= dirname(realpath(abs_path(__FILE__))); +my $port = server_port; +if ($port < 65535) { + $port++; +} else { + $port--; +} +$ENV{TEST_NGINX_SERVER_SSL_PORT} = $port; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +env_to_nginx("PATH"); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: read process, pattern is read line +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"echo", "hello world"}) + + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello world + + + +=== TEST 2: read process, read line without line break +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"echo", "-n", "hello world"}) + + local data, err, partial = proc:stdout_read_line() + if not data then + ngx.say(err) + ngx.say(partial) + else + ngx.say(data) + end + } + } +--- response_body +closed +hello world + + + +=== TEST 3: read process, pattern is read bytes +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"echo", "hello world"}) + + local data, err = proc:stdout_read_bytes(5) + if not data then + ngx.say(err) + else + ngx.say(data) + end + + data, err = proc:stdout_read_bytes(6) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello + world + + + +=== TEST 4: read process, bytes length is zero +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"echo", "hello world"}) + + local data, err = proc:stdout_read_bytes(0) + if not data then + ngx.say(err) + else + ngx.say("data:", data) + end + } + } +--- response_body +data: + + + +=== TEST 5: read process, bytes length is less than zero +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"echo", "hello world"}) + + local ok, err = pcall(proc.stdout_read_bytes, proc, -1) + if not ok then + ngx.say(err) + end + } + } +--- response_body +bad len argument + + + +=== TEST 6: read process, bytes length is more than data +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"echo", "hello world"}) + + local data, err = proc:stdout_read_bytes(20) + if not data then + ngx.say(err) + else + ngx.say("data:", data) + end + } + } +--- response_body +closed + + + +=== TEST 7: read process, pattern is read all +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "echo -n hello && sleep 0.05 && echo -n world"}) + + local data, err = proc:stdout_read_all() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +helloworld + + + +=== TEST 8: read process, pattern is read any +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "echo -n hello && sleep 0.05 && echo -n world"}) + + -- increase timeout to ensure read_any could return before timeout + proc:set_timeouts(5000, 5000, 5000, nil) + local data, err = proc:stdout_read_any(1024) + if not data then + ngx.say(err) + else + ngx.say(data) + end + + data, err = proc:stdout_read_any(1024) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello +world + + + +=== TEST 9: read process, pattern is read any, with limited, max <= 0 +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "echo -n hello && sleep 0.05 && echo -n world"}) + + local ok, err = pcall(proc.stdout_read_any, proc, 0) + if not ok then + ngx.say(err) + end + } + } +--- response_body +bad max argument + + + +=== TEST 10: read process, pattern is read any, with limited, limit larger than read data +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "echo -n hello && sleep 0.05 && echo -n world"}) + + local data, err = proc:stdout_read_any(1024) + if not data then + ngx.say(err) + else + ngx.say(data) + end + + data, err = proc:stdout_read_any(512) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello +world + + + +=== TEST 11: read process, pattern is read any, with limited, limit smaller than read data +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "echo -n hello && sleep 0.05 && echo -n world"}) + + local data, err = proc:stdout_read_any(4) + if not data then + ngx.say(err) + else + ngx.say(data) + end + + data, err = proc:stdout_read_any(3) + if not data then + ngx.say(err) + else + ngx.say(data) + end + + data, err = proc:stdout_read_any(1024) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hell +o +world + + + +=== TEST 12: read process, without yield +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"echo", "hello world"}) + + ngx.sleep(0.05) + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello world + + + +=== TEST 13: read process, without yield, get partial data +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"echo", "-n", "hello world"}) + + ngx.sleep(0.05) + local data, err, partial = proc:stdout_read_line() + if not data then + ngx.say(err) + ngx.say(partial) + else + ngx.say(data) + end + } + } +--- response_body +closed +hello world + + + +=== TEST 14: read process, without yield, pattern is read bytes +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"echo", "hello world"}) + + ngx.sleep(0.05) + local data, err = proc:stdout_read_bytes(9) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello wor + + + +=== TEST 15: read process, without yield, pattern is read all +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "echo hello && echo world"}) + + ngx.sleep(0.05) + local data, err = proc:stdout_read_all() + if not data then + ngx.say(err) + else + ngx.print(data) + end + } + } +--- response_body +hello +world + + + +=== TEST 16: read process, without yield, pattern is read any +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "echo -n hello && sleep 0.01 && echo -n world"}) + + ngx.sleep(0.05) + local data, err = proc:stdout_read_any(1024) + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +helloworld + + + +=== TEST 17: read process, without yield, read more data than preallocated buffer +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local s = ("0123"):rep(1024) + local proc = ngx_pipe.spawn({"echo", "-n", s}) + + ngx.sleep(0.05) + local data, err = proc:stdout_read_all() + if not data then + ngx.say(err) + elseif data ~= s then + ngx.say("actual read:", data) + else + ngx.say("ok") + end + } + } +--- response_body +ok + + + +=== TEST 18: read process, without yield, read more partial data than preallocated buffer +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local s = ("0123"):rep(1024) + local proc = ngx_pipe.spawn({"echo", "-n", s}) + + ngx.sleep(0.05) + local data, err, partial = proc:stdout_read_line() + if not data then + ngx.say(err) + if partial ~= s then + ngx.say("actual read:", data) + else + ngx.say("ok") + end + end + } + } +--- response_body +closed +ok + + + +=== TEST 19: read process, with yield, read more data than preallocated buffer +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local s = ("0123"):rep(1024) + local proc = ngx_pipe.spawn({"echo", "-n", s}) + + local data, err = proc:stdout_read_all() + if not data then + ngx.say(err) + elseif data ~= s then + ngx.say("actual read:", data) + else + ngx.say("ok") + end + } + } +--- response_body +ok +--- no_error_log +[error] +--- error_log +lua pipe read yielding + + + +=== TEST 20: read process, with yield, read more partial data than preallocated buffer +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local s = ("0123"):rep(1024) + local proc = ngx_pipe.spawn({"echo", "-n", s}) + + local data, err, partial = proc:stdout_read_line() + if not data then + ngx.say(err) + if partial ~= s then + ngx.say("actual read:", data) + else + ngx.say("ok") + end + end + } + } +--- no_error_log +[error] +--- error_log +lua pipe read yielding +--- response_body +closed +ok + + + +=== TEST 21: read process, mix read pattern +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local script = [[ + echo -n hello + sleep 0.1 + echo world + echo more + sleep 0.1 + echo -n da + sleep 0.1 + echo -n ta + ]] + local proc = ngx_pipe.spawn({"sh", "-c", script}) + + local function check_call(proc, func, ...) + local data, err = func(proc, ...) + if not data then + ngx.say(err) + ngx.exit(ngx.OK) + end + ngx.say(data) + end + + ngx.say("reading any") + check_call(proc, proc.stdout_read_any, 1024) + + ngx.say("reading 3") + check_call(proc, proc.stdout_read_bytes, 3) + + ngx.say("reading line") + check_call(proc, proc.stdout_read_line) + + ngx.say("reading 2") + check_call(proc, proc.stdout_read_bytes, 2) + + ngx.say("reading any") + check_call(proc, proc.stdout_read_any, 1024) + + ngx.say("reading all") + check_call(proc, proc.stdout_read_all) + } + } +--- response_body +reading any +hello +reading 3 +wor +reading line +ld +reading 2 +mo +reading any +re + +reading all +data + + + +=== TEST 22: read process, no data to read +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sleep", 0.01}) + + local data, err = proc:stdout_read_any(1024) + if not data then + ngx.say(err) + else + ngx.say("data:", data) + end + } + } +--- response_body +closed + + + +=== TEST 23: read process, no data to read, use read all +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sleep", 0.01}) + + local data, err = proc:stdout_read_all() + if not data then + ngx.say(err) + else + ngx.say("data:", data) + end + } + } +--- response_body +data: + + + +=== TEST 24: read process after waiting +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"echo", "hello world"}) + + local ok, err = proc:wait() + if not ok then + ngx.say("wait failed: ", err) + return + end + + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +closed + + + +=== TEST 25: more than one coroutines read a process +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "sleep 0.1 && echo hello && echo world"}) + + local function read() + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + end + + local th1 = ngx.thread.spawn(read) + local th2 = ngx.thread.spawn(read) + ngx.thread.wait(th1) + ngx.thread.wait(th2) + ngx.thread.spawn(read) + } + } +--- response_body +pipe busy reading +hello +world + + + +=== TEST 26: read process, timeout +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sleep", "10s"}) + + proc:set_timeouts(nil, 100) + + local ok, err = proc:stdout_read_line() + if not ok then + ngx.say(err) + else + ngx.say("ok") + end + } + } +--- response_body +timeout +--- no_error_log +[error] +--- error_log +lua pipe add timer for reading: 100(ms) + + + +=== TEST 27: read process, aborted by uthread kill +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "sleep 0.1 && echo hello"}) + + local function read() + proc:stdout_read_line() + ngx.log(ngx.ERR, "can't reach here") + end + + local th = ngx.thread.spawn(read) + ngx.thread.kill(th) + + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello +--- no_error_log +[error] +--- error_log +lua pipe read process: +lua pipe proc read stdout cleanup + + + +=== TEST 28: read process while waiting process in other request +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + package.loaded.proc = ngx_pipe.spawn({"sh", "-c", "sleep 0.01 && echo hello world && exit 2"}) + local res1 = ngx.location.capture("/req1") + local res2 = ngx.location.capture("/req2") + ngx.print(res1.body) + ngx.print(res2.body) + } + } + + location = /req1 { + content_by_lua_block { + local data, err = package.loaded.proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } + + location = /req2 { + content_by_lua_block { + local ok, reason, status = package.loaded.proc:wait() + if not ok then + ngx.say(reason) + ngx.say(status) + end + } + } +--- response_body +hello world +exit +2 + + + +=== TEST 29: read process while waiting process in other request, return error +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + package.loaded.proc = ngx_pipe.spawn({"sh", "-c", "sleep 10s"}) + local res1, res2 = ngx.location.capture_multi{{"/req1"}, {"/req2"}} + ngx.print(res1.body) + ngx.print(res2.body) + } + } + + location = /req1 { + content_by_lua_block { + local proc = package.loaded.proc + proc:set_timeouts(nil, 100) + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } + + location = /req2 { + content_by_lua_block { + local proc = package.loaded.proc + -- sleep to ensure proc is already spawned under Valgrind + ngx.sleep(0.2) + os.execute("kill -TERM " .. proc:pid()) + local ok, reason, status = proc:wait() + if not ok then + ngx.say(reason) + ngx.say(status) + end + } + } +--- response_body +timeout +signal +15 + + + +=== TEST 30: user case with read and wait +--- no_checke_leak +--- 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; + } +--- config + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + + location /t { + set $addr 127.0.0.1:$TEST_NGINX_SERVER_SSL_PORT; + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local addr = ngx.var.addr; + local f, err = ngx_pipe.spawn({"sh", "-c", "echo 'Q' | openssl s_client -connect " .. addr}) + if not f then + ngx.say(err) + return + end + + local out = f:stdout_read_all() + if out:find("CONNECTED", 1, true) ~= 1 then + ngx.say("could not find CONNECTED in output: ", out) + local stderr_out = f:stderr_read_all() + ngx.say("the message from stderr is: ", stderr_out) + return + end + + local ok, reason = f:wait() + if not ok then + ngx.say(reason) + else + ngx.say('ok') + ngx.say(reason) + end + } + } +--- response_body +ok +exit + + + +=== TEST 31: ensure reading process in phases without yield support is disabled +--- http_config + init_worker_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"echo", "hello world"}) + + local ok, err = pcall(proc.stdout_read_line, proc) + if not ok then + package.loaded.res = err + end + } +--- config + location = /t { + content_by_lua_block { + ngx.say(package.loaded.res) + } + } +--- response_body_like +.+ API disabled in the context of init_worker_by_lua\* + + + +=== TEST 32: but we could spawn it in init_worker_by_lua and read it later +--- http_config + init_worker_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"echo", "hello world"}) + package.loaded.proc = proc + } + +--- config + location = /t { + content_by_lua_block { + local proc = package.loaded.proc + + if proc then + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + + -- we could only read once as we only spawn the proess once + package.loaded.proc = nil + + else + + -- so just return the expected data in repeated tests. + ngx.say("closed") + end + } + } +--- response_body +closed + + + +=== TEST 33: read process, aborted by uthread kill, with graceful shutdown +--- user_files +>>> a.lua +local ngx_pipe = require "ngx.pipe" +local proc = ngx_pipe.spawn({"bash"}) + +local function func() + proc:stdout_read_line() + ngx.log(ngx.ERR, "can't reach here") +end + +local th = ngx.thread.spawn(func) +ngx.thread.kill(th) + +local data, err = proc:kill(9) -- SIGKILL +if not data then + io.stdout:write("proc:kill(9) err: ", err) +else + io.stdout:write("ok") +end + +--- config + location = /t { + content_by_lua_block { + local helper = require "helper" + local f = io.open("$TEST_NGINX_HTML_DIR/a.lua") + local code = f:read("*a") + local proc = helper.run_lua_with_graceful_shutdown("$TEST_NGINX_HTML_DIR", code) + proc:set_timeouts(100, 100, 100, 100) + + local data, err = proc:stdout_read_all() + if not data then + ngx.say("stdout err: ", err) + else + ngx.say("stdout: ", data) + end + + local data, err = proc:stderr_read_any(4096) + if not data then + ngx.say("stderr err: ", err) + else + ngx.say("stderr: ", data) + end + } + } +--- response_body +stdout: ok +stderr err: closed +--- no_error_log +[error] + + + +=== TEST 34: spawn process with stdout_read_timeout option +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sleep", "10s"}, { + stdout_read_timeout = 100 + }) + + local data, err = proc:stdout_read_line() + if not data then + ngx.say("stdout err: ", err) + else + ngx.say("stdout: ", data) + end + } + } +--- response_body +stdout err: timeout +--- error_log +lua pipe add timer for reading: 100(ms) +--- no_error_log +[error] + + + +=== TEST 35: start a daemon process +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "daemonize /usr/bin/sleep 30 >/dev/null 2>&1"}) + + local data, err = proc:stdout_read_all() + if not data then + ngx.say(err) + end + + ngx.say("OK") + } + } +--- response_body +OK +--- no_error_log +[error] diff --git a/t/pipe.t b/t/pipe.t new file mode 100644 index 000000000..03ef9a7cf --- /dev/null +++ b/t/pipe.t @@ -0,0 +1,1910 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 19) + 2; + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + my $init_by_lua_block = $block->init_by_lua_block || ''; + + $http_config .= <<_EOC_; + lua_package_path '$t::TestCore::lua_package_path'; + init_by_lua_block { + $t::TestCore::init_by_lua_block + $init_by_lua_block + } +_EOC_ + + $block->set_value("http_config", $http_config); + + if (!defined $block->error_log) { + $block->set_value("no_error_log", "[error]"); + } + + if (!defined $block->request) { + $block->set_value("request", "GET /t"); + } +}); + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::lua_package_path"; + +env_to_nginx("PATH"); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: check pipe spawn arguments +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + + local function check_error(...) + local data, err = pcall(...) + if not data then + ngx.say(err) + else + ngx.say('ok') + end + end + + check_error(ngx_pipe.spawn, nil) + check_error(ngx_pipe.spawn, {}) + check_error(ngx_pipe.spawn, {"ls"}, {buffer_size = 0}) + check_error(ngx_pipe.spawn, {"ls"}, {buffer_size = 0.5}) + check_error(ngx_pipe.spawn, {"ls"}, {buffer_size = "1"}) + check_error(ngx_pipe.spawn, {"ls"}, {buffer_size = true}) + } + } +--- response_body +bad args arg: table expected, got nil +bad args arg: non-empty table expected +bad buffer_size option +bad buffer_size option +ok +bad buffer_size option + + + +=== TEST 2: spawn process, with buffer_size option +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"ls"}, {buffer_size = 256}) + if not proc then + ngx.say(err) + else + ngx.say('ok') + end + } + } +--- response_body +ok +--- error_log eval +qr/lua pipe spawn process:[0-9A-F]+ pid:\d+ merge_stderr:0 buffer_size:256/ +--- no_error_log +[error] + + + +=== TEST 3: ensure process is destroyed in GC +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + do + local proc, err = ngx_pipe.spawn({"ls", "-l"}) + if not proc then + ngx.say(err) + return + end + end + + collectgarbage() + ngx.say("ok") + } + } +--- response_body +ok +--- no_error_log +[error] +--- error_log +lua pipe destroy process: + + + +=== TEST 4: check phase for process wait +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", 0.1}) + if not proc then + ngx.say(err) + return + end + + package.loaded.proc = proc + } + + log_by_lua_block { + package.loaded.proc:wait() + } + } +--- error_log +API disabled in the context of log_by_lua + + + +=== TEST 5: check process wait arguments +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", 0.1}) + proc.wait() + } + } +--- error_code: 500 +--- ignore_response_body +--- error_log eval +qr/\[error\] .*? runtime error: content_by_lua\(nginx\.conf\:\d+\):\d+: not a process instance/ +--- no_error_log +[crit] + + + +=== TEST 6: wait an already waited process +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"ls"}) + if not proc then + ngx.say(err) + return + end + + local ok, err = proc:wait() + if not ok then + ngx.say(err) + return + end + + local ok, err = proc:wait() + if not ok then + ngx.say(err) + end + } + } +--- response_body +exited + + + +=== TEST 7: more than one coroutines wait a process +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", 0.1}) + if not proc then + ngx.say(err) + return + end + + local function wait() + local ok, err = proc:wait() + if not ok then + ngx.say(err) + end + end + + local th1 = ngx.thread.spawn(wait) + local th2 = ngx.thread.spawn(wait) + ngx.thread.wait(th1) + ngx.thread.wait(th2) + ngx.thread.spawn(wait) + } + } +--- response_body +pipe busy waiting +exited + + + +=== TEST 8: wait process, process exited abnormally before waiting +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sh", "-c", "sleep 0.1 && exit 2"}) + if not proc then + ngx.say(err) + return + end + + ngx.sleep(0.2) + local ok, reason, status = proc:wait() + if ok == false then + ngx.say(reason, " status: ", status) + elseif ok == nil then + ngx.say(reason) + else + ngx.say("ok") + end + } + } +--- response_body +exit status: 2 +--- no_error_log +[error] +--- error_log +lua pipe wait process: + + + +=== TEST 9: wait process, process killed by signal before waiting +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", "10s"}) + if not proc then + ngx.say(err) + return + end + + -- sleep to ensure proc is already spawned under Valgrind + ngx.sleep(0.2) + os.execute("kill -INT " .. proc:pid()) + + ngx.sleep(0.2) + local ok, reason, status = proc:wait() + if ok == false then + ngx.say(reason, " status: ", status) + elseif ok == nil then + ngx.say(reason) + else + ngx.say("ok") + end + } + } +--- response_body +--- response_body +signal status: 2 +--- no_error_log +[error] +--- error_log +lua pipe wait process: + + + +=== TEST 10: wait process, process exited before waiting +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", 0.1}) + if not proc then + ngx.say(err) + return + end + + ngx.sleep(0.2) + local ok, reason, status = proc:wait() + if ok ~= nil then + ngx.say("ok: ", ok) + ngx.say(reason, " status: ", status) + else + ngx.say(reason) + end + } + } +--- response_body +ok: true +exit status: 0 +--- no_error_log +[error] +--- error_log +lua pipe wait process: + + + +=== TEST 11: pid() return process pid +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", 0.1}) + if not proc then + ngx.say(err) + return + end + + local pid = proc:pid() + ngx.say("pid: ", pid, " type: ", type(pid)) + + local ok, reason, status = proc:wait() + if not ok then + ngx.say(reason) + return + end + + pid = proc:pid() + ngx.say("pid: ", pid, " type: ", type(pid)) + } + } +--- response_body_like +pid: \d+ type: number +pid: \d+ type: number + + + +=== TEST 12: wait process, aborted by uthread kill +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", "10s"}) + if not proc then + ngx.say(err) + return + end + + local function wait() + proc:wait() + ngx.log(ngx.ERR, "can't reach here") + end + + local th = ngx.thread.spawn(wait) + ngx.thread.kill(th) + + proc:set_timeouts(nil, nil, nil, 100) + local ok, err = proc:wait() + if not ok then + ngx.say(err) + else + ngx.say("ok") + end + } + } +--- response_body +timeout +--- no_error_log +[error] +--- error_log +lua pipe wait process: +lua pipe proc wait cleanup + + + +=== TEST 13: wait process which exited abnormally +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sh", "-c", "sleep 0.1 && exit 2"}) + if not proc then + ngx.say(err) + return + end + + local ok, reason, status = proc:wait() + if not ok then + ngx.say(reason) + ngx.say(status) + else + ngx.say("ok") + end + } + } +--- response_body +exit +2 + + + +=== TEST 14: wait process which terminated by signal +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", "5s"}) + if not proc then + ngx.say(err) + return + end + + local pid = proc:pid() + + local function wait() + local ok, reason, status = proc:wait() + if not ok then + ngx.say(reason) + ngx.say(status) + else + ngx.say("ok") + end + end + + ngx.thread.spawn(wait) + -- sleep to ensure proc is already spawned under Valgrind + ngx.sleep(0.2) + os.execute("kill -TERM " .. pid) + } + } +--- response_body +signal +15 + + + +=== TEST 15: avoid set_timeouts overflow +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", "5s"}) + if not proc then + ngx.say(err) + return + end + + local function check_timeouts_overflow(write, stdout_read, stderr_read, wait) + local ok, err = pcall(proc.set_timeouts, proc, write, stdout_read, stderr_read, wait) + if not ok then + ngx.say("failed to set timeouts: ", err) + else + ngx.say("set_timeouts: ok") + end + end + + ngx.say("write_timeout:") + check_timeouts_overflow((2 ^ 32) - 1, 500, 500, 500) + check_timeouts_overflow(2 ^ 32, 500, 500, 500) + + ngx.say("\nstdout_read_timeout:") + check_timeouts_overflow(500, (2 ^ 32) - 1, 500, 500) + check_timeouts_overflow(500, 2 ^ 32, 500, 500) + + ngx.say("\nstderr_read_timeout:") + check_timeouts_overflow(500, 500, (2 ^ 32) - 1, 500) + check_timeouts_overflow(500, 500, 2 ^ 32, 500) + + ngx.say("\nwait_timeout:") + check_timeouts_overflow(500, 500, 500, (2 ^ 32) - 1) + check_timeouts_overflow(500, 500, 500, 2 ^ 32) + } + } +--- response_body +write_timeout: +set_timeouts: ok +failed to set timeouts: bad write_timeout option + +stdout_read_timeout: +set_timeouts: ok +failed to set timeouts: bad stdout_read_timeout option + +stderr_read_timeout: +set_timeouts: ok +failed to set timeouts: bad stderr_read_timeout option + +wait_timeout: +set_timeouts: ok +failed to set timeouts: bad wait_timeout option + + + +=== TEST 16: avoid setting negative timeout +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", "5s"}) + if not proc then + ngx.say(err) + return + end + + local function check_timeouts(write, stdout_read, stderr_read, wait) + local ok, err = pcall(proc.set_timeouts, proc, write, stdout_read, stderr_read, wait) + if not ok then + ngx.say("failed to set timeouts: ", err) + else + ngx.say("set_timeouts: ok") + end + end + + check_timeouts(0, 0, 0, 0) + + ngx.say("\nwrite_timeout:") + check_timeouts(-1, 0, 0, 0) + + ngx.say("\nstdout_read_timeout:") + check_timeouts(0, -1, 0, 0) + + ngx.say("\nstderr_read_timeout:") + check_timeouts(0, 0, -1, 0) + + ngx.say("\nwait_timeout:") + check_timeouts(0, 0, 0, -1) + } + } +--- response_body +set_timeouts: ok + +write_timeout: +failed to set timeouts: bad write_timeout option + +stdout_read_timeout: +failed to set timeouts: bad stdout_read_timeout option + +stderr_read_timeout: +failed to set timeouts: bad stderr_read_timeout option + +wait_timeout: +failed to set timeouts: bad wait_timeout option + + + +=== TEST 17: wait process, timeout +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", "10s"}) + if not proc then + ngx.say(err) + return + end + + proc:set_timeouts(nil, nil, nil, 100) + + local ok, err = proc:wait() + if not ok then + ngx.say(err) + else + ngx.say("ok") + end + } + } +--- response_body +timeout +--- no_error_log +[error] +--- error_log +lua pipe wait process: +lua pipe add timer for waiting: 100(ms) + + + +=== TEST 18: wait process, timeout, test for race condition +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + -- specify a larger timeout, so that there could be enough change + -- to run posted event path + local proc, err = ngx_pipe.spawn({"sleep", 1}) + if not proc then + ngx.say(err) + return + end + + proc:set_timeouts(nil, nil, nil, 1000) + + local ok, err = proc:wait() + if not ok and err ~= 'timeout' then + ngx.say(err) + else + ngx.say("ok") + end + } + } +--- response_body +ok + + + +=== TEST 19: user case with send and shutdown +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + + local function count_char(...) + local proc = ngx_pipe.spawn({'wc', '-c'}) + local args = {...} + for i = 1, #args do + local bytes, err = proc:write(args[i]) + if not bytes then + ngx.say(err) + return + end + end + + local ok, err = proc:shutdown('stdin') + if not ok then + ngx.say(err) + return + end + + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + return + end + + ngx.say(data) + end + + count_char('') + count_char('a') + count_char('hello') + count_char(("1234"):rep(2048)) + } + } +--- response_body +0 +1 +5 +8192 + + + +=== TEST 20: shutdown before write/stdout_read/stderr_read +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "echo 'he\nllo' && >&2 echo 'wo\nrld'"}) + + local function shutdown(direction) + local ok, err = proc:shutdown(direction) + if not ok then + ngx.log(ngx.ERR, err) + end + end + + shutdown('stdin') + local ok, err = proc:write("test") + if not ok then + ngx.say("stdin: ", err) + end + + shutdown('stdout') + local ok, err = proc:stdout_read_line() + if not ok then + ngx.say("stdout: ", err) + end + + shutdown('stderr') + local ok, err = proc:stderr_read_line() + if not ok then + ngx.say("stderr: ", err) + end + } + } +--- response_body +stdin: closed +stdout: closed +stderr: closed + + + +=== TEST 21: shutdown after write/stdout_read/stderr_read +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "echo 'he\nllo' && >&2 echo 'wo\nrld'"}) + + local function shutdown(direction) + local ok, err = proc:shutdown(direction) + if not ok then + ngx.log(ngx.ERR, err) + end + end + + proc:write("test") + shutdown('stdin') + local ok, err = proc:write("test") + if not ok then + ngx.say("stdin: ", err) + end + + proc:stdout_read_line() + shutdown('stdout') + local ok, err = proc:stdout_read_line() + if not ok then + ngx.say("stdout: ", err) + end + + proc:stderr_read_line() + shutdown('stderr') + local ok, err = proc:stderr_read_line() + if not ok then + ngx.say("stderr: ", err) + end + } + } +--- response_body +stdin: closed +stdout: closed +stderr: closed + + + +=== TEST 22: shutdown repeatedly is harmless +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "echo 'he\nllo' && >&2 echo 'wo\nrld'"}) + + local function shutdown(direction) + local ok, err = proc:shutdown(direction) + if not ok then + ngx.log(ngx.ERR, err) + end + end + + proc:write("test") + shutdown('stdin') + shutdown('stdin') + local ok, err = proc:write("test") + if not ok then + ngx.say("stdin: ", err) + end + + proc:stdout_read_line() + shutdown('stdout') + shutdown('stdout') + local ok, err = proc:stdout_read_line() + if not ok then + ngx.say("stdout: ", err) + end + + proc:stderr_read_line() + shutdown('stderr') + shutdown('stderr') + local ok, err = proc:stderr_read_line() + if not ok then + ngx.say("stderr: ", err) + end + } + } +--- response_body +stdin: closed +stdout: closed +stderr: closed + + + +=== TEST 23: shutdown unknown direction +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sh", "-c", "echo hello && >&2 echo world"}) + + local function shutdown(direction) + local ok, err = pcall(proc.shutdown, proc, direction) + if not ok then + ngx.say(err) + end + end + + shutdown("read") + shutdown(0) + } + } +--- response_body +bad shutdown arg: read +bad shutdown arg: 0 + + + +=== TEST 24: shutdown a direction while a coroutine is waiting on it +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sleep", "10s"}) + proc:set_timeouts(100, 100, 100) + + ngx.thread.spawn(function() + local ok, err = proc:stdout_read_line() + if not ok then + ngx.say("read stdout err: ", err) + else + ngx.say("read stdout ok") + end + end) + local ok, err = proc:shutdown('stdout') + if not ok then + ngx.say("shutdown stdout err: ", err) + end + + ngx.thread.spawn(function() + local ok, err = proc:stderr_read_line() + if not ok then + ngx.say("read stderr err: ", err) + else + ngx.say("read stderr ok") + end + end) + local ok, err = proc:shutdown('stderr') + if not ok then + ngx.say("shutdown stderr err: ", err) + end + + local data = ("1234"):rep(2048) + local total = 0 + local step = #data + -- make writers blocked later + while true do + local data, err = proc:write(data) + if not data then + ngx.say("write stdin err: ", err) + break + end + + total = total + step + if total > 64 * step then + break + end + end + + ngx.thread.spawn(function() + local ok, err = proc:write(data) + if not ok then + ngx.say("write stdin err: ", err) + else + ngx.say("write stdin ok") + end + end) + local ok, err = proc:shutdown('stdin') + if not ok then + ngx.say("shutdown stdin err: ", err) + end + } + } +--- response_body +read stdout err: aborted +read stderr err: aborted +write stdin err: timeout +write stdin err: aborted +--- no_error_log +[error] +--- grep_error_log eval +qr/lua pipe \w+ yielding/ +--- grep_error_log_out +lua pipe read yielding +lua pipe read yielding +lua pipe write yielding +lua pipe write yielding + + + +=== TEST 25: shutdown when merge_stderr is true +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local args = {"sh", "-c", "echo 'he\nllo' && >&2 echo 'wo\nrld'"} + local opts = {merge_stderr = true} + local function shutdown(proc, direction) + local ok, err = proc:shutdown(direction) + if not ok then + ngx.say("shutdown: ", err) + end + end + + local function read_proc(proc) + local ok, err = proc:stdout_read_line() + if not ok then + ngx.say("stdout: ", err) + end + end + + ngx.say("shutdown stdout after read") + local proc = ngx_pipe.spawn(args, opts) + proc:stdout_read_line() + shutdown(proc, 'stdout') + read_proc(proc) + + ngx.say("shutdown stdout before read") + local proc = ngx_pipe.spawn(args, opts) + shutdown(proc, 'stdout') + read_proc(proc) + + ngx.say("shutdown stderr") + local proc = ngx_pipe.spawn(args, opts) + shutdown(proc, 'stderr') + } + } +--- response_body +shutdown stdout after read +stdout: closed +shutdown stdout before read +stdout: closed +shutdown stderr +shutdown: merged to stdout + + + +=== TEST 26: interrupt signals which break io.popen should not break ngx.pipe IO +github issue openresty/resty-cli#35 +--- http_config + lua_sa_restart off; + +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local filename = "$TEST_NGINX_HTML_DIR/testfile" + local f = io.open(filename, 'w') + f:write("testfile") + f:close() + + local proc = ngx_pipe.spawn({"openssl", "dgst", "-md5", filename}) + local data, err = proc:stdout_read_all() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body_like +MD5\([^)]+\)= 8bc944dbd052ef51652e70a5104492e3 + + + +=== TEST 27: ensure signals ignored by Nginx are reset. +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + + local binname = "$TEST_NGINX_HTML_DIR/sigpipe" + local filename = "$TEST_NGINX_HTML_DIR/sigpipe.c" + local f = io.open(filename, 'w') + f:write([[ + #include + #include + #include + #include + + int main(void) + { + kill(getpid(), SIGPIPE); + printf("I was not killed by SIGPIPE\n"); + return 0; + } + ]]) + f:close() + + local cmd = "gcc " .. filename .. " -o " .. binname + local proc, err = ngx_pipe.spawn({"sh", "-c", cmd}) + if not proc then + ngx.say("spawn ", cmd, " failed: ", err) + return + end + + local msg = proc:stderr_read_all() + local ok = proc:wait() + if not ok then + ngx.say("wait failed: ", msg) + return + end + + local proc, err = ngx_pipe.spawn({binname}) + if not proc then + ngx.say("spawn ", binname, " failed: ", err) + return + end + + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +closed + + + +=== TEST 28: ensure spawning process in init_by_lua is disabled. +--- init_by_lua_block + local ngx_pipe = require "ngx.pipe" + + local ok, err = pcall(ngx_pipe.spawn, {"echo", "hello world"}) + if not ok then + package.loaded.res = err + end +--- config + location = /t { + content_by_lua_block { + ngx.say(package.loaded.res) + } + } +--- response_body +API disabled in the current context + + + +=== TEST 29: interact with bc +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + + local proc = ngx_pipe.spawn({"bc"}, {merge_stderr = true}) + proc:set_timeouts(1000, 1000, 1000) + + local function check_call(proc, func, ...) + local data, err = func(proc, ...) + if not data then + ngx.say("ERR: ", err) + ngx.exit(ngx.OK) + else + ngx.log(ngx.WARN, "bc say ", data) + end + end + + local step = 0.05 + ngx.sleep(step) + + proc:write("1 + 2\n") + ngx.sleep(step) + check_call(proc, proc.stdout_read_any, 1024) + + proc:write("2 ^ 3\n") + ngx.sleep(step) + check_call(proc, proc.stdout_read_any, 1024) + + proc:write("quit\n") + + local ok, reason, status = proc:wait() + if not ok then + ngx.say(reason) + ngx.say(status) + else + ngx.say("ok") + end + } + } +--- response_body +ok +--- no_error_log +[error] +--- error_log +bc say 3 +bc say 8 + + + +=== TEST 30: allow to specify nil as terminator +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local args = {"sh", "-c", "echo hello", nil, "echo world"} + local proc, err = ngx_pipe.spawn(args) + if not proc then + ngx.say(err) + return + end + + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +hello + + + +=== TEST 31: specify a string to spawn works like os.execute +--- config + location = /t { + content_by_lua_block { + collectgarbage() + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn("echo 'hello' && >&2 echo 'world'") + + local data, err = proc:stdout_read_line() + if not data then + ngx.say("stdout: ", err) + else + ngx.say("stdout: ", data) + end + + local data, err = proc:stderr_read_line() + if not data then + ngx.say("stderr: ", err) + else + ngx.say("stderr: ", data) + end + } + } +--- response_body +stdout: hello +stderr: world + + + +=== TEST 32: wait process which executed failed +When execvp failed, we let OS free the memory. Therefore we have to skip this +test under Valgrind mode. +--- skip_eval: 3: defined $ENV{TEST_NGINX_USE_VALGRIND} +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"/exit", "2"}) + if not proc then + ngx.say(err) + return + end + + local ok, reason, status = proc:wait() + if not ok then + ngx.say(reason) + ngx.say(status) + else + ngx.say("ok") + end + } + } +--- response_body +exit +1 +--- error_log +lua pipe child execvp() failed while executing /exit (2: No such file or directory) + + + +=== TEST 33: wait process, process exited normally after waiting +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", 0.1}) + if not proc then + ngx.say(err) + return + end + + local ok, reason, status = proc:wait() + if ok ~= nil then + ngx.say("ok: ", ok) + ngx.say(reason, " status: ", status) + else + ngx.say(reason) + end + } + } +--- response_body +ok: true +exit status: 0 +--- no_error_log +[error] +--- error_log +lua pipe wait process: + + + +=== TEST 34: kill process with invalid signal +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sleep", 10}) + + local function check_pcall(f, ...) + local ok, err = pcall(f, ...) + if not ok then + ngx.say(err) + end + end + + local function check_call(f, ...) + local ok, err = f(...) + if not ok then + ngx.say(err) + end + end + + check_pcall(proc.kill, proc) + check_call(proc.kill, proc, 10000) + } + } +--- response_body +bad signal arg: number expected, got nil +invalid signal + + + +=== TEST 35: kill exited process +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc = ngx_pipe.spawn({"sleep", 0.1}) + + local ok, err = proc:wait() + if not ok then + ngx.say(err) + return + end + + local function check_call(f, ...) + local ok, err = f(...) + if not ok then + ngx.say(err) + end + end + + local SIGKILL = 9 + check_call(proc.kill, proc, SIGKILL) + + proc = ngx_pipe.spawn({"sleep", 0.1}) + ngx.sleep(0.5) + check_call(proc.kill, proc, SIGKILL) + } + } +--- response_body +exited +exited + + + +=== TEST 36: wait process which is terminated by a signal, using proc.kill +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", "5s"}) + if not proc then + ngx.say(err) + return + end + + local pid = proc:pid() + + local function wait() + local ok, reason, status = proc:wait() + if not ok then + ngx.say(reason) + ngx.say(status) + else + ngx.say("ok") + end + end + + ngx.thread.spawn(wait) + -- sleep to ensure proc is already spawned under Valgrind + ngx.sleep(0.2) + + local SIGTERM = 15 + local ok, err = proc:kill(SIGTERM) + if not ok then + ngx.say(err) + end + } + } +--- response_body +signal +15 + + + +=== TEST 37: kill living sub-process when the process instance is collected by GC. +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + do + local proc, err = ngx_pipe.spawn({"sleep", 3600}) + if not proc then + ngx.say(err) + return + end + end + + collectgarbage() + ngx.say("ok") + } + } +--- response_body +ok +--- no_error_log +[error] +--- error_log +lua pipe destroy process: +lua pipe kill process: + + + +=== TEST 38: kill living sub-process during Lua VM destruction. +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", 3600}) + if not proc then + ngx.say(err) + return + end + + ngx.say("ok") + } + } +--- response_body +ok +--- error_log +lua pipe destroy process: +lua pipe kill process: +--- no_shutdown_error_log +lua pipe destroy process: +lua pipe kill process: + + + +=== TEST 39: avoided overwritting log fd when stderr is used as destination. +--- config + location = /t { + content_by_lua_block { + local function get_ngx_bin_path() + local ffi = require "ffi" + ffi.cdef[[char **ngx_argv;]] + return ffi.string(ffi.C.ngx_argv[0]) + end + + local conf_file = "$TEST_NGINX_HTML_DIR/nginx.conf" + local nginx = get_ngx_bin_path() + + local cmd = nginx .. " -p $TEST_NGINX_HTML_DIR -c " .. conf_file + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn(cmd) + if not proc then + ngx.log(ngx.ERR, err) + return + end + + local data, err = proc:stderr_read_all() + if not data then + ngx.log(ngx.ERR, err) + return + end + + ngx.say(data) + } + } +--- user_files +>>> nginx.conf +events { + worker_connections 64; +} +error_log stderr error; +daemon off; +master_process off; +worker_processes 1; +http { + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + init_worker_by_lua_block { + ngx.timer.at(0, function() + require "ngx.pipe".spawn{"no-such-cmd"}:wait() + os.exit(0) + end) + } +} +>>> logs/error.log +--- response_body_like +lua pipe child execvp\(\) failed while executing no-such-cmd \(2: No such file or directory\) + + + +=== TEST 40: avoid shell cmd's constants being GCed +--- init_by_lua_block + local ngx_pipe = require "ngx.pipe" + package.loaded.pipe = ngx_pipe + +--- config + location = /t { + content_by_lua_block { + collectgarbage() + local proc, err = package.loaded.pipe.spawn("wc --help") + if not proc then + ngx.say(err) + return + end + + local ok, reason, status = proc:wait() + if not ok then + ngx.say(reason) + ngx.say(status) + else + ngx.say("ok") + end + } + } +--- response_body +ok + + + +=== TEST 41: log the signal info like what Nginx does for SIGCHLD +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"ls"}) + if not proc then + ngx.say(err) + return + end + + local ok, err = proc:wait() + if not ok then + ngx.say(err) + return + end + ngx.say('ok') + } + } +--- response_body +ok +--- error_log eval +qr/\[notice\] .* signal \d+ \(SIGCHLD\) received from \d+/ +--- no_error_log +[error] + + + +=== TEST 42: return nil plus string 'timeout' when waiting process timed out +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", "10s"}) + if not proc then + ngx.say(err) + return + end + + proc:set_timeouts(nil, nil, nil, 10) + local ok, err = proc:wait() + if not ok then + ngx.say(ok) + ngx.say(err) + else + ngx.say("ok") + end + } + } +--- response_body +nil +timeout +--- no_error_log +[error] +--- error_log +lua pipe wait process: + + + +=== TEST 43: spawn sub-process when error_log is configured with syslog +--- config + location = /t { + content_by_lua_block { + local function get_ngx_bin_path() + local ffi = require "ffi" + ffi.cdef[[char **ngx_argv;]] + return ffi.string(ffi.C.ngx_argv[0]) + end + + local conf_file = "$TEST_NGINX_HTML_DIR/nginx.conf" + local nginx = get_ngx_bin_path() + + local cmd = nginx .. " -p $TEST_NGINX_HTML_DIR -c " .. conf_file + local ngx_pipe = require "ngx.pipe" + local ok, reason, status = ngx_pipe.spawn(cmd):wait() + ngx.say(ok) + ngx.say(reason) + ngx.say(status) + } + } +--- user_files +>>> nginx.conf +events { + worker_connections 64; +} +error_log syslog:server=127.0.0.1:$TEST_NGINX_MEMCACHED_PORT,facility=local1 info; +daemon off; +master_process off; +worker_processes 1; +http { + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + init_worker_by_lua_block { + ngx.timer.at(0, function() + local ngx_pipe = require "ngx.pipe" + local ok, reason, status = ngx_pipe.spawn("echo"):wait() + if not ok then + os.exit(status) + end + os.exit(0) + end) + } +} +>>> logs/error.log +--- response_body +true +exit +0 +--- no_error_log +[error] + + + +=== TEST 44: wait process, aborted by uthread kill, with graceful shutdown +--- user_files +>>> a.lua +local ngx_pipe = require "ngx.pipe" +local proc = ngx_pipe.spawn({"bash"}) + +local function func() + proc:wait() + ngx.log(ngx.ERR, "can't reach here") +end + +local th = ngx.thread.spawn(func) +ngx.thread.kill(th) + +local data, err = proc:kill(9) +if not data then + io.stdout:write("proc:kill(9) err: ", err) +else + io.stdout:write("ok") +end + +--- config + location = /t { + content_by_lua_block { + local helper = require "helper" + local f = io.open("$TEST_NGINX_HTML_DIR/a.lua") + local code = f:read("*a") + local proc = helper.run_lua_with_graceful_shutdown("$TEST_NGINX_HTML_DIR", code) + proc:set_timeouts(300, 300, 300, 300) + + local data, err = proc:stdout_read_all() + if not data then + ngx.say("stdout err: ", err) + else + ngx.say("stdout: ", data) + end + + local data, err = proc:stderr_read_any(4096) + if not data then + ngx.say("stderr err: ", err) + else + ngx.say("stderr: ", data) + end + } + } +--- response_body +stdout: ok +stderr err: closed +--- no_error_log +[error] + + + +=== TEST 45: spawn process, with environ option (sanity) +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + + local proc, err = ngx_pipe.spawn('echo $TEST_ENV', { + environ = { "TEST_ENV=blahblah" } + }) + if not proc then + ngx.say(err) + return + end + + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +blahblah + + + +=== TEST 46: spawn process, with environ option (multiple values) +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + + local proc, err = ngx_pipe.spawn('echo "$TEST_ENV $TEST_FOO"', { + environ = { "TEST_ENV=blahblah", "TEST_FOO=hello" } + }) + if not proc then + ngx.say(err) + return + end + + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +blahblah hello + + + +=== TEST 47: spawn process, with empty environ option (no values) +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + + local proc, err = ngx_pipe.spawn('echo "TEST_ENV:$TEST_ENV"', { + environ = {} + }) + if not proc then + ngx.say(err) + return + end + + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + } + } +--- response_body +TEST_ENV: + + + +=== TEST 48: spawn process, with invalid environ option +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + + local function spawn(environ) + local pok, perr = pcall(ngx_pipe.spawn, 'echo $TEST_ENV', { + environ = environ + }) + if not pok then + ngx.say(perr) + else + ngx.say("ok") + end + end + + spawn("TEST_ENV=1") + spawn({ "TEST_ENV=", 1 }) + spawn({ "TEST_ENV" }) + spawn({ "=1" }) + } + } +--- response_body +bad environ option: table expected, got string +bad value at index 2 of environ option: string expected, got number +bad value at index 1 of environ option: 'name=[value]' format expected, got 'TEST_ENV' +bad value at index 1 of environ option: 'name=[value]' format expected, got '=1' + + + +=== TEST 49: spawn process, with invalid environ option +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + + local function spawn(environ) + local pok, perr = pcall(ngx_pipe.spawn, 'echo "TEST_ENV:$TEST_ENV"', { + environ = environ + }) + if not pok then + ngx.say(perr) + return + end + + local proc = perr + + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + end + + spawn({ "TEST_ENV =1" }) + spawn({ "TEST_ENV= 1" }) + spawn({ "TEST_ENV==1" }) + } + } +--- response_body +TEST_ENV: +TEST_ENV: 1 +TEST_ENV:=1 + + + +=== TEST 50: spawn process, with environ option containing nil holes +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + + local function spawn(environ) + local proc, err = ngx_pipe.spawn('echo "$TEST_ENV2$TEST_ENV"', { + environ = environ + }) + if not proc then + ngx.say(err) + return + end + + local data, err = proc:stdout_read_line() + if not data then + ngx.say(err) + else + ngx.say(data) + end + end + + spawn({ "TEST_ENV=1", nil, "TEST_ENV2=2", nil }) + spawn({ "TEST_ENV=1", nil, "TEST_ENV2=2" }) + spawn({ nil, "TEST_ENV=1", "TEST_ENV2=2"}) + spawn({ hash_key = true, "TEST_ENV=1", nil, "TEST_ENV2=2", nil }) + spawn({ hash_key = true, "TEST_ENV=1", nil, "TEST_ENV2=2" }) + } + } +--- response_body +1 +1 + +1 +1 + + + +=== TEST 51: spawn process with wait_timeout option +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + local proc, err = ngx_pipe.spawn({"sleep", 1}, { + wait_timeout = 100 + }) + + local ok, err = proc:wait() + if not ok then + ngx.say(err) + else + ngx.say("ok") + end + } + } +--- response_body +timeout + + + +=== TEST 52: validate timeout options when spawning process +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + + local function spawn(opts) + local ok, err = pcall(ngx_pipe.spawn, {"sleep", "5s"}, opts) + if not ok then + ngx.say(err) + else + ngx.say("ok") + end + end + + ngx.say("write_timeout:") + spawn({write_timeout = 2 ^ 32}) + spawn({write_timeout = -1}) + + ngx.say("\nstdout_read_timeout:") + spawn({stdout_read_timeout = 2 ^ 32}) + spawn({stdout_read_timeout = -1}) + + ngx.say("\nstderr_read_timeout:") + spawn({stderr_read_timeout = 2 ^ 32}) + spawn({stderr_read_timeout = -1}) + + ngx.say("\nwait_timeout:") + spawn({wait_timeout = 2 ^ 32}) + spawn({wait_timeout = -1}) + } + } +--- response_body +write_timeout: +bad write_timeout option +bad write_timeout option + +stdout_read_timeout: +bad stdout_read_timeout option +bad stdout_read_timeout option + +stderr_read_timeout: +bad stderr_read_timeout option +bad stderr_read_timeout option + +wait_timeout: +bad wait_timeout option +bad wait_timeout option + + + +=== TEST 53: validate timeout options when spawning process +--- config + location = /t { + content_by_lua_block { + local ngx_pipe = require "ngx.pipe" + + local function spawn(opts) + local ok, err = pcall(ngx_pipe.spawn, {"sleep", "5s"}, opts) + if not ok then + ngx.log(ngx.ERR, err) + end + end + + spawn({write_timeout = -1}) + spawn({stdout_read_timeout = -1}) + spawn({stderr_read_timeout = -1}) + spawn({wait_timeout = -1}) + } + } +--- ignore_response_body +--- error_log eval +[ + qr/\[error\] .*? content_by_lua\(nginx\.conf:\d+\):\d+: .*? bad write_timeout option/, + qr/\[error\] .*? content_by_lua\(nginx\.conf:\d+\):\d+: .*? bad stdout_read_timeout option/, + qr/\[error\] .*? content_by_lua\(nginx\.conf:\d+\):\d+: .*? bad stderr_read_timeout option/, + qr/\[error\] .*? content_by_lua\(nginx\.conf:\d+\):\d+: .*? bad wait_timeout option/ +] +--- no_error_log +[crit] diff --git a/t/process-type-cache.t b/t/process-type-cache.t new file mode 100644 index 000000000..1e3067385 --- /dev/null +++ b/t/process-type-cache.t @@ -0,0 +1,77 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +BEGIN { + undef $ENV{TEST_NGINX_USE_STAP}; +} + +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +master_on(); +#log_level('info'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +our $HttpConfig = <<_EOC_; + proxy_cache_path /tmp/proxy_cache_dir keys_zone=cache_one:200m; + + $t::TestCore::HttpConfig + + init_worker_by_lua_block { + local base = require "resty.core.base" + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + if v == "helper" then + ngx.log(ngx.WARN, "process type: ", v) + end + } +_EOC_ + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + ngx.sleep(0.1) + local v + local typ = (require "ngx.process").type + for i = 1, 200 do + v = typ() + end + ngx.say("type: ", v) + } + } +--- request +GET /t +--- response_body +type: worker +--- grep_error_log eval +qr/\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):\d+ loop\]|\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):\d loop\]/ +--- grep_error_log_out eval +[ +qr/\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):\d+ loop\] +\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):\d+ loop\] +/, +qr/\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):\d+ loop\] +\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):\d+ loop\] +/ +] +--- no_error_log +[error] + -- NYI: +--- skip_nginx: 5: < 1.11.2 +--- wait: 0.2 diff --git a/t/process-type-hup.t b/t/process-type-hup.t new file mode 100644 index 000000000..f8203942b --- /dev/null +++ b/t/process-type-hup.t @@ -0,0 +1,88 @@ +# 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 lib '.'; +use t::TestCore $SkipReason ? (skip_all => $SkipReason) : (); +use Cwd qw(cwd); + +#worker_connections(1014); +master_process_enabled(1); +log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path '$t::TestCore::lua_package_path'; + init_by_lua_block { + $t::TestCore::init_by_lua_block + + local process = require "ngx.process" + local ok, err = process.enable_privileged_agent() + if not ok then + ngx.log(ngx.ERR, "enable_privileged_agent failed: ", err) + end + } + + init_worker_by_lua_block { + local base = require "resty.core.base" + local typ = require "ngx.process".type + + if typ() == "privileged agent" then + ngx.log(ngx.WARN, "process type: ", typ()) + end + } +_EOC_ + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local typ = require "ngx.process".type + + local f, err = io.open(ngx.config.prefix() .. "/logs/nginx.pid", "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() + + ngx.say("type: ", typ()) + os.execute("kill -HUP " .. pid) + } + } +--- request +GET /t +--- response_body +type: worker +--- error_log +init_worker_by_lua(nginx.conf:48):6: process type: privileged +--- no_error_log +[error] +--- skip_nginx: 4: < 1.11.2 diff --git a/t/process-type-master.t b/t/process-type-master.t new file mode 100644 index 000000000..82af5b862 --- /dev/null +++ b/t/process-type-master.t @@ -0,0 +1,67 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +BEGIN { + undef $ENV{TEST_NGINX_USE_STAP}; +} + +use lib '.'; +use t::TestCore; +use Cwd qw(cwd); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +my $pwd = cwd(); + +our $HttpConfig = <<_EOC_; + lua_package_path "$t::TestCore::lua_package_path"; + init_by_lua_block { + $t::TestCore::init_by_lua_block + + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + package.loaded.process_type = v + } +_EOC_ + +#worker_connections(1014); +master_on(); +#log_level('error'); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + ngx.say("process type: ", package.loaded.process_type) + } + } +--- request +GET /t +--- response_body +process type: master +--- grep_error_log eval +qr/\[TRACE\s+\d+ init_by_lua\(nginx.conf:\d+\):\d+ loop\]/ +--- grep_error_log_out eval +[ +qr/\A\[TRACE\s+\d+ init_by_lua\(nginx.conf:\d+\):\d+ loop\] +\z/, +qr/\A\[TRACE\s+\d+ init_by_lua\(nginx.conf:\d+\):\d+ loop\] +\z/ +] +--- no_error_log +[error] + -- NYI: +--- skip_nginx: 5: < 1.11.2 diff --git a/t/process-type-privileged-agent-connections.t b/t/process-type-privileged-agent-connections.t new file mode 100644 index 000000000..42592e907 --- /dev/null +++ b/t/process-type-privileged-agent-connections.t @@ -0,0 +1,301 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use lib '.'; +use t::TestCore; + +master_process_enabled(1); + +repeat_each(1); + +plan tests => repeat_each() * (blocks() * 5); + + +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = $t::TestCore::lua_package_path; +$ENV{TEST_NGINX_RANDOM_PORT} = Test::Nginx::Util::server_port(); + +run_tests(); + +__DATA__ + +=== TEST 1: specify connections to enable_privileged_agent +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + local process = require "ngx.process" + local ok, err = process.enable_privileged_agent(10) + if not ok then + ngx.log(ngx.ERR, "enable_privileged_agent failed: ", err) + end + } + + init_worker_by_lua_block { + local base = require "resty.core.base" + local typ = (require "ngx.process").type() + + if typ == "privileged agent" then + ngx.log(ngx.WARN, "process type: ", typ) + ngx.timer.at(0, function() + local tcpsock = ngx.socket.tcp() + local ok, err = tcpsock:connect("127.0.0.1", $TEST_NGINX_RANDOM_PORT) + + if ok then + ngx.log(ngx.INFO, "connect ok ") + else + ngx.log(ngx.INFO, "connect failed " .. tostring(err)) + end + end) + end + } +--- config + location = /t { + content_by_lua_block { + ngx.sleep(0.1) + local typ = require "ngx.process".type() + ngx.say("type: ", typ) + } + } +--- request +GET /t +--- response_body +type: worker +--- error_log +connect ok +--- no_error_log +connect failed +enable_privileged_agent failed +--- skip_nginx: 5: < 1.11.2 +--- wait: 0.2 + + + +=== TEST 2: connections exceed limit +the real connections you can create is always less than you set. +timer will take fake connections. +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + local process = require "ngx.process" + local ok, err = process.enable_privileged_agent(10) + if not ok then + ngx.log(ngx.ERR, "enable_privileged_agent failed: ", err) + end + } + + init_worker_by_lua_block { + local base = require "resty.core.base" + local typ = (require "ngx.process").type() + + if typ == "privileged agent" then + ngx.log(ngx.WARN, "process type: ", typ) + + ngx.timer.at(0, function() + + for i = 1, 10 do + local tcpsock = ngx.socket.tcp() + local ok, err = tcpsock:connect("127.0.0.1", $TEST_NGINX_RANDOM_PORT) + + if ok then + ngx.log(ngx.INFO, "connect ok ") + else + ngx.log(ngx.INFO, "connect failed " .. tostring(err)) + end + end + + end) + end + } +--- config + location = /t { + content_by_lua_block { + ngx.sleep(0.1) + local typ = require "ngx.process".type() + ngx.say("type: ", typ) + } + } +--- request +GET /t +--- response_body +type: worker +--- error_log +connect failed +worker_connections are not enough +--- no_error_log +enable_privileged_agent failed +--- skip_nginx: 5: < 1.11.2 +--- wait: 0.2 + + + +=== TEST 3: enable_privileged_agent with bad connections +connections < 0 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + local process = require "ngx.process" + local ok, err = process.enable_privileged_agent(-1) + if not ok then + ngx.log(ngx.ERR, "enable_privileged_agent failed: ", err) + end + } + + init_worker_by_lua_block { + local base = require "resty.core.base" + local typ = (require "ngx.process").type() + + if typ == "privileged agent" then + ngx.log(ngx.WARN, "process type: ", typ) + + ngx.timer.at(0, function() + + for i = 1, 10 do + local tcpsock = ngx.socket.tcp() + local ok, err = tcpsock:connect("127.0.0.1", $TEST_NGINX_RANDOM_PORT) + + if ok then + ngx.log(ngx.INFO, "connect ok ") + else + ngx.log(ngx.INFO, "connect failed " .. tostring(err)) + end + end + + end) + end + } +--- config + location = /t { + content_by_lua_block { + ngx.sleep(0.1) + local typ = require "ngx.process".type() + ngx.say("type: ", typ) + } + } +--- request +GET /t +--- response_body +type: worker +--- error_log +enable_privileged_agent failed: bad 'connections' argument +--- no_error_log +connect ok +connect failed +--- skip_nginx: 5: < 1.11.2 +--- wait: 0.2 + + + +=== TEST 4: enable_privileged_agent with bad connections +connections is not a number +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + local process = require "ngx.process" + local ok, err = process.enable_privileged_agent("10") + if not ok then + ngx.log(ngx.ERR, "enable_privileged_agent failed: ", err) + end + } + + init_worker_by_lua_block { + local base = require "resty.core.base" + local typ = (require "ngx.process").type() + + if typ == "privileged agent" then + ngx.log(ngx.WARN, "process type: ", typ) + + ngx.timer.at(0, function() + + for i = 1, 10 do + local tcpsock = ngx.socket.tcp() + local ok, err = tcpsock:connect("127.0.0.1", $TEST_NGINX_RANDOM_PORT) + + if ok then + ngx.log(ngx.INFO, "connect ok ") + else + ngx.log(ngx.INFO, "connect failed " .. tostring(err)) + end + end + + end) + end + } +--- config + location = /t { + content_by_lua_block { + ngx.sleep(0.1) + local typ = require "ngx.process".type() + ngx.say("type: ", typ) + } + } +--- request +GET /t +--- response_body +type: worker +--- error_log +enable_privileged_agent failed: bad 'connections' argument +--- no_error_log +connect ok +connect failed +--- skip_nginx: 5: < 1.11.2 +--- wait: 0.2 + + + +=== TEST 5: enable_privileged_agent with bad connections +connections = 0 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + local process = require "ngx.process" + local ok, err = process.enable_privileged_agent(0) + if not ok then + ngx.log(ngx.ERR, "enable_privileged_agent failed: ", err) + end + } + + init_worker_by_lua_block { + local base = require "resty.core.base" + local typ = (require "ngx.process").type() + + if typ == "privileged agent" then + ngx.log(ngx.WARN, "process type: ", typ) + + ngx.timer.at(0, function() + + for i = 1, 10 do + local tcpsock = ngx.socket.tcp() + local ok, err = tcpsock:connect("127.0.0.1", $TEST_NGINX_RANDOM_PORT) + + if ok then + ngx.log(ngx.INFO, "connect ok ") + else + ngx.log(ngx.INFO, "connect failed " .. tostring(err)) + end + end + + end) + end + } +--- config + location = /t { + content_by_lua_block { + ngx.sleep(0.1) + local typ = require "ngx.process".type() + ngx.say("type: ", typ) + } + } +--- request +GET /t +--- response_body +type: worker +--- error_log +0 worker_connection is not enough, privileged agent process cannot be spawned +--- no_error_log +process type: privileged agent +connect ok +--- skip_nginx: 5: < 1.11.2 +--- wait: 0.2 diff --git a/t/process-type-privileged-agent.t b/t/process-type-privileged-agent.t new file mode 100644 index 000000000..bd6a55d7a --- /dev/null +++ b/t/process-type-privileged-agent.t @@ -0,0 +1,176 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +BEGIN { + undef $ENV{TEST_NGINX_USE_STAP}; +} + +use lib '.'; +use t::TestCore; + +$ENV{TEST_NGINX_RANDOM_PORT} = Test::Nginx::Util::server_port(); + +#worker_connections(1014); +master_process_enabled(1); +#log_level('error'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5 - 5); + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + my $init_by_lua_block = $block->init_by_lua_block || ''; + + $http_config .= <<_EOC_; + lua_package_path '$t::TestCore::lua_package_path'; + init_by_lua_block { + $t::TestCore::init_by_lua_block + + local process = require "ngx.process" + local ok, err = process.enable_privileged_agent(8) + if not ok then + ngx.log(ngx.ERR, "enable_privileged_agent failed: ", err) + end + } + + init_worker_by_lua_block { + local base = require "resty.core.base" + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + if v == "privileged agent" then + ngx.log(ngx.WARN, "process type: ", v) + + ngx.timer.at(0, function() + for i = 1, 4 do + local tcpsock = ngx.socket.tcp() + local ok, err = tcpsock:connect("127.0.0.1", $ENV{TEST_NGINX_RANDOM_PORT}) + + if ok then + ngx.log(ngx.INFO, "connect ok ") + else + ngx.log(ngx.INFO, "connect not ok " .. tostring(err)) + end + end + end) + end + } +_EOC_ + + $block->set_value("http_config", $http_config); +}); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- config + location = /t { + content_by_lua_block { + ngx.sleep(0.1) + local v + local typ = require "ngx.process".type + for i = 1, 200 do + v = typ() + end + + ngx.say("type: ", v) + } + } +--- request +GET /t +--- response_body +type: worker +--- grep_error_log eval +qr/\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):\d+ loop\]|\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):\d+ loop\]|init_worker_by_lua\(nginx.conf:\d+\):\d+: process type: \w+/ +--- grep_error_log_out eval +[ +qr/\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):\d+ loop\] +(?:\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):\d+ loop\] +)?\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):\d+ loop\] +init_worker_by_lua\(nginx.conf:\d+\):10: process type: privileged +/, +qr/\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):\d+ loop\] +(?:\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):\d+ loop\] +)?\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):\d+ loop\] +init_worker_by_lua\(nginx.conf:\d+\):10: process type: privileged +/ +] +--- no_error_log +[error] + -- NYI: +--- skip_nginx: 5: < 1.11.2 +--- wait: 0.2 + + + +=== TEST 2: `enable_privileged_agent` disabled +--- config + location = /t { + content_by_lua_block { + local process = require "ngx.process" + local ok, err = process.enable_privileged_agent() + if not ok then + error(err) + end + } + } +--- request +GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log eval +qr/\[error\] .*? API disabled in the current context/ +--- skip_nginx: 3: < 1.11.2 + + + +=== TEST 3: `enable_privileged_agent` not patched +--- config + location = /t { + content_by_lua_block { + local process = require "ngx.process" + local ok, err = process.enable_privileged_agent() + if not ok then + error(err) + end + } + } +--- request +GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +missing privileged agent process patch in the nginx core +API disabled in the current context +--- skip_nginx: 4: >= 1.11.2 + + + +=== TEST 4: connections exceed limits +--- config + location = /t { + content_by_lua_block { + local process = require "ngx.process" + local ok, err = process.enable_privileged_agent() + if not ok then + error(err) + end + } + } +--- request +GET /t +--- response_body_like: 500 Internal Server Error +--- error_code: 500 +--- error_log +worker_connections are not enough +--- skip_nginx: 3: < 1.11.2 diff --git a/t/process-type-single.t b/t/process-type-single.t new file mode 100644 index 000000000..f1d1f2686 --- /dev/null +++ b/t/process-type-single.t @@ -0,0 +1,89 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +BEGIN { + undef $ENV{TEST_NGINX_USE_STAP}; +} + +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +our $HttpConfig = <<_EOC_; + lua_package_path '$t::TestCore::lua_package_path'; + init_by_lua_block { + $t::TestCore::init_by_lua_block + + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + package.loaded.process_type = v + } + + init_worker_by_lua_block { + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + ngx.log(ngx.WARN, "process type in init_by_lua*: ", + package.loaded.process_type) + ngx.log(ngx.WARN, "process type: ", v) + } +_EOC_ + +#worker_connections(1014); +#log_level('error'); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + ngx.say("process type: ", v) + } + } +--- request +GET /t +--- response_body +process type: single +--- grep_error_log eval +qr/\[TRACE\s+\d+ init_by_lua\(nginx.conf:\d+\):\d+ loop\]|\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):\d loop\]|\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):\d loop\]|process type in init_by_lua\*: \w+|init_worker_by_lua\(nginx.conf:\d+\):\d+: process type: \w+/ +--- grep_error_log_out eval +[ +qr/\[TRACE\s+\d+ init_by_lua\(nginx.conf:\d+\):\d+ loop\] +\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):\d+ loop\] +\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):4 loop\] +process type in init_by_lua\*: single +init_worker_by_lua\(nginx.conf:\d+\):10: process type: single +/, +qr/\[TRACE\s+\d+ init_by_lua\(nginx.conf:\d+\):\d+ loop\] +\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):\d+ loop\] +\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):4 loop\] +process type in init_by_lua\*: single +init_worker_by_lua\(nginx.conf:\d+\):10: process type: single +/ +] +--- no_error_log +[error] + -- NYI: +--- skip_nginx: 5: < 1.11.2 diff --git a/t/process-type-worker.t b/t/process-type-worker.t new file mode 100644 index 000000000..e6ba764d8 --- /dev/null +++ b/t/process-type-worker.t @@ -0,0 +1,73 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +BEGIN { + undef $ENV{TEST_NGINX_USE_STAP}; +} + +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +our $HttpConfig = <<_EOC_; + $t::TestCore::HttpConfig + + init_worker_by_lua_block { + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + ngx.log(ngx.WARN, "process type: ", v) + } +_EOC_ + +#worker_connections(1014); +master_on(); +#log_level('error'); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + ngx.say("process type: ", v) + } + } +--- request +GET /t +--- response_body +process type: worker +--- grep_error_log eval +qr/\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):\d loop\]|\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):\d loop\]|init_worker_by_lua\(nginx.conf:\d+\):\d: process type: \w+/ +--- grep_error_log_out eval +[ +qr/\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):4 loop\] +\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):4 loop\] +init_worker_by_lua\(nginx.conf:\d+\):8: process type: worker +/, +qr/\[TRACE\s+\d+ init_worker_by_lua\(nginx.conf:\d+\):4 loop\] +\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):4 loop\] +init_worker_by_lua\(nginx.conf:\d+\):8: process type: worker +/ +] +--- no_error_log +[error] + -- NYI: +--- skip_nginx: 5: < 1.11.2 diff --git a/t/re-base.t b/t/re-base.t new file mode 100644 index 000000000..9bf6b33af --- /dev/null +++ b/t/re-base.t @@ -0,0 +1,212 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: bad pattern +--- config + location /re { + content_by_lua_block { + local it, err = ngx.re.gmatch("hello\\nworld", "(abc") + if not err then + ngx.say("good") + else + ngx.say("error: ", err) + end + } + } +--- request + GET /re +--- response_body +error: pcre_compile() failed: missing ) in "(abc" +--- no_error_log +[error] + + + +=== TEST 2: bad UTF-8 +--- config + location = /t { + content_by_lua_block { + local target = "你好" + local regex = "你好" + + -- Note the D here + local it, err = ngx.re.gmatch(string.sub(target, 1, 4), regex, "u") + + if err then + ngx.say("error: ", err) + return + end + + local m, err = it() + if err then + ngx.say("error: ", err) + return + end + + if m then + ngx.say("matched: ", m[0]) + else + ngx.say("not matched") + end + } + } +--- request +GET /t +--- response_body_like chop +error: pcre_exec\(\) failed: -10 + +--- no_error_log +[error] + + + +=== TEST 3: UTF-8 mode without UTF-8 sequence checks +--- config + location /re { + content_by_lua_block { + local it = ngx.re.gmatch("你好", ".", "U") + local m = it() + if m then + ngx.say(m[0]) + else + ngx.say("not matched!") + end + } + } +--- stap +probe process("$LIBPCRE_PATH").function("pcre_compile") { + printf("compile opts: %x\n", $options) +} + +probe process("$LIBPCRE_PATH").function("pcre_exec") { + printf("exec opts: %x\n", $options) +} + +--- stap_out +compile opts: 800 +exec opts: 2000 + +--- request + GET /re +--- response_body +你 +--- no_error_log +[error] + + + +=== TEST 4: UTF-8 mode with UTF-8 sequence checks +--- config + location /re { + content_by_lua_block { + local it = ngx.re.gmatch("你好", ".", "u") + local m = it() + if m then + ngx.say(m[0]) + else + ngx.say("not matched!") + end + } + } +--- stap +probe process("$LIBPCRE_PATH").function("pcre_compile") { + printf("compile opts: %x\n", $options) +} + +probe process("$LIBPCRE_PATH").function("pcre_exec") { + printf("exec opts: %x\n", $options) +} + +--- stap_out +compile opts: 800 +exec opts: 0 + +--- request + GET /re +--- response_body +你 +--- no_error_log +[error] + + + +=== TEST 5: just hit match limit +--- http_config eval: "lua_regex_match_limit 5000;" . $t::TestCore::HttpConfig +--- config + location /re { + content_by_lua_file html/a.lua; + } + +--- user_files +>>> 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]+))]==] + +local s = string.rep([[ABCDEFG]], 10) + +local it, err = ngx.re.gmatch(s, re, "o") +if not it then + ngx.say("failed to gen iterator: ", err) + return +end + +local res, err = it() +if not res then + if err then + ngx.say("error: ", err) + return + end + ngx.say("failed to match") + return +end + +--- request + GET /re +--- response_body +error: pcre_exec() failed: -8 + + + +=== TEST 6: just not hit match limit +--- http_config eval: "lua_regex_match_limit 5100;" . $t::TestCore::HttpConfig +--- config + location /re { + content_by_lua_file html/a.lua; + } + +--- user_files +>>> 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]+))]==] + +local s = string.rep([[ABCDEFG]], 10) + +local it, err = ngx.re.gmatch(s, re, "o") +if not it then + ngx.say("failed to gen iterator: ", err) + return +end + +local res, err = it() +if not res then + if err then + ngx.say("error: ", err) + return + end + ngx.say("failed to match") + return +end + +--- request + GET /re +--- response_body +failed to match diff --git a/t/re-bugs.t b/t/re-bugs.t new file mode 100644 index 000000000..eefca2804 --- /dev/null +++ b/t/re-bugs.t @@ -0,0 +1,176 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4 - 4); + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + my $init_by_lua_block = $block->init_by_lua_block || ''; + + $http_config .= <<_EOC_; + lua_package_path '$t::TestCore::lua_package_path'; + init_by_lua_block { + $t::TestCore::init_by_lua_block + $init_by_lua_block + } +_EOC_ + + $block->set_value("http_config", $http_config); +}); + +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: PCRE MAP_JIT workaround on macOS +--- init_by_lua_block + ngx.re.match("c", "test", "jo") +--- config + location /re { + content_by_lua_block { + ngx.say(ngx.re.sub("c", "a", "b", "")) + ngx.say(ngx.re.sub("c", "a", "b", "jo")) + } + } +--- request +GET /re +--- response_body +c0 +c0 +--- grep_error_log eval +qr/.+parse_regex_opts\(\): .*? disabled in init phase under macOS/ +--- grep_error_log_out eval +qr/parse_regex_opts\(\): regex compilation disabled in init phase under macOS +.*?parse_regex_opts\(\): regex compilation cache disabled in init phase under macOS/s +--- no_error_log +[error] +--- skip_eval +4: $^O ne 'darwin' + + + +=== TEST 2: PCRE MAP_JIT workaround on macOS logs only once per flag +--- init_by_lua_block + jit.off() -- must disable in this test or logs will be fuzzy + + for i = 1, 2 do + ngx.re.match("c", "test", "j") + end + + for i = 1, 2 do + ngx.re.match("c", "test", "o") + end + + for i = 1, 2 do + ngx.re.match("c", "test", "jo") + end +--- config + location /re { + content_by_lua_block { + ngx.say(ngx.re.sub("c", "a", "b", "")) + ngx.say(ngx.re.sub("c", "a", "b", "jo")) + } + } +--- request +GET /re +--- response_body +c0 +c0 +--- grep_error_log eval +qr/parse_regex_opts\(\): .*? disabled in init phase under macOS/ +--- grep_error_log_out eval +qr/\A(?:parse_regex_opts\(\): regex compilation (?:cache )?disabled in init phase under macOS\s*){4}\z/ +--- no_error_log +[error] +--- skip_eval +4: $^O ne 'darwin' + + + +=== TEST 3: PCRE MAP_JIT workaround is reverted after init phase +--- init_by_lua_block + ngx.re.match("c", "test", "jo") +--- config + location /re { + content_by_lua_block { + ngx.say(ngx.re.sub("c", "a", "b", "")) + ngx.say(ngx.re.sub("c", "a", "b", "jo")) + } + } +--- request +GET /re +--- response_body +c0 +c0 +--- no_error_log +[error] +disabled in init phase under macOS, client: +--- skip_eval +4: $^O ne 'darwin' + + + +=== TEST 4: PCRE MAP_JIT workaround is not in effect under other OSs +--- init_by_lua_block + ngx.re.match("c", "test", "jo") +--- config + location /re { + content_by_lua_block { + ngx.say(ngx.re.sub("c", "a", "b", "")) + ngx.say(ngx.re.sub("c", "a", "b", "jo")) + } + } +--- request +GET /re +--- response_body +c0 +c0 +--- no_error_log +[error] +disabled in init phase under macOS +--- skip_eval +4: $^O ne 'linux' + + + +=== TEST 5: bug: sub incorrectly dropped the last character +--- config + location /re { + content_by_lua_block { + local s, n = ngx.re.sub("abcd", "(?<=c)", ".") + ngx.say(s) + ngx.say(n) + } + } +--- request + GET /re +--- response_body +abc.d +1 + + + +=== TEST 6: bug: sub incorrectly dropped the last character(replace function) +--- config + location /re { + content_by_lua_block { + local function repl(m) + return "[" .. m[0] .. "]" + end + local s, n = ngx.re.sub("abcd", "(?<=c)", repl) + ngx.say(s) + ngx.say(n) + } + } +--- request + GET /re +--- response_body +abc[]d +1 diff --git a/t/re-find.t b/t/re-find.t new file mode 100644 index 000000000..66a7ba462 --- /dev/null +++ b/t/re-find.t @@ -0,0 +1,231 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: matched, no submatch, no jit compile, no regex cache +--- config + location = /re { + access_log off; + content_by_lua_block { + local from, to, err + local find = ngx.re.find + local s = "a" + for i = 1, $TEST_NGINX_HOTLOOP * 20 do + from, to, err = find(s, "a") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not from then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("from: ", from) + ngx.say("to: ", to) + ngx.say("matched: ", string.sub(s, from, to)) + } + } +--- request +GET /re +--- response_body +from: 1 +to: 1 +matched: a +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):5 loop\]/ +--- no_error_log +[error] +bad argument type + + + +=== TEST 2: matched, no submatch, jit compile, regex cache +--- config + location = /re { + access_log off; + content_by_lua_block { + local from, to, err + local find = ngx.re.find + local s = "a" + for i = 1, 200 do + from, to, err = find(s, "a", "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not from then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("from: ", from) + ngx.say("to: ", to) + ngx.say("matched: ", string.sub(s, from, to)) + } + } +--- request +GET /re +--- response_body +from: 1 +to: 1 +matched: a +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):5 loop\]/ +--- no_error_log +[error] +NYI + + + +=== TEST 3: not matched, no submatch, jit compile, regex cache +--- config + location = /re { + access_log off; + content_by_lua_block { + local from, to, err + local find = ngx.re.find + local s = "b" + for i = 1, 200 do + from, to, err = find(s, "a", "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not from then + ngx.say("no match") + return + end + ngx.say("from: ", from) + ngx.say("to: ", to) + ngx.say("matched: ", string.sub(s, from, to)) + } + } +--- request +GET /re +--- response_body +no match +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):5 loop\]/ +--- no_error_log +[error] +NYI + + + +=== TEST 4: nil submatch (2nd) +--- config + location /re { + content_by_lua_block { + local s = "hello, 1234" + local from, to, err + for i = 1, $TEST_NGINX_HOTLOOP * 20 do + from, to, err = ngx.re.find(s, "([0-9])|(hello world)", "jo", nil, 2) + end + if from or to 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 +not matched! +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ + + + +=== TEST 5: nil submatch (1st) +--- config + location /re { + content_by_lua_block { + local s = "hello, 1234" + local from, to, err + for i = 1, 400 do + from, to, err = ngx.re.find(s, "(hello world)|([0-9])", "jo", nil, 1) + end + if from or to 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 +not matched! +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ + + + +=== TEST 6: specify the group (2) +--- config + location /re { + content_by_lua_block { + local s = "hello, 1234" + local from, to, err + for i = 1, $TEST_NGINX_HOTLOOP * 20 do + from, to, err = ngx.re.find(s, "([0-9])([0-9]+)", "jo", nil, 2) + end + 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) + end + ngx.say("not matched!") + end + } + } +--- request + GET /re +--- response_body +from: 9 +to: 11 +matched: 234 +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ diff --git a/t/re-gmatch.t b/t/re-gmatch.t new file mode 100644 index 000000000..7439c5f7f --- /dev/null +++ b/t/re-gmatch.t @@ -0,0 +1,509 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 9); + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: matched, no submatch, no jit compile, no regex cache +--- config + location = /re { + content_by_lua_block { + local m1, m2 + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello, world", [[\w+]]) + m1 = iter() + m2 = iter() + end + ngx.say("matched: ", m1[0]) + ngx.say("matched: ", m2[0]) + } + } +--- request +GET /re +--- response_body +matched: hello +matched: world +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + + + +=== TEST 2: matched, no submatch, jit compile, regex cache +--- config + location = /re { + content_by_lua_block { + local m1, m2 + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello, world", [[\w+]], "jo") + m1 = iter() + m2 = iter() + end + ngx.say("matched: ", m1[0]) + ngx.say("matched: ", m2[0]) + } + } +--- request +GET /re +--- response_body +matched: hello +matched: world +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + + + +=== TEST 3: not matched, no submatch, jit compile, regex cache +--- config + location = /re { + content_by_lua_block { + local m, err + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello, world", "[abc]+", "jo") + m, err = iter() + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + end + if not m then + ngx.say("no match") + return + end + } + } +--- request +GET /re +--- response_body +no match +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + + + +=== TEST 4: not matched, no submatch, no jit compile, no regex cache +--- config + location = /re { + content_by_lua_block { + local m, err + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello, world", "[abc]+") + m, err = iter() + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + end + if not m then + ngx.say("no match") + return + end + } + } +--- request +GET /re +--- response_body +no match +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + + + +=== TEST 5: submatches, matched, no regex cache +--- config + location = /re { + content_by_lua_block { + local m1, m2 + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello, world", [[(\w)(\w+)]]) + m1 = iter() + m2 = iter() + end + ngx.say("matched: ", m1[0]) + ngx.say("$1: ", m1[1]) + ngx.say("$2: ", m1[2]) + ngx.say("$3: ", m1[3]) + ngx.say("matched: ", m2[0]) + ngx.say("$1: ", m2[1]) + ngx.say("$2: ", m2[2]) + ngx.say("$3: ", m2[3]) + } + } +--- request +GET /re +--- response_body +matched: hello +$1: h +$2: ello +$3: nil +matched: world +$1: w +$2: orld +$3: nil +--- no_error_log +[error] + + + +=== TEST 6: submatches, matched, with regex cache +--- config + location = /re { + content_by_lua_block { + local m1, m2 + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello, world", [[(\w)(\w+)]], "jo") + m1 = iter() + m2 = iter() + end + ngx.say("matched: ", m1[0]) + ngx.say("$1: ", m1[1]) + ngx.say("$2: ", m1[2]) + ngx.say("$3: ", m1[3]) + ngx.say("matched: ", m2[0]) + ngx.say("$1: ", m2[1]) + ngx.say("$2: ", m2[2]) + ngx.say("$3: ", m2[3]) + } + } +--- request +GET /re +--- response_body +matched: hello +$1: h +$2: ello +$3: nil +matched: world +$1: w +$2: orld +$3: nil +--- error_log eval +qr/\[TRACE\s+\d+\s+/ +--- no_error_log +[error] + + + +=== TEST 7: named submatches +--- config + location = /re { + content_by_lua_block { + local m1, m2 + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello,world", [[(?\w)(\w+)]], "jo") + m1 = iter() + m2 = iter() + end + ngx.say("matched: ", m1[0]) + ngx.say("$1: ", m1[1]) + ngx.say("$2: ", m1[2]) + ngx.say("$first: ", m1.first) + ngx.say("$second: ", m1.second) + ngx.say("matched: ", m2[0]) + ngx.say("$1: ", m2[1]) + ngx.say("$2: ", m2[2]) + ngx.say("$first: ", m2.first) + ngx.say("$second: ", m2.second) + } + } +--- request +GET /re +--- response_body +matched: hello +$1: h +$2: ello +$first: h +$second: nil +matched: world +$1: w +$2: orld +$first: w +$second: nil +--- error_log eval +qr/\[TRACE\s+\d+\s+/ +--- no_error_log +[error] + + + +=== TEST 8: unmatched captures are false +--- config + location = /re { + content_by_lua_block { + local iter = ngx.re.gmatch( + "hello! world!", [[(\w+)(, .+)?(!)]], "jo") + if iter then + while true do + local m = iter() + if not m then + return + end + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + end + end + } + } +--- request +GET /re +--- response_body +hello! +hello +false +! +world! +world +false +! +--- error_log eval +qr/\[TRACE\s+\d+\s+/ +--- no_error_log +[error] + + + +=== TEST 9: unmatched trailing captures are false +--- config + location = /re { + content_by_lua_block { + local iter = ngx.re.gmatch("hello", [[(\w+)(, .+)?(!)?]], "jo") + if iter then + while true do + local m = iter() + if not m then + return + end + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + end + end + } + } +--- request +GET /re +--- response_body +hello +hello +false +false +--- error_log eval +qr/\[TRACE\s+\d+\s+/ +--- no_error_log +[error] + + + +=== TEST 10: unmatched named captures are false +--- config + location = /re { + content_by_lua_block { + local iter = ngx.re.gmatch( + "hello! world!", + [[(?\w+)(?, .+)?(?!)]], "jo") + if iter then + while true do + local m = iter() + if not m then + return + end + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + ngx.say(m.first) + ngx.say(m.second) + ngx.say(m.third) + end + end + } + } +--- request +GET /re +--- response_body +hello! +hello +false +! +hello +false +! +world! +world +false +! +world +false +! +--- error_log eval +qr/\[TRACE\s+\d+\s+/ +--- no_error_log +[error] + + + +=== TEST 11: subject is not a string type +--- config + location /re { + content_by_lua_block { + local iter = ngx.re.gmatch(120345, "[1-9]+", "jo") + local m1 = iter() + local m2 = iter() + ngx.say(m1[0]) + ngx.say(m2[0]) + } + } +--- request +GET /re +--- response_body +12 +345 +--- no_error_log +[error] +attempt to get length of local 'subj' (a number value) + + + +=== TEST 12: an exhausted gmatch iterator should return nil +--- config + location = /re { + content_by_lua_block { + local iter = ngx.re.gmatch("hello", [[\w+]]) + local m = iter() + ngx.say("matched: ", m[0]) + ngx.say("matched: ", iter()) + ngx.say("matched: ", iter()) + } + } +--- request +GET /re +--- response_body +matched: hello +matched: nil +matched: nil +--- no_error_log +[error] + + + +=== TEST 13: an error-ed out gmatch iterator should return nil +--- config + location = /re { + content_by_lua_block { + local target = "你好" + local regex = "你好" + + -- trigger a BADUTF8 error + local iter = ngx.re.gmatch(string.sub(target, 1, 4), regex, "u") + local m, err = iter() + + if err then + ngx.say("error: ", err) + local m = iter() + if m then + ngx.say("matched: ", m[0]) + else + ngx.say("not matched") + end + return + end + + if m then + ngx.say("matched: ", m[0]) + else + ngx.say("not matched") + end + } + } +--- request +GET /re +--- response_body +error: pcre_exec() failed: -10 +not matched +--- no_error_log +[error] + + + +=== TEST 14: each gmatch iterator is separate +--- config + location = /re { + content_by_lua_block { + local gmatch = ngx.re.gmatch + local iter1 = gmatch("98", [[\d]]) + local iter2 = gmatch("12", [[\d]]) + + local m1 = iter1() + local m2 = iter2() + ngx.say("matched iter1 (1/2): ", m1[0]) + ngx.say("matched iter2 (1/2): ", m2[0]) + + m1 = iter1() + m2 = iter2() + ngx.say("matched iter1 (2/2): ", m1[0]) + ngx.say("matched iter2 (2/2): ", m2[0]) + } + } +--- request +GET /re +--- response_body +matched iter1 (1/2): 9 +matched iter2 (1/2): 1 +matched iter1 (2/2): 8 +matched iter2 (2/2): 2 +--- no_error_log +[error] + + + +=== TEST 15: gmatch (empty matched string) +--- config + location /re { + content_by_lua_block { + for m in ngx.re.gmatch("hello", "a|") do + if m then + ngx.say("matched: [", m[0], "]") + else + ngx.say("not matched: ", m) + end + end + } + } +--- request + GET /re +--- response_body +matched: [] +matched: [] +matched: [] +matched: [] +matched: [] +matched: [] diff --git a/t/re-match.t b/t/re-match.t new file mode 100644 index 000000000..6ef724903 --- /dev/null +++ b/t/re-match.t @@ -0,0 +1,552 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5 + 1); + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: matched, no submatch, no jit compile, no regex cache +--- config + location = /re { + access_log off; + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 400 do + m, err = match("a", "a") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + -- ngx.say("$2: ", m[2]) + -- ngx.say("$3: ", m[3]) + -- collectgarbage() + } + } +--- request +GET /re +--- response_body +matched: a +$1: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type + + + +=== TEST 2: matched, no submatch, jit compile, regex cache +--- config + location = /re { + access_log off; + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 400 do + m, err = match("a", "a", "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + -- collectgarbage() + } + } +--- request +GET /re +--- response_body +matched: a +$1: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +NYI + + + +=== TEST 3: not matched, no submatch, jit compile, regex cache +--- config + location = /re { + access_log off; + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 200 do + m, err = match("b", "a", "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.say("no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + -- collectgarbage() + } + } +--- request +GET /re +--- response_body +no match +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + + + +=== TEST 4: not matched, no submatch, no jit compile, no regex cache +--- config + location = /re { + access_log off; + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("b", "a") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.say("no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + -- collectgarbage() + } + } +--- request +GET /re +--- response_body +no match +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type + + + +=== TEST 5: submatches, matched, no regex cache +--- config + location = /re { + access_log off; + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("hello, 1234", [[(\d)(\d+)]]) + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + ngx.say("$2: ", m[2]) + ngx.say("$3: ", m[3]) + -- collectgarbage() + } + } +--- request +GET /re +--- response_body +matched: 1234 +$1: 1 +$2: 234 +$3: nil +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 6: submatches, matched, with regex cache +--- config + location = /re { + access_log off; + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("hello, 1234", [[(\d)(\d+)]], "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + ngx.say("$2: ", m[2]) + ngx.say("$3: ", m[3]) + -- ngx.say(table.maxn(m)) + -- collectgarbage() + } + } +--- request +GET /re +--- response_body +matched: 1234 +$1: 1 +$2: 234 +$3: nil +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 7: named subpatterns w/ extraction (matched) +--- config + location /re { + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("hello, 1234", "(?[a-z]+), [0-9]+", "jo") + end + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m.first) + ngx.say(m.second) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + } + } +--- request + GET /re +--- response_body +hello, 1234 +hello +hello +nil + +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 8: named subpatterns w/ extraction (use of duplicate names in non-duplicate mode) +--- config + location /re { + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 200 do + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "jo") + end + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m.first) + ngx.say(m.second) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + } + } +--- request + GET /re +--- response_body_like chop +error: pcre_compile\(\) failed: two named subpatterns have the same name + +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 9: named subpatterns w/ extraction (use of duplicate names in duplicate mode) +--- config + location /re { + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + end + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(table.concat(m.first, "|")) + ngx.say(m.second) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + } + } +--- request + GET /re +--- response_body_like +hello, 1234 +h +ello +h|ello +nil + +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 10: captures input table in ngx.re.match +--- config + location /re { + content_by_lua_block { + local new_tab = require "table.new" + local clear_tab = require "table.clear" + local m + local res = new_tab(5, 0) + res[5] = "hello" + for i = 1, 100 do + m = ngx.re.match("hello, 1234", "([0-9])([0-9])([0-9])([0-9])", "jo", nil, res) + end + + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + ngx.say(m[4]) + ngx.say(m[5]) + else + ngx.say("not matched!") + end + } + } +--- request + GET /re +--- response_body +1234 +1 +2 +3 +4 +hello +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+\s+/ + + + +=== TEST 11: unmatched captures are false +--- config + location /re { + content_by_lua_block { + local m = ngx.re.match("hello!", "(hello)(, .+)?(!)", "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 +hello! +hello +false +! +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+\s+/ + + + +=== TEST 12: unmatched trailing captures are false +--- config + location /re { + content_by_lua_block { + local m = ngx.re.match("hello", "(hello)(, .+)?(!)?", "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 +hello +hello +false +false +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+\s+/ + + + +=== TEST 13: unmatched named captures are false +--- config + location /re { + content_by_lua_block { + local m = ngx.re.match("hello!", "(?hello)(?, .+)?(?!)", "jo") + + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + ngx.say(m.first) + ngx.say(m.second) + ngx.say(m.third) + else + ngx.say("not matched!") + end + } + } +--- request + GET /re +--- response_body +hello! +hello +false +! +hello +false +! +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+\s+/ + + + +=== TEST 14: subject is not a string type +--- config + location /re { + content_by_lua_block { + local m = ngx.re.match(12345, [=[(\d+)]=], "jo") + + if m then + ngx.say(m[0]) + ngx.say(m[1]) + else + ngx.say("not matched") + end + } + } +--- request + GET /re +--- response_body +12345 +12345 +--- no_error_log +[error] +attempt to get length of local 'subj' (a number value) + + + +=== TEST 15: subject is not a string type +--- config + location /re { + content_by_lua_block { + local m = ngx.re.match(12345, "123", "jo") + + if m then + ngx.say(m[0]) + else + ngx.say("not matched") + end + } + } +--- request + GET /re +--- response_body +123 +--- no_error_log +[error] +attempt to get length of local 'regex' (a number value) diff --git a/t/re-opt.t b/t/re-opt.t new file mode 100644 index 000000000..8ed907899 --- /dev/null +++ b/t/re-opt.t @@ -0,0 +1,136 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * blocks() * 3; + +no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: default jit_stack_size too small +--- config + location /re { + content_by_lua_block { + -- regex is taken from https://github.com/JuliaLang/julia/issues/8278 + local very_long_string = [[71.163.72.113 - - [30/Jul/2014:16:40:55 -0700] "GET emptymind.org/thevacantwall/wp-content/uploads/2013/02/DSC_006421.jpg HTTP/1.1" 200 492513 "http://images.search.yahoo.com/images/view;_ylt=AwrB8py9gdlTGEwADcSjzbkF;_ylu=X3oDMTI2cGZrZTA5BHNlYwNmcC1leHAEc2xrA2V4cARvaWQDNTA3NTRiMzYzY2E5OTEwNjBiMjc2YWJhMjkxMTEzY2MEZ3BvcwM0BGl0A2Jpbmc-?back=http%3A%2F%2Fus.yhs4.search.yahoo.com%2Fyhs%2Fsearch%3Fei%3DUTF-8%26p%3Dapartheid%2Bwall%2Bin%2Bpalestine%26type%3Dgrvydef%26param1%3D1%26param2%3Dsid%253Db01676f9c26355f014f8a9db87545d61%2526b%253DChrome%2526ip%253D71.163.72.113%2526p%253Dgroovorio%2526x%253DAC811262A746D3CD%2526dt%253DS940%2526f%253D7%2526a%253Dgrv_tuto1_14_30%26hsimp%3Dyhs-fullyhosted_003%26hspart%3Dironsource&w=588&h=387&imgurl=occupiedpalestine.files.wordpress.com%2F2012%2F08%2F5-peeking-through-the-wall.jpg%3Fw%3D588%26h%3D387&rurl=http%3A%2F%2Fwww.stopdebezetting.com%2Fwereldpers%2Fcompare-the-berlin-wall-vs-israel-s-apartheid-wall-in-palestine.html&size=49.0KB&name=...+%3Cb%3EApartheid+wall+in+Palestine%3C%2Fb%3E...+%7C+Or+you+go+peeking+through+the+%3Cb%3Ewall%3C%2Fb%3E&p=apartheid+wall+in+palestine&oid=50754b363ca991060b276aba291113cc&fr2=&fr=&tt=...+%3Cb%3EApartheid+wall+in+Palestine%3C%2Fb%3E...+%7C+Or+you+go+peeking+through+the+%3Cb%3Ewall%3C%2Fb%3E&b=0&ni=21&no=4&ts=&tab=organic&sigr=13evdtqdq&sigb=19k7nsjvb&sigi=12o2la1db&sigt=12lia2m0j&sign=12lia2m0j&.crumb=.yUtKgFI6DE&hsimp=yhs-fullyhosted_003&hspart=ironsource" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36]] + local very_complicated_regex = [[([\d\.]+) ([\w.-]+) ([\w.-]+) (\[.+\]) "([^"\r\n]*|[^"\r\n\[]*\[.+\][^"]+|[^"\r\n]+.[^"]+)" (\d{3}) (\d+|-) ("(?:[^"]|\")+)"? ("(?:[^"]|\")+)"?]] + local from, to, err = ngx.re.find(very_long_string, very_complicated_regex, "jo") + if from or to then + ngx.say("from: ", from) + ngx.say("to: ", to) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + } + } +--- request + GET /re +--- response_body +error: pcre_exec() failed: -27 +--- no_error_log +[error] +--- timeout: 10 + + + +=== TEST 2: increase jit_stack_size +--- http_config eval +qq{ + lua_package_path '$t::TestCore::lua_package_path'; + init_by_lua_block { + $t::TestCore::init_by_lua_block + + local ngx_re = require "ngx.re" + ngx_re.opt("jit_stack_size", 128 * 1024) + } +} +--- config + location /re { + content_by_lua_block { + -- regex is taken from https://github.com/JuliaLang/julia/issues/8278 + local very_long_string = [[71.163.72.113 - - [30/Jul/2014:16:40:55 -0700] "GET emptymind.org/thevacantwall/wp-content/uploads/2013/02/DSC_006421.jpg HTTP/1.1" 200 492513 "http://images.search.yahoo.com/images/view;_ylt=AwrB8py9gdlTGEwADcSjzbkF;_ylu=X3oDMTI2cGZrZTA5BHNlYwNmcC1leHAEc2xrA2V4cARvaWQDNTA3NTRiMzYzY2E5OTEwNjBiMjc2YWJhMjkxMTEzY2MEZ3BvcwM0BGl0A2Jpbmc-?back=http%3A%2F%2Fus.yhs4.search.yahoo.com%2Fyhs%2Fsearch%3Fei%3DUTF-8%26p%3Dapartheid%2Bwall%2Bin%2Bpalestine%26type%3Dgrvydef%26param1%3D1%26param2%3Dsid%253Db01676f9c26355f014f8a9db87545d61%2526b%253DChrome%2526ip%253D71.163.72.113%2526p%253Dgroovorio%2526x%253DAC811262A746D3CD%2526dt%253DS940%2526f%253D7%2526a%253Dgrv_tuto1_14_30%26hsimp%3Dyhs-fullyhosted_003%26hspart%3Dironsource&w=588&h=387&imgurl=occupiedpalestine.files.wordpress.com%2F2012%2F08%2F5-peeking-through-the-wall.jpg%3Fw%3D588%26h%3D387&rurl=http%3A%2F%2Fwww.stopdebezetting.com%2Fwereldpers%2Fcompare-the-berlin-wall-vs-israel-s-apartheid-wall-in-palestine.html&size=49.0KB&name=...+%3Cb%3EApartheid+wall+in+Palestine%3C%2Fb%3E...+%7C+Or+you+go+peeking+through+the+%3Cb%3Ewall%3C%2Fb%3E&p=apartheid+wall+in+palestine&oid=50754b363ca991060b276aba291113cc&fr2=&fr=&tt=...+%3Cb%3EApartheid+wall+in+Palestine%3C%2Fb%3E...+%7C+Or+you+go+peeking+through+the+%3Cb%3Ewall%3C%2Fb%3E&b=0&ni=21&no=4&ts=&tab=organic&sigr=13evdtqdq&sigb=19k7nsjvb&sigi=12o2la1db&sigt=12lia2m0j&sign=12lia2m0j&.crumb=.yUtKgFI6DE&hsimp=yhs-fullyhosted_003&hspart=ironsource" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36]] + local very_complicated_regex = [[([\d\.]+) ([\w.-]+) ([\w.-]+) (\[.+\]) "([^"\r\n]*|[^"\r\n\[]*\[.+\][^"]+|[^"\r\n]+.[^"]+)" (\d{3}) (\d+|-) ("(?:[^"]|\")+)"? ("(?:[^"]|\")+)"?]] + local from, to, err = ngx.re.find(very_long_string, very_complicated_regex, "jo") + if from or to then + ngx.say("from: ", from) + ngx.say("to: ", to) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + } + } +--- request + GET /re +--- response_body +from: 1 +to: 1563 +--- no_error_log +[error] +--- timeout: 10 + + + +=== TEST 3: jit_stack_size change disallowed once regex cache is populated +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local status, err = pcall(ngx_re.opt, "jit_stack_size", 128 * 1024) + if err then ngx.log(ngx.ERR, err) end + local s = "hello, 1234" + local from, to = ngx.re.find(s, "(hello world)|([0-9])", "jo") + ngx.say("from: ", from) + ngx.say("to: ", to) + } + } +--- request + GET /re +--- response_body +from: 8 +to: 8 + +--- grep_error_log eval +qr/changing jit stack size is not allowed when some regexs have already been compiled and cached/ + +--- grep_error_log_out eval +["", "changing jit stack size is not allowed when some regexs have already been compiled and cached\n"] +--- timeout: 10 + + + +=== TEST 4: passing unknown options to ngx_re.opt throws an error +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local status, err = pcall(ngx_re.opt, "foo", 123) + ngx.say(err) + } + } +--- request + GET /re +--- response_body_like chomp +unrecognized option name$ +--- no_error_log +[error] +--- timeout: 10 diff --git a/t/re-split.t b/t/re-split.t new file mode 100644 index 000000000..06bb47cea --- /dev/null +++ b/t/re-split.t @@ -0,0 +1,1273 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * blocks() * 4 + (2 * repeat_each()); + +no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: split matches, no submatch, no jit compile, no regex cache +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d", ",") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +b +c +d +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 2: split matches, no submatch, no jit compile, no regex cache +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a;,b;,c;,d;e", ";,") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +b +c +d;e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 3: split matches, no submatch, jit compile, regex cache +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d", ",", "jo") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +b +c +d +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 4: split matches + submatch (matching) +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a;,b;,c;,d,e", "(;),") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +; +b +; +c +; +d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 5: split matches + submatch (not matching) +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", "(;)|,") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +b +c +d +e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 6: split matches + max limiter +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, 3) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +b +c,d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 7: split matches + submatch + max limiter +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", "(,)", nil, nil, 3) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +, +b +, +c,d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 8: split matches + max limiter set to 0 +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, 0) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +b +c +d +e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 9: split matches + max limiter set to a negative value +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, -1) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +b +c +d +e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 10: split matches + max limiter set to 1 +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, 1) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a,b,c,d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 11: split matches, provided res table +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local my_table = {} + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, nil, my_table) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +b +c +d +e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 12: split matches, provided res table (non-cleared) +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local my_table = {} + + for i = 1, 10 do + my_table[i] = i.." hello world" + end + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, nil, my_table) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i in ipairs(my_table) do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +b +c +d +e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 13: split matches, provided res table + max limiter +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local my_table = {"hello, world"} + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, 3, my_table) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #my_table do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +b +c,d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 14: split matches, provided res table (non-cleared) + max limiter +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local my_table = {} + + for i = 1, 10 do + my_table[i] = i.." hello world" + end + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, 3, my_table) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i in ipairs(my_table) do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +b +c,d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 15: split matches, provided res table + max limiter + sub-match capturing group +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local my_table = {"hello, world"} + + local res, err = ngx_re.split("a,b,c,d,e", "(,)", nil, nil, 3, my_table) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #my_table do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +a +, +b +, +c,d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 16: split matches, ctx arg +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, { pos = 5 }) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +c +d +e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 17: split matches, trailing subjects +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split(",a,b,c,d,", ",") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + if res[i] == "" then + ngx.say("_blank_") + else + ngx.say(res[i]) + end + end + } + } +--- request +GET /re +--- response_body +_blank_ +a +b +c +d +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] +attempt to get length of local 'regex' (a number value) + + + +=== TEST 18: split matches, real use-case +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("abcd,erfg,ghij;hello world;aaa", ",|;") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +abcd +erfg +ghij +hello world +aaa +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 19: split no matches +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("abcd", ",") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +abcd +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 20: subject is not a string type +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split(1234512345, "23", "jo") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } + } +--- request +GET /re +--- response_body +1 +451 +45 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] +attempt to get length of local 'subj' (a number value) + + + +=== TEST 21: split matches, pos is larger than subject length +--- config + location = /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, { pos = 10 }) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +len: 0 +--- no_error_log +[error] +[TRACE + + + +=== TEST 22: regex is "" +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("12345", "", "jo") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +1 +2 +3 +4 +5 +len: 5 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 23: regex is "" with max +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("12345", "", "jo", nil, 3) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +1 +2 +345 +len: 3 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 24: regex is "" with pos +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("12345", "", "jo", { pos = 2 }) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +2 +3 +4 +5 +len: 4 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 25: regex is "" with pos larger than subject length +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("12345", "", "jo", { pos = 10 }) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +len: 0 +--- no_error_log +[error] +[TRACE + + + +=== TEST 26: regex is "" with pos & max +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("12345", "", "jo", { pos = 2 }, 2) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +2 +345 +len: 2 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 27: no match separator (github issue #104) +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("abcd", "|") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +a:b:c:d +len: 4 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 28: no match separator (github issue #104) & max +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("abcd", "|", nil, nil, 2) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +a:bcd +len: 2 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 29: no match separator bis (github issue #104) +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("abcd", "()") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +a::b::c::d +len: 7 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 30: behavior with /^/ differs from Perl's split +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("ab\ncd\nef", "^") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +ab +cd +ef +len: 1 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 31: behavior with /^/m +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("ab\ncd\nef", "^", "m") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +ab +:cd +:ef +len: 3 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 32: behavior with /^()/m (capture) +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("ab\ncd\nef", "^()", "m") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +ab +::cd +::ef +len: 5 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 33: behavior with /^/m & max +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("ab\ncd\nef", "^", "m", nil, 2) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +ab +:cd +ef +len: 2 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 34: behavior with /^\d/m +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("ab\n1cdefg\n2hij", "^\\d", "m") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +ab +:cdefg +:hij +len: 3 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 35: behavior with /^(\d)/m (capture) +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("ab\n1cdefg\n2hij", "^(\\d)", "m") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + + ngx.say("len: ", #res) + } + } +--- request +GET /re +--- response_body +ab +:1:cdefg +:2:hij +len: 5 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 36: split by unit separator 1/2 (GH issue lua-nginx-module #1217) +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local subjs = { + "1\x1fT\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f15", + "1\x1fT\x1fT\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f15", + "1\x1fT\x1fT\x1fT\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f15", + } + + for _, subj in ipairs(subjs) do + local col_list = ngx_re.split(subj, "\\x1f") + ngx.say(#col_list, " ", table.concat(col_list, "|")) + end + } + } +--- request +GET /re +--- response_body +15 1|T|||||||||||||15 +15 1|T|T||||||||||||15 +15 1|T|T|T|||||||||||15 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 37: split by unit separator 2/2 (with ctx.pos) +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local subjs = { + "1\x1fT\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f15", + "1\x1fT\x1fT\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f15", + "1\x1fT\x1fT\x1fT\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f15", + } + + for _, subj in ipairs(subjs) do + local col_list = ngx_re.split(subj, "\\x1f", nil, { pos = 6 }) + ngx.say(#col_list, " ", table.concat(col_list, "|")) + end + } + } +--- request +GET /re +--- response_body +12 |||||||||||15 +13 ||||||||||||15 +13 |T|||||||||||15 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 38: remaining characters are matched by regex (without max) +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local subj = "a,b,cd,,," + + local res, err = ngx_re.split(subj, ",") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(#res, " ", table.concat(res, "|")) + } + } +--- request +GET /re +--- response_body +3 a|b|cd +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 39: remaining characters are matched by regex (with max) +--- config + location /re { + content_by_lua_block { + local ngx_re = require "ngx.re" + + local subj = "a,b,cd,,," + + for max = 1, 7 do + local res, err = ngx_re.split(subj, ",", nil, nil, max) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(#res, " ", table.concat(res, "|")) + end + } + } +--- request +GET /re +--- response_body +1 a,b,cd,,, +2 a|b,cd,,, +3 a|b|cd,,, +4 a|b|cd|,, +5 a|b|cd||, +6 a|b|cd||| +6 a|b|cd||| +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 40: cannot load ngx.re module when lacking PCRE support +--- config + location /re { + content_by_lua_block { + package.loaded["ngx.re"] = nil + + local core_regex = require "resty.core.regex" + core_regex.no_pcre = true + + local pok, perr = pcall(require, "ngx.re") + if not pok then + ngx.say(perr) + end + } + } +--- request +GET /re +--- response_body +no support for 'ngx.re' module: OpenResty was compiled without PCRE support +--- no_error_log +[error] +[crit] diff --git a/t/re-sub.t b/t/re-sub.t new file mode 100644 index 000000000..8097a89f6 --- /dev/null +++ b/t/re-sub.t @@ -0,0 +1,359 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4 + 9); + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sub, no submatch, no jit compile, regex cache +--- config + location = /re { + access_log off; + content_by_lua_block { + local m, err + local sub = ngx.re.sub + for i = 1, 300 do + s, n, err = sub("abcbd", "b", "B", "jo") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + } + } +--- request +GET /re +--- response_body +s: aBcbd +n: 1 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 2: sub, no submatch, no jit compile, no regex cache +--- config + location = /re { + access_log off; + content_by_lua_block { + local m, err + local sub = ngx.re.sub + for i = 1, 300 do + s, n, err = sub("abcbd", "b", "B") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + } + } +--- request +GET /re +--- response_body +s: aBcbd +n: 1 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type + + + +=== TEST 3: func + submatches +--- config + location = /re { + access_log off; + content_by_lua_block { + local m, err + local function f(m) + return "[" .. m[0] .. "(" .. m[1] .. ")]" + end + local sub = ngx.re.sub + for i = 1, 300 do + s, n, err = sub("abcbd", "b(c)", f, "jo") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + } + } +--- request +GET /re +--- response_body +s: a[bc(c)]bd +n: 1 +--- no_error_log eval +[ +"[error]", +"bad argument type", +qr/NYI (?!bytecode 51 at)/, +] + + + +=== TEST 4: replace template + submatches +--- config + location = /re { + access_log off; + content_by_lua_block { + local m, err + local sub = ngx.re.sub + for i = 1, 300 do + s, n, err = sub("abcbd", "b(c)", "[$0($1)]", "jo") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + } + } +--- request +GET /re +--- response_body +s: a[bc(c)]bd +n: 1 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ + +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 5: replace template + submatches (exceeding buffers) +--- config + location = /re { + access_log off; + content_by_lua_block { + local m, err + local gsub = ngx.re.gsub + local subj = string.rep("bcbd", 2048) + for i = 1, 10 do + s, n, err = gsub(subj, "b(c)", "[$0($1)]", "jo") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + } + } +--- request +GET /re +--- response_body eval +"s: " . ("[bc(c)]bd" x 2048) . +"\nn: 2048\n" + +--- no_error_log +[error] +bad argument type + + + +=== TEST 6: ngx.re.gsub: use of ngx.req.get_headers in the user callback +--- config + +location = /t { + content_by_lua_block { + local data = [[ + INNER + INNER +]] + + -- ngx.say(data) + + local res = ngx.re.gsub(data, "INNER", function(inner_matches) + local header = ngx.req.get_headers()["Host"] + -- local header = ngx.var["http_HEADER"] + return "INNER_REPLACED" + end, "s") + + ngx.print(res) + } +} + +--- request +GET /t +--- response_body + INNER_REPLACED + INNER_REPLACED + +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 7: ngx.re.gsub: use of ngx.var in the user callback +--- config + +location = /t { + content_by_lua_block { + local data = [[ + INNER + INNER +]] + + -- ngx.say(data) + + local res = ngx.re.gsub(data, "INNER", function(inner_matches) + -- local header = ngx.req.get_headers()["Host"] + local header = ngx.var["http_HEADER"] + return "INNER_REPLACED" + end, "s") + + ngx.print(res) + } +} + +--- request +GET /t +--- response_body + INNER_REPLACED + INNER_REPLACED + +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 8: ngx.re.gsub: recursive calling (github openresty/lua-nginx-module#445) +--- config + +location = /t { + content_by_lua_block { + function test() + local data = [[ + OUTER {FIRST} +]] + + local p1 = "(OUTER)(.+)" + local p2 = "{([A-Z]+)}" + + ngx.print(data) + + local res = ngx.re.gsub(data, p1, function(m) + -- ngx.say("pre: m[1]: [", m[1], "]") + -- ngx.say("pre: m[2]: [", m[2], "]") + + local res = ngx.re.gsub(m[2], p2, function(_) + return "REPLACED" + end, "") + + -- ngx.say("post: m[1]: [", m[1], "]") + -- ngx.say("post m[2]: [", m[2], "]") + return m[1] .. res + end, "") + + ngx.print(res) + end + + test() + } +} +--- request +GET /t +--- response_body + OUTER {FIRST} + OUTER REPLACED +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 9: string replace subj is not a string type +--- config + location /re { + content_by_lua_block { + local newstr, n, err = ngx.re.sub(1234, "([0-9])[0-9]", 5, "jo") + + ngx.say(newstr) + } + } +--- request + GET /re +--- response_body +534 +--- no_error_log +[error] +attempt to get length of local 'subj' (a number value) + + + +=== TEST 10: func replace return is not a string type (ngx.re.sub) +--- config + location /re { + content_by_lua_block { + local lookup = function(m) + -- note we are returning a number type here + return 5 + end + + local newstr, n, err = ngx.re.sub("hello, 1234", "([0-9])[0-9]", lookup, "jo") + ngx.say(newstr) + } + } +--- request + GET /re +--- response_body +hello, 534 +--- no_error_log +[error] +attempt to get length of local 'bit' (a number value) + + + +=== TEST 11: func replace return is not a string type (ngx.re.gsub) +--- config + location /re { + content_by_lua_block { + local lookup = function(m) + -- note we are returning a number type here + return 5 + end + + local newstr, n, err = ngx.re.gsub("hello, 1234", "([0-9])[0-9]", lookup, "jo") + ngx.say(newstr) + } + } +--- request + GET /re +--- response_body +hello, 55 +--- no_error_log +[error] +attempt to get length of local 'bit' (a number value) diff --git a/t/request.t b/t/request.t new file mode 100644 index 000000000..8d09110f6 --- /dev/null +++ b/t/request.t @@ -0,0 +1,695 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4 + 19); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.req.get_headers +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ffi = require "ffi" + local headers + for i = 1, 500 do + headers = ngx.req.get_headers() + end + local keys = {} + for k, _ in pairs(headers) do + keys[#keys + 1] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + ngx.say(k, ": ", headers[k]) + end + } + } +--- request +GET /t +--- response_body +bar: bar +baz: baz +connection: close +foo: foo +host: localhost +--- more_headers +Foo: foo +Bar: bar +Baz: baz +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ .*? -> \d+\]/ +--- no_error_log eval +[ +"[error]", +qr/ -- NYI: (?!return to lower frame)/, +] + + + +=== TEST 2: ngx.req.get_headers (raw) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ffi = require "ffi" + local headers + for i = 1, 500 do + headers = ngx.req.get_headers(100, true) + end + local keys = {} + for k, _ in pairs(headers) do + keys[#keys + 1] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + ngx.say(k, ": ", headers[k]) + end + } + } +--- request +GET /t +--- response_body +Bar: bar +Baz: baz +Connection: close +Foo: foo +Host: localhost +--- more_headers +Foo: foo +Bar: bar +Baz: baz +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ .*? -> \d+\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 3: ngx.req.get_headers (count is 2) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ffi = require "ffi" + local headers + for i = 1, 500 do + headers = ngx.req.get_headers(2, true) + end + local keys = {} + for k, _ in pairs(headers) do + keys[#keys + 1] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + ngx.say(k, ": ", headers[k]) + end + } + } +--- request +GET /t +--- response_body +Connection: close +Host: localhost +--- more_headers +Foo: foo +Bar: bar +Baz: baz +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 4: ngx.req.get_headers (metatable) +--- http_config eval +" +$::HttpConfig +underscores_in_headers on; +" +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ffi = require "ffi" + local headers, header + for i = 1, 500 do + headers = ngx.req.get_headers() + header = headers["foo_BAR"] + end + ngx.say("foo_BAR: ", header) + local keys = {} + for k, _ in pairs(headers) do + keys[#keys + 1] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + ngx.say(k, ": ", headers[k]) + end + + ngx.say("X_Bar_Header: ", headers["X_Bar_Header"]) + ngx.say("x_Bar_Header: ", headers["x_Bar_Header"]) + ngx.say("x_bar_header: ", headers["x_bar_header"]) + } + } +--- request +GET /t +--- response_body +foo_BAR: foo +baz: baz +connection: close +foo-bar: foo +host: localhost +x_bar_header: bar +X_Bar_Header: bar +x_Bar_Header: bar +x_bar_header: bar +--- more_headers +Foo-Bar: foo +Baz: baz +X_Bar_Header: bar +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ .*? -> \d+\]/ +--- no_error_log eval +["[error]", +qr/ -- NYI: (?!return to lower frame at)(?!C function 0x[0-9a-f]+ at content_by_lua\(nginx.conf:\d+\):15)/, +] + + + +=== TEST 5: ngx.req.get_uri_args +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ffi = require "ffi" + local args + for i = 1, 500 do + args = ngx.req.get_uri_args() + end + if type(args) ~= "table" then + ngx.say("bad args type found: ", args) + return + end + local keys = {} + for k, _ in pairs(args) do + keys[#keys + 1] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + local v = args[k] + if type(v) == "table" then + ngx.say(k, ": ", table.concat(v, ", ")) + else + ngx.say(k, ": ", v) + end + end + } + } +--- request +GET /t?a=3%200&foo%20bar=&a=hello&blah +--- response_body +a: 3 0, hello +blah: true +foo bar: +--- error_log eval +qr/\[TRACE\s+\d+ .*? -> \d+\]/ +--- no_error_log +[error] + -- NYI: +--- wait: 0.2 + + + +=== TEST 6: ngx.req.get_uri_args (empty) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ffi = require "ffi" + local args + for i = 1, 500 do + args = ngx.req.get_uri_args() + end + if type(args) ~= "table" then + ngx.say("bad args type found: ", args) + return + end + local keys = {} + for k, _ in pairs(args) do + keys[#keys + 1] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + local v = args[k] + if type(v) == "table" then + ngx.say(k, ": ", table.concat(v, ", ")) + else + ngx.say(k, ": ", v) + end + end + } + } +--- request +GET /t? +--- response_body +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 7: ngx.req.start_time() +--- config + location = /t { + access_log off; + content_by_lua_block { + local t + for i = 1, 500 do + t = ngx.req.start_time() + end + ngx.sleep(0.10) + local elapsed = ngx.now() - t + ngx.say(t > 1399867351) + ngx.say(">= 0.099: ", elapsed >= 0.099) + ngx.say("< 0.11: ", elapsed < 0.11) + -- ngx.say(t, " ", elapsed) + } + } +--- request +GET /t +--- response_body +true +>= 0.099: true +< 0.11: true + +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 8: ngx.req.get_method (GET) +--- config + location = /t { + access_log off; + content_by_lua_block { + local t + for i = 1, 500 do + t = ngx.req.get_method() + end + ngx.say("method: ", t) + } + } +--- request +GET /t +--- response_body +method: GET + +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 9: ngx.req.get_method (OPTIONS) +--- config + location = /t { + access_log off; + content_by_lua_block { + local t + for i = 1, 500 do + t = ngx.req.get_method() + end + ngx.say("method: ", t) + } + } +--- request +OPTIONS /t +--- response_body +method: OPTIONS + +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 10: ngx.req.get_method (POST) +--- config + location = /t { + access_log off; + content_by_lua_block { + local t + for i = 1, 500 do + t = ngx.req.get_method() + end + ngx.say("method: ", t) + ngx.req.discard_body() + } + } +--- request +POST /t +hello +--- response_body +method: POST + +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 11: ngx.req.get_method (unknown method) +--- config + location = /t { + access_log off; + content_by_lua_block { + local t + for i = 1, 500 do + t = ngx.req.get_method() + end + ngx.say("method: ", t) + ngx.req.discard_body() + } + } +--- request +BLAH /t +hello +--- response_body +method: BLAH + +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 12: ngx.req.get_method (CONNECT) +--- config + location = /t { + access_log off; + content_by_lua_block { + local t + for i = 1, 500 do + t = ngx.req.get_method() + end + ngx.say("method: ", t) + ngx.req.discard_body() + } + } +--- request +CONNECT /t +hello +--- response_body +method: CONNECT + +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch +--- skip_nginx +6: >= 1.21.1 + + + +=== TEST 13: ngx.req.set_method (GET -> PUT) +--- config + location = /t { + access_log off; + content_by_lua_block { + local t + for i = 1, 500 do + ngx.req.set_method(ngx.HTTP_PUT) + end + ngx.say("method: ", ngx.req.get_method()) + } + } +--- request +GET /t +--- response_body +method: PUT + +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 14: ngx.req.set_header (single number value) +--- config + location = /t { + access_log off; + content_by_lua_block { + local t + for i = 1, 500 do + ngx.req.set_header("foo", i) + end + ngx.say("header foo: ", ngx.var.http_foo) + } + } +--- request +GET /t +--- response_body +header foo: 500 + +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 15: ngx.req.set_header (nil value) +--- config + location = /t { + access_log off; + content_by_lua_block { + local t + for i = 1, 500 do + ngx.req.set_header("foo", nil) + end + ngx.say("header foo: ", type(ngx.var.http_foo)) + } + } +--- request +GET /t +--- response_body +header foo: nil + +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 16: ngx.req.clear_header +--- config + location = /t { + access_log off; + content_by_lua_block { + ngx.req.set_header("foo", "hello") + local t + for i = 1, 500 do + t = ngx.req.clear_header("foo") + end + ngx.say("header foo: ", type(ngx.var.http_foo)) + } + } +--- request +GET /t +--- response_body +header foo: nil + +--- wait: 0.2 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 17: ngx.req.set_header (multiple values) +--- config + location = /t { + content_by_lua_block { + ngx.req.set_header("Foo", { "baz", 123 }) + + ngx.say("Foo: ", table.concat(ngx.req.get_headers()["Foo"], ", ")) + } + } +--- request +GET /t +--- more_headers +Foo: bar +--- response_body +Foo: baz, 123 +--- no_error_log +[error] + + + +=== TEST 18: ngx.req.get_header (metatable is nil) +--- config + location = /t { + content_by_lua_block { + local headers = ngx.req.get_headers() + + ngx.say(string.format("%s,%s",type(headers), type(getmetatable(headers)))) + } + } + +--- raw_request eval +"GET /t \r\n" +--- http09 +--- response_body +table,table + + + +=== TEST 19: CONNECT method is considered invalid since nginx 1.21.1 +--- config + location = /t { + access_log off; + content_by_lua_block { + local t + for i = 1, 500 do + t = ngx.req.get_method() + end + ngx.say("method: ", t) + ngx.req.discard_body() + } + } +--- request +CONNECT /t +hello +--- error_code: 405 +--- no_error_log +[error] +--- skip_nginx +2: < 1.21.1 + + + +=== TEST 20: get_uri_args allows to reuse table +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local base = require "resty.core.base" + local args = base.new_tab(0, 3) + local id = tostring(args) + for i = 1, 5 do + base.clear_tab(args) + args = ngx.req.get_uri_args(-1, args) + assert(tostring(args) == id) + end + local keys = {} + for k, _ in pairs(args) do + keys[#keys + 1] = k + end + table.sort(keys) + for _, k in ipairs(keys) do + local v = args[k] + if type(v) == "table" then + ngx.say(k, ": ", table.concat(v, ", ")) + else + ngx.say(k, ": ", v) + end + end + } + } +--- request +GET /t?a=3%200&foo%20bar=&a=hello&blah +--- response_body +a: 3 0, hello +blah: true +foo bar: +--- no_error_log +[error] + + + +=== TEST 21: get_uri_args allows to reuse table (empty) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local base = require "resty.core.base" + local args = base.new_tab(0, 3) + local id = tostring(args) + for i = 1, 5 do + args = ngx.req.get_uri_args(-1, args) + assert(tostring(args) == id) + end + local n_key = 0 + for k, _ in pairs(args) do + n_key = n_key + 1 + end + ngx.say(n_key) + } + } +--- request +GET /t +--- response_body +0 +--- no_error_log +[error] diff --git a/t/require.t b/t/require.t new file mode 100644 index 000000000..68e98d721 --- /dev/null +++ b/t/require.t @@ -0,0 +1,74 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +plan tests => repeat_each() * blocks() * 3; + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + my $init_by_lua_block = $block->init_by_lua_block || ''; + + $http_config .= <<_EOC_; + lua_package_path '\$prefix/html/?.lua;$t::TestCore::lua_package_path'; + init_by_lua_block { + $t::TestCore::init_by_lua_block + $init_by_lua_block + } +_EOC_ + + $block->set_value("http_config", $http_config); +}); + +no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: utils.str_replace_char() sanity (replaces a single character) +--- config + location /t { + content_by_lua_block { + local has_foo, foo + has_foo, foo= pcall(require, "foo") + if not has_foo then + ngx.say("failed to load foo: ", foo) + end + + has_foo, foo = pcall(require, "foo") + if not has_foo then + ngx.say("failed to load foo again: ", foo) + else + ngx.say("type(a)=", type(a), " foo=", foo) + end + } + } +--- user_files +>>> foo.lua +local ffi = require("ffi") + +local _M = {} + +ffi.cdef[[ +int xxxx(); +]] + +local function get_caches_array() + return tonumber(ffi.C.xxxx()) +end + +local a = get_caches_array() + +_M.a = a +_M.get_caches_array = get_caches_array + +return _M +--- request +GET /t +--- response_body eval +qr|failed to load foo: .*/html/foo.lua:10: .*/lib/libluajit-5.1.so.2: undefined symbol: xxxx +failed to load foo again: ./lib/resty/core/base.lua:\d+: loop or previous error loading module 'foo'|ms +--- no_error_log +[error] diff --git a/t/response.t b/t/response.t new file mode 100644 index 000000000..948764ed5 --- /dev/null +++ b/t/response.t @@ -0,0 +1,205 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5 + 5); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: write to ngx.header.HEADER (single value) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + for i = 1, 100 do + ngx.header["Foo"] = i + end + ngx.say("Foo: ", ngx.header["Foo"]) + } + } +--- request +GET /t +--- response_body +Foo: 100 + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):2 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 2: write to ngx.header.HEADER (nil) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + for i = 1, 200 do + ngx.header["Foo"] = i + ngx.header["Foo"] = nil + end + ngx.say("Foo: ", ngx.header["Foo"]) + } + } +--- request +GET /t +--- response_body +Foo: nil + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):2 loop\]/ +--- wait: 0.2 +--- no_error_log +[error] + -- NYI: + + + +=== TEST 3: write to ngx.header.HEADER (multi-value) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + for i = 1, 200 do + ngx.header["Foo"] = {i, i + 1} + end + local v = ngx.header["Foo"] + if type(v) == "table" then + ngx.say("Foo: ", table.concat(v, ", ")) + else + ngx.say("Foo: ", v) + end + } + } +--- request +GET /t +--- response_body +Foo: 200, 201 + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):2 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 4: read from ngx.header.HEADER (single value) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local v + for i = 1, 100 do + ngx.header["Foo"] = i + v = ngx.header["Foo"] + end + ngx.say("Foo: ", v) + } + } +--- request +GET /t +--- response_body +Foo: 100 + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log eval +[ +"[error]", +qr/ -- NYI: (?!return to lower frame)/, +"stitch", +] + + + +=== TEST 5: read from ngx.header.HEADER (not found) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local v + for i = 1, 100 do + v = ngx.header["Foo"] + end + ngx.say("Foo: ", v) + } + } +--- request +GET /t +--- response_body +Foo: nil + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: +stitch + + + +=== TEST 6: read from ngx.header.HEADER (multi-value) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + ngx.header["Foo"] = {"foo", "bar"} + local v + for i = 1, 100 do + v = ngx.header["Foo"] + end + ngx.say("Foo: ", table.concat(v, ", ")) + } + } +--- request +GET /t +--- response_body +Foo: foo, bar + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: +stitch + + + +=== TEST 7: set multi values to cache-control and override it with multiple values +--- config + location /lua { + content_by_lua_block { + ngx.header.cache_control = { "private", "no-store" } + ngx.header.cache_control = { "no-cache", "blah", "foo" } + local v + for i = 1, 400 do + v = ngx.header.cache_control + end + ngx.say("Cache-Control: ", table.concat(v, ", ")) + } + } +--- request + GET /lua +--- response_headers +Cache-Control: no-cache, blah, foo +--- response_body_like chop +^Cache-Control: no-cache[;,] blah[;,] foo$ +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):5 (?:loop|-> \d+)\]/ +--- no_error_log +[error] + -- NYI: +stitch diff --git a/t/semaphore.t b/t/semaphore.t new file mode 100644 index 000000000..064831e38 --- /dev/null +++ b/t/semaphore.t @@ -0,0 +1,1840 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(10140); +#workers(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 3); + +no_long_string(); +#no_diff(); +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::lua_package_path"; +our $HttpConfig = <<_EOC_; + lua_package_path "$t::TestCore::lua_package_path"; +_EOC_ + +run_tests(); + +__DATA__ + +=== TEST 1: basic semaphore in uthread +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + + local function sem_wait() + ngx.say("enter waiting") + + local ok, err = sem:wait(1) + if not ok then + ngx.say("err: ", err) + else + ngx.say("wait success") + end + end + + local co = ngx.thread.spawn(sem_wait) + + ngx.say("back in main thread") + + sem:post() + + ngx.say("still in main thread") + + ngx.sleep(0.01) + + ngx.say("main thread end") + } + } +--- request +GET /test +--- response_body +enter waiting +back in main thread +still in main thread +wait success +main thread end +--- no_error_log +[error] + + + +=== TEST 2: semaphore wait order +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + + local function sem_wait(id) + ngx.say("enter waiting, id: ", id) + + local ok, err = sem:wait(1) + if not ok then + ngx.say("err: ", err) + else + ngx.say("wait success, id: ", id) + end + end + + local co1 = ngx.thread.spawn(sem_wait, 1) + local co2 = ngx.thread.spawn(sem_wait, 2) + + ngx.say("back in main thread") + + sem:post(2) + + local ok, err = sem:wait(0) + if ok then + ngx.say("wait success in main thread") + else + ngx.say("wait failed in main thread: ", err) -- busy + end + + ngx.say("still in main thread") + + local ok, err = sem:wait(0.01) + if ok then + ngx.say("wait success in main thread") + else + ngx.say("wait failed in main thread: ", err) + end + + ngx.sleep(0.01) + + ngx.say("main thread end") + } + } +--- request +GET /test +--- response_body +enter waiting, id: 1 +enter waiting, id: 2 +back in main thread +wait failed in main thread: timeout +still in main thread +wait success, id: 1 +wait success, id: 2 +wait failed in main thread: timeout +main thread end +--- no_error_log +[error] + + + +=== TEST 3: semaphore wait time=0 +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(1) + + local function wait_1s() + ngx.say("enter 1s wait") + + local ok, err = sem:wait(1) + if not ok then + ngx.say("err in wait 1s: ", err) + else + ngx.say("wait success in 1s wait") + end + end + + local function wait_0() + local ok, err = sem:wait(0) + if not ok then + ngx.say("err: ", err) + else + ngx.say("wait success") + end + end + + wait_0() + wait_0() + + local co = ngx.thread.spawn(wait_1s) + + ngx.say("back in main thread") + + wait_0() + + sem:post(2) + + wait_0() + + ngx.say("still in main thread") + + ngx.sleep(0.01) + + wait_0() + + ngx.say("main thread end") + } + } +--- request +GET /test +--- response_body +wait success +err: timeout +enter 1s wait +back in main thread +err: timeout +err: timeout +still in main thread +wait success in 1s wait +wait success +main thread end +--- no_error_log +[error] + + + +=== TEST 4: basic semaphore in subrequest +--- http_config eval: $::HttpConfig +--- config + location = /test { + content_by_lua_block { + local res1, res2 = ngx.location.capture_multi{ + { "/sem_wait"}, + { "/sem_post"}, + } + ngx.say(res1.status) + ngx.say(res1.body) + ngx.say(res2.status) + ngx.say(res2.body) + } + } + + location /sem_wait { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local g = package.loaded["semaphore_test"] or {} + package.loaded["semaphore_test"] = g + + if not g.test then + local sem, err = semaphore.new(0) + if not sem then + ngx.say(err) + return + end + g.test = sem + end + local sem = g.test + local ok, err = sem:wait(1) + if ok then + ngx.print("wait") + end + } + } + + location /sem_post { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local g = package.loaded["semaphore_test"] or {} + package.loaded["semaphore_test"] = g + + if not g.test then + local sem, err = semaphore.new(0) + if not sem then + ngx.say(err) + ngx.exit(500) + end + g.test = sem + end + local sem = g.test + ngx.sleep(0.001) + collectgarbage("collect") + sem:post() + ngx.print("post") + } + } +--- request +GET /test +--- response_body +200 +wait +200 +post +--- no_error_log +[error] +[crit] + + + +=== TEST 5: semaphore.new in init_by_lua* (w/o shdict) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + init_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + else + ngx.log(ngx.WARN, "sema created: ", tostring(sem)) + end + sem:post(2) + package.loaded.my_sema = sem + } +--- config + location /test { + content_by_lua_block { + local sem = package.loaded.my_sema + ngx.say("sem count: ", sem:count()) + -- sem:post(1) + local ok, err = sem:wait(0) + if not ok then + ngx.say("failed to wait: ", err) + return + end + ngx.say("waited successfully.") + } + } +--- request +GET /test +--- response_body_like +sem count: [12] +waited successfully. +--- grep_error_log eval +qr/\[lua\] init_by_lua\(nginx.conf:\d+\):\d+: sema created: table: 0x[a-f0-9]+/ +--- grep_error_log_out eval +[ +qr/\[lua\] init_by_lua\(nginx.conf:\d+\):\d+: sema created: table: 0x[a-f0-9]+/, +"", +] + + + +=== TEST 6: semaphore.new in init_by_lua* (with shdict) +--- http_config + lua_shared_dict dogs 1m; + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + init_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + else + ngx.log(ngx.WARN, "sema created: ", tostring(sem)) + end + sem:post(2) + package.loaded.my_sema = sem + } +--- config + location /test { + content_by_lua_block { + local sem = package.loaded.my_sema + ngx.say("sem count: ", sem:count()) + -- sem:post(1) + local ok, err = sem:wait(0) + if not ok then + ngx.say("failed to wait: ", err) + return + end + ngx.say("waited successfully.") + } + } +--- request +GET /test +--- response_body_like +sem count: [12] +waited successfully. +--- grep_error_log eval +qr/\[lua\] init_by_lua\(nginx.conf:\d+\):\d+: sema created: table: 0x[a-f0-9]+/ +--- grep_error_log_out eval +[ +qr/\[lua\] init_by_lua\(nginx.conf:\d+\):\d+: sema created: table: 0x[a-f0-9]+/, +"", +] + + + +=== TEST 7: semaphore in init_worker_by_lua (wait is not allowed) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + init_worker_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem new: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem count: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem wait: ", err) + end + } +--- config + location /t { + echo "ok"; + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem \w+: .*?,/ +--- grep_error_log_out eval +[ +"sem count: 1, +sem wait: API disabled in the context of init_worker_by_lua*, +", +"", +] + + + +=== TEST 8: semaphore in init_worker_by_lua (new and post) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + init_worker_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem new: ", err) + end + + sem:post(2) + + local count = sem:count() + ngx.log(ngx.WARN, "sem count: ", count) + + package.loaded.my_sema = sem + } +--- config + location /t { + content_by_lua_block { + local sem = package.loaded.my_sema + local ok, err = sem:wait(0.1) + if not ok then + ngx.say("failed to wait: ", err) + return + end + ngx.say("sem wait successfully.") + } + } +--- request +GET /t +--- response_body +sem wait successfully. +--- grep_error_log eval: qr/sem \w+: .*?,/ +--- grep_error_log_out eval +[ +"sem count: 2, +", +"" +] +--- no_error_log +[error] + + + +=== TEST 9: semaphore in set_by_lua (wait is not allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + set_by_lua_block $res { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } + echo "ok"; + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: 1, +sem: API disabled in the context of set_by_lua*, +", +"sem: 1, +sem: API disabled in the context of set_by_lua*, +", +] + + + +=== TEST 10: semaphore in rewrite_by_lua (all allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + rewrite_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + local ok, err = sem:wait(0.01) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } + echo "ok"; + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: timeout, +sem: 1, +", +"sem: timeout, +sem: 1, +", +] + + + +=== TEST 11: semaphore in access_by_lua (all allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + access_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + local ok, err = sem:wait(0.01) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } + echo "ok"; + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: timeout, +sem: 1, +", +"sem: timeout, +sem: 1, +", +] + + + +=== TEST 12: semaphore in content_by_lua (all allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + local ok, err = sem:wait(0.01) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + else + ngx.say("ok") + end + } + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: timeout, +sem: 1, +", +"sem: timeout, +sem: 1, +", +] + + + +=== TEST 13: semaphore in log_by_lua (wait not allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + echo "ok"; + log_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: 1 while logging request, +sem: API disabled in the context of log_by_lua* while logging request, +", +"sem: 1 while logging request, +sem: API disabled in the context of log_by_lua* while logging request, +", +] +--- wait: 0.2 + + + +=== TEST 14: semaphore in header_filter_by_lua (wait not allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + echo "ok"; + header_filter_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: 1, +sem: API disabled in the context of header_filter_by_lua*, +", +"sem: 1, +sem: API disabled in the context of header_filter_by_lua*, +", +] + + + +=== TEST 15: semaphore in body_filter_by_lua (wait not allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + echo "ok"; + body_filter_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: 1, +sem: API disabled in the context of body_filter_by_lua*, +sem: 1, +sem: API disabled in the context of body_filter_by_lua*, +", +"sem: 1, +sem: API disabled in the context of body_filter_by_lua*, +sem: 1, +sem: API disabled in the context of body_filter_by_lua*, +", +] + + + +=== TEST 16: semaphore in ngx.timer (all allowed) +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua_block { + local function func_sem() + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + local ok, err = sem:wait(0.01) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + end + + local ok, err = ngx.timer.at(0, func_sem) + if ok then + ngx.sleep(0.01) + ngx.say("ok") + end + } + } +--- request +GET /t +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: timeout, +sem: 1, +", +"sem: timeout, +sem: 1, +", +] +--- wait: 0.2 + + + +=== TEST 17: semaphore post in all phase (in a request) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + init_worker_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + end + package.loaded.sem = sem + + local function wait() + local i = 0 + while true do + local ok, err = sem:wait(1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + i = i + 1 + if i % 6 == 0 then + ngx.log(ngx.ERR, "sem: 6 times") + end + end + end + + local ok, err = ngx.timer.at(0, wait) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } +--- config + location /test { + set_by_lua_block $res { + local sem = package.loaded.sem + sem:post() + } + rewrite_by_lua_block { + local sem = package.loaded.sem + sem:post() + } + access_by_lua_block { + local sem = package.loaded.sem + sem:post() + } + content_by_lua_block { + local sem = package.loaded.sem + sem:post() + ngx.say("ok") + } + header_filter_by_lua_block { + local sem = package.loaded.sem + sem:post() + } + body_filter_by_lua_block { + local sem = package.loaded.sem + sem:post() + } + } +--- request +GET /test +--- response_body +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: 6 times, +", +"sem: 6 times, +", +] +--- wait: 0.2 + + + +=== TEST 18: semaphore wait post in access_by_lua +--- http_config eval: $::HttpConfig +--- config + location /test { + access_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + + local func_wait = function () + ngx.say("enter wait") + + local ok, err = sem:wait(1) + if ok then + ngx.say("wait success") + end + end + local func_post = function () + ngx.say("enter post") + + sem:post() + ngx.say("post success") + end + + local co1 = ngx.thread.spawn(func_wait) + local co2 = ngx.thread.spawn(func_post) + + ngx.thread.wait(co1) + ngx.thread.wait(co2) + } + } +--- request +GET /test +--- response_body +enter wait +enter post +post success +wait success +--- no_error_log +[error] + + + +=== TEST 19: semaphore wait post in rewrite_by_lua +--- http_config eval: $::HttpConfig +--- config + location /t { + rewrite_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + + local func_wait = function () + ngx.say("enter wait") + + local ok, err = sem:wait(1) + if ok then + ngx.say("wait success") + end + end + local func_post = function () + ngx.say("enter post") + + sem:post() + ngx.say("post success") + end + + local co1 = ngx.thread.spawn(func_wait) + local co2 = ngx.thread.spawn(func_post) + + ngx.thread.wait(co1) + ngx.thread.wait(co2) + } + } +--- request +GET /test +--- response_body +enter wait +enter post +post success +wait success +--- no_error_log +[error] + + + +=== TEST 20: semaphore wait in timer.at +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + + local function func_wait(premature) + local ok, err = sem:wait(1) + if not ok then + ngx.log(ngx.ERR, err) + else + ngx.log(ngx.ERR, "wait success") + end + end + + ngx.timer.at(0, func_wait) + + sem:post() + ngx.sleep(0.01) + ngx.say("ok") + } + } +--- request +GET /test +--- response_body +ok +--- error_log +wait success + + + +=== TEST 21: semaphore post in header_filter_by_lua (subrequest) +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local res1, res2 = ngx.location.capture_multi{ + {"/sem_wait"}, + {"/sem_post"}, + } + ngx.say(res1.status) + ngx.say(res1.body) + ngx.say(res2.status) + ngx.say(res2.body) + } + } + + location /sem_wait { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + if not package.loaded.sem then + local sem, err = semaphore.new(0) + if not sem then + ngx.say(err) + ngx.exit(500) + end + package.loaded.sem = sem + end + local sem = package.loaded.sem + local ok, err = sem:wait(1) + if ok then + ngx.print("wait") + ngx.exit(200) + else + ngx.exit(500) + end + } + } + + location /sem_post { + header_filter_by_lua_block { + local semaphore = require "ngx.semaphore" + if not package.loaded.sem then + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + end + package.loaded.sem = sem + end + local sem = package.loaded.sem + sem:post() + } + + content_by_lua_block { + ngx.print("post") + ngx.exit(200) + } + } +--- request +GET /test +--- response_body +200 +wait +200 +post +--- no_error_log +[error] + + + +=== TEST 22: semaphore post in body_filter_by_lua (subrequest) +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local res1, res2 = ngx.location.capture_multi{ + {"/sem_wait"}, + {"/sem_post"}, + } + ngx.say(res1.status) + ngx.say(res1.body) + ngx.say(res2.status) + ngx.say(res2.body) + } + } + + location /sem_wait { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + if not package.loaded.sem then + local sem, err = semaphore.new(0) + if not sem then + ngx.say(err) + ngx.exit(500) + end + package.loaded.sem = sem + end + local sem = package.loaded.sem + local ok, err = sem:wait(10) + if ok then + ngx.print("wait") + ngx.exit(200) + else + ngx.exit(500) + end + } + } + + location /sem_post { + body_filter_by_lua_block { + local semaphore = require "ngx.semaphore" + if not package.loaded.sem then + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + end + package.loaded.sem = sem + end + local sem = package.loaded.sem + sem:post() + } + + content_by_lua_block { + ngx.print("post") + ngx.exit(200) + } + } +--- request +GET /test +--- response_body +200 +wait +200 +post +--- log_level: debug +--- no_error_log +[error] + + + +=== TEST 23: semaphore post in set_by_lua +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local res1, res2 = ngx.location.capture_multi{ + {"/sem_wait"}, + {"/sem_post"}, + } + ngx.say(res1.status) + ngx.say(res1.body) + ngx.say(res2.status) + ngx.say(res2.body) + } + } + + location /sem_wait { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + if not package.loaded.sem then + local sem, err = semaphore.new(0) + if not sem then + ngx.say(err) + ngx.exit(500) + end + package.loaded.sem = sem + end + local sem = package.loaded.sem + local ok, err = sem:wait(10) + if ok then + ngx.print("wait") + ngx.exit(200) + else + ngx.exit(500) + end + } + } + + location /sem_post { + set_by_lua_block $res { + local semaphore = require "ngx.semaphore" + if not package.loaded.sem then + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + end + package.loaded.sem = sem + end + local sem = package.loaded.sem + sem:post() + } + content_by_lua_block { + ngx.print("post") + ngx.exit(200) + } + } +--- request +GET /test +--- response_body +200 +wait +200 +post +--- log_level: debug +--- no_error_log +[error] + + + +=== TEST 24: semaphore post in timer.at +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + package.loaded.sem = semaphore.new(0) + local res1, res2 = ngx.location.capture_multi{ + {"/sem_wait"}, + {"/sem_post"}, + } + ngx.say(res1.status) + ngx.say(res1.body) + ngx.say(res2.status) + ngx.say(res2.body) + } + } + + location /sem_wait { + content_by_lua_block { + local sem = package.loaded.sem + local ok, err = sem:wait(2) + if ok then + ngx.print("wait") + ngx.exit(200) + else + ngx.status = 500 + ngx.say(err) + end + } + } + + location /sem_post { + content_by_lua_block { + local function func(premature) + local sem = package.loaded.sem + sem:post() + end + ngx.timer.at(0, func, g) + ngx.sleep(0) + ngx.print("post") + ngx.exit(200) + } + } +--- request +GET /test +--- response_body +200 +wait +200 +post +--- log_level: debug +--- no_error_log +[error] + + + +=== TEST 25: two thread wait for each other +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem_A = semaphore.new(0) + local sem_B = semaphore.new(0) + if not sem_A or not sem_B then + error("create failed") + end + + local function th_A() + for i = 1, 11 do + local ok, err = sem_A:wait(1) + if not ok then + ngx.log(ngx.ERR, err) + end + sem_B:post(1) + end + ngx.say("count in A: ", sem_A:count()) + end + local function th_B() + for i = 1, 10 do + local ok, err = sem_B:wait(1) + if not ok then + ngx.log(ngx.ERR, err) + end + sem_A:post(1) + end + ngx.say("count in B: ", sem_B:count()) + end + + local co_A = ngx.thread.spawn(th_A) + local co_B = ngx.thread.spawn(th_B) + + sem_A:post(1) + } + } +--- log_level: debug +--- request +GET /test +--- response_body +count in B: 0 +count in A: 0 +--- no_error_log +[error] + + + +=== TEST 26: kill a light thread that is waiting on a semaphore (no resource) +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + + local function func_wait() + sem:wait(1) + end + local co = ngx.thread.spawn(func_wait) + local ok, err = ngx.thread.kill(co) + if ok then + ngx.say("ok") + else + ngx.say(err) + end + } + } +--- log_level: debug +--- request +GET /test +--- response_body +ok +--- no_error_log +[error] + + + +=== TEST 27: kill a light thread that is waiting on a semaphore (after post) +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + + local function func_wait() + sem:wait(1) + end + local co = ngx.thread.spawn(func_wait) + + sem:post() + local ok, err = ngx.thread.kill(co) + + if ok then + ngx.say("ok") + else + ngx.say(err) + end + + ngx.sleep(0.01) + + local count = sem:count() + ngx.say("count: ", count) + } + } +--- log_level: debug +--- request +GET /test +--- response_body +ok +count: 1 +--- no_error_log +[error] + + + +=== TEST 28: kill a thread that is waiting on another thread that is waiting on semaphore +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + + local function sem_wait() + ngx.say("sem waiting start") + local ok, err = sem:wait(0.1) + if not ok then + ngx.say("sem wait err: ", err) + end + ngx.say("sem waiting done") + end + + local function thread_wait() + local co = ngx.thread.spawn(sem_wait) + + ngx.say("thread waiting start") + local ok, err = ngx.thread.wait(co) + if not ok then + ngx.say("thread wait err: ", err) + end + ngx.say("thread waiting done") + end + + local co2 = ngx.thread.spawn(thread_wait) + ngx.sleep(0.01) + + local ok, err = ngx.thread.kill(co2) + if ok then + ngx.say("thread kill success") + else + ngx.say("kill err: ", err) + end + } + } +--- log_level: debug +--- request +GET /test +--- response_body +sem waiting start +thread waiting start +thread kill success +sem wait err: timeout +sem waiting done +--- no_error_log +[error] + + + +=== TEST 29: a light thread that is going to exit is waiting on a semaphore +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + local function func(sem) + ngx.say("sem waiting") + local ok, err = sem:wait(0.1) + if ok then + ngx.say("wait success") + else + ngx.say("err: ", err) + end + end + local co = ngx.thread.spawn(func, sem) + ngx.say("ok") + ngx.exit(200) + } + } +--- log_level: debug +--- request +GET /test +--- response_body +sem waiting +ok +--- error_log +http lua semaphore cleanup + + + +=== TEST 30: main thread wait a light thread that is waiting on a semaphore +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + local function func(sem) + local ok, err = sem:wait(0.001) + if ok then + ngx.say("wait success") + else + ngx.say("err: ", err) + end + end + local co = ngx.thread.spawn(func, sem) + ngx.thread.wait(co) + } + } +--- log_level: debug +--- request +GET /test +--- response_body +err: timeout +--- no_error_log +[error] + + + +=== TEST 31: multi wait and mult post with one semaphore +--- http_config eval: $::HttpConfig +--- config + location = /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + ngx.exit(500) + end + + local function func(op, id) + ngx.say(op, ": ", id) + if op == "wait" then + local ok, err = sem:wait(1) + if ok then + ngx.say("wait success: ", id) + end + else + sem:post() + end + end + local tco = {} + + for i = 1, 3 do + tco[#tco + 1] = ngx.thread.spawn(func, "wait", i) + end + + for i = 1, 3 do + tco[#tco + 1] = ngx.thread.spawn(func, "post", i) + end + + for i = 1, #tco do + ngx.thread.wait(tco[i]) + end + } + } +--- request +GET /test +--- response_body +wait: 1 +wait: 2 +wait: 3 +post: 1 +post: 2 +post: 3 +wait success: 1 +wait success: 2 +wait success: 3 +--- no_error_log +[error] + + + +=== TEST 32: semaphore wait time is zero +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + local ok, err = sem:wait(0) + if not ok then + ngx.say(err) + end + } + } +--- request +GET /test +--- response_body +timeout +--- no_error_log +[error] + + + +=== TEST 33: test semaphore gc +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if sem then + ngx.say("success") + end + sem = nil + collectgarbage("collect") + } + } +--- request +GET /test +--- response_body +success +--- log_level: debug +--- error_log +in lua gc, semaphore + + + +=== TEST 34: basic semaphore_mm alloc +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if sem then + ngx.say("ok") + end + } + } +--- log_level: debug +--- request +GET /test +--- response_body +ok +--- grep_error_log eval: qr/(new block, alloc semaphore|from head of free queue, alloc semaphore)/ +--- grep_error_log_out eval +[ +"new block, alloc semaphore +", +"from head of free queue, alloc semaphore +", +] + + + +=== TEST 35: basic semaphore_mm free insert tail +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sems = package.loaded.sems or {} + package.loaded.sems = sems + + local num_per_block = 4095 + if not sems[num_per_block] then + for i = 1, num_per_block * 3 do + sems[i] = semaphore.new(0) + end + end + + for i = 1, 2 do + if sems[i] then + sems[i] = nil + ngx.say("ok") + break + end + end + collectgarbage("collect") + } + } +--- log_level: debug +--- request +GET /t +--- response_body +ok +--- error_log +add to free queue tail + + + +=== TEST 36: basic semaphore_mm free insert head +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sems = package.loaded.sems or {} + package.loaded.sems = sems + + local num_per_block = 4095 + if not sems[num_per_block] then + for i = 1, num_per_block * 3 do + sems[i] = semaphore.new(0) + end + end + + if sems[#sems] then + sems[#sems] = nil + ngx.say("ok") + end + collectgarbage("collect") + } + } +--- log_level: debug +--- request +GET /test +--- response_body +ok +--- error_log +add to free queue head + + + +=== TEST 37: semaphore_mm free block (load <= 50% & the on the older side) +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sems = package.loaded.sems or {} + package.loaded.sems = sems + + local num_per_block = 4095 + if not sems[num_per_block * 3] then + for i = 1, num_per_block * 3 do + sems[i] = semaphore.new(0) + end + + for i = num_per_block + 1, num_per_block * 2 do + sems[i] = nil + end + else + for i = 1, num_per_block do + sems[i] = nil + end + end + + collectgarbage("collect") + ngx.say("ok") + } + } +--- log_level: debug +--- request +GET /test +--- response_body +ok +--- grep_error_log eval: qr/free semaphore block/ +--- grep_error_log_out eval +[ +"", +"free semaphore block +", +] +--- timeout: 10 + + + +=== TEST 38: basic semaphore count +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(10) + local count = sem:count() + ngx.say(count) + + sem:wait(0) + local count = sem:count() + ngx.say(count) + + sem:post(3) + local count = sem:count() + ngx.say(count) + } + } +--- request +GET /test +--- response_body +10 +9 +12 +--- no_error_log +[error] + + + +=== TEST 39: basic semaphore count (negative number) +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + local count = sem:count() + ngx.say(count) + + local function wait() + sem:wait(0.01) + end + local co = ngx.thread.spawn(wait) + + local count = sem:count() + ngx.say(count) + } + } +--- request +GET /test +--- response_body +0 +-1 +--- no_error_log +[error] + + + +=== TEST 40: bugfix: semaphore instance can't be garbage collected when someone is waiting on it +--- http_config eval: $::HttpConfig +--- config + location /test { + content_by_lua_block { + local semaphore = require "ngx.semaphore" + + local my_sema = {} + local key = "my key" + + local function my_clean() + print("cleaning up") + + my_sema[key]:post() + my_sema[key] = nil + + collectgarbage() + end + + local ok, err = ngx.timer.at(0.001, my_clean) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + ngx.exit(500) + end + + my_sema[key] = semaphore:new(0) + + local ok, err = my_sema[key]:wait(2) + ngx.say(ok, ", ", err) + } + } +--- request +GET /test +--- response_body +true, nil +--- no_error_log +[error] +[crit] diff --git a/t/sha1-bin.t b/t/sha1-bin.t new file mode 100644 index 000000000..39e30fb60 --- /dev/null +++ b/t/sha1-bin.t @@ -0,0 +1,104 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: set sha1_bin (string) +--- config + location = /sha1_bin { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.sha1_bin("hello") + end + ngx.say(string.len(s)) + } + } +--- request +GET /sha1_bin +--- response_body +20 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 2: set sha1_bin (nil) +--- config + location = /sha1_bin { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.sha1_bin(nil) + end + ngx.say(string.len(s)) + } + } +--- request +GET /sha1_bin +--- response_body +20 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 3: set sha1_bin (number) +--- config + location = /sha1_bin { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.sha1_bin(3.14) + end + ngx.say(string.len(s)) + } + } +--- request +GET /sha1_bin +--- response_body +20 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 4: set sha1_bin (boolean) +--- config + location = /sha1_bin { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.sha1_bin(true) + end + ngx.say(string.len(s)) + } + } +--- request +GET /sha1_bin +--- response_body +20 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] diff --git a/t/shared.t b/t/shared.t new file mode 100644 index 000000000..78ba7dc7b --- /dev/null +++ b/t/shared.t @@ -0,0 +1,434 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * blocks() * 4; + +our $HttpConfig = <<_EOC_; + lua_shared_dict dogs 1m; + $t::TestCore::HttpConfig +_EOC_ + +no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: shared.ttl errors on nil key +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local ttl, err = ngx.shared.dogs:ttl() + if not ttl then + ngx.say("failed to get ttl: ", err) + end + } + } +--- request +GET /t +--- response_body +failed to get ttl: nil key +--- no_error_log +[error] +[alert] + + + +=== TEST 2: shared.ttl errors on empty key +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local ttl, err = ngx.shared.dogs:ttl("") + if not ttl then + ngx.say("failed to get ttl: ", err) + end + } + } +--- request +GET /t +--- response_body +failed to get ttl: empty key +--- no_error_log +[error] +[alert] + + + +=== TEST 3: shared.ttl returns error on not found key +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local ttl, err = ngx.shared.dogs:ttl("key") + if not ttl then + ngx.say("failed to get ttl: ", err) + end + } + } +--- request +GET /t +--- response_body +failed to get ttl: not found +--- no_error_log +[error] +[alert] + + + +=== TEST 4: shared.ttl returns key ttl for non-default (positive) ttl +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local ok, err = ngx.shared.dogs:set("key", true, 0.2) + + local ttl, err = ngx.shared.dogs:ttl("key") + if not ttl then + ngx.log(ngx.ERR, "failed to get ttl: ", err) + end + + ngx.say(ttl) + + ngx.say("sleep for 0.1s...") + ngx.sleep(0.1) + + ttl, err = ngx.shared.dogs:ttl("key") + if not ttl then + ngx.log(ngx.ERR, "failed to get ttl: ", err) + end + + ngx.say(ttl) + } + } +--- request +GET /t +--- response_body_like chomp +\A0.2 +sleep for 0.1s... +0.\d* +\z +--- no_error_log +[error] +[alert] + + + +=== TEST 5: shared.ttl returns key ttl for non-default (negative) ttl +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local ok, err = ngx.shared.dogs:set("key", true, 0.1) + + local ttl, err = ngx.shared.dogs:ttl("key") + if not ttl then + ngx.log(ngx.ERR, "failed to get ttl: ", err) + end + + ngx.say(ttl) + + ngx.say("sleep for 0.2s...") + ngx.sleep(0.2) + + ttl, err = ngx.shared.dogs:ttl("key") + if not ttl then + ngx.log(ngx.ERR, "failed to get ttl: ", err) + end + + ngx.say(ttl) + } + } +--- request +GET /t +--- response_body_like chomp +\A0.1 +sleep for 0.2s... +-0.\d* +\z +--- no_error_log +[error] +[alert] + + + +=== TEST 6: shared.ttl returns key ttl for default ttl (0) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local ok, err = ngx.shared.dogs:set("key", true) + + local ttl, err = ngx.shared.dogs:ttl("key") + if not ttl then + ngx.log(ngx.ERR, "failed to get ttl: ", err) + end + + ngx.say(ttl) + + ngx.say("sleep for 0.1s...") + ngx.sleep(0.11) + + ttl, err = ngx.shared.dogs:ttl("key") + if not ttl then + ngx.log(ngx.ERR, "failed to get ttl: ", err) + end + + ngx.say(ttl) + } + } +--- request +GET /t +--- response_body +0 +sleep for 0.1s... +0 +--- no_error_log +[error] +[alert] + + + +=== TEST 7: shared.ttl JIT compiles +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local ok, err = ngx.shared.dogs:set("key", true) + + for i = 1, 30 do + local ttl, err = ngx.shared.dogs:ttl("key") + if not ttl then + ngx.log(ngx.ERR, "failed to get ttl: ", err) + end + end + } + } +--- request +GET /t +--- response_body + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + + + +=== TEST 8: shared.expire errors on invalid exptime +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + + local ok, err = pcall(dogs.expire, dogs) + if not ok then + ngx.say(err) + end + } + } +--- request +GET /t +--- response_body +bad "exptime" argument +--- no_error_log +[error] +[alert] + + + +=== TEST 9: shared.expire returns error on nil key +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + + local ok, err = dogs:expire(nil, 1) + if not ok then + ngx.say("failed to set ttl: ", err) + end + } + } +--- request +GET /t +--- response_body +failed to set ttl: nil key +--- no_error_log +[error] +[alert] + + + +=== TEST 10: shared.expire returns error on empty key +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + + local ok, err = dogs:expire("", 1) + if not ok then + ngx.say("failed to set ttl: ", err) + end + } + } +--- request +GET /t +--- response_body +failed to set ttl: empty key +--- no_error_log +[error] +[alert] + + + +=== TEST 11: shared.expire returns error on not found key +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + + local ok, err = dogs:expire("key", 1) + if not ok then + ngx.say("failed to set ttl: ", err) + end + } + } +--- request +GET /t +--- response_body +failed to set ttl: not found +--- no_error_log +[error] +[alert] + + + +=== TEST 12: shared.expire updates ttl of key with non-default ttl +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + + local ok, err = dogs:set("key", true, 0.1) + if not ok then + ngx.log(ngx.ERR, "failed to set: ", err) + end + + ok, err = dogs:expire("key", 0.3) + if not ok then + ngx.say("failed to set ttl: ", err) + end + + ngx.sleep(0.2) + + local val, err = dogs:get("key") + if err then + ngx.log(ngx.ERR, "failed to get: ", err) + end + + ngx.say("after 0.2s: ", val) + + ngx.sleep(0.2) + + val, err = dogs:get("key") + if err then + ngx.log(ngx.ERR, "failed to get: ", err) + end + + ngx.say("after 0.4s: ", val) + } + } +--- request +GET /t +--- response_body +after 0.2s: true +after 0.4s: nil +--- no_error_log +[error] +[alert] + + + +=== TEST 13: shared.expire updates ttl of key with default ttl (0) +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + + local ok, err = dogs:set("key", true) + if not ok then + ngx.log(ngx.ERR, "failed to set: ", err) + end + + local val, err = dogs:get("key") + if err then + ngx.log(ngx.ERR, "failed to get: ", err) + end + + ngx.say("after set: ", val) + + ok, err = dogs:expire("key", 0.3) + if not ok then + ngx.say("failed to set ttl: ", err) + end + + ngx.sleep(0.4) + + val, err = dogs:get("key") + if err then + ngx.log(ngx.ERR, "failed to get: ", err) + end + + ngx.say("after 0.4s: ", val) + } + } +--- request +GET /t +--- response_body +after set: true +after 0.4s: nil +--- no_error_log +[error] +[alert] + + + +=== TEST 14: shared.expire JIT compiles +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + + local ok, err = dogs:set("key", true, 0.1) + if not ok then + ngx.log(ngx.ERR, "failed to set: ", err) + end + + for i = 1, 30 do + local ok, err = dogs:expire("key", 0.3) + if not ok then + ngx.say("failed to set ttl: ", err) + end + end + } + } +--- request +GET /t +--- response_body + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):9 loop\]/ +--- no_error_log +[error] diff --git a/t/shdict.t b/t/shdict.t new file mode 100644 index 000000000..edd88a8b1 --- /dev/null +++ b/t/shdict.t @@ -0,0 +1,1653 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5 + 2); + +add_block_preprocessor(sub { + my $block = shift; + + my $http_config = $block->http_config || ''; + + $http_config .= <<_EOC_; + lua_shared_dict dogs 1m; + lua_shared_dict cats 16k; + lua_shared_dict birds 100k; + $t::TestCore::HttpConfig +_EOC_ + + $block->set_value("http_config", $http_config); +}); + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: get a string value +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible = dogs:set("foo", "bar", 0, 72) + if not ok then + ngx.say("failed to set: ", err) + return + end + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: string +value: bar +flags: 72 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):11 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 2: get an nonexistent key +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + -- dogs:set("foo", "bar") + for i = 1, 100 do + val, flags = dogs:get("nonexistent") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: nil +value: nil +flags: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 3: get a boolean value (true) +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", true, 0, 5678) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: boolean +value: true +flags: 5678 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 4: get a boolean value (false) +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", false, 0, 777) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: boolean +value: false +flags: 777 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 5: get a number value (int) +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", 51203) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: number +value: 51203 +flags: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 6: get a number value (double) +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", 3.1415926, 0, 78) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: number +value: 3.1415926 +flags: 78 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 7: get a large string value +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:flush_all() + dogs:flush_expired() + dogs:set("foo", string.rep("bbbb", 1024) .. "a", 0, 912) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body eval +"value type: string +value: " . ("bbbb" x 1024) . "a +flags: 912 +" +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):9 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 8: get_stale (false) +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags, stale + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", "bar", 0, 72) + for i = 1, 100 do + val, flags, stale = dogs:get_stale("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + ngx.say("stale: ", stale) + } + } +--- request +GET /t +--- response_body +value type: string +value: bar +flags: 72 +stale: false +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 9: get_stale (true) +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags, stale + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible = dogs:set("foo", "bar", 0.01, 72) + if not ok then + ngx.say("failed to set: ", err) + return + end + ngx.update_time() + ngx.sleep(0.02) + for i = 1, 30 do + val, flags, stale = dogs:get_stale("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + ngx.say("stale: ", stale) + } + } +--- request +GET /t +--- response_body +value type: string +value: bar +flags: 72 +stale: true +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):13 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 10: incr int +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible = dogs:set("foo", 56) + if not ok then + ngx.say("failed to set: ", err) + return + end + for i = 1, 100 do + val, err = dogs:incr("foo", 2) + end + ngx.say("value: ", val) + ngx.say("err: ", err) + } + } +--- request +GET /t +--- response_body +value: 256 +err: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):11 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 11: incr double +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, err + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", 56) + for i = 1, 150 do + val, err = dogs:incr("foo", 2.1) + end + ngx.say("value: ", val) + ngx.say("err: ", err) + } + } +--- request +GET /t +--- response_body +value: 371 +err: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 12: set a string value +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:set("foo", "bar", 0, 72) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: string +value: bar +flags: 72 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 13: set a boolean value (true) +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:set("foo", true, 0, 5678) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: boolean +value: true +flags: 5678 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 14: set a boolean value (false) +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 100 do + dogs:set("foo", false, 0, 777) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: boolean +value: false +flags: 777 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 15: set a number value (int) +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 100 do + dogs:set("foo", 51203) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: number +value: 51203 +flags: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 16: set a number value (double) +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 100 do + dogs:set("foo", 3.1415926, 0, 78) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: number +value: 3.1415926 +flags: 78 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 17: set a number value and a nil +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 150 do + dogs:set("foo", 3.1415926, 0, 78) + dogs:set("foo", nil) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: nil +value: nil +flags: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 18: safe set a number value +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:flush_all() + dogs:flush_expired() + for i = 1, 100 do + dogs:safe_set("foo", 3.1415926, 0, 78) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: number +value: 3.1415926 +flags: 78 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):8 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 19: add a string value +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:flush_all() + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:add("foo" .. i, "bar", 0, 72) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo100") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: string +value: bar +flags: 72 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):8 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 20: safe add a string value +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:flush_all() + dogs:flush_expired() + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:safe_add("foo" .. i, "bar", 0, 72) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo100") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: string +value: bar +flags: 72 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):9 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 21: replace a string value +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", "hello") + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:replace("foo", "bar" .. i, 0, 72) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: string +value: bar100 +flags: 72 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):8 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 22: set a number value and delete +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 150 do + dogs:set("foo", 3.1415926, 0, 78) + dogs:delete("foo") + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: nil +value: nil +flags: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: +stitch + + + +=== TEST 23: set nil key +--- config + location = /t { + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + local ok, err = dogs:set(nil, "bar") + if not ok then + ngx.say("failed to set: ", err) + end + } + } +--- request +GET /t +--- response_body +failed to set: nil key +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 24: get nil key +--- config + location = /t { + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + local value, err = dogs:get(nil, "bar") + if not ok then + ngx.say("failed to get: ", err) + end + } + } +--- request +GET /t +--- response_body +failed to get: nil key +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 25: get stale key +--- config + location = /t { + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + local value, err = dogs:get_stale(nil, "bar") + if not ok then + ngx.say("failed to get stale: ", err) + end + } + } +--- request +GET /t +--- response_body +failed to get stale: nil key +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 26: incr key +--- config + location = /t { + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + local value, err = dogs:incr(nil, 32) + if not value then + ngx.say("failed to incr: ", err) + end + } + } +--- request +GET /t +--- response_body +failed to incr: nil key +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 27: flush_all +--- config + location = /t { + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + dogs:set("foo", "bah") + -- local cd = ffi.cast("void *", dogs) + for i = 1, 150 do + dogs:flush_all() + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } + } +--- request +GET /t +--- response_body +value type: nil +value: nil +flags: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: +stitch + + + +=== TEST 28: incr, value is not number +--- config + location = /t { + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + local value, err = dogs:incr("foo", "bar") + if not value then + ngx.say("failed to incr: ", err) + end + } + } +--- request +GET /t +--- error_code: 500 +--- response_body_like: 500 +--- error_log +cannot convert 'nil' to 'double' +--- no_error_log +[alert] +[crit] + + + +=== TEST 29: incr with init +--- config + location = /t { + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + dogs:flush_all() + + local value, err = dogs:incr("foo", 10) + if not value then + ngx.say("failed to incr: ", err) + end + + local value, err, forcible = dogs:incr("foo", 10, 10) + if not value then + ngx.say("failed to incr: ", err) + return + end + + ngx.say("incr ok, value: ", value, ", forcible: ", forcible) + } + } +--- request +GET /t +--- response_body +failed to incr: not found +incr ok, value: 20, forcible: false +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 30: incr, init is not number +--- config + location = /t { + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + local value, err = dogs:incr("foo", 10, "bar") + if not ok then + ngx.say("failed to incr: ", err) + end + } + } +--- request +GET /t +--- error_code: 500 +--- response_body_like: 500 +--- error_log +number expected, got string +--- no_error_log +[alert] +[crit] + + + +=== TEST 31: capacity +--- config + location = /t { + content_by_lua_block { + local cats = ngx.shared.cats + local capacity = cats:capacity() + ngx.say("capacity type: ", type(capacity)) + ngx.say("capacity: ", capacity) + } + } +--- request +GET /t +--- response_body +capacity type: number +capacity: 16384 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 32: free_space, empty (16k zone) +--- skip_nginx: 5: < 1.11.7 +--- config + location = /t { + content_by_lua_block { + local cats = ngx.shared.cats + cats:flush_all() + cats:flush_expired() + local free_page_bytes = cats:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } + } +--- request +GET /t +--- response_body +free_page_bytes type: number +free_page_bytes: 4096 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 33: free_space, empty (100k zone) +--- skip_nginx: 5: < 1.11.7 +--- config + location = /t { + content_by_lua_block { + local birds = ngx.shared.birds + birds:flush_all() + birds:flush_expired() + local free_page_bytes = birds:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } + } +--- request +GET /t +--- response_body_like chomp +\Afree_page_bytes type: number +free_page_bytes: (?:90112|94208) +\z +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 34: free_space, about half full, one page left +--- skip_nginx: 5: < 1.11.7 +--- config + location = /t { + content_by_lua_block { + local cats = ngx.shared.cats + cats:flush_all() + cats:flush_expired() + for i = 1, 31 do + local key = string.format("key%05d", i) + local val = string.format("val%05d", i) + local success, err, forcible = cats:set(key, val) + if err ~= nil then + ngx.say(string.format("got error, i=%d, err=%s", i, err)) + end + if forcible then + ngx.say(string.format("got forcible, i=%d", i)) + end + if not success then + ngx.say(string.format("got not success, i=%d", i)) + end + end + local free_page_bytes = cats:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } + } +--- request +GET /t +--- response_body +free_page_bytes type: number +free_page_bytes: 4096 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 35: free_space, about half full, no page left +--- skip_nginx: 5: < 1.11.7 +--- config + location = /t { + content_by_lua_block { + local cats = ngx.shared.cats + cats:flush_all() + cats:flush_expired() + for i = 1, 32 do + local key = string.format("key%05d", i) + local val = string.format("val%05d", i) + local success, err, forcible = cats:set(key, val) + if err ~= nil then + ngx.say(string.format("got error, i=%d, err=%s", i, err)) + end + if forcible then + ngx.say(string.format("got forcible, i=%d", i)) + end + if not success then + ngx.say(string.format("got not success, i=%d", i)) + end + end + local free_page_bytes = cats:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } + } +--- request +GET /t +--- response_body_like chomp +\Afree_page_bytes type: number +free_page_bytes: (?:0|4096) +\z +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 36: free_space, full +--- skip_nginx: 5: < 1.11.7 +--- config + location = /t { + content_by_lua_block { + local cats = ngx.shared.cats + cats:flush_all() + cats:flush_expired() + for i = 1, 63 do + local key = string.format("key%05d", i) + local val = string.format("val%05d", i) + local success, err, forcible = cats:set(key, val) + if err ~= nil then + ngx.say(string.format("got error, i=%d, err=%s", i, err)) + end + if forcible then + ngx.say(string.format("got forcible, i=%d", i)) + end + if not success then + ngx.say(string.format("got not success, i=%d", i)) + end + end + local free_page_bytes = cats:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } + } +--- request +GET /t +--- response_body +free_page_bytes type: number +free_page_bytes: 0 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 37: free_space, got forcible +--- skip_nginx: 5: < 1.11.7 +--- config + location = /t { + content_by_lua_block { + local cats = ngx.shared.cats + cats:flush_all() + cats:flush_expired() + for i = 1, 64 do + local key = string.format("key%05d", i) + local val = string.format("val%05d", i) + local success, err, forcible = cats:set(key, val) + if err ~= nil then + ngx.say(string.format("got error, i=%d, err=%s", i, err)) + end + if forcible then + ngx.say(string.format("got forcible, i=%d", i)) + end + if not success then + ngx.say(string.format("got not success, i=%d", i)) + end + end + local free_page_bytes = cats:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } + } +--- request +GET /t +--- response_body_like chomp +\A(?:got forcible, i=64 +)?free_page_bytes type: number +free_page_bytes: 0 +\z +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 38: free_space, full (100k) +--- skip_nginx: 5: < 1.11.7 +--- config + location = /t { + content_by_lua_block { + local birds = ngx.shared.birds + birds:flush_all() + birds:flush_expired() + for i = 1, 1000 do + local key = string.format("key%05d", i) + local val = string.format("val%05d", i) + local ok, err, forcible = birds:set(key, val) + if err ~= nil then + ngx.say(string.format("got error, i=%d, err=%s", i, err)) + end + if forcible then + ngx.say(string.format("got forcible, i=%d", i)) + break + end + if not ok then + ngx.say(string.format("got not ok, i=%d", i)) + break + end + end + local free_page_bytes = birds:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } + } +--- request +GET /t +--- response_body_like chomp +\A(?:got forcible, i=736 +)?free_page_bytes type: number +free_page_bytes: (?:0|32768) +\z +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 39: incr bad init_ttl argument +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + local pok, err = pcall(dogs.incr, dogs, "foo", 1, 0, -1) + if not pok then + ngx.say("not ok: ", err) + return + end + + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +not ok: bad "init_ttl" argument +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 40: incr init_ttl argument is not a number +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + local pok, err = pcall(dogs.incr, dogs, "foo", 1, 0, "bar") + if not pok then + ngx.say("not ok: ", err) + return + end + + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +not ok: bad init_ttl arg: number expected, got string +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 41: incr init_ttl argument without init +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + local pok, err = pcall(dogs.incr, dogs, "foo", 1, nil, 0.01) + if not pok then + ngx.say("not ok: ", err) + return + end + + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +not ok: must provide "init" when providing "init_ttl" +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 42: incr key with init_ttl (key exists) +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:set("foo", 32) + + local res, err = dogs:incr("foo", 10502, 0, 0.01) + ngx.say("incr: ", res, " ", err) + ngx.say("foo = ", dogs:get("foo")) + + ngx.update_time() + ngx.sleep(0.02) + + ngx.say("foo after incr init_ttl = ", dogs:get("foo")) + } + } +--- request +GET /t +--- response_body +incr: 10534 nil +foo = 10534 +foo after incr init_ttl = 10534 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 43: incr key with init and init_ttl (key not exists) +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:flush_all() + + local res, err = dogs:incr("foo", 10502, 1, 0.01) + ngx.say("incr: ", res, " ", err) + ngx.say("foo = ", dogs:get("foo")) + + ngx.update_time() + ngx.sleep(0.02) + + ngx.say("foo after init_ttl = ", dogs:get("foo")) + } + } +--- request +GET /t +--- response_body +incr: 10503 nil +foo = 10503 +foo after init_ttl = nil +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 44: incr key with init and init_ttl as string (key not exists) +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:flush_all() + + local res, err = dogs:incr("foo", 10502, 1, "0.01") + ngx.say("incr: ", res, " ", err) + ngx.say("foo = ", dogs:get("foo")) + + ngx.update_time() + ngx.sleep(0.02) + + ngx.say("foo after init_ttl = ", dogs:get("foo")) + } + } +--- request +GET /t +--- response_body +incr: 10503 nil +foo = 10503 +foo after init_ttl = nil +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 45: incr key with init and init_ttl (key expired and size matched) +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + for i = 1, 20 do + dogs:set("bar" .. i, i, 0.02) + end + dogs:set("foo", 32, 0.02) + ngx.update_time() + ngx.sleep(0.03) + + local res, err = dogs:incr("foo", 10502, 0, 0.01) + ngx.say("incr: ", res, " ", err) + ngx.say("foo = ", dogs:get("foo")) + + ngx.update_time() + ngx.sleep(0.02) + + ngx.say("foo after init_ttl = ", dogs:get("foo")) + } + } +--- request +GET /t +--- response_body +incr: 10502 nil +foo = 10502 +foo after init_ttl = nil +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 46: incr key with init and init_ttl (forcibly override other valid entries) +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:flush_all() + + local long_prefix = string.rep("1234567890", 100) + for i = 1, 1000 do + local success, err, forcible = dogs:set(long_prefix .. i, i) + if forcible then + dogs:delete(long_prefix .. i) + break + end + end + + local res, err, forcible = dogs:incr(long_prefix .. "bar", 10502, 0) + ngx.say("incr: ", res, " ", err, " ", forcible) + + local res, err, forcible = dogs:incr(long_prefix .. "foo", 10502, 0, 0.01) + ngx.say("incr: ", res, " ", err, " ", forcible) + ngx.say("foo = ", dogs:get(long_prefix .. "foo")) + + ngx.update_time() + ngx.sleep(0.02) + ngx.say("foo after init_ttl = ", dogs:get("foo")) + } + } +--- request +GET /t +--- response_body +incr: 10502 nil false +incr: 10502 nil true +foo = 10502 +foo after init_ttl = nil +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 47: exptime uses long type to avoid overflow in set() + ttl() +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:flush_all() + + local ok, err = dogs:set("huge_ttl", true, 2 ^ 31) + if not ok then + ngx.say("err setting: ", err) + return + end + + local ttl, err = dogs:ttl("huge_ttl") + if not ttl then + ngx.say("err retrieving ttl: ", err) + return + end + + ngx.say("ttl: ", ttl) + } + } +--- request +GET /t +--- response_body +ttl: 2147483648 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 48: exptime uses long type to avoid overflow in expire() + ttl() +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:flush_all() + + local ok, err = dogs:set("updated_huge_ttl", true) + if not ok then + ngx.say("err setting: ", err) + return + end + + local ok, err = dogs:expire("updated_huge_ttl", 2 ^ 31) + if not ok then + ngx.say("err expire: ", err) + return + end + + local ttl, err = dogs:ttl("updated_huge_ttl") + if not ttl then + ngx.say("err retrieving ttl: ", err) + return + end + + ngx.say("ttl: ", ttl) + } + } +--- request +GET /t +--- response_body +ttl: 2147483648 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 49: init_ttl uses long type to avoid overflow in incr() + ttl() +--- config + location = /t { + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:flush_all() + + local ok, err = dogs:incr("incr_huge_ttl", 1, 0, 2 ^ 31) + if not ok then + ngx.say("err incr: ", err) + return + end + + local ttl, err = dogs:ttl("incr_huge_ttl") + if not ttl then + ngx.say("err retrieving ttl: ", err) + return + end + + ngx.say("ttl: ", ttl) + } + } +--- request +GET /t +--- response_body +ttl: 2147483648 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 50: check zone argument +--- config + location = /t { + content_by_lua_block { + local function check_in_pcall(f, ...) + local ok, err = pcall(f, ...) + if not ok then + ngx.say(err) + else + ngx.say("ok") + end + end + + local dogs = ngx.shared.dogs + check_in_pcall(dogs.set, dogs, 'k', 1) + check_in_pcall(dogs.set, 'k', 1) + check_in_pcall(dogs.set, {1}, 'k', 1) + check_in_pcall(dogs.set, {ngx.null}, 'k', 1) + } + } +--- request +GET /t +--- response_body +ok +bad "zone" argument +bad "zone" argument +bad "zone" argument +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 51: free_space, not supported in NGINX < 1.11.7 +--- skip_nginx: 5: >= 1.11.7 +--- config + location = /t { + content_by_lua_block { + local birds = ngx.shared.birds + + local pok, perr = pcall(function () + birds:free_space() + end) + if not pok then + ngx.say(perr) + end + } + } +--- request +GET /t +--- response_body_like +content_by_lua\(nginx\.conf:\d+\):\d+: 'shm:free_space\(\)' not supported in NGINX < 1.11.7 +--- no_error_log +[error] +[alert] +[crit] diff --git a/t/socket-tcp-getoption.t b/t/socket-tcp-getoption.t new file mode 100644 index 000000000..d70089142 --- /dev/null +++ b/t/socket-tcp-getoption.t @@ -0,0 +1,288 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 2 + 7); + +#worker_connections(1024); +#no_diff(); +no_long_string(); + +run_tests(); + +__DATA__ + +=== TEST 1: no parameters. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + + ok, err = sock:getoption() + if not ok then + ngx.say("getoption failed: ", err) + return + end + + sock:close() + } + } +--- request +GET /t +--- response_body +getoption failed: missing the "option" argument +--- no_error_log +[error] + + + +=== TEST 2: unsuppotrted option name. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + + local sndbuf, err = sock:getoption("abc") + if not sndbuf then + ngx.say("getoption abc failed: ", err) + return + end + + sock:close() + } + } +--- request +GET /t +--- response_body +getoption abc failed: unsupported option abc +--- no_error_log +[error] + + + +=== TEST 3: getoption before calling connect. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + local sock = ngx.socket.tcp() + local sndbuf, err = sock:getoption("sndbuf") + if not sndbuf then + ngx.say("getoption sndbuf failed: ", err) + return + end + + sock:close() + } + } +--- request +GET /t +--- error_code: 500 +--- error_log +socket is never created nor connected + + + +=== TEST 4: get keepalive. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + + local val, err = sock:getoption("keepalive") + if not val then + ngx.say("getoption keepalive failed: ", err) + return + end + + ngx.say("keepalive: ", val) + + sock:close() + } + } +--- request +GET /t +--- response_body_like eval +qr/keepalive: \d+/ +--- no_error_log +[error] + + + +=== TEST 5: get reuseaddr. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + + local val, err = sock:getoption("reuseaddr") + if not val then + ngx.say("getoption reuseaddr failed: ", err) + return + end + + ngx.say("reuseaddr: ", val) + + sock:close() + } + } +--- request +GET /t +--- response_body_like eval +qr/reuseaddr: \d+/ +--- no_error_log +[error] + + + +=== TEST 6: get tcp-nodelay. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + + local val, err = sock:getoption("tcp-nodelay") + if not val then + ngx.say("getoption tcp-nodelay failed: ", err) + return + end + + ngx.say("tcp-nodelay: ", val) + + sock:close() + } + } +--- request +GET /t +--- response_body_like eval +qr/tcp-nodelay: \d+/ +--- no_error_log +[error] + + + +=== TEST 7: get sndbuf. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + + local val, err = sock:getoption("sndbuf") + if not val then + ngx.say("getoption sndbuf failed: ", err) + return + end + + ngx.say("sndbuf: ", val) + + sock:close() + } + } +--- request +GET /t +--- response_body_like eval +qr/sndbuf: \d+/ +--- no_error_log +[error] + + + +=== TEST 8: get rcvbuf. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + + local val, err = sock:getoption("rcvbuf") + if not val then + ngx.say("getoption rcvbuf failed: ", err) + return + end + + ngx.say("rcvbuf: ", val) + + sock:close() + } + } +--- request +GET /t +--- response_body_like eval +qr/rcvbuf: \d+/ +--- no_error_log +[error] diff --git a/t/socket-tcp-setoption.t b/t/socket-tcp-setoption.t new file mode 100644 index 000000000..a3ebb11ae --- /dev/null +++ b/t/socket-tcp-setoption.t @@ -0,0 +1,787 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use lib '.'; +use t::TestCore; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 - 1); + +#worker_connections(1024); +#no_diff(); +no_long_string(); + +run_tests(); + +__DATA__ + +=== TEST 1: no parameters. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + + ok, err = sock:setoption() + if not ok then + ngx.say("setoption failed: ", err) + end + + ok, err = sock:setoption("sndbuf") + if not ok then + ngx.say("setoption failed: ", err) + end + + sock:close() + } + } +--- request +GET /t +--- response_body +setoption failed: missing the "option" argument +setoption failed: missing the "value" argument +--- no_error_log +[error] + + + +=== TEST 2: unsuppotrted option name. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + + local ok, err = sock:setoption("abc", 123) + if not ok then + ngx.say("setoption abc failed: ", err) + return + end + + sock:close() + } + } +--- request +GET /t +--- response_body +setoption abc failed: unsupported option abc +--- no_error_log +[error] + + + +=== TEST 3: getoption before calling connect. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + local sock = ngx.socket.tcp() + local sndbuf, err = sock:setoption("sndbuf", 4000) + if not sndbuf then + ngx.say("getoption sndbuf failed: ", err) + return + end + + sock:close() + } + } +--- request +GET /t +--- error_code: 500 +--- error_log +socket is never created nor connected + + + +=== TEST 4: keepalive set by 1/0. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + local v1, err = sock:getoption("keepalive") + if not v1 then + ngx.say("get default keepalive failed: ", err) + return + end + + ok, err = sock:setoption("keepalive", 1) + if not ok then + ngx.say("enabling keepalive failed: ", err) + return + end + local v2, err = sock:getoption("keepalive") + if not v2 then + ngx.say("get enabled keepalive failed: ", err) + return + end + ngx.say("keepalive changes from ", v1, " to ", v2) + + ok, err = sock:setoption("keepalive", 0) + if not ok then + ngx.say("disable keepalive failed: ", err) + return + end + local v3, err = sock:getoption("keepalive") + if not v3 then + ngx.say("get disabled keepalive failed: ", err) + return + end + ngx.say("keepalive changes from ", v2, " to ", v3) + + sock:close() + } + } +--- request +GET /t +--- response_body +keepalive changes from 0 to 1 +keepalive changes from 1 to 0 +--- no_error_log +[error] + + + +=== TEST 5: keepalive set by true/false. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + local v1, err = sock:getoption("keepalive") + if not v1 then + ngx.say("get default keepalive failed: ", err) + return + end + + ok, err = sock:setoption("keepalive", true) + if not ok then + ngx.say("enabling keepalive failed: ", err) + return + end + local v2, err = sock:getoption("keepalive") + if not v2 then + ngx.say("get enabled keepalive failed: ", err) + return + end + ngx.say("keepalive changes from ", v1, " to ", v2) + + ok, err = sock:setoption("keepalive", false) + if not ok then + ngx.say("disable keepalive failed: ", err) + return + end + local v3, err = sock:getoption("keepalive") + if not v3 then + ngx.say("get disabled keepalive failed: ", err) + return + end + ngx.say("keepalive changes from ", v2, " to ", v3) + + sock:close() + } + } +--- request +GET /t +--- response_body +keepalive changes from 0 to 1 +keepalive changes from 1 to 0 +--- no_error_log +[error] + + + +=== TEST 6: keepalive set by ~0/0. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + local v1, err = sock:getoption("keepalive") + if not v1 then + ngx.say("get default keepalive failed: ", err) + return + end + + ok, err = sock:setoption("keepalive", 10) + if not ok then + ngx.say("enabling keepalive failed: ", err) + return + end + local v2, err = sock:getoption("keepalive") + if not v2 then + ngx.say("get enabled keepalive failed: ", err) + return + end + ngx.say("keepalive changes from ", v1, " to ", v2) + + ok, err = sock:setoption("keepalive", 0) + if not ok then + ngx.say("disable keepalive failed: ", err) + return + end + local v3, err = sock:getoption("keepalive") + if not v3 then + ngx.say("get disabled keepalive failed: ", err) + return + end + ngx.say("keepalive changes from ", v2, " to ", v3) + + sock:close() + } + } +--- request +GET /t +--- response_body +keepalive changes from 0 to 1 +keepalive changes from 1 to 0 +--- no_error_log +[error] + + + +=== TEST 7: reuseaddr set by 1/0. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + local v1, err = sock:getoption("reuseaddr") + if not v1 then + ngx.say("get default reuseaddr failed: ", err) + return + end + + ok, err = sock:setoption("reuseaddr", 1) + if not ok then + ngx.say("enabling reuseaddr failed: ", err) + return + end + local v2, err = sock:getoption("reuseaddr") + if not v2 then + ngx.say("get enabled reuseaddr failed: ", err) + return + end + ngx.say("reuseaddr changes from ", v1, " to ", v2) + + ok, err = sock:setoption("reuseaddr", 0) + if not ok then + ngx.say("disable reuseaddr failed: ", err) + return + end + local v3, err = sock:getoption("reuseaddr") + if not v3 then + ngx.say("get disabled reuseaddr failed: ", err) + return + end + ngx.say("reuseaddr changes from ", v2, " to ", v3) + + sock:close() + } + } +--- request +GET /t +--- response_body +reuseaddr changes from 0 to 1 +reuseaddr changes from 1 to 0 +--- no_error_log +[error] + + + +=== TEST 8: reuseaddr set by true/false. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + local v1, err = sock:getoption("reuseaddr") + if not v1 then + ngx.say("get default reuseaddr failed: ", err) + return + end + + ok, err = sock:setoption("reuseaddr", true) + if not ok then + ngx.say("enabling reuseaddr failed: ", err) + return + end + local v2, err = sock:getoption("reuseaddr") + if not v2 then + ngx.say("get enabled reuseaddr failed: ", err) + return + end + ngx.say("reuseaddr changes from ", v1, " to ", v2) + + ok, err = sock:setoption("reuseaddr", false) + if not ok then + ngx.say("disable reuseaddr failed: ", err) + return + end + local v3, err = sock:getoption("reuseaddr") + if not v3 then + ngx.say("get disabled reuseaddr failed: ", err) + return + end + ngx.say("reuseaddr changes from ", v2, " to ", v3) + + sock:close() + } + } +--- request +GET /t +--- response_body +reuseaddr changes from 0 to 1 +reuseaddr changes from 1 to 0 +--- no_error_log +[error] + + + +=== TEST 9: reuseaddr set by ~0/0. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + local v1, err = sock:getoption("reuseaddr") + if not v1 then + ngx.say("get default reuseaddr failed: ", err) + return + end + + ok, err = sock:setoption("reuseaddr", 10) + if not ok then + ngx.say("enabling reuseaddr failed: ", err) + return + end + local v2, err = sock:getoption("reuseaddr") + if not v2 then + ngx.say("get enabled reuseaddr failed: ", err) + return + end + ngx.say("reuseaddr changes from ", v1, " to ", v2) + + ok, err = sock:setoption("reuseaddr", 0) + if not ok then + ngx.say("disable reuseaddr failed: ", err) + return + end + local v3, err = sock:getoption("reuseaddr") + if not v3 then + ngx.say("get disabled reuseaddr failed: ", err) + return + end + ngx.say("reuseaddr changes from ", v2, " to ", v3) + + sock:close() + } + } +--- request +GET /t +--- response_body +reuseaddr changes from 0 to 1 +reuseaddr changes from 1 to 0 +--- no_error_log +[error] + + + +=== TEST 10: tcp-nodelay set by 1/0. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + local v1, err = sock:getoption("tcp-nodelay") + if not v1 then + ngx.say("get default tcp-nodelay failed: ", err) + return + end + + ok, err = sock:setoption("tcp-nodelay", 1) + if not ok then + ngx.say("enabling tcp-nodelay failed: ", err) + return + end + local v2, err = sock:getoption("tcp-nodelay") + if not v2 then + ngx.say("get enabled tcp-nodelay failed: ", err) + return + end + ngx.say("tcp-nodelay changes from ", v1, " to ", v2) + + ok, err = sock:setoption("tcp-nodelay", 0) + if not ok then + ngx.say("disable tcp-nodelay failed: ", err) + return + end + local v3, err = sock:getoption("tcp-nodelay") + if not v3 then + ngx.say("get disabled tcp-nodelay failed: ", err) + return + end + ngx.say("tcp-nodelay changes from ", v2, " to ", v3) + + sock:close() + } + } +--- request +GET /t +--- response_body +tcp-nodelay changes from 0 to 1 +tcp-nodelay changes from 1 to 0 +--- no_error_log +[error] + + + +=== TEST 11: tcp-nodelay set by true/false. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + local v1, err = sock:getoption("tcp-nodelay") + if not v1 then + ngx.say("get default tcp-nodelay failed: ", err) + return + end + + ok, err = sock:setoption("tcp-nodelay", true) + if not ok then + ngx.say("enabling tcp-nodelay failed: ", err) + return + end + local v2, err = sock:getoption("tcp-nodelay") + if not v2 then + ngx.say("get enabled tcp-nodelay failed: ", err) + return + end + ngx.say("tcp-nodelay changes from ", v1, " to ", v2) + + ok, err = sock:setoption("tcp-nodelay", false) + if not ok then + ngx.say("disable tcp-nodelay failed: ", err) + return + end + local v3, err = sock:getoption("tcp-nodelay") + if not v3 then + ngx.say("get disabled tcp-nodelay failed: ", err) + return + end + ngx.say("tcp-nodelay changes from ", v2, " to ", v3) + + sock:close() + } + } +--- request +GET /t +--- response_body +tcp-nodelay changes from 0 to 1 +tcp-nodelay changes from 1 to 0 +--- no_error_log +[error] + + + +=== TEST 12: tcp-nodelay set by ~0/0. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + local v1, err = sock:getoption("tcp-nodelay") + if not v1 then + ngx.say("get default tcp-nodelay failed: ", err) + return + end + + ok, err = sock:setoption("tcp-nodelay", 10) + if not ok then + ngx.say("enabling tcp-nodelay failed: ", err) + return + end + local v2, err = sock:getoption("tcp-nodelay") + if not v2 then + ngx.say("get enabled tcp-nodelay failed: ", err) + return + end + ngx.say("tcp-nodelay changes from ", v1, " to ", v2) + + ok, err = sock:setoption("tcp-nodelay", 0) + if not ok then + ngx.say("disable tcp-nodelay failed: ", err) + return + end + local v3, err = sock:getoption("tcp-nodelay") + if not v3 then + ngx.say("get disabled tcp-nodelay failed: ", err) + return + end + ngx.say("tcp-nodelay changes from ", v2, " to ", v3) + + sock:close() + } + } +--- request +GET /t +--- response_body +tcp-nodelay changes from 0 to 1 +tcp-nodelay changes from 1 to 0 +--- no_error_log +[error] + + + +=== TEST 13: sndbuf. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + local v1, err = sock:getoption("sndbuf") + if not v1 then + ngx.say("get default sndbuf failed: ", err) + return + end + + ok, err = sock:setoption("sndbuf", 4096) + if not ok then + ngx.say("enabling sndbuf failed: ", err) + return + end + local v2, err = sock:getoption("sndbuf") + if not v2 then + ngx.say("get enabled sndbuf failed: ", err) + return + end + ngx.say("sndbuf changes from ", v1, " to ", v2) + + sock:close() + } + } +--- request +GET /t +--- response_body_like eval +qr/\Asndbuf changes from \d+ to \d+\n\z/ +--- no_error_log +[error] + + + +=== TEST 14: rcvbuf. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + + 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 + local v1, err = sock:getoption("rcvbuf") + if not v1 then + ngx.say("get default rcvbuf failed: ", err) + return + end + + ok, err = sock:setoption("rcvbuf", 4096) + if not ok then + ngx.say("enabling rcvbuf failed: ", err) + return + end + local v2, err = sock:getoption("rcvbuf") + if not v2 then + ngx.say("get enabled rcvbuf failed: ", err) + return + end + ngx.say("rcvbuf changes from ", v1, " to ", v2) + + sock:close() + } + } +--- request +GET /t +--- response_body_like eval +qr/\Arcvbuf changes from \d+ to \d+\n\z/ +--- no_error_log +[error] + + + +=== TEST 15: strerr. +--- config + set $port $TEST_NGINX_SERVER_PORT; + + location /t { + content_by_lua_block { + require "resty.core.socket" + local ffi = require "ffi" + local base = require "resty.core.base" + + ffi.cdef[[ + typedef struct ngx_http_lua_socket_tcp_upstream_s + ngx_http_lua_socket_tcp_upstream_t; + + int ngx_http_lua_ffi_socket_tcp_hack_fd( + ngx_http_lua_socket_tcp_upstream_t *u, int fd, + unsigned char *errstr, size_t *errlen); + ]] + + 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 + + local errstr = base.get_string_buf(4096) + local errlen = base.get_size_ptr() + errlen[0] = 4096 + local SOCKET_CTX_INDEX = 1 + local tcpsock = sock[SOCKET_CTX_INDEX] + + -- hack the fd of the socket + local bad_fd = 12345 + local realfd = ffi.C.ngx_http_lua_ffi_socket_tcp_hack_fd(tcpsock, + bad_fd, errstr, errlen) + if realfd == -1 then + ngx.say("hack fd failed: ", ffi.string(err, errlen[0])) + return + end + + ok, err = sock:setoption("rcvbuf", 4096) + if not ok then + ngx.say("enabling rcvbuf failed: ", err) + + -- restore the fd of the socket + ffi.C.ngx_http_lua_ffi_socket_tcp_hack_fd(tcpsock, + realfd, errstr, errlen) + return + end + } + } +--- request +GET /t +--- response_body_like eval +qr/\Aenabling rcvbuf failed: [\/\s\w]+\n\z/ +--- no_error_log +[error] diff --git a/t/ssl-client-hello.t b/t/ssl-client-hello.t new file mode 100644 index 000000000..4e798e3b8 --- /dev/null +++ b/t/ssl-client-hello.t @@ -0,0 +1,966 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(10140); +#workers(1); +#log_level('warn'); + +repeat_each(2); + +# 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() * 6 - 2) - 4; +} + +no_long_string(); +#no_diff(); + +env_to_nginx("PATH=" . $ENV{'PATH'}); +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::lua_package_path"; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +run_tests(); + +__DATA__ + +=== TEST 1: read SNI name via ssl.clienthello.get_client_hello_server_name() +--- skip_nginx: 6: < 1.19.9 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local ssl_clt = require "ngx.ssl.clienthello" + print("read SNI name from Lua: ", ssl_clt.get_client_hello_server_name()) + } + 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(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) + 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" +read SNI name from Lua: test.com + +--- no_error_log +[error] +[alert] + + + +=== TEST 2: read SNI name via ssl.clienthello.get_client_hello_server_name() when no SNI name specified +--- skip_nginx: 6: < 1.19.9 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local ssl_clt = require "ngx.ssl.clienthello" + local name = ssl_clt.get_client_hello_server_name() + print("read SNI name from Lua: ", name, ", type: ", type(name)) + } + 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(3000) + + 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 + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, nil, 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 +read SNI name from Lua: nil, type: nil + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 3: read SNI name via ssl.clienthello.get_client_hello_ext() +--- skip_nginx: 6: < 1.19.9 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local ssl_clt = require "ngx.ssl.clienthello" + local byte = string.byte + local ext = ssl_clt.get_client_hello_ext(0) + if not ext then + print("failed to get_client_hello_ext(0)") + ngx.exit(ngx.ERROR) + end + local total_len = string.len(ext) + if total_len <= 2 then + print("bad SSL Client Hello Extension") + ngx.exit(ngx.ERROR) + end + local len = byte(ext, 1) * 256 + byte(ext, 2) + if len + 2 ~= total_len then + print("bad SSL Client Hello Extension") + ngx.exit(ngx.ERROR) + end + if byte(ext, 3) ~= 0 then + print("bad SSL Client Hello Extension") + ngx.exit(ngx.ERROR) + end + if total_len <= 5 then + print("bad SSL Client Hello Extension") + ngx.exit(ngx.ERROR) + end + len = byte(ext, 4) * 256 + byte(ext, 5) + if len + 5 > total_len then + print("bad SSL Client Hello Extension") + ngx.exit(ngx.ERROR) + end + local name = string.sub(ext, 6, 6 + len -1) + + print("read SNI name from Lua: ", name) + } + 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(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) + 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" +read SNI name from Lua: test.com + +--- no_error_log +[error] +[alert] + + + +=== TEST 4: read SNI name via ssl.clienthello.get_client_hello_ext() when no SNI name specified +--- skip_nginx: 6: < 1.19.9 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local ssl_clt = require "ngx.ssl.clienthello" + local ext = ssl_clt.get_client_hello_ext(0) + print("read SNI name from Lua: ", ext, ", type: ", type(ext)) + } + + 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(3000) + + 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 + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, nil, 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 +read SNI name from Lua: nil, type: nil + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 5: dynamically set ssl protocol - allow TLSv1.2 +--- skip_nginx: 6: < 1.19.9 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local ssl_clt = require "ngx.ssl.clienthello" + local ok = ssl_clt.set_protocols({"TLSv1.2", "TLSv1.3"}) + if not ok then + print("failed to set_protocols") + ngx.exit(ngx.ERROR) + end + } + ssl_protocols TLSv1 TLSv1.1; + 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_protocols TLSv1.2; + + location /t { + 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, nil, 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/SSL: TLSv1.2, cipher:/] + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 6: dynamically set ssl protocol - allow TLSv1.3 +--- skip_nginx: 6: < 1.19.9 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local ssl_clt = require "ngx.ssl.clienthello" + local ok = ssl_clt.set_protocols({"TLSv1.2", "TLSv1.3"}) + if not ok then + print("failed to set_protocols") + ngx.exit(ngx.ERROR) + end + } + ssl_protocols TLSv1 TLSv1.1; + 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_protocols TLSv1.3; + + location /t { + 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, nil, 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/SSL: TLSv1.3, cipher:/] + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 7: dynamically set ssl protocol - deny TLSv1.1 +--- skip_nginx: 5: < 1.19.9 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local ssl_clt = require "ngx.ssl.clienthello" + local ok = ssl_clt.set_protocols({"TLSv1.2", "TLSv1.3"}) + if not ok then + print("failed to set_protocols") + ngx.exit(ngx.ERROR) + end + } + ssl_protocols TLSv1 TLSv1.1; + 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_protocols TLSv1.1; + + location /t { + 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, nil, 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 +failed to do SSL handshake: handshake failed + +--- error_log eval +[qr/SSL_do_handshake\(\) failed .*?tls_early_post_process_client_hello:unsupported protocol/] + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 8: dynamically set ssl protocol - deny TLSv1 +--- skip_nginx: 5: < 1.19.9 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local ssl_clt = require "ngx.ssl.clienthello" + local ok = ssl_clt.set_protocols({"TLSv1.2", "TLSv1.3"}) + if not ok then + print("failed to set_protocols") + ngx.exit(ngx.ERROR) + end + } + ssl_protocols TLSv1 TLSv1.1; + 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_protocols TLSv1; + + location /t { + 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, nil, 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 +failed to do SSL handshake: handshake failed + +--- error_log eval +[qr/SSL_do_handshake\(\) failed .*?tls_early_post_process_client_hello:unsupported protocol/] + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 9: get client hello supported versions - allow TLSv1.2 +--- skip_nginx: 4: < 1.19.9 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_client_hello_by_lua_block { + local ssl_clt = require "ngx.ssl.clienthello" + local types, err = ssl_clt.get_supported_versions() + if not err and types then + for _, ssl_type in pairs(types) do + if ssl_type == "TLSv1.2" then + ngx.exit(ngx.OK) + end + end + end + ngx.log(ngx.ERR, "failed to get_supported_versions") + ngx.exit(ngx.ERROR) + } + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + 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_protocols TLSv1 TLSv1.1 ; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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 + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, nil, 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 +failed to do SSL handshake: handshake failed + +--- error_log +failed to get_supported_versions + +--- no_error_log +[alert] diff --git a/t/ssl-session-fetch.t b/t/ssl-session-fetch.t new file mode 100644 index 000000000..48878602b --- /dev/null +++ b/t/ssl-session-fetch.t @@ -0,0 +1,600 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; +use Cwd qw(abs_path realpath); +use File::Basename; + +#worker_connections(10140); +#workers(1); +#log_level('warn'); +#master_on(); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6); + +no_long_string(); +#no_diff(); + +env_to_nginx("PATH=" . $ENV{'PATH'}); +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::lua_package_path"; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; +$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; +$ENV{TEST_NGINX_CERT_DIR} ||= dirname(realpath(abs_path(__FILE__))); + +run_tests(); + +__DATA__ + +=== TEST 1: get resume session id serialized +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + ssl_session_fetch_by_lua_block { + local ssl = require "ngx.ssl.session" + local sid = ssl.get_session_id() + print("session id: ", sid) + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_session_tickets off; + 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; + resolver $TEST_NGINX_RESOLVER; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_verify_depth 3; + + 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 + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil + +--- grep_error_log eval +qr/ssl_session_fetch_by_lua\(nginx.conf:\d+\):\d+: session id: [a-fA-f\d]+/s + +--- grep_error_log_out eval +[ +qr/ssl_session_fetch_by_lua\(nginx.conf:\d+\):4: session id: [a-fA-f\d]+/s, +qr/ssl_session_fetch_by_lua\(nginx.conf:\d+\):4: session id: [a-fA-f\d]+/s, +qr/ssl_session_fetch_by_lua\(nginx.conf:\d+\):4: session id: [a-fA-f\d]+/s, +] + +--- no_error_log +[alert] +[emerg] +[error] + + + +=== TEST 2: attempt to fetch new session in lua_ctx during resumption. +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + ssl_session_fetch_by_lua_block { + local ssl = require "ngx.ssl.session" + local sess, err = ssl.get_serialized_session() + if sess then + print("session size: ", #sess) + end + + if err then + print("get session error: ", err) + end + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_session_tickets off; + 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; + resolver $TEST_NGINX_RESOLVER; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_verify_depth 3; + + 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 + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil + +--- grep_error_log eval +qr/ssl_session_fetch_by_lua:\d: session size: [a-fA-f\d]+|get session error: bad session in lua context/s + +--- grep_error_log_out eval +[ +'get session error: bad session in lua context +', +'get session error: bad session in lua context +', +'get session error: bad session in lua context +', +] + +--- no_error_log +[alert] +[emerg] +[error] + + + +=== TEST 3: store new session, and resume it +Use a tmp file to store and resume session. This is for testing only. +In practice, never store session in plaintext on persistent storage. +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + ssl_session_store_by_lua_block { + local ssl = require "ngx.ssl.session" + + local sid = ssl.get_session_id() + print("session id: ", sid) + local sess = ssl.get_serialized_session() + print("session size: ", #sess) + + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/session.tmp", "w")) + f:write(sess) + f:close() + } + + ssl_session_fetch_by_lua_block { + local ssl = require "ngx.ssl.session" + local sid = ssl.get_session_id() + print("session id: ", sid) + local f = io.open("$TEST_NGINX_SERVER_ROOT/html/session.tmp") + if f == nil then + return + end + + local sess = f:read("*a") + f:close() + ssl.set_serialized_session(sess) + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_session_tickets off; + 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; + resolver $TEST_NGINX_RESOLVER; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_verify_depth 3; + + 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 + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil + +--- grep_error_log eval +qr/ssl_session_(fetch|store)_by_lua\(nginx.conf:\d+\):\d+: session id: [a-fA-F\d]+/s + +--- grep_error_log_out eval +[ +qr/ssl_session_store_by_lua\(nginx.conf:\d+\):5: session id: [a-fA-F\d]+/s, +qr/ssl_session_fetch_by_lua\(nginx.conf:\d+\):4: session id: [a-fA-F\d]+/s, +qr/ssl_session_fetch_by_lua\(nginx.conf:\d+\):4: session id: [a-fA-F\d]+/s, +] + +--- no_error_log +[alert] +[emerg] +[error] + + + +=== TEST 4: attempt to resume a corrupted session +Session resumption should fail, but the handshake should be +able to carry on and negotiate a new session. +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + ssl_session_store_by_lua_block { + local ssl = require "ngx.ssl.session" + + local sid = ssl.get_session_id() + print("session id: ", sid) + } + + ssl_session_fetch_by_lua_block { + local ssl = require "ngx.ssl.session" + local sid = ssl.get_session_id() + print("session id: ", sid) + local sess = "==garbage data==" + local ok, err = ssl.set_serialized_session(sess) + if not ok or err then + print("failed to resume session: ", err) + end + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_session_tickets off; + 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; + resolver $TEST_NGINX_RESOLVER; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_verify_depth 3; + + 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 + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil + +--- grep_error_log eval +qr/failed to resume session: failed to de-serialize session|ssl_session_(fetch|store)_by_lua\(nginx.conf:\d+\):\d+: session id: [a-fA-F\d]+/s + +--- grep_error_log_out eval +[ +qr/^ssl_session_fetch_by_lua\(nginx.conf:\d+\):4: session id: [a-fA-F\d]+ +failed to resume session: failed to de-serialize session +ssl_session_store_by_lua\(nginx.conf:\d+\):5: session id: [a-fA-F\d]+ +$/s, +qr/^ssl_session_fetch_by_lua\(nginx.conf:\d+\):4: session id: [a-fA-F\d]+ +failed to resume session: failed to de-serialize session +ssl_session_store_by_lua\(nginx.conf:\d+\):5: session id: [a-fA-F\d]+ +$/s, +qr/ssl_session_fetch_by_lua:4: session id: [a-fA-F\d]+ +failed to resume session: failed to de-serialize session +ssl_session_store_by_lua\(nginx.conf:\d+\):5: session id: [a-fA-F\d]+ +$/s, +] +--- no_error_log +[alert] +[emerg] +[error] + + + +=== TEST 5: yield during doing handshake with client which uses low version OpenSSL +--- no_check_leak +--- http_config + lua_shared_dict done 16k; + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + ssl_session_store_by_lua_block { + local ssl = require "ngx.ssl.session" + + local sid = ssl.get_session_id() + print("session id: ", sid) + } + + ssl_session_fetch_by_lua_block { + local ssl = require "ngx.ssl.session" + + ngx.sleep(0.01) -- yield + + local sid = ssl.get_session_id() + print("session id: ", sid) + local sess = "==garbage data==" + local ok, err = ssl.set_serialized_session(sess) + if not ok or err then + print("failed to resume session: ", err) + end + } + + server { + listen $TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_session_tickets off; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + + location / { + content_by_lua_block { + ngx.shared.done:set("handshake", true) + } + } + } +--- config + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + + location /t { + set $sess_file $TEST_NGINX_HTML_DIR/sess; + set $addr 127.0.0.1:$TEST_NGINX_RAND_PORT_1; + content_by_lua_block { + ngx.shared.done:delete("handshake") + local addr = ngx.var.addr; + local sess = ngx.var.sess_file + local req = "'GET / HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n'" + local f, err + if not package.loaded.session then + f, err = io.popen("echo -n " .. req .. " | timeout 3s openssl s_client -connect " .. addr .. " -sess_out " .. sess) + package.loaded.session = true + else + f, err = io.popen("echo -n " .. req .. " | timeout 3s openssl s_client -connect " .. addr .. " -sess_in " .. sess) + end + + if not f then + ngx.say(err) + return + end + + local step = 0.001 + while step < 2 do + ngx.sleep(step) + step = step * 2 + + if ngx.shared.done:get("handshake") then + local out = f:read('*a') + ngx.log(ngx.INFO, out) + ngx.say("ok") + f:close() + return + end + end + + ngx.log(ngx.ERR, "openssl client handshake timeout") + } + } + +--- request +GET /t +--- response_body +ok +--- error_log eval +qr/content_by_lua\(nginx\.conf:\d+\):\d+: CONNECTED/ +--- grep_error_log eval +qr/failed to resume session: failed to de-serialize session|ssl_session_(fetch|store)_by_lua\(nginx.conf:\d+\):\d+: session id: [a-fA-F\d]+/s +--- grep_error_log_out eval +[ +qr/^ssl_session_fetch_by_lua\(nginx.conf:\d+\):\d+: session id: [a-fA-F\d]+ +failed to resume session: failed to de-serialize session +ssl_session_store_by_lua\(nginx.conf:\d+\):\d+: session id: [a-fA-F\d]+ +$/s, +qr/^ssl_session_fetch_by_lua\(nginx.conf:\d+\):\d+: session id: [a-fA-F\d]+ +failed to resume session: failed to de-serialize session +ssl_session_store_by_lua\(nginx.conf:\d+\):\d+: session id: [a-fA-F\d]+ +$/s, +qr/^ssl_session_fetch_by_lua\(nginx.conf:\d+\):\d+: session id: [a-fA-F\d]+ +failed to resume session: failed to de-serialize session +ssl_session_store_by_lua\(nginx.conf:\d+\):\d+: session id: [a-fA-F\d]+ +$/s, +] + +--- no_error_log +[alert] +[emerg] +[error] +--- timeout: 5 + + + +=== TEST 6: store new session, and resume it, avoid memory leak when calling repeatly +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + ssl_session_store_by_lua_block { + local ssl = require "ngx.ssl.session" + local sess = ssl.get_serialized_session() + local f = assert(io.open("$TEST_NGINX_SERVER_ROOT/html/session.tmp", "w")) + f:write(sess) + f:close() + } + + ssl_session_fetch_by_lua_block { + local ssl = require "ngx.ssl.session" + local f = io.open("$TEST_NGINX_SERVER_ROOT/html/session.tmp") + if f == nil then + return + end + local sess = f:read("*a") + f:close() + ssl.set_serialized_session(sess) + ssl.set_serialized_session(sess) + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_session_tickets off; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + + server_tokens off; + } +--- config + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_verify_depth 3; + + 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 + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil +--- no_error_log +[alert] +[emerg] +[error] diff --git a/t/ssl-session-store.t b/t/ssl-session-store.t new file mode 100644 index 000000000..04b9c49ba --- /dev/null +++ b/t/ssl-session-store.t @@ -0,0 +1,282 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; +use Cwd qw(abs_path realpath); +use File::Basename; + +#worker_connections(10140); +#workers(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6 + 2); + +no_long_string(); +#no_diff(); + +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::lua_package_path"; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; +$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; +$ENV{TEST_NGINX_CERT_DIR} ||= dirname(realpath(abs_path(__FILE__))); + +run_tests(); + +__DATA__ + +=== TEST 1: get new session serialized +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + ssl_session_store_by_lua_block { + local ssl = require "ngx.ssl.session" + local sess = ssl.get_serialized_session() + print("session size: ", #sess) + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_session_tickets off; + 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; + resolver $TEST_NGINX_RESOLVER; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_verify_depth 3; + + 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(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 + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil + +--- error_log eval +qr/ssl_session_store_by_lua\(nginx.conf:\d+\):4: session size: \d+/s + +--- no_error_log +[alert] +[emerg] +[error] + + + +=== TEST 2: get new session id serialized +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + ssl_session_store_by_lua_block { + local ssl = require "ngx.ssl.session" + local sid = ssl.get_session_id() + print("session id: ", sid) + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_session_tickets off; + 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; + resolver $TEST_NGINX_RESOLVER; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_verify_depth 3; + + 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(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 + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil + +--- error_log eval +qr/ssl_session_store_by_lua\(nginx.conf:\d+\):4: session id: [a-fA-f\d]+/s + +--- no_error_log +[alert] +[emerg] +[error] + + + +=== TEST 3: store the session via timer to memcached +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + ssl_session_store_by_lua_block { + local ssl = require "ngx.ssl.session" + local function f(premature, key, value) + local sock = ngx.socket.tcp() + + sock:settimeout(5000) + + 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("set " .. key .. " 0 0 " + .. tostring(#value) .. " \r\n" + .. value .. "\r\n") + if not bytes then + ngx.log(ngx.ERR, "failed to send set 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) + end + + local sid = ssl.get_session_id() + print("session id: ", sid) + local sess = ssl.get_serialized_session() + print("session size: ", #sess) + + local ok, err = ngx.timer.at(0, f, sid, sess) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_session_tickets off; + 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; + resolver $TEST_NGINX_RESOLVER; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + lua_ssl_verify_depth 3; + + 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(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 + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +close: 1 nil + +--- error_log eval +[ +qr/ssl_session_store_by_lua\(nginx.conf:\d+\):32: session id: [a-fA-f\d]+/s, +qr/ssl_session_store_by_lua\(nginx.conf:\d+\):34: session size: \d+/s, +qr/received memc reply: STORED/s, +] + +--- no_error_log +[alert] +[emerg] +[error] +--- wait: 0.2 diff --git a/t/ssl.t b/t/ssl.t new file mode 100644 index 000000000..139e164ff --- /dev/null +++ b/t/ssl.t @@ -0,0 +1,2839 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(10140); +#workers(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6 + 1); + +no_long_string(); +#no_diff(); + +env_to_nginx("PATH=" . $ENV{'PATH'}); +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::lua_package_path"; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +run_tests(); + +__DATA__ + +=== TEST 1: clear certs +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + ssl.clear_certs() + } + 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(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) + 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 +failed to do SSL handshake: handshake failed + +--- error_log +lua ssl server name: "test.com" +sslv3 alert handshake failure + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 2: set DER cert and private key +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/test.crt.der")) + local cert_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_cert(cert_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/test.key.der")) + local pkey_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + 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.crt; + + location /t { + 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) + 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] +[emerg] + + + +=== TEST 3: read SNI name via ssl.server_name() +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + print("read SNI name from Lua: ", ssl.server_name()) + } + 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(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) + 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" +read SNI name from Lua: test.com + +--- no_error_log +[error] +[alert] + + + +=== TEST 4: read SNI name via ssl.server_name() when no SNI name specified +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local name = ssl.server_name(), + print("read SNI name from Lua: ", name, ", type: ", type(name)) + } + 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(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, nil, 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 +read SNI name from Lua: nil, type: nil + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 5: read raw server addr via ssl.raw_server_addr() (unix domain socket) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local addr, addrtyp, err = ssl.raw_server_addr() + if not addr then + ngx.log(ngx.ERR, "failed to fetch raw server addr: ", err) + return + end + if addrtyp == "inet" then -- IPv4 + ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2), + byte(addr, 3), byte(addr, 4)) + print("Using IPv4 address: ", ip) + + elseif addrtyp == "inet6" then -- IPv6 + ip = string.format("%d.%d.%d.%d", byte(addr, 13), byte(addr, 14), + byte(addr, 15), byte(addr, 16)) + print("Using IPv6 address: ", ip) + + else -- unix + print("Using unix socket file ", addr) + 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(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) + 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/Using unix socket file .*?nginx\.sock/ +] + +--- no_error_log +[error] +[alert] +--- no_check_leak + + + +=== TEST 6: read raw server addr via ssl.raw_server_addr() (IPv4) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local byte = string.byte + + local addr, addrtyp, err = ssl.raw_server_addr() + if not addr then + ngx.log(ngx.ERR, "failed to fetch raw server addr: ", err) + return + end + if addrtyp == "inet" then -- IPv4 + ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2), + byte(addr, 3), byte(addr, 4)) + print("Using IPv4 address: ", ip) + + elseif addrtyp == "inet6" then -- IPv6 + ip = string.format("%d.%d.%d.%d", byte(addr, 13), byte(addr, 14), + byte(addr, 15), byte(addr, 16)) + print("Using IPv6 address: ", ip) + + else -- unix + print("Using unix socket file ", addr) + 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(3000) + + 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 +lua ssl server name: "test.com" +Using IPv4 address: 127.0.0.1 + +--- no_error_log +[error] +[alert] +--- no_check_leak + + + +=== TEST 7: read raw server addr via ssl.raw_server_addr() (IPv6) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen [::1]:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local byte = string.byte + + local addr, addrtyp, err = ssl.raw_server_addr() + if not addr then + ngx.log(ngx.ERR, "failed to fetch raw server addr: ", err) + return + end + if addrtyp == "inet" then -- IPv4 + ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2), + byte(addr, 3), byte(addr, 4)) + print("Using IPv4 address: ", ip) + + elseif addrtyp == "inet6" then -- IPv6 + ip = string.format("%d.%d.%d.%d", byte(addr, 13), byte(addr, 14), + byte(addr, 15), byte(addr, 16)) + print("Using IPv6 address: ", ip) + + else -- unix + print("Using unix socket file ", addr) + 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(3000) + + local ok, err = sock:connect("[::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 +lua ssl server name: "test.com" +Using IPv6 address: 0.0.0.1 + +--- no_error_log +[error] +[alert] +--- skip_eval: 6: system("ping6 -c 1 ::1 >/dev/null 2>&1") ne 0 +--- no_check_leak + + + +=== TEST 8: set DER cert chain +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain.der")) + local cert_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_cert(cert_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.der")) + local pkey_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + 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/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + location /t { + 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) + 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] +[emerg] + + + +=== TEST 9: read PEM cert chain but set DER cert chain +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.cert_pem_to_der(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local ok, err = ssl.set_der_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.der")) + local pkey_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER private key: ", err) + 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/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + location /t { + 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) + 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] +[emerg] + + + +=== TEST 10: tls version - SSLv3 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols SSLv3; + + 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; + lua_ssl_protocols SSLv3; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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 + 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 +got TLS1 version: SSLv3, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 11: tls version - TLSv1 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1; + + 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; + lua_ssl_protocols TLSv1; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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 + 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 +got TLS1 version: TLSv1, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 12: tls version - TLSv1.1 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.1; + + 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; + lua_ssl_protocols TLSv1.1; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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 + 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 +got TLS1 version: TLSv1.1, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 13: tls version - TLSv1.2 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.2; + + 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; + lua_ssl_protocols TLSv1.2; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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 + 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 +got TLS1 version: TLSv1.2, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 14: ngx.semaphore in ssl_certificate_by_lua* +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local semaphore = require "ngx.semaphore" + + local sema = assert(semaphore.new()) + + local function f() + assert(sema:wait(1)) + end + + local t = assert(ngx.thread.spawn(f)) + ngx.sleep(0.25) + + assert(sema:post()) + + assert(ngx.thread.wait(t)) + print("ssl cert by lua done") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.2; + + 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; + lua_ssl_protocols TLSv1.2; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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 + 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/http lua semaphore (?:wait yielding|\w[^:,]*)/ +--- grep_error_log_out +http lua semaphore new +http lua semaphore wait +http lua semaphore wait yielding +http lua semaphore post +--- error_log +ssl cert by lua done + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 15: read PEM key chain but set DER key chain +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.cert_pem_to_der(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local ok, err = ssl.set_der_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.pem")) + local pkey_data = f:read("*a") + f:close() + + pkey_data, err = ssl.priv_key_pem_to_der(pkey_data) + if not pkey_data then + ngx.log(ngx.ERR, "failed to convert pem key to der key: ", err) + return + end + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + 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/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + location /t { + 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) + 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] +[emerg] + + + +=== TEST 16: parse PEM cert and key to cdata +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = ssl.set_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.pem")) + local pkey_data = f:read("*a") + f:close() + + local pkey, err = ssl.parse_pem_priv_key(pkey_data) + if not pkey then + ngx.log(ngx.ERR, "failed to parse pem key: ", err) + return + end + + local ok, err = ssl.set_priv_key(pkey) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + 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/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + location /t { + 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) + 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] +[emerg] + + + +=== TEST 17: parse PEM cert and key to cdata (bad cert 0 in the chain) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain-bad0.pem")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = ssl.set_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.pem")) + local pkey_data = f:read("*a") + f:close() + + local pkey, err = ssl.parse_pem_priv_key(pkey_data) + if not pkey then + ngx.log(ngx.ERR, "failed to parse pem key: ", err) + return + end + + local ok, err = ssl.set_priv_key(pkey) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + 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/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + location /t { + 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) + 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 +failed to do SSL handshake: handshake failed + +--- error_log eval +qr/\[error\] .*? failed to parse pem cert: PEM_read_bio_X509_AUX\(\) failed/ + +--- no_error_log +[alert] +[emerg] +[crit] + + + +=== TEST 18: parse PEM cert and key to cdata (bad cert 2 in the chain) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain-bad2.pem")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = ssl.set_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.pem")) + local pkey_data = f:read("*a") + f:close() + + local pkey, err = ssl.parse_pem_priv_key(pkey_data) + if not pkey then + ngx.log(ngx.ERR, "failed to parse pem key: ", err) + return + end + + local ok, err = ssl.set_priv_key(pkey) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + 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/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + location /t { + 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) + 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 +failed to do SSL handshake: handshake failed + +--- error_log eval +qr/\[error\] .*? failed to parse pem cert: PEM_read_bio_X509\(\) failed/ + +--- no_error_log +[alert] +[emerg] +[crit] + + + +=== TEST 19: parse PEM cert and key to cdata (bad priv key) +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = ssl.set_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com-bad.key.pem")) + local pkey_data = f:read("*a") + f:close() + + local pkey, err = ssl.parse_pem_priv_key(pkey_data) + if not pkey then + ngx.log(ngx.ERR, "failed to parse pem key: ", err) + return + end + + local ok, err = ssl.set_priv_key(pkey) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + 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/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + location /t { + 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) + 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 +failed to do SSL handshake: handshake failed + +--- error_log eval +qr/\[error\] .*? failed to parse pem key: PEM_read_bio_PrivateKey\(\) failed/ + +--- no_error_log +[alert] +[emerg] +[crit] + + + +=== TEST 20: read client addr via ssl.raw_client_addr() +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_certificate_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(3000) + + 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, nil, 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] +[emerg] + + + +=== TEST 21: yield during doing handshake with client which uses low version OpenSSL +--- no_check_leak +--- http_config + lua_shared_dict done 16k; + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + server { + listen $TEST_NGINX_RAND_PORT_1 ssl; + server_name test.com; + ssl_session_tickets off; + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/test.crt.der")) + local cert_data = f:read("*a") + f:close() + + ngx.sleep(0.01) -- yield + + local ok, err = ssl.set_der_cert(cert_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/test.key.der")) + local pkey_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + } + + location / { + content_by_lua_block { + ngx.shared.done:set("handshake", true) + } + } + } +--- config + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + ngx.shared.done:delete("handshake") + local addr = ngx.var.addr; + local req = "'GET / HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n'" + local f, err = io.popen("echo -n " .. req .. " | timeout 3s openssl s_client -connect 127.0.0.1:$TEST_NGINX_RAND_PORT_1") + if not f then + ngx.say(err) + return + end + + local step = 0.001 + while step < 2 do + ngx.sleep(step) + step = step * 2 + + if ngx.shared.done:get("handshake") then + local out = f:read('*a') + ngx.log(ngx.INFO, out) + ngx.say("ok") + f:close() + return + end + end + + ngx.log(ngx.ERR, "openssl client handshake timeout") + } + } + +--- request +GET /t +--- response_body +ok +--- error_log eval +[ +qr/content_by_lua\(nginx\.conf:\d+\):\d+: CONNECTED/, +qr/subject=\/?C(?\s?=\s?)US(?\/|,\s)ST\kCalifornia\kL\kSan Francisco\kO\kOpenResty\kOU\kOpenResty\kCN\ktest\.com\kemailAddress\kagentzh\@gmail\.com/, +] + +--- no_error_log +[error] +[alert] + +--- timeout: 4 + + + +=== TEST 22: tls version - TLSv1.3 +--- skip_openssl: 6: < 1.1.1 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + server_tokens off; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.3; + + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1.3; + + location /t { + 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(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 +got TLS1 version: TLSv1.3, +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 23: verify client with CA certificates +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local f = assert(io.open("t/cert/test.crt")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = ssl.verify_client(cert, 1) + if not ok then + ngx.log(ngx.ERR, "failed to verify client: ", err) + 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 +SUCCESS + +--- error_log +client certificate subject: emailAddress=agentzh@gmail.com,CN=test.com + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 24: verify client without CA certificates +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ok, err = ssl.verify_client() + if not ok then + ngx.log(ngx.ERR, "failed to verify client: ", err) + 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 +FAILED:self signed certificate + +--- error_log +client certificate subject: emailAddress=agentzh@gmail.com,CN=test.com + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 25: verify client but client provides no certificate +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local f = assert(io.open("t/cert/test.crt")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = ssl.verify_client(cert, 1) + if not ok then + ngx.log(ngx.ERR, "failed to verify client: ", err) + 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_session_reuse off; + } + +--- request +GET /t +--- response_body +NONE + +--- error_log +client certificate subject: nil + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 26: read server port via ssl.server_port() with ipv4 +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_2 ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local port, err = ssl.server_port() + print("read server port from Lua: ", port) + } + 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(3000) + + local ok, err = sock:connect("127.0.0.1", $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(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/read server port from Lua: \d+/ + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 27: read server port via ssl.server_port() with unix domain socket +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local port, err = ssl.server_port() + print("read server port from Lua: ", port, err) + } + 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(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) + 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 +read server port from Lua: nilunix domain has no port + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 28: PEM key protected by passphrase +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/test_passphrase.crt")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.cert_pem_to_der(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local ok, err = ssl.set_der_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/test_passphrase.key")) + local pkey_data = f:read("*a") + f:close() + + pkey_data, err = ssl.priv_key_pem_to_der(pkey_data, "123456") + if not pkey_data then + ngx.log(ngx.ERR, "failed to convert pem key to der key: ", err) + return + end + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + 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/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + location /t { + 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", 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 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] +[emerg] diff --git a/t/status.t b/t/status.t new file mode 100644 index 000000000..41990cafe --- /dev/null +++ b/t/status.t @@ -0,0 +1,69 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +log_level('warn'); + +#repeat_each(120); +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 7); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: get ngx.status +--- config + location = /t { + return 201; + header_filter_by_lua_block { + local sum = 0 + for i = 1, 30 do + sum = sum + ngx.status + end + ngx.log(ngx.WARN, "sum: ", sum) + } + } +--- request +GET /t +--- response_body +--- error_code: 201 +--- no_error_log +[error] + -- NYI: + bad argument +--- error_log eval +["sum: 6030,", +qr/\[TRACE\s+\d+\s+header_filter_by_lua\(nginx.conf:\d+\):3 loop\]/ +] + + + +=== TEST 2: set ngx.status +--- config + location = /t { + return 201; + header_filter_by_lua_block { + for i = 100, 200 do + ngx.status = i + end + ngx.log(ngx.WARN, "status: ", ngx.status) + } + } +--- request +GET /t +--- response_body +--- no_error_log +[error] + -- NYI: + bad argument +--- error_log eval +["status: 200,", +qr/\[TRACE\s+\d+\s+header_filter_by_lua\(nginx.conf:\d+\):2 loop\]/ +] diff --git a/t/stream/balancer-timeout.t b/t/stream/balancer-timeout.t new file mode 100644 index 000000000..b037b55c5 --- /dev/null +++ b/t/stream/balancer-timeout.t @@ -0,0 +1,341 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +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 (defined $ENV{MOCKEAGAIN} && $ENV{MOCKEAGAIN} eq 'r') { + $ENV{MOCKEAGAIN} = 'rw'; + + } else { + $ENV{MOCKEAGAIN} = 'w'; + } + + $ENV{TEST_NGINX_EVENT_TYPE} = 'poll'; + $ENV{TEST_NGINX_POSTPONE_OUTPUT} = 1; +} + +use lib '.'; +use t::TestCore::Stream; + +#worker_connections(1014); +#master_on(); +#workers(2); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 5); + +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::Stream::lua_package_path"; + +#worker_connections(1024); +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: set_timeouts +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen $TEST_NGINX_RAND_PORT_1; + return "fake origin\n"; + } + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(1.234, 5.678, 7.689)) + assert(b.set_current_peer("127.0.0.1", tonumber($TEST_NGINX_RAND_PORT_1))) + } + } +--- stream_server_config + proxy_pass backend; +--- response_body +fake origin +--- grep_error_log eval: qr/event timer add: \d+: (?:1234|5678|7689):/ +--- grep_error_log_out eval +qr/\Aevent timer add: \d+: 1234: +event timer add: \d+: 7689: +\z/ +--- no_error_log +[warn] + + + +=== TEST 2: set_timeouts (nil connect timeout) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + proxy_connect_timeout 1234ms; + + server { + listen $TEST_NGINX_RAND_PORT_1; + return "fake origin\n"; + } + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(nil, 5.678, 7.689)) + assert(b.set_current_peer("127.0.0.1", tonumber($TEST_NGINX_RAND_PORT_1))) + } + } +--- stream_server_config + proxy_pass backend; +--- stream_response +fake origin +--- grep_error_log eval: qr/event timer add: \d+: (?:1234|5678|7689):/ +--- grep_error_log_out eval +qr/\Aevent timer add: \d+: 1234: +event timer add: \d+: 7689: +\z/ +--- no_error_log +[warn] + + + +=== TEST 3: set_timeouts (nil send timeout) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + proxy_timeout 5678ms; + + server { + listen $TEST_NGINX_RAND_PORT_1; + return "fake origin\n"; + } + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(1.234, nil, 7.689)) + assert(b.set_current_peer("127.0.0.1", tonumber($TEST_NGINX_RAND_PORT_1))) + } + } +--- stream_server_config + proxy_pass backend; +--- stream_response +fake origin +--- grep_error_log eval: qr/event timer add: \d+: (?:1234|5678|7689):/ +--- grep_error_log_out eval +qr/\Aevent timer add: \d+: 1234: +event timer add: \d+: 7689: +\z/ +--- no_error_log +[warn] + + + +=== TEST 4: set_timeouts (nil read timeout) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + proxy_timeout 7689ms; + + server { + listen $TEST_NGINX_RAND_PORT_1; + return "fake origin\n"; + } + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(1.234, 5.678, nil)) + assert(b.set_current_peer("127.0.0.1", tonumber($TEST_NGINX_RAND_PORT_1))) + } + } +--- stream_server_config + proxy_pass backend; +--- stream_response +fake origin +--- grep_error_log eval: qr/event timer add: \d+: (?:1234|5678|7689):/ +--- grep_error_log_out eval +qr/\Aevent timer add: \d+: 1234: +event timer add: \d+: 5678: +\z/ +--- no_error_log +[warn] + + + +=== TEST 5: set connect timeout to 0 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(0, 1.234, 5.678)) + assert(b.set_current_peer("127.0.0.1", tonumber($TEST_NGINX_RAND_PORT_1))) + } + } +--- stream_server_config + proxy_pass backend; +--- error_log eval +qr/\[error\] .*? balancer_by_lua:3: bad connect timeout/ +--- no_error_log +[warn] + + + +=== TEST 6: set connect timeout to -1 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(-1, 1.234, 5.678)) + assert(b.set_current_peer("127.0.0.1", tonumber($TEST_NGINX_RAND_PORT_1))) + } + } +--- stream_server_config + proxy_pass backend; +--- error_log eval +qr/\[error\] .*? balancer_by_lua:3: bad connect timeout/ +--- no_error_log +[warn] + + + +=== TEST 7: set send timeout to 0 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(1.234, 0, 5.678)) + assert(b.set_current_peer("127.0.0.1", tonumber($TEST_NGINX_RAND_PORT_1))) + } + } +--- stream_server_config + proxy_pass backend; +--- error_log eval +qr/\[error\] .*? balancer_by_lua:3: bad send timeout/ +--- no_error_log +[warn] + + + +=== TEST 8: set send timeout to -1 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(1.234, -1, 5.678)) + assert(b.set_current_peer("127.0.0.1", tonumber($TEST_NGINX_RAND_PORT_1))) + } + } +--- stream_server_config + proxy_pass backend; +--- error_log eval +qr/\[error\] .*? balancer_by_lua:3: bad send timeout/ +--- no_error_log +[warn] + + + +=== TEST 9: set read timeout to 0 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(1.234, 4.567, 0)) + assert(b.set_current_peer("127.0.0.1", tonumber($TEST_NGINX_RAND_PORT_1))) + } + } +--- stream_server_config + proxy_pass backend; +--- error_log eval +qr/\[error\] .*? balancer_by_lua:3: bad read timeout/ +--- no_error_log +[warn] + + + +=== TEST 10 set read timeout to -1 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + local b = require "ngx.balancer" + assert(b.set_timeouts(1.234, 4.567, -1)) + assert(b.set_current_peer("127.0.0.1", tonumber($TEST_NGINX_RAND_PORT_1))) + } + } +--- stream_server_config + proxy_pass backend; +--- error_log eval +qr/\[error\] .*? balancer_by_lua:3: bad read timeout/ +--- no_error_log +[warn] + + + +=== TEST 11: set_timeouts called in a wrong context +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + +--- stream_server_config + + content_by_lua_block { + local balancer = require "ngx.balancer" + local ok, err = balancer.set_timeouts(1, 1, 1) + if not ok then + ngx.say("failed to call: ", err) + return + end + ngx.say("unexpected success!") + } + +--- stream_response +failed to call: API disabled in the current context +--- no_error_log +[error] +[alert] + + + +=== TEST 12: set_timeouts called with a non-numerical parameter +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + local balancer = require "ngx.balancer" + local ok, err = balancer.set_timeouts("1.234", 1, 1) + if not ok then + ngx.log(ngx.ERR, "failed to call: ", err) + end + } + } + +--- stream_server_config + proxy_pass backend; +--- error_log eval +qr/\[error\] .*? bad connect timeout/ +--- no_error_log +[alert] diff --git a/t/stream/balancer.t b/t/stream/balancer.t new file mode 100644 index 000000000..d90c8e9f1 --- /dev/null +++ b/t/stream/balancer.t @@ -0,0 +1,220 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +#worker_connections(1014); +#master_on(); +#workers(2); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 2); + +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::Stream::lua_package_path"; + +#worker_connections(1024); +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: set current peer (separate addr and port) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- stream_server_config + proxy_pass backend; +--- error_log eval +[ +'[lua] balancer_by_lua:2: hello from balancer by lua! while connecting to upstream,', +qr{connect\(\) failed .*?, upstream: "127\.0\.0\.3:12345"}, +] +--- no_error_log +[warn] + + + +=== TEST 2: set current peer & next upstream (3 tries) +--- skip_nginx: 4: < 1.7.5 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + proxy_next_upstream on; + proxy_next_upstream_tries 10; + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- stream_server_config + proxy_pass backend; +--- grep_error_log eval: qr{connect\(\) failed .*, upstream: ".*?"} +--- grep_error_log_out eval +qr#^(?:connect\(\) failed .*?, upstream: "127.0.0.3:12345"\n){3}$# +--- no_error_log +[warn] + + + +=== TEST 3: set current peer & next upstream (no retries) +--- skip_nginx: 4: < 1.7.5 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + proxy_next_upstream on; + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- stream_server_config + proxy_pass backend; +--- grep_error_log eval: qr{connect\(\) failed .*, upstream: ".*?"} +--- grep_error_log_out eval +qr#^(?:connect\(\) failed .*?, upstream: "127.0.0.3:12345"\n){1}$# +--- no_error_log +[warn] + + + +=== TEST 4: set current peer & next upstream (3 tries exceeding the limit) +--- skip_nginx: 4: < 1.7.5 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + proxy_next_upstream on; + proxy_next_upstream_tries 2; + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + local b = require "ngx.balancer" + + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- stream_server_config + proxy_pass backend; +--- grep_error_log eval: qr{connect\(\) failed .*, upstream: ".*?"} +--- grep_error_log_out eval +qr#^(?:connect\(\) failed .*?, upstream: "127.0.0.3:12345"\n){2}$# +--- error_log +set more tries: reduced tries due to limit + + + +=== TEST 5: get last peer failure status (connect failed) +--- skip_nginx: 4: < 1.7.5 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + proxy_next_upstream on; + proxy_next_upstream_tries 10; + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + local b = require "ngx.balancer" + + local state, status = b.get_last_failure() + print("last peer failure: ", state, " ", status) + + if not ngx.ctx.tries then + ngx.ctx.tries = 0 + end + + if ngx.ctx.tries < 2 then + local ok, err = b.set_more_tries(1) + if not ok then + return error("failed to set more tries: ", err) + elseif err then + ngx.log(ngx.WARN, "set more tries: ", err) + end + end + ngx.ctx.tries = ngx.ctx.tries + 1 + assert(b.set_current_peer("127.0.0.3", 12345)) + } + } +--- stream_server_config + proxy_pass backend; +--- grep_error_log eval: qr{last peer failure: \S+ \S+} +--- grep_error_log_out +last peer failure: nil nil +last peer failure: failed 0 +last peer failure: failed 0 + +--- no_error_log +[warn] + + + +=== TEST 6: set current peer (port embedded in addr) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + upstream backend { + server 0.0.0.1:1234; + balancer_by_lua_block { + print("hello from balancer by lua!") + local b = require "ngx.balancer" + assert(b.set_current_peer("127.0.0.3:12345")) + } + } +--- stream_server_config + proxy_pass backend; +--- error_log eval +[ +'[lua] balancer_by_lua:2: hello from balancer by lua! while connecting to upstream,', +qr{connect\(\) failed .*?, upstream: "127\.0\.0\.3:12345"}, +] +--- no_error_log +[warn] diff --git a/t/stream/ctx.t b/t/stream/ctx.t new file mode 100644 index 000000000..61bae8282 --- /dev/null +++ b/t/stream/ctx.t @@ -0,0 +1,282 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 4); + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.ctx in ssl_certificate_by_lua +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + ngx.ctx.answer = 42 + ngx.log(ngx.WARN, "ngx.ctx.answer = ", ngx.ctx.answer) + + ngx.ctx.count = 0 + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + content_by_lua_block { + ngx.say(ngx.ctx.answer) + ngx.ctx.count = ngx.ctx.count + 1 + ngx.say(ngx.ctx.count) + } + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local function run() + 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(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + while true do + local line, err = sock:receive() + if not line then + break + end + + ngx.say("received: ", line) + end + + sock:close() + end + + run() + end -- do + -- collectgarbage() + } + +--- stream_response +received: 42 +received: 1 +--- error_log +ngx.ctx.answer = 42 +--- grep_error_log eval +qr/lua release ngx.ctx at ref \d+/ +--- grep_error_log_out eval +["lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +", +"lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +"] +--- no_error_log +[error] + + + +=== TEST 2: ngx.ctx in ssl_certificate_by_lua (share objects) +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + ngx.ctx.req = { count = 0 } + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + content_by_lua_block { + ngx.ctx.req.count = ngx.ctx.req.count + 1 + ngx.say(ngx.ctx.req.count) + } + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local function run() + 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(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + while true do + local line, err = sock:receive() + if not line then + break + end + + ngx.say("received: ", line) + end + + sock:close() + end + + run() + end -- do + -- collectgarbage() + } + +--- stream_response +received: 1 +--- no_error_log +[error] + + + +=== TEST 3: ngx.ctx in ssl_certificate_by_lua (release ctx when client aborted) +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + ssl.clear_certs() + ngx.ctx.answer = 42 + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + return "ok"; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + 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 + + local sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + end + + sock:close() + end -- do + -- collectgarbage() + } + +--- stream_response +failed to do SSL handshake: handshake failed +--- grep_error_log eval +qr/lua release ngx.ctx at ref \d+/ +--- grep_error_log_out eval +["lua release ngx.ctx at ref 1 +", +"lua release ngx.ctx at ref 1 +lua release ngx.ctx at ref 1 +"] + + + +=== TEST 4: ngx.ctx in ssl_client_hello_by_lua +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_client_hello_by_lua_block { + ngx.ctx.answer = 42 + ngx.log(ngx.WARN, "ngx.ctx.answer = ", ngx.ctx.answer) + + ngx.ctx.count = 0 + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + content_by_lua_block { + ngx.say(ngx.ctx.answer) + ngx.ctx.count = ngx.ctx.count + 1 + ngx.say(ngx.ctx.count) + } + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local function run() + 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(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + while true do + local line, err = sock:receive() + if not line then + break + end + + ngx.say("received: ", line) + end + + sock:close() + end + + run() + end -- do + -- collectgarbage() + } + +--- stream_response +received: 42 +received: 1 +--- error_log +ngx.ctx.answer = 42 +--- grep_error_log eval +qr/lua release ngx.ctx at ref \d+/ +--- grep_error_log_out eval +["lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +", +"lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +lua release ngx.ctx at ref 2 +lua release ngx.ctx at ref 1 +"] +--- no_error_log +[error] diff --git a/t/stream/errlog-raw-log.t b/t/stream/errlog-raw-log.t new file mode 100644 index 000000000..b6ba6c9d2 --- /dev/null +++ b/t/stream/errlog-raw-log.t @@ -0,0 +1,201 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +log_level('error'); + +repeat_each(1); + +plan tests => repeat_each() * (blocks() * 2 + 5); + +add_block_preprocessor(sub { + my $block = shift; + + my $stream_config = $block->stream_config || ''; + my $init_by_lua_block = $block->init_by_lua_block || ''; + + $stream_config .= <<_EOC_; + lua_package_path '$t::TestCore::Stream::lua_package_path'; + init_by_lua_block { + $t::TestCore::Stream::init_by_lua_block + $init_by_lua_block + } +_EOC_ + + $block->set_value("stream_config", $stream_config); +}); + +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: errlog.raw_log with bad log level (ngx.ERROR, -1) +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + + local pok, err = pcall(errlog.raw_log, ngx.ERROR, "hello, log") + if not pok then + ngx.say("not ok: ", err) + return + end + + ngx.say("ok") + } +--- stream_response +not ok: bad log level +--- no_error_log +[error] + + + +=== TEST 2: errlog.raw_log with bad levels (9) +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + + local pok, err = pcall(errlog.raw_log, 9, "hello, log") + if not pok then + ngx.say("not ok: ", err) + return + end + + ngx.say("ok") + } +--- stream_response +not ok: bad log level +--- no_error_log +[error] + + + +=== TEST 3: errlog.raw_log with bad log message +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + + local pok, err = pcall(errlog.raw_log, ngx.ERR, 123) + if not pok then + ngx.say("not ok: ", err) + return + end + + ngx.say("ok") + } +--- stream_response +not ok: bad argument #2 to 'raw_log' (must be a string) +--- no_error_log +[error] + + + +=== TEST 4: errlog.raw_log test log-level ERR +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + + errlog.raw_log(ngx.ERR, "hello world") + } +--- error_log eval +qr/\[error\] \S+: \S+ hello world/ + + + +=== TEST 5: errlog.raw_log JITs +--- init_by_lua_block + -- local verbose = true + local verbose = false + local outfile = errlog_file + -- local outfile = "/tmp/v.log" + if verbose then + local dump = require "jit.dump" + dump.on(nil, outfile) + else + local v = require "jit.v" + v.on(outfile) + end + + require "resty.core" + -- jit.opt.start("hotloop=1") + -- jit.opt.start("loopunroll=1000000") + -- jit.off() +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + + for i = 1, 100 do + errlog.raw_log(ngx.ERR, "hello world") + end + } +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):4 loop\]/ + + + +=== TEST 6: errlog.raw_log in init_by_lua +--- init_by_lua_block + local errlog = require "ngx.errlog" + errlog.raw_log(ngx.ERR, "hello world from init_by_lua") +--- stream_server_config + content_by_lua_block { + ngx.say("ok") + } +--- grep_error_log chop +hello world from init_by_lua +--- grep_error_log_out eval +["hello world from init_by_lua\n", ""] + + + +=== TEST 7: errlog.raw_log in init_worker_by_lua +--- stream_config + init_worker_by_lua_block { + local errlog = require "ngx.errlog" + errlog.raw_log(ngx.ERR, "hello world from init_worker_by_lua") + } +--- stream_server_config + content_by_lua_block { + ngx.say("ok") + } +--- grep_error_log chop +hello world from init_worker_by_lua +--- grep_error_log_out eval +["hello world from init_worker_by_lua\n", ""] + + + +=== TEST 8: errlog.raw_log with \0 in the log message +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + errlog.raw_log(ngx.ERR, "hello\0world") + ngx.say("ok") + } +--- stream_response +ok +--- error_log eval +"hello\0world, client: " + + + +=== TEST 9: errlog.raw_log is captured by errlog.get_logs() +--- stream_config + lua_capture_error_log 4k; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + errlog.raw_log(ngx.ERR, "hello from raw_log()") + + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + + ngx.say("log lines: ", #res / 3) + } +--- stream_response +log lines: 1 +--- error_log eval +qr/\[error\] .*? hello from raw_log\(\)/ +--- skip_nginx: 3: <1.11.2 diff --git a/t/stream/errlog.t b/t/stream/errlog.t new file mode 100644 index 000000000..d1670fdae --- /dev/null +++ b/t/stream/errlog.t @@ -0,0 +1,1106 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +#worker_connections(1014); +#master_process_enabled(1); +log_level('error'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 2 + 10); + +add_block_preprocessor(sub { + my $block = shift; + + my $stream_config = $block->stream_config || ''; + + $stream_config .= <<_EOC_; + lua_package_path '$t::TestCore::Stream::lua_package_path'; + init_by_lua_block { + $t::TestCore::Stream::init_by_lua_block + $init_by_lua_block + } +_EOC_ + + $block->set_value("stream_config", $stream_config); +}); + +#no_diff(); +no_long_string(); +#check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- stream_config + lua_capture_error_log 4m; +--- stream_server_config + content_by_lua_block { + ngx.log(ngx.ERR, "enter 1") + ngx.log(ngx.ERR, "enter 11") + + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.say("log lines:", #res / 3) + } +--- stream_response +log lines:2 +--- grep_error_log eval +qr/enter \d+/ +--- grep_error_log_out eval +[ +"enter 1 +enter 11 +", +"enter 1 +enter 11 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 2: overflow captured error logs +--- stream_config + lua_capture_error_log 4k; +--- stream_server_config + content_by_lua_block { + ngx.log(ngx.ERR, "enter 1") + ngx.log(ngx.ERR, "enter 22" .. string.rep("a", 4096)) + + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.say("log lines:", #res / 3) + } +--- stream_response +log lines:1 +--- grep_error_log eval +qr/enter \d+/ +--- grep_error_log_out eval +[ +"enter 1 +enter 22 +", +"enter 1 +enter 22 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 3: client connected info +--- log_level: info +--- stream_config + lua_capture_error_log 4m; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.log(ngx.ERR, "capture log line:", #res / 3) + } +--- grep_error_log eval +qr/capture log line:\d+|client .*? connected to .*?/ +--- grep_error_log_out eval +[ +qr/^client .*? connected to .*? +capture log line:1 +$/, +qr/^client .*? connected to .*? +capture log line:2 +$/ +] +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 4: 500 error +--- stream_config + lua_capture_error_log 4m; +--- stream_server_config + content_by_lua_block { + local t = {}/4 + } + log_by_lua_block { + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.log(ngx.ERR, "capture log line:", #res / 3) + } +--- grep_error_log eval +qr/capture log line:\d+|attempt to perform arithmetic on a table value/ +--- grep_error_log_out eval +[ +qr/^attempt to perform arithmetic on a table value +capture log line:1 +$/, +qr/^attempt to perform arithmetic on a table value +capture log line:2 +$/ +] +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 5: no error log +--- stream_config + lua_capture_error_log 4m; +--- stream_server_config + content_by_lua_block { + ngx.say("hello") + } + log_by_lua_block { + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.log(ngx.ERR, "capture log line:", #res / 3) + } +--- stream_response +hello +--- grep_error_log eval +qr/capture log line:\d+/ +--- grep_error_log_out eval +[ +qr/^capture log line:0 +$/, +qr/^capture log line:1 +$/ +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 6: customize the log path +--- stream_config + lua_capture_error_log 4m; + error_log logs/error_stream.log error; +--- stream_server_config + error_log logs/error.log error; + content_by_lua_block { + ngx.log(ngx.ERR, "enter access /t") + ngx.say("hello") + } + log_by_lua_block { + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.log(ngx.ERR, "capture log line:", #res / 3) + + } +--- stream_response +hello +--- grep_error_log eval +qr/capture log line:\d+|enter access/ +--- grep_error_log_out eval +[ +qr/^enter access +capture log line:1 +$/, +qr/^enter access +capture log line:2 +$/ +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 7: invalid size (< 4k) +--- stream_config + lua_capture_error_log 3k; +--- stream_server_config + content_by_lua_block { + ngx.say("hello") + } +--- must_die +--- error_log +invalid capture error log size "3k", minimum size is 4096 +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 8: invalid size (no argu) +--- stream_config + lua_capture_error_log; +--- stream_server_config + content_by_lua_block { + ngx.say("hello") + } +--- must_die +--- error_log +invalid number of arguments in "lua_capture_error_log" directive +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 9: without directive + ngx.errlog +--- stream_server_config + content_by_lua_block { + ngx.log(ngx.ERR, "enter 1") + + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs() + if not res then + error("FAILED " .. err) + end + ngx.say("log lines:", #res / 3) + } +--- error_log +directive "lua_capture_error_log" is not set +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 10: without directive + ngx.set_filter_level +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.ERR) + if not status then + error(err) + end + } +--- error_log +directive "lua_capture_error_log" is not set +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 11: filter log by level(ngx.INFO) +--- stream_config + lua_capture_error_log 4m; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.INFO) + if not status then + error(err) + end + + ngx.log(ngx.NOTICE, "-->1") + ngx.log(ngx.WARN, "-->2") + ngx.log(ngx.ERR, "-->3") + + local res = errlog.get_logs() + ngx.say("log lines:", #res / 3) + } +--- log_level: notice +--- stream_response +log lines:3 +--- grep_error_log eval +qr/-->\d+/ +--- grep_error_log_out eval +[ +"-->1 +-->2 +-->3 +", +"-->1 +-->2 +-->3 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 12: filter log by level(ngx.WARN) +--- stream_config + lua_capture_error_log 4m; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + ngx.log(ngx.NOTICE, "-->1") + ngx.log(ngx.WARN, "-->2") + ngx.log(ngx.ERR, "-->3") + + local res = errlog.get_logs() + ngx.say("log lines:", #res / 3) + } +--- log_level: notice +--- stream_response +log lines:2 +--- grep_error_log eval +qr/-->\d+/ +--- grep_error_log_out eval +[ +"-->1 +-->2 +-->3 +", +"-->1 +-->2 +-->3 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 13: filter log by level(ngx.CRIT) +--- stream_config + lua_capture_error_log 4m; +--- log_level: notice +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.CRIT) + if not status then + error(err) + end + + ngx.log(ngx.NOTICE, "-->1") + ngx.log(ngx.WARN, "-->2") + ngx.log(ngx.ERR, "-->3") + + local res = errlog.get_logs() + ngx.say("log lines:", #res / 3) + } +--- stream_response +log lines:0 +--- grep_error_log eval +qr/-->\d+/ +--- grep_error_log_out eval +[ +"-->1 +-->2 +-->3 +", +"-->1 +-->2 +-->3 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 14: set max count and reuse table +--- stream_config + lua_capture_error_log 4m; +--- stream_server_config + content_by_lua_block { + tab_clear = require "table.clear" + ngx.log(ngx.ERR, "enter 1") + ngx.log(ngx.ERR, "enter 22") + ngx.log(ngx.ERR, "enter 333") + + local errlog = require "ngx.errlog" + local res = {} + local err + res, err = errlog.get_logs(2, res) + if not res then + error("FAILED " .. err) + end + ngx.say("log lines:", #res / 3) + + tab_clear(res) + res, err = errlog.get_logs(2, res) + if not res then + error("FAILED " .. err) + end + ngx.say("log lines:", #res / 3) + } +--- stream_response +log lines:2 +log lines:1 +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 15: wrong argument +--- stream_config + lua_capture_error_log 4m; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level() + if not status then + error(err) + end + } +--- grep_error_log eval +qr/missing \"level\" argument/ +--- grep_error_log_out eval +[ +"missing \"level\" argument +", +"missing \"level\" argument +", +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 16: check the captured error log body +--- stream_config + lua_capture_error_log 4m; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + ngx.log(ngx.NOTICE, "-->1") + ngx.log(ngx.WARN, "-->2") + ngx.log(ngx.ERR, "-->3") + + local res = errlog.get_logs() + for i = 1, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + } +--- log_level: notice +--- stream_response_like +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: -->2, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: -->3, client: 127.0.0.1, server: 0.0.0.0:\d+ +--- grep_error_log eval +qr/-->\d+/ +--- grep_error_log_out eval +[ +"-->1 +-->2 +-->3 +", +"-->1 +-->2 +-->3 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 17: flood the capturing buffer (4k) +--- stream_config + lua_capture_error_log 4k; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + for i = 1, 100 do + ngx.log(ngx.NOTICE, "--> ", i) + ngx.log(ngx.WARN, "--> ", i) + ngx.log(ngx.ERR, "--> ", i) + end + + local res = errlog.get_logs(1000) + ngx.say("log lines: #", #res / 3) + + -- first 3 logs + for i = 1, 3 * 3, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + + -- last 3 logs + for i = #res - 8, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + } +--- log_level: notice +--- stream_response_like chomp +\A(?:log lines: #26 +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> 88, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> 88, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> 89, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> 99, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> 100, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> 100, client: 127.0.0.1, server: 0.0.0.0:\d+ +)\z +--- skip_nginx: 2: <1.11.2 +--- wait: 0.1 + + + +=== TEST 18: flood the capturing buffer (5k) +--- stream_config + lua_capture_error_log 5k; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + for i = 1, 100 do + ngx.log(ngx.NOTICE, "--> ", i) + ngx.log(ngx.WARN, "--> ", i) + ngx.log(ngx.ERR, "--> ", i) + end + + local res = errlog.get_logs(1000) + ngx.say("log lines: #", #res / 3) + + -- first 3 logs + for i = 1, 3 * 3, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + + -- last 3 logs + for i = #res - 8, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + } +--- log_level: notice +--- stream_response_like chomp +\A(?:log lines: #33 +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> 84, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> 85, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> 85, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> 99, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> 100, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> 100, client: 127.0.0.1, server: 0.0.0.0:\d+ +)\z +--- skip_nginx: 2: <1.11.2 +--- wait: 0.1 + + + +=== TEST 19: fetch a few and generate a few, then fetch again (overflown again) +--- stream_config + lua_capture_error_log 5k; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + for i = 1, 100 do + ngx.log(ngx.NOTICE, "--> ", i) + ngx.log(ngx.WARN, "--> ", i) + ngx.log(ngx.ERR, "--> ", i) + end + + local res = errlog.get_logs(3) + ngx.say("msg count: ", #res / 3) + + -- first 3 logs + for i = 1, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + + ngx.log(ngx.ERR, "--> 101") + ngx.log(ngx.ERR, "--> 102") + ngx.log(ngx.ERR, "--> 103") + ngx.log(ngx.ERR, "--> 104") + + local res = errlog.get_logs(3) + ngx.say("msg count: ", #res / 3) + + -- first 3 logs + for i = 1, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + + local res = errlog.get_logs(1000) + -- last 3 logs + for i = #res - 8, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + } +--- log_level: notice +--- stream_response_like chomp +\Amsg count: 3 +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 84, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 85, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 85, client: 127.0.0.1, server: 0.0.0.0:\d+ +msg count: 3 +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 86, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 86, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 87, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 102, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 103, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 104, client: 127.0.0.1, server: 0.0.0.0:\d+ +|msg count: 3 +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 84, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 85, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 85, client: 127.0.0.1, server: 0.0.0.0:\d+ +msg count: 3 +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 87, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 87, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 88, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 102, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 103, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 104, client: 127.0.0.1, server: 0.0.0.0:\d+ +\z +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 20: fetch a few and generate a few, then fetch again (not overflown again) +--- stream_config + lua_capture_error_log 5k; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + for i = 1, 100 do + ngx.log(ngx.NOTICE, "--> ", i) + ngx.log(ngx.WARN, "--> ", i) + ngx.log(ngx.ERR, "--> ", i) + end + + local res = errlog.get_logs(3) + ngx.say("msg count: ", #res / 3) + + -- first 3 logs + for i = 1, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + + ngx.log(ngx.ERR, "howdy, something new!") + ngx.log(ngx.ERR, "howdy, something even newer!") + + local res = errlog.get_logs(3) + ngx.say("msg count: ", #res / 3) + + -- first 3 logs + for i = 1, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + + local res = errlog.get_logs(1000) + -- last 3 logs + for i = #res - 8, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + } +--- log_level: notice +--- stream_response_like chomp +\Amsg count: 3 +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 84, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 85, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 85, client: 127.0.0.1, server: 0.0.0.0:\d+ +msg count: 3 +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 86, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 86, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:5 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[warn\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 87, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: --> 100, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: howdy, something new!, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: howdy, something even newer!, client: 127.0.0.1, server: 0.0.0.0:\d+ +\z +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 21: multi-line error log +--- stream_config + lua_capture_error_log 4k; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + ngx.log(ngx.ERR, "-->\n", "new line") + + local res = errlog.get_logs() + ngx.say("log lines: #", #res / 3) + + for i = 1, #res, 3 do + ngx.say("log level:", res[i]) + ngx.say("log body:", res[i + 2]) + end + } +--- log_level: notice +--- stream_response_like chomp +\Alog lines: #1 +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> +new line, client: 127.0.0.1, server: 0.0.0.0:\d+ +\z +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 22: user-supplied Lua table to hold the result (get one log + no log) +--- stream_config + lua_capture_error_log 4k; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local status, err = errlog.set_filter_level(ngx.WARN) + if not status then + error(err) + end + + ngx.log(ngx.ERR, "-->\n", "new line") + + local t = {} + + for i = 1, 2 do + local res = errlog.get_logs(10, t) + ngx.say("maybe log lines: #", #res / 3) + for j = 1, #res, 3 do + local level, msg = res[j], res[j + 2] + if not level then + break + end + ngx.say("log level:", level) + ngx.say("log body:", msg) + end + ngx.say("end") + end + } +--- log_level: notice +--- stream_response_like chomp +\Amaybe log lines: #1 +log level:4 +log body:\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*content_by_lua\(nginx.conf:\d+\):\d+: --> +new line, client: 127.0.0.1, server: 0.0.0.0:\d+ +end +maybe log lines: #1 +end +\z +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 23: the system default filter level is "debug" +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + ngx.print('Is "debug" the system default filter level? ', + errlog.get_sys_filter_level() == ngx.DEBUG) + } +--- log_level: debug +--- stream_response chomp +Is "debug" the system default filter level? true + + + +=== TEST 24: the system default filter level is "emerg" +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + ngx.print('Is "emerg" the system default filter level? ', + errlog.get_sys_filter_level() == ngx.EMERG) + } +--- log_level: emerg +--- stream_response chomp +Is "emerg" the system default filter level? true + + + +=== TEST 25: get system default filter level during Nginx starts (init) +--- SKIP +--- init_by_lua_block + require "resty.core" + local errlog = require "ngx.errlog" + package.loaded.log_level = errlog.get_sys_filter_level() + +--- stream_server_config + content_by_lua_block { + local log_level = package.loaded.log_level + + if log_level >= ngx.WARN then + ngx.log(ngx.WARN, "log a warning event") + else + ngx.log(ngx.WARN, "do not log another warning event") + end + } +--- log_level: warn +--- error_log +log a warning event +--- no_error_log +do not log another warning event + + + +=== TEST 26: get system default filter level during Nginx worker starts (init worker) +--- SKIP +--- stream_config + init_worker_by_lua_block { + local errlog = require "ngx.errlog" + package.loaded.log_level = errlog.get_sys_filter_level() + } +--- stream_server_config + content_by_lua_block { + local log_level = package.loaded.log_level + + if log_level >= ngx.WARN then + ngx.log(ngx.WARN, "log a warning event") + else + ngx.log(ngx.WARN, "do not log another warning event") + end + } +--- log_level: warn +--- error_log +log a warning event +--- no_error_log +do not log another warning event + + + +=== TEST 27: sanity (with log time) +--- stream_config + lua_capture_error_log 4m; +--- stream_server_config + content_by_lua_block { + ngx.log(ngx.ERR, "enter 1") + ngx.log(ngx.ERR, "enter 11") + + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs(nil, nil, {fetch_time = true}) + if not res then + error("FAILED " .. err) + end + ngx.say("log lines:", #res / 3) + } +--- stream_response +log lines:2 +--- grep_error_log eval +qr/enter \d+/ +--- grep_error_log_out eval +[ +"enter 1 +enter 11 +", +"enter 1 +enter 11 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 28: log time eq ngx.now +--- stream_config + lua_capture_error_log 4m; +--- stream_server_config + content_by_lua_block { + local now = ngx.now() + ngx.log(ngx.CRIT, "enter 1") + ngx.log(ngx.ERR, "enter 11") + + local errlog = require "ngx.errlog" + local res, err = errlog.get_logs(nil, nil, {fetch_time = true}) + if not res then + error("FAILED " .. err) + end + ngx.say("log lines: ", #res / 3) + + for i = 1, #res, 3 do + ngx.say("log level: ", res[i]) + ngx.say("log time: ", res[i + 1]) + ngx.say("log body: ", res[i + 2]) + ngx.say("same with now: ", res[i + 1] == now) + end + } +--- stream_response_like chomp +\Alog lines: 2 +log level: 3 +log time: \d+(?:\.\d+)? +log body: \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[crit\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: enter 1, client: 127.0.0.1, server: 0.0.0.0:\d+ +same with now: true +log level: 4 +log time: \d{10}(?:\.\d+)? +log body: \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: enter 11, client: 127.0.0.1, server: 0.0.0.0:\d+ +same with now: true +--- grep_error_log eval +qr/enter \d+/ +--- grep_error_log_out eval +[ +"enter 1 +enter 11 +", +"enter 1 +enter 11 +" +] +--- skip_nginx: 3: <1.11.2 + + + +=== TEST 29: ringbuf overflow bug +--- stream_config + lua_capture_error_log 4k; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local msg = string.rep("*", 10) + + for i = 1, 2 do + ngx.log(ngx.ERR, msg .. i) + end + + for i = 1, 40 do + local res = errlog.get_logs(1) + if res and #res then + ngx.log(ngx.ERR, msg .. i) + end + end + + local res = errlog.get_logs() + for i = 1, #res, 3 do + ngx.say("log level: ", res[i]) + ngx.say("log time: ", res[i + 1]) + ngx.say("log body: ", res[i + 2]) + end + } +--- log_level: notice +--- stream_response_like chomp +log level: 4 +log time: \d+(?:\.\d+)? +log body: \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: \*\*\*\*\*\*\*\*\*\*39, client: 127.0.0.1, server: 0.0.0.0:\d+ +log level: 4 +log time: \d{10}(?:\.\d+)? +log body: \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[error\] (\d+).*?content_by_lua\(nginx.conf:\d+\):\d+: \*\*\*\*\*\*\*\*\*\*40, client: 127.0.0.1, server: 0.0.0.0:\d+ +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 30: ringbuf sentinel bug1 +--- stream_config + lua_capture_error_log 4k; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local msg = string.rep("a", 20) + local bigmsg = string.rep("A", 3000) + + for i = 1, 10 do + ngx.log(ngx.ERR, msg) + end + ngx.log(ngx.ERR, bigmsg) + ngx.log(ngx.ERR, msg) + + local res = errlog.get_logs(2) + ngx.say("log lines: #", #res / 3) + + for i = 1, #res, 3 do + ngx.say(string.gsub(res[i + 2], "^.*([Aa][Aa][Aa]).*$", "%1"), "") + end + } +--- log_level: notice +--- stream_response +log lines: #2 +AAA +aaa +--- skip_nginx: 2: <1.11.2 + + + +=== TEST 31: ringbuf sentinel bug2 +--- stream_config + lua_capture_error_log 4k; +--- stream_server_config + content_by_lua_block { + local errlog = require "ngx.errlog" + local msg = string.rep("a", 20) + + for i = 1, 20 do + ngx.log(ngx.ERR, msg) + end + + local res = errlog.get_logs(18) + ngx.say("log lines: #", #res / 3) + ngx.flush(true) + + for i = 1, 18 do + ngx.log(ngx.ERR, msg) + end + + local bigmsg = string.rep("A", 2000) + ngx.log(ngx.ERR, bigmsg) + + local res = errlog.get_logs() + ngx.say("log lines: #", #res / 3) + ngx.flush(true) + } +--- log_level: notice +--- stream_response_like chomp +\A(?:log lines: #18 +log lines: #1 +|log lines: #18 +log lines: #2 +)\z +--- skip_nginx: 2: <1.11.2 diff --git a/t/stream/misc.t b/t/stream/misc.t new file mode 100644 index 000000000..688b7ed6c --- /dev/null +++ b/t/stream/misc.t @@ -0,0 +1,52 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +#worker_connections(1014); +#master_on(); +#workers(2); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +#worker_connections(1024); +#no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: base.check_subsystem +--- stream_server_config + content_by_lua_block { + local base = require "resty.core.base" + base.allows_subsystem('http', 'stream') + base.allows_subsystem('stream') + + ngx.say("ok") + } +--- stream_response +ok +--- no_error_log +[error] + -- NYI: + bad argument + + + +=== TEST 2: base.check_subsystem with non-stream subsystem +--- stream_server_config + content_by_lua_block { + local base = require "resty.core.base" + base.allows_subsystem('http') + + ngx.say("ok") + } +--- stream_response +--- no_error_log + -- NYI: + bad argument +--- error_log +unsupported subsystem: stream diff --git a/t/stream/os-getenv-hup.t b/t/stream/os-getenv-hup.t new file mode 100644 index 000000000..6a6effa9e --- /dev/null +++ b/t/stream/os-getenv-hup.t @@ -0,0 +1,141 @@ +# 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 { + undef $ENV{TEST_NGINX_USE_STAP}; + } +} + +use lib '.'; +use t::TestCore::Stream $SkipReason ? (skip_all => $SkipReason) : (); + +plan tests => repeat_each() * (blocks() * 2 + 1); + +no_shuffle(); +use_hup(); + +$ENV{TEST_NGINX_BAR} = 'old'; +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::Stream::lua_package_path"; + +run_tests(); + +__DATA__ + +=== TEST 1: env directive explicit value is visible within init_by_lua* +--- main_config +env FOO=old; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- stream_server_config + content_by_lua_block { + ngx.say(package.loaded.foo) + } +--- stream_response +old +--- error_log +[notice] + + + +=== TEST 2: HUP reload changes env value (1/3) +--- main_config +env FOO=new; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- stream_server_config + content_by_lua_block { + ngx.say(package.loaded.foo) + } +--- stream_response +new + + + +=== TEST 3: HUP reload changes env value (2/3) +--- main_config +env FOO=; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- stream_server_config + content_by_lua_block { + ngx.say(package.loaded.foo) + } +--- stream_response_like chomp +\s + + + +=== TEST 4: HUP reload changes env value (3/3) +--- main_config +env FOO; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- stream_server_config + content_by_lua_block { + ngx.say(package.loaded.foo) + } +--- stream_response +nil + + + +=== TEST 5: HUP reload changes visible environment variable (1/2) +--- main_config +env TEST_NGINX_BAR; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.test_nginx_bar = os.getenv("TEST_NGINX_BAR") + } +--- stream_server_config + content_by_lua_block { + ngx.say(package.loaded.test_nginx_bar) + } +--- stream_response +old + + + +=== TEST 6: HUP reload changes visible environment variable (2/2) +--- main_config +env TEST_NGINX_BAR=new; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.test_nginx_bar = os.getenv("TEST_NGINX_BAR") + } +--- stream_server_config + content_by_lua_block { + ngx.say(package.loaded.test_nginx_bar) + } +--- stream_response +new diff --git a/t/stream/os-getenv.t b/t/stream/os-getenv.t new file mode 100644 index 000000000..d41e52241 --- /dev/null +++ b/t/stream/os-getenv.t @@ -0,0 +1,222 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +plan tests => repeat_each() * (blocks() * 2 + 1); + +$ENV{TEST_NGINX_BAR} = 'world'; +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::Stream::lua_package_path"; + +run_tests(); + +__DATA__ + +=== TEST 1: env directive explicit value is visible within init_by_lua* +--- main_config +env FOO=hello; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- stream_server_config + content_by_lua_block { + ngx.say(package.loaded.foo, "\n", os.getenv("FOO")) + } +--- stream_response +hello +hello + + + +=== TEST 2: env directive explicit value is visible within init_by_lua* with lua_shared_dict +--- main_config +env FOO=hello; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + lua_shared_dict dogs 24k; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- stream_server_config + content_by_lua_block { + ngx.say(package.loaded.foo, "\n", os.getenv("FOO")) + } +--- stream_response +hello +hello + + + +=== TEST 3: env directive explicit value is case-sensitive within init_by_lua* +--- main_config +env FOO=hello; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("foo") + } +--- stream_server_config + content_by_lua_block { + ngx.say(package.loaded.foo, "\n", os.getenv("foo")) + } +--- stream_response +nil +nil + + + +=== TEST 4: env directives with no value are ignored +--- main_config +env FOO; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- stream_server_config + content_by_lua_block { + ngx.say(package.loaded.foo, "\n", os.getenv("FOO")) + } +--- stream_response +nil +nil + + + +=== TEST 5: env is visible from environment +--- main_config +env TEST_NGINX_BAR; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("TEST_NGINX_BAR") + } +--- stream_server_config + content_by_lua_block { + ngx.say(package.loaded.foo, "\n", os.getenv("TEST_NGINX_BAR")) + } +--- stream_response +world +world + + + +=== TEST 6: env explicit set vs environment set +--- main_config +env TEST_NGINX_BAR=goodbye; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("TEST_NGINX_BAR") + } +--- stream_server_config + content_by_lua_block { + ngx.say(package.loaded.foo, "\n", os.getenv("TEST_NGINX_BAR")) + } +--- stream_response +goodbye +goodbye + + + +=== TEST 7: env directive with empty value +--- main_config +env FOO=; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + require "resty.core" + package.loaded.foo = os.getenv("FOO") + } +--- stream_server_config + content_by_lua_block { + ngx.say("in init: ", package.loaded.foo, "\n", + "in content: ", os.getenv("FOO")) + } +--- stream_response_like +in init:\s+ +in content:\s+ + + + +=== TEST 8: os.getenv() overwrite is reverted in worker phases +--- main_config +env FOO=hello; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + package.loaded.init_os_getenv = os.getenv + } +--- stream_server_config + content_by_lua_block { + ngx.say("FOO=", os.getenv("FOO")) + + if os.getenv ~= package.loaded.init_os_getenv then + ngx.say("os.getenv() overwrite was reverted") + + else + ngx.say("os.getenv() overwrite was not reverted") + end + } +--- stream_response +FOO=hello +os.getenv() overwrite was reverted + + + +=== TEST 9: os.getenv() can be localized after loading resty.core +--- main_config +env FOO=hello; +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + init_by_lua_block { + do + local getenv = os.getenv + + package.loaded.f = function () + ngx.log(ngx.NOTICE, "FOO: ", getenv("FOO")) + end + end + + require "resty.core" + + package.loaded.f() + + package.loaded.is_os_getenv = os.getenv == package.loaded.os_getenv + } +--- stream_server_config + content_by_lua_block { + package.loaded.f() + package.loaded.f() + + if os.getenv ~= package.loaded.init_os_getenv then + ngx.say("os.getenv() overwrite was reverted") + + else + ngx.say("os.getenv() overwrite was not reverted") + end + } +--- stream_response +os.getenv() overwrite was reverted +--- grep_error_log eval +qr/FOO: [a-z]+/ +--- grep_error_log_out +FOO: hello +FOO: hello +FOO: hello diff --git a/t/stream/process-type-hup.t b/t/stream/process-type-hup.t new file mode 100644 index 000000000..2d3c99ec4 --- /dev/null +++ b/t/stream/process-type-hup.t @@ -0,0 +1,73 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use lib '.'; +use t::TestCore::Stream; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4); + +add_block_preprocessor(sub { + my $block = shift; + + my $stream_config = $block->stream_config || ''; + + $stream_config .= <<_EOC_; + lua_package_path '$t::TestCore::Stream::lua_package_path'; + init_by_lua_block { + $t::TestCore::Stream::init_by_lua_block + $init_by_lua_block + + local process = require "ngx.process" + local ok, err = process.enable_privileged_agent() + if not ok then + ngx.log(ngx.ERR, "enable_privileged_agent failed: ", err) + end + } + + init_worker_by_lua_block { + local base = require "resty.core.base" + local typ = require "ngx.process".type + + if typ() == "privileged agent" then + ngx.log(ngx.WARN, "process type: ", typ()) + end + } +_EOC_ + + $block->set_value("stream_config", $stream_config); +}); + +master_on(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- stream_server_config + content_by_lua_block { + local typ = require "ngx.process".type + + local f, err = io.open(ngx.config.prefix() .. "/logs/nginx.pid", "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() + + ngx.say("type: ", typ()) + os.execute("kill -HUP " .. pid) + } +--- stream_response +type: worker +--- error_log +init_worker_by_lua:6: process type: privileged +--- no_error_log +[error] +--- skip_nginx: 4: < 1.11.2 +--- wait: 0.1 diff --git a/t/stream/process-type-master.t b/t/stream/process-type-master.t new file mode 100644 index 000000000..a47e7520b --- /dev/null +++ b/t/stream/process-type-master.t @@ -0,0 +1,61 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use lib '.'; +use t::TestCore::Stream; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +add_block_preprocessor(sub { + my $block = shift; + + my $stream_config = $block->stream_config || ''; + + $stream_config .= <<_EOC_; + lua_package_path '$t::TestCore::Stream::lua_package_path'; + init_by_lua_block { + $t::TestCore::Stream::init_by_lua_block + $init_by_lua_block + + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + package.loaded.process_type = v + } +_EOC_ + + $block->set_value("stream_config", $stream_config); +}); + +master_on(); +#no_diff(); +# no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- stream_server_config + content_by_lua_block { + ngx.say("process type: ", package.loaded.process_type) + } +--- stream_response +process type: master +--- grep_error_log eval +qr/\[TRACE\s+\d+ init_by_lua:\d+ loop\]/ +--- grep_error_log_out eval +[ +qr/\A\[TRACE\s+\d+ init_by_lua:\d+ loop\] +\z/, +qr/\A\[TRACE\s+\d+ init_by_lua:\d+ loop\] +\z/ +] +--- no_error_log +[error] + -- NYI: +--- skip_nginx: 5: < 1.11.2 diff --git a/t/stream/process-type-privileged-agent.t b/t/stream/process-type-privileged-agent.t new file mode 100644 index 000000000..02835c955 --- /dev/null +++ b/t/stream/process-type-privileged-agent.t @@ -0,0 +1,101 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use lib '.'; +use t::TestCore::Stream; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4) - 2; + +add_block_preprocessor(sub { + my $block = shift; + + my $stream_config = $block->stream_config || ''; + + $stream_config .= <<_EOC_; + lua_package_path '$t::TestCore::Stream::lua_package_path'; + init_by_lua_block { + $t::TestCore::Stream::init_by_lua_block + $init_by_lua_block + + local process = require "ngx.process" + local ok, err = process.enable_privileged_agent() + if not ok then + ngx.log(ngx.ERR, "enable_privileged_agent failed: ", err) + end + } + + init_worker_by_lua_block { + local base = require "resty.core.base" + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + if v == "privileged agent" then + ngx.log(ngx.WARN, "process type: ", v) + end + } +_EOC_ + + $block->set_value("stream_config", $stream_config); +}); + +#no_diff(); +# no_long_string(); +master_process_enabled(1); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- stream_server_config + content_by_lua_block { + ngx.sleep(0.1) + local v + local typ = require "ngx.process".type + for i = 1, 200 do + v = typ() + end + + ngx.say("type: ", v) + } +--- stream_response +type: worker +--- grep_error_log eval +qr/\[TRACE\s+\d+ init_worker_by_lua:\d+ loop\]|\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):\d+ loop\]|init_worker_by_lua:\d+: process type: \w+/ +--- grep_error_log_out eval +[ +qr/\[TRACE\s+\d+ init_worker_by_lua:\d+ loop\] +(?:\[TRACE\s+\d+ init_worker_by_lua:\d+ loop\] +)?\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):\d+ loop\] +init_worker_by_lua:10: process type: privileged +/, +qr/\[TRACE\s+\d+ init_worker_by_lua:\d+ loop\] +(?:\[TRACE\s+\d+ init_worker_by_lua:\d+ loop\] +)?\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):\d+ loop\] +init_worker_by_lua:10: process type: privileged +/ +] +--- no_error_log +[error] + -- NYI: +--- skip_nginx: 5: < 1.11.2 +--- wait: 0.2 + + + +=== TEST 2: `enable_privileged_agent` disabled +--- stream_server_config + content_by_lua_block { + local process = require "ngx.process" + local ok, err = process.enable_privileged_agent() + if not ok then + error(err) + end + } +--- error_log eval +qr/\[error\] .*? API disabled in the current context/ +--- skip_nginx: 3: < 1.11.2 diff --git a/t/stream/process-type-single.t b/t/stream/process-type-single.t new file mode 100644 index 000000000..a65326a7f --- /dev/null +++ b/t/stream/process-type-single.t @@ -0,0 +1,86 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use lib '.'; +use t::TestCore::Stream; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +add_block_preprocessor(sub { + my $block = shift; + + my $stream_config = $block->stream_config || ''; + + $stream_config .= <<_EOC_; + lua_package_path '$t::TestCore::Stream::lua_package_path'; + init_by_lua_block { + $t::TestCore::Stream::init_by_lua_block + $init_by_lua_block + + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + package.loaded.process_type = v + } + + init_worker_by_lua_block { + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + ngx.log(ngx.WARN, "process type in init_by_lua*: ", + package.loaded.process_type) + ngx.log(ngx.WARN, "process type: ", v) + } +_EOC_ + + $block->set_value("stream_config", $stream_config); +}); + +#no_diff(); +# no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- stream_server_config + content_by_lua_block { + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + ngx.say("process type: ", v) + } +--- stream_response +process type: single +--- grep_error_log eval +qr/\[TRACE\s+\d+ init_by_lua:\d+ loop\]|\[TRACE\s+\d+ init_worker_by_lua:\d loop\]|\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):\d loop\]|process type in init_by_lua\*: \w+|init_worker_by_lua:\d+: process type: \w+/ +--- grep_error_log_out eval +[ +qr/\[TRACE\s+\d+ init_by_lua:\d+ loop\] +\[TRACE\s+\d+ init_worker_by_lua:\d+ loop\] +\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):4 loop\] +process type in init_by_lua\*: single +init_worker_by_lua:10: process type: single +/, +qr/\[TRACE\s+\d+ init_by_lua:\d+ loop\] +\[TRACE\s+\d+ init_worker_by_lua:\d+ loop\] +\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):4 loop\] +process type in init_by_lua\*: single +init_worker_by_lua:10: process type: single +/ +] +--- no_error_log +[error] + -- NYI: +--- skip_nginx: 5: < 1.11.2 diff --git a/t/stream/process-type-worker.t b/t/stream/process-type-worker.t new file mode 100644 index 000000000..8b68262c9 --- /dev/null +++ b/t/stream/process-type-worker.t @@ -0,0 +1,73 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use lib '.'; +use t::TestCore::Stream; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +add_block_preprocessor(sub { + my $block = shift; + + my $stream_config = $block->stream_config || ''; + + $stream_config .= <<_EOC_; + lua_package_path '$t::TestCore::Stream::lua_package_path'; + init_by_lua_block { + $t::TestCore::Stream::init_by_lua_block + $init_by_lua_block + } + + init_worker_by_lua_block { + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + ngx.log(ngx.WARN, "process type: ", v) + } +_EOC_ + + $block->set_value("stream_config", $stream_config); +}); + +master_on(); +#no_diff(); +# no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- stream_server_config + content_by_lua_block { + local v + local typ = (require "ngx.process").type + for i = 1, 400 do + v = typ() + end + + ngx.say("process type: ", v) + } +--- stream_response +process type: worker +--- grep_error_log eval +qr/\[TRACE\s+\d+ init_worker_by_lua:\d loop\]|\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):\d loop\]|init_worker_by_lua:\d: process type: \w+/ +--- grep_error_log_out eval +[ +qr/\[TRACE\s+\d+ init_worker_by_lua:4 loop\] +\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):4 loop\] +init_worker_by_lua:8: process type: worker +/, +qr/\[TRACE\s+\d+ init_worker_by_lua:4 loop\] +\[TRACE\s+\d+ content_by_lua\(nginx.conf:\d+\):4 loop\] +init_worker_by_lua:8: process type: worker +/ +] +--- no_error_log +[error] + -- NYI: +--- skip_nginx: 5: < 1.11.2 diff --git a/t/stream/re-base.t b/t/stream/re-base.t new file mode 100644 index 000000000..e82f98349 --- /dev/null +++ b/t/stream/re-base.t @@ -0,0 +1,188 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: bad pattern +--- stream_server_config + content_by_lua_block { + local it, err = ngx.re.gmatch("hello\\nworld", "(abc") + if not err then + ngx.say("good") + else + ngx.say("error: ", err) + end + } +--- stream_response +error: pcre_compile() failed: missing ) in "(abc" +--- no_error_log +[error] + + + +=== TEST 2: bad UTF-8 +--- stream_server_config + content_by_lua_block { + local target = "你好" + local regex = "你好" + + -- Note the D here + local it, err = ngx.re.gmatch(string.sub(target, 1, 4), regex, "u") + + if err then + ngx.say("error: ", err) + return + end + + local m, err = it() + if err then + ngx.say("error: ", err) + return + end + + if m then + ngx.say("matched: ", m[0]) + else + ngx.say("not matched") + end + } +--- stream_response_like chop +error: pcre_exec\(\) failed: -10 + +--- no_error_log +[error] + + + +=== TEST 3: UTF-8 mode without UTF-8 sequence checks +--- stream_server_config + content_by_lua_block { + local it = ngx.re.gmatch("你好", ".", "U") + local m = it() + if m then + ngx.say(m[0]) + else + ngx.say("not matched!") + end + } +--- stap +probe process("$LIBPCRE_PATH").function("pcre_compile") { + printf("compile opts: %x\n", $options) +} + +probe process("$LIBPCRE_PATH").function("pcre_exec") { + printf("exec opts: %x\n", $options) +} + +--- stap_out +compile opts: 800 +exec opts: 2000 + +--- stream_response +你 +--- no_error_log +[error] + + + +=== TEST 4: UTF-8 mode with UTF-8 sequence checks +--- stream_server_config + content_by_lua_block { + local it = ngx.re.gmatch("你好", ".", "u") + local m = it() + if m then + ngx.say(m[0]) + else + ngx.say("not matched!") + end + } +--- stap +probe process("$LIBPCRE_PATH").function("pcre_compile") { + printf("compile opts: %x\n", $options) +} + +probe process("$LIBPCRE_PATH").function("pcre_exec") { + printf("exec opts: %x\n", $options) +} + +--- stap_out +compile opts: 800 +exec opts: 0 + +--- stream_response +你 +--- no_error_log +[error] + + + +=== TEST 5: just hit match limit +--- stream_config eval: "lua_regex_match_limit 5000;" . $t::TestCore::Stream::StreamConfig +--- stream_server_config + content_by_lua_file html/a.lua; + +--- user_files +>>> 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]+))]==] + +local s = string.rep([[ABCDEFG]], 10) + +local it, err = ngx.re.gmatch(s, re, "o") +if not it then + ngx.say("failed to gen iterator: ", err) + return +end + +local res, err = it() +if not res then + if err then + ngx.say("error: ", err) + return + end + ngx.say("failed to match") + return +end + +--- stream_response +error: pcre_exec() failed: -8 + + + +=== TEST 6: just not hit match limit +--- stream_config eval: "lua_regex_match_limit 5100;" . $t::TestCore::Stream::StreamConfig +--- stream_server_config + content_by_lua_file html/a.lua; + +--- user_files +>>> 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]+))]==] + +local s = string.rep([[ABCDEFG]], 10) + +local it, err = ngx.re.gmatch(s, re, "o") +if not it then + ngx.say("failed to gen iterator: ", err) + return +end + +local res, err = it() +if not res then + if err then + ngx.say("error: ", err) + return + end + ngx.say("failed to match") + return +end + +--- stream_response +failed to match diff --git a/t/stream/re-find.t b/t/stream/re-find.t new file mode 100644 index 000000000..40090b7c4 --- /dev/null +++ b/t/stream/re-find.t @@ -0,0 +1,204 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: matched, no submatch, no jit compile, no regex cache +--- stream_server_config + content_by_lua_block { + local from, to, err + local find = ngx.re.find + local s = "a" + for i = 1, $TEST_NGINX_HOTLOOP * 20 do + from, to, err = find(s, "a") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not from then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("from: ", from) + ngx.say("to: ", to) + ngx.say("matched: ", string.sub(s, from, to)) + } +--- stream_response +from: 1 +to: 1 +matched: a +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):5 loop\]/ +--- no_error_log +[error] +bad argument type + + + +=== TEST 2: matched, no submatch, jit compile, regex cache +--- stream_server_config + content_by_lua_block { + local from, to, err + local find = ngx.re.find + local s = "a" + for i = 1, 200 do + from, to, err = find(s, "a", "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not from then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("from: ", from) + ngx.say("to: ", to) + ngx.say("matched: ", string.sub(s, from, to)) + } +--- stream_response +from: 1 +to: 1 +matched: a +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):5 loop\]/ +--- no_error_log +[error] +NYI + + + +=== TEST 3: not matched, no submatch, jit compile, regex cache +--- stream_server_config + content_by_lua_block { + local from, to, err + local find = ngx.re.find + local s = "b" + for i = 1, 200 do + from, to, err = find(s, "a", "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not from then + ngx.say("no match") + return + end + ngx.say("from: ", from) + ngx.say("to: ", to) + ngx.say("matched: ", string.sub(s, from, to)) + } +--- stream_response +no match +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):5 loop\]/ +--- no_error_log +[error] +NYI + + + +=== TEST 4: nil submatch (2nd) +--- stream_server_config + content_by_lua_block { + local s = "hello, 1234" + local from, to, err + for i = 1, $TEST_NGINX_HOTLOOP * 20 do + from, to, err = ngx.re.find(s, "([0-9])|(hello world)", "jo", nil, 2) + end + if from or to 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 + } +--- stream_response +not matched! +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ + + + +=== TEST 5: nil submatch (1st) +--- stream_server_config + content_by_lua_block { + local s = "hello, 1234" + local from, to, err + for i = 1, 400 do + from, to, err = ngx.re.find(s, "(hello world)|([0-9])", "jo", nil, 1) + end + if from or to 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 + } +--- stream_response +not matched! +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ + + + +=== TEST 6: specify the group (2) +--- stream_server_config + content_by_lua_block { + local s = "hello, 1234" + local from, to, err + for i = 1, $TEST_NGINX_HOTLOOP * 20 do + from, to, err = ngx.re.find(s, "([0-9])([0-9]+)", "jo", nil, 2) + end + 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) + end + ngx.say("not matched!") + end + } +--- stream_response +from: 9 +to: 11 +matched: 234 +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ diff --git a/t/stream/re-gmatch.t b/t/stream/re-gmatch.t new file mode 100644 index 000000000..b9d8f3b53 --- /dev/null +++ b/t/stream/re-gmatch.t @@ -0,0 +1,449 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 9); + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: matched, no submatch, no jit compile, no regex cache +--- stream_server_config + content_by_lua_block { + local m1, m2 + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello, world", [[\w+]]) + m1 = iter() + m2 = iter() + end + ngx.say("matched: ", m1[0]) + ngx.say("matched: ", m2[0]) + } +--- stream_response +matched: hello +matched: world +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + + + +=== TEST 2: matched, no submatch, jit compile, regex cache +--- stream_server_config + content_by_lua_block { + local m1, m2 + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello, world", [[\w+]], "jo") + m1 = iter() + m2 = iter() + end + ngx.say("matched: ", m1[0]) + ngx.say("matched: ", m2[0]) + } +--- stream_response +matched: hello +matched: world +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + + + +=== TEST 3: not matched, no submatch, jit compile, regex cache +--- stream_server_config + content_by_lua_block { + local m, err + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello, world", "[abc]+", "jo") + m, err = iter() + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + end + if not m then + ngx.say("no match") + return + end + } +--- stream_response +no match +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + + + +=== TEST 4: not matched, no submatch, no jit compile, no regex cache +--- stream_server_config + content_by_lua_block { + local m, err + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello, world", "[abc]+") + m, err = iter() + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + end + if not m then + ngx.say("no match") + return + end + } +--- stream_response +no match +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + + + +=== TEST 5: submatches, matched, no regex cache +--- stream_server_config + content_by_lua_block { + local m1, m2 + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello, world", [[(\w)(\w+)]]) + m1 = iter() + m2 = iter() + end + ngx.say("matched: ", m1[0]) + ngx.say("$1: ", m1[1]) + ngx.say("$2: ", m1[2]) + ngx.say("$3: ", m1[3]) + ngx.say("matched: ", m2[0]) + ngx.say("$1: ", m2[1]) + ngx.say("$2: ", m2[2]) + ngx.say("$3: ", m2[3]) + } +--- stream_response +matched: hello +$1: h +$2: ello +$3: nil +matched: world +$1: w +$2: orld +$3: nil +--- no_error_log +[error] + + + +=== TEST 6: submatches, matched, with regex cache +--- stream_server_config + content_by_lua_block { + local m1, m2 + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello, world", [[(\w)(\w+)]], "jo") + m1 = iter() + m2 = iter() + end + ngx.say("matched: ", m1[0]) + ngx.say("$1: ", m1[1]) + ngx.say("$2: ", m1[2]) + ngx.say("$3: ", m1[3]) + ngx.say("matched: ", m2[0]) + ngx.say("$1: ", m2[1]) + ngx.say("$2: ", m2[2]) + ngx.say("$3: ", m2[3]) + } +--- stream_response +matched: hello +$1: h +$2: ello +$3: nil +matched: world +$1: w +$2: orld +$3: nil +--- error_log eval +qr/\[TRACE\s+\d+\s+/ +--- no_error_log +[error] + + + +=== TEST 7: named submatches +--- stream_server_config + content_by_lua_block { + local m1, m2 + local gmatch = ngx.re.gmatch + for _ = 1, 200 do + local iter = gmatch("hello,world", [[(?\w)(\w+)]], "jo") + m1 = iter() + m2 = iter() + end + ngx.say("matched: ", m1[0]) + ngx.say("$1: ", m1[1]) + ngx.say("$2: ", m1[2]) + ngx.say("$first: ", m1.first) + ngx.say("$second: ", m1.second) + ngx.say("matched: ", m2[0]) + ngx.say("$1: ", m2[1]) + ngx.say("$2: ", m2[2]) + ngx.say("$first: ", m2.first) + ngx.say("$second: ", m2.second) + } +--- stream_response +matched: hello +$1: h +$2: ello +$first: h +$second: nil +matched: world +$1: w +$2: orld +$first: w +$second: nil +--- error_log eval +qr/\[TRACE\s+\d+\s+/ +--- no_error_log +[error] + + + +=== TEST 8: unmatched captures are false +--- stream_server_config + content_by_lua_block { + local iter = ngx.re.gmatch( + "hello! world!", [[(\w+)(, .+)?(!)]], "jo") + if iter then + while true do + local m = iter() + if not m then + return + end + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + end + end + } +--- stream_response +hello! +hello +false +! +world! +world +false +! +--- error_log eval +qr/\[TRACE\s+\d+\s+/ +--- no_error_log +[error] + + + +=== TEST 9: unmatched trailing captures are false +--- stream_server_config + content_by_lua_block { + local iter = ngx.re.gmatch("hello", [[(\w+)(, .+)?(!)?]], "jo") + if iter then + while true do + local m = iter() + if not m then + return + end + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + end + end + } +--- stream_response +hello +hello +false +false +--- error_log eval +qr/\[TRACE\s+\d+\s+/ +--- no_error_log +[error] + + + +=== TEST 10: unmatched named captures are false +--- stream_server_config + content_by_lua_block { + local iter = ngx.re.gmatch( + "hello! world!", + [[(?\w+)(?, .+)?(?!)]], "jo") + if iter then + while true do + local m = iter() + if not m then + return + end + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + ngx.say(m.first) + ngx.say(m.second) + ngx.say(m.third) + end + end + } +--- stream_response +hello! +hello +false +! +hello +false +! +world! +world +false +! +world +false +! +--- error_log eval +qr/\[TRACE\s+\d+\s+/ +--- no_error_log +[error] + + + +=== TEST 11: subject is not a string type +--- stream_server_config + content_by_lua_block { + local iter = ngx.re.gmatch(120345, "[1-9]+", "jo") + local m1 = iter() + local m2 = iter() + ngx.say(m1[0]) + ngx.say(m2[0]) + } +--- stream_response +12 +345 +--- no_error_log +[error] +attempt to get length of local 'subj' (a number value) + + + +=== TEST 12: an exhausted gmatch iterator should return nil +--- stream_server_config + content_by_lua_block { + local iter = ngx.re.gmatch("hello", [[\w+]]) + local m = iter() + ngx.say("matched: ", m[0]) + ngx.say("matched: ", iter()) + ngx.say("matched: ", iter()) + } +--- stream_response +matched: hello +matched: nil +matched: nil +--- no_error_log +[error] + + + +=== TEST 13: an error-ed out gmatch iterator should return nil +--- stream_server_config + content_by_lua_block { + local target = "你好" + local regex = "你好" + + -- trigger a BADUTF8 error + local iter = ngx.re.gmatch(string.sub(target, 1, 4), regex, "u") + local m, err = iter() + + if err then + ngx.say("error: ", err) + local m = iter() + if m then + ngx.say("matched: ", m[0]) + else + ngx.say("not matched") + end + return + end + + if m then + ngx.say("matched: ", m[0]) + else + ngx.say("not matched") + end + } +--- stream_response +error: pcre_exec() failed: -10 +not matched +--- no_error_log +[error] + + + +=== TEST 14: each gmatch iterator is separate +--- stream_server_config + content_by_lua_block { + local gmatch = ngx.re.gmatch + local iter1 = gmatch("98", [[\d]]) + local iter2 = gmatch("12", [[\d]]) + + local m1 = iter1() + local m2 = iter2() + ngx.say("matched iter1 (1/2): ", m1[0]) + ngx.say("matched iter2 (1/2): ", m2[0]) + + m1 = iter1() + m2 = iter2() + ngx.say("matched iter1 (2/2): ", m1[0]) + ngx.say("matched iter2 (2/2): ", m2[0]) + } +--- stream_response +matched iter1 (1/2): 9 +matched iter2 (1/2): 1 +matched iter1 (2/2): 8 +matched iter2 (2/2): 2 +--- no_error_log +[error] + + + +=== TEST 15: gmatch (empty matched string) +--- stream_server_config + content_by_lua_block { + for m in ngx.re.gmatch("hello", "a|") do + if m then + ngx.say("matched: [", m[0], "]") + else + ngx.say("not matched: ", m) + end + end + } +--- stream_response +matched: [] +matched: [] +matched: [] +matched: [] +matched: [] +matched: [] diff --git a/t/stream/re-match.t b/t/stream/re-match.t new file mode 100644 index 000000000..5a25b62e9 --- /dev/null +++ b/t/stream/re-match.t @@ -0,0 +1,486 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5 + 1); + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: matched, no submatch, no jit compile, no regex cache +--- stream_server_config + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 400 do + m, err = match("a", "a") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + -- ngx.say("$2: ", m[2]) + -- ngx.say("$3: ", m[3]) + -- collectgarbage() + } +--- stream_response +matched: a +$1: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type + + + +=== TEST 2: matched, no submatch, jit compile, regex cache +--- stream_server_config + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 400 do + m, err = match("a", "a", "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + -- collectgarbage() + } +--- stream_response +matched: a +$1: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +NYI + + + +=== TEST 3: not matched, no submatch, jit compile, regex cache +--- stream_server_config + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 200 do + m, err = match("b", "a", "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.say("no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + -- collectgarbage() + } +--- stream_response +no match +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + + + +=== TEST 4: not matched, no submatch, no jit compile, no regex cache +--- stream_server_config + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("b", "a") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.say("no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + -- collectgarbage() + } +--- stream_response +no match +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type + + + +=== TEST 5: submatches, matched, no regex cache +--- stream_server_config + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("hello, 1234", [[(\d)(\d+)]]) + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + ngx.say("$2: ", m[2]) + ngx.say("$3: ", m[3]) + -- collectgarbage() + } +--- stream_response +matched: 1234 +$1: 1 +$2: 234 +$3: nil +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 6: submatches, matched, with regex cache +--- stream_server_config + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("hello, 1234", [[(\d)(\d+)]], "jo") + end + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + if not m then + ngx.log(ngx.ERR, "no match") + return + end + ngx.say("matched: ", m[0]) + ngx.say("$1: ", m[1]) + ngx.say("$2: ", m[2]) + ngx.say("$3: ", m[3]) + -- ngx.say(table.maxn(m)) + -- collectgarbage() + } +--- stream_response +matched: 1234 +$1: 1 +$2: 234 +$3: nil +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 7: named subpatterns w/ extraction (matched) +--- stream_server_config + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("hello, 1234", "(?[a-z]+), [0-9]+", "jo") + end + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m.first) + ngx.say(m.second) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + } +--- stream_response +hello, 1234 +hello +hello +nil + +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 8: named subpatterns w/ extraction (use of duplicate names in non-duplicate mode) +--- stream_server_config + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 200 do + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "jo") + end + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m.first) + ngx.say(m.second) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + } +--- stream_response_like chop +error: pcre_compile\(\) failed: two named subpatterns have the same name + +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 9: named subpatterns w/ extraction (use of duplicate names in duplicate mode) +--- stream_server_config + content_by_lua_block { + local m, err + local match = ngx.re.match + for i = 1, 100 do + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + m, err = match("hello, 1234", "(?[a-z])(?[a-z]+), [0-9]+", "joD") + end + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(table.concat(m.first, "|")) + ngx.say(m.second) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + } +--- stream_response_like +hello, 1234 +h +ello +h|ello +nil + +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 10: captures input table in ngx.re.match +--- stream_server_config + content_by_lua_block { + local new_tab = require "table.new" + local clear_tab = require "table.clear" + local m + local res = new_tab(5, 0) + res[5] = "hello" + for i = 1, 100 do + m = ngx.re.match("hello, 1234", "([0-9])([0-9])([0-9])([0-9])", "jo", nil, res) + end + + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + ngx.say(m[4]) + ngx.say(m[5]) + else + ngx.say("not matched!") + end + } +--- stream_response +1234 +1 +2 +3 +4 +hello +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+\s+/ + + + +=== TEST 11: unmatched captures are false +--- stream_server_config + content_by_lua_block { + local m = ngx.re.match("hello!", "(hello)(, .+)?(!)", "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 + } +--- stream_response +hello! +hello +false +! +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+\s+/ + + + +=== TEST 12: unmatched trailing captures are false +--- stream_server_config + content_by_lua_block { + local m = ngx.re.match("hello", "(hello)(, .+)?(!)?", "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 + } +--- stream_response +hello +hello +false +false +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+\s+/ + + + +=== TEST 13: unmatched named captures are false +--- stream_server_config + content_by_lua_block { + local m = ngx.re.match("hello!", "(?hello)(?, .+)?(?!)", "jo") + + if m then + ngx.say(m[0]) + ngx.say(m[1]) + ngx.say(m[2]) + ngx.say(m[3]) + ngx.say(m.first) + ngx.say(m.second) + ngx.say(m.third) + else + ngx.say("not matched!") + end + } +--- stream_response +hello! +hello +false +! +hello +false +! +--- no_error_log +[error] +NYI +--- error_log eval +qr/\[TRACE\s+\d+\s+/ + + + +=== TEST 14: subject is not a string type +--- stream_server_config + content_by_lua_block { + local m = ngx.re.match(12345, [=[(\d+)]=], "jo") + + if m then + ngx.say(m[0]) + ngx.say(m[1]) + else + ngx.say("not matched") + end + } +--- stream_response +12345 +12345 +--- no_error_log +[error] +attempt to get length of local 'subj' (a number value) + + + +=== TEST 15: subject is not a string type +--- stream_server_config + content_by_lua_block { + local m = ngx.re.match(12345, "123", "jo") + + if m then + ngx.say(m[0]) + else + ngx.say("not matched") + end + } +--- stream_response +123 +--- no_error_log +[error] +attempt to get length of local 'regex' (a number value) diff --git a/t/stream/re-opt.t b/t/stream/re-opt.t new file mode 100644 index 000000000..e353a3585 --- /dev/null +++ b/t/stream/re-opt.t @@ -0,0 +1,121 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use lib '.'; +use t::TestCore::Stream; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * blocks() * 3; + +no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: default jit_stack_size too small +--- stream_server_config + content_by_lua_block { + -- regex is taken from https://github.com/JuliaLang/julia/issues/8278 + local very_long_string = [[71.163.72.113 - - [30/Jul/2014:16:40:55 -0700] "GET emptymind.org/thevacantwall/wp-content/uploads/2013/02/DSC_006421.jpg HTTP/1.1" 200 492513 "http://images.search.yahoo.com/images/view;_ylt=AwrB8py9gdlTGEwADcSjzbkF;_ylu=X3oDMTI2cGZrZTA5BHNlYwNmcC1leHAEc2xrA2V4cARvaWQDNTA3NTRiMzYzY2E5OTEwNjBiMjc2YWJhMjkxMTEzY2MEZ3BvcwM0BGl0A2Jpbmc-?back=http%3A%2F%2Fus.yhs4.search.yahoo.com%2Fyhs%2Fsearch%3Fei%3DUTF-8%26p%3Dapartheid%2Bwall%2Bin%2Bpalestine%26type%3Dgrvydef%26param1%3D1%26param2%3Dsid%253Db01676f9c26355f014f8a9db87545d61%2526b%253DChrome%2526ip%253D71.163.72.113%2526p%253Dgroovorio%2526x%253DAC811262A746D3CD%2526dt%253DS940%2526f%253D7%2526a%253Dgrv_tuto1_14_30%26hsimp%3Dyhs-fullyhosted_003%26hspart%3Dironsource&w=588&h=387&imgurl=occupiedpalestine.files.wordpress.com%2F2012%2F08%2F5-peeking-through-the-wall.jpg%3Fw%3D588%26h%3D387&rurl=http%3A%2F%2Fwww.stopdebezetting.com%2Fwereldpers%2Fcompare-the-berlin-wall-vs-israel-s-apartheid-wall-in-palestine.html&size=49.0KB&name=...+%3Cb%3EApartheid+wall+in+Palestine%3C%2Fb%3E...+%7C+Or+you+go+peeking+through+the+%3Cb%3Ewall%3C%2Fb%3E&p=apartheid+wall+in+palestine&oid=50754b363ca991060b276aba291113cc&fr2=&fr=&tt=...+%3Cb%3EApartheid+wall+in+Palestine%3C%2Fb%3E...+%7C+Or+you+go+peeking+through+the+%3Cb%3Ewall%3C%2Fb%3E&b=0&ni=21&no=4&ts=&tab=organic&sigr=13evdtqdq&sigb=19k7nsjvb&sigi=12o2la1db&sigt=12lia2m0j&sign=12lia2m0j&.crumb=.yUtKgFI6DE&hsimp=yhs-fullyhosted_003&hspart=ironsource" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36]] + local very_complicated_regex = [[([\d\.]+) ([\w.-]+) ([\w.-]+) (\[.+\]) "([^"\r\n]*|[^"\r\n\[]*\[.+\][^"]+|[^"\r\n]+.[^"]+)" (\d{3}) (\d+|-) ("(?:[^"]|\")+)"? ("(?:[^"]|\")+)"?]] + local from, to, err = ngx.re.find(very_long_string, very_complicated_regex, "jo") + if from or to then + ngx.say("from: ", from) + ngx.say("to: ", to) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + } +--- stream_response +error: pcre_exec() failed: -27 +--- no_error_log +[error] +--- timeout: 10 + + + +=== TEST 2: increase jit_stack_size +--- stream_config eval +qq{ + lua_package_path "$t::TestCore::Stream::lua_package_path"; + init_by_lua_block { + $t::TestCore::Stream::init_by_lua_block + + local ngx_re = require "ngx.re" + ngx_re.opt("jit_stack_size", 128 * 1024) + } +} +--- stream_server_config + content_by_lua_block { + -- regex is taken from https://github.com/JuliaLang/julia/issues/8278 + local very_long_string = [[71.163.72.113 - - [30/Jul/2014:16:40:55 -0700] "GET emptymind.org/thevacantwall/wp-content/uploads/2013/02/DSC_006421.jpg HTTP/1.1" 200 492513 "http://images.search.yahoo.com/images/view;_ylt=AwrB8py9gdlTGEwADcSjzbkF;_ylu=X3oDMTI2cGZrZTA5BHNlYwNmcC1leHAEc2xrA2V4cARvaWQDNTA3NTRiMzYzY2E5OTEwNjBiMjc2YWJhMjkxMTEzY2MEZ3BvcwM0BGl0A2Jpbmc-?back=http%3A%2F%2Fus.yhs4.search.yahoo.com%2Fyhs%2Fsearch%3Fei%3DUTF-8%26p%3Dapartheid%2Bwall%2Bin%2Bpalestine%26type%3Dgrvydef%26param1%3D1%26param2%3Dsid%253Db01676f9c26355f014f8a9db87545d61%2526b%253DChrome%2526ip%253D71.163.72.113%2526p%253Dgroovorio%2526x%253DAC811262A746D3CD%2526dt%253DS940%2526f%253D7%2526a%253Dgrv_tuto1_14_30%26hsimp%3Dyhs-fullyhosted_003%26hspart%3Dironsource&w=588&h=387&imgurl=occupiedpalestine.files.wordpress.com%2F2012%2F08%2F5-peeking-through-the-wall.jpg%3Fw%3D588%26h%3D387&rurl=http%3A%2F%2Fwww.stopdebezetting.com%2Fwereldpers%2Fcompare-the-berlin-wall-vs-israel-s-apartheid-wall-in-palestine.html&size=49.0KB&name=...+%3Cb%3EApartheid+wall+in+Palestine%3C%2Fb%3E...+%7C+Or+you+go+peeking+through+the+%3Cb%3Ewall%3C%2Fb%3E&p=apartheid+wall+in+palestine&oid=50754b363ca991060b276aba291113cc&fr2=&fr=&tt=...+%3Cb%3EApartheid+wall+in+Palestine%3C%2Fb%3E...+%7C+Or+you+go+peeking+through+the+%3Cb%3Ewall%3C%2Fb%3E&b=0&ni=21&no=4&ts=&tab=organic&sigr=13evdtqdq&sigb=19k7nsjvb&sigi=12o2la1db&sigt=12lia2m0j&sign=12lia2m0j&.crumb=.yUtKgFI6DE&hsimp=yhs-fullyhosted_003&hspart=ironsource" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36]] + local very_complicated_regex = [[([\d\.]+) ([\w.-]+) ([\w.-]+) (\[.+\]) "([^"\r\n]*|[^"\r\n\[]*\[.+\][^"]+|[^"\r\n]+.[^"]+)" (\d{3}) (\d+|-) ("(?:[^"]|\")+)"? ("(?:[^"]|\")+)"?]] + local from, to, err = ngx.re.find(very_long_string, very_complicated_regex, "jo") + if from or to then + ngx.say("from: ", from) + ngx.say("to: ", to) + else + if err then + ngx.say("error: ", err) + return + end + ngx.say("not matched!") + end + } +--- stream_response +from: 1 +to: 1563 +--- no_error_log +[error] +--- timeout: 10 + + + +=== TEST 3: jit_stack_size change disallowed once regex cache is populated +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local status, err = pcall(ngx_re.opt, "jit_stack_size", 128 * 1024) + if err then ngx.log(ngx.ERR, err) end + local s = "hello, 1234" + local from, to = ngx.re.find(s, "(hello world)|([0-9])", "jo") + ngx.say("from: ", from) + ngx.say("to: ", to) + } +--- stream_response +from: 8 +to: 8 + +--- grep_error_log eval +qr/changing jit stack size is not allowed when some regexs have already been compiled and cached/ + +--- grep_error_log_out eval +["", "changing jit stack size is not allowed when some regexs have already been compiled and cached\n"] +--- timeout: 10 + + + +=== TEST 4: passing unknown options to ngx_re.opt throws an error +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local status, err = pcall(ngx_re.opt, "foo", 123) + ngx.say(err) + } +--- stream_response_like chomp +unrecognized option name$ +--- no_error_log +[error] +--- timeout: 10 diff --git a/t/stream/re-split.t b/t/stream/re-split.t new file mode 100644 index 000000000..a24301e42 --- /dev/null +++ b/t/stream/re-split.t @@ -0,0 +1,1092 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * blocks() * 4 + (2 * repeat_each()); + +no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: split matches, no submatch, no jit compile, no regex cache +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d", ",") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +a +b +c +d +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 2: split matches, no submatch, no jit compile, no regex cache +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a;,b;,c;,d;e", ";,") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +a +b +c +d;e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 3: split matches, no submatch, jit compile, regex cache +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d", ",", "jo") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +a +b +c +d +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 4: split matches + submatch (matching) +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a;,b;,c;,d,e", "(;),") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +a +; +b +; +c +; +d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 5: split matches + submatch (not matching) +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", "(;)|,") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +a +b +c +d +e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 6: split matches + max limiter +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, 3) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +a +b +c,d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 7: split matches + submatch + max limiter +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", "(,)", nil, nil, 3) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +a +, +b +, +c,d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 8: split matches + max limiter set to 0 +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, 0) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +a +b +c +d +e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 9: split matches + max limiter set to a negative value +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, -1) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +a +b +c +d +e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 10: split matches + max limiter set to 1 +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, 1) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +a,b,c,d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 11: split matches, provided res table +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local my_table = {} + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, nil, my_table) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +a +b +c +d +e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 12: split matches, provided res table (non-cleared) +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local my_table = {} + + for i = 1, 10 do + my_table[i] = i.." hello world" + end + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, nil, my_table) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i in ipairs(my_table) do + ngx.say(res[i]) + end + } +--- stream_response +a +b +c +d +e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 13: split matches, provided res table + max limiter +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local my_table = {"hello, world"} + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, 3, my_table) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #my_table do + ngx.say(res[i]) + end + } +--- stream_response +a +b +c,d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 14: split matches, provided res table (non-cleared) + max limiter +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local my_table = {} + + for i = 1, 10 do + my_table[i] = i.." hello world" + end + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, nil, 3, my_table) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i in ipairs(my_table) do + ngx.say(res[i]) + end + } +--- stream_response +a +b +c,d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 15: split matches, provided res table + max limiter + sub-match capturing group +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local my_table = {"hello, world"} + + local res, err = ngx_re.split("a,b,c,d,e", "(,)", nil, nil, 3, my_table) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #my_table do + ngx.say(res[i]) + end + } +--- stream_response +a +, +b +, +c,d,e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 16: split matches, ctx arg +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, { pos = 5 }) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +c +d +e +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 17: split matches, trailing subjects +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split(",a,b,c,d,", ",") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + if res[i] == "" then + ngx.say("_blank_") + else + ngx.say(res[i]) + end + end + } +--- stream_response +_blank_ +a +b +c +d +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] +attempt to get length of local 'regex' (a number value) + + + +=== TEST 18: split matches, real use-case +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("abcd,erfg,ghij;hello world;aaa", ",|;") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +abcd +erfg +ghij +hello world +aaa +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 19: split no matches +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("abcd", ",") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +abcd +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 20: subject is not a string type +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split(1234512345, "23", "jo") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + } +--- stream_response +1 +451 +45 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] +attempt to get length of local 'subj' (a number value) + + + +=== TEST 21: split matches, pos is larger than subject length +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("a,b,c,d,e", ",", nil, { pos = 10 }) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + ngx.say("len: ", #res) + } +--- stream_response +len: 0 +--- no_error_log +[error] +[TRACE + + + +=== TEST 22: regex is "" +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("12345", "", "jo") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + + ngx.say("len: ", #res) + } +--- stream_response +1 +2 +3 +4 +5 +len: 5 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 23: regex is "" with max +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("12345", "", "jo", nil, 3) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + + ngx.say("len: ", #res) + } +--- stream_response +1 +2 +345 +len: 3 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 24: regex is "" with pos +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("12345", "", "jo", { pos = 2 }) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + + ngx.say("len: ", #res) + } +--- stream_response +2 +3 +4 +5 +len: 4 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 25: regex is "" with pos larger than subject length +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("12345", "", "jo", { pos = 10 }) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + + ngx.say("len: ", #res) + } +--- stream_response +len: 0 +--- no_error_log +[error] +[TRACE + + + +=== TEST 26: regex is "" with pos & max +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("12345", "", "jo", { pos = 2 }, 2) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + for i = 1, #res do + ngx.say(res[i]) + end + + ngx.say("len: ", #res) + } +--- stream_response +2 +345 +len: 2 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 27: no match separator (github issue #104) +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("abcd", "|") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + ngx.say("len: ", #res) + } +--- stream_response +a:b:c:d +len: 4 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 28: no match separator (github issue #104) & max +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("abcd", "|", nil, nil, 2) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + ngx.say("len: ", #res) + } +--- stream_response +a:bcd +len: 2 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 29: no match separator bis (github issue #104) +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("abcd", "()") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + ngx.say("len: ", #res) + } +--- stream_response +a::b::c::d +len: 7 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 30: behavior with /^/ differs from Perl's split +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("ab\ncd\nef", "^") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + + ngx.say("len: ", #res) + } +--- stream_response +ab +cd +ef +len: 1 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 31: behavior with /^/m +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("ab\ncd\nef", "^", "m") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + + ngx.say("len: ", #res) + } +--- stream_response +ab +:cd +:ef +len: 3 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 32: behavior with /^()/m (capture) +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("ab\ncd\nef", "^()", "m") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + + ngx.say("len: ", #res) + } +--- stream_response +ab +::cd +::ef +len: 5 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 33: behavior with /^/m & max +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("ab\ncd\nef", "^", "m", nil, 2) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + + ngx.say("len: ", #res) + } +--- stream_response +ab +:cd +ef +len: 2 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 34: behavior with /^\d/m +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("ab\n1cdefg\n2hij", "^\\d", "m") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + + ngx.say("len: ", #res) + } +--- stream_response +ab +:cdefg +:hij +len: 3 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 35: behavior with /^(\d)/m (capture) +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local res, err = ngx_re.split("ab\n1cdefg\n2hij", "^(\\d)", "m") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(table.concat(res, ":")) + + ngx.say("len: ", #res) + } +--- stream_response +ab +:1:cdefg +:2:hij +len: 5 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 36: split by unit separator 1/2 (GH issue lua-nginx-module #1217) +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local subjs = { + "1\x1fT\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f15", + "1\x1fT\x1fT\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f15", + "1\x1fT\x1fT\x1fT\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f15", + } + + for _, subj in ipairs(subjs) do + local col_list = ngx_re.split(subj, "\\x1f") + ngx.say(#col_list, " ", table.concat(col_list, "|")) + end + } +--- stream_response +15 1|T|||||||||||||15 +15 1|T|T||||||||||||15 +15 1|T|T|T|||||||||||15 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 37: split by unit separator 2/2 (with ctx.pos) +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local subjs = { + "1\x1fT\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f15", + "1\x1fT\x1fT\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f15", + "1\x1fT\x1fT\x1fT\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f\x1f15", + } + + for _, subj in ipairs(subjs) do + local col_list = ngx_re.split(subj, "\\x1f", nil, { pos = 6 }) + ngx.say(#col_list, " ", table.concat(col_list, "|")) + end + } +--- stream_response +12 |||||||||||15 +13 ||||||||||||15 +13 |T|||||||||||15 +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 38: remaining characters are matched by regex (without max) +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local subj = "a,b,cd,,," + + local res, err = ngx_re.split(subj, ",") + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(#res, " ", table.concat(res, "|")) + } +--- stream_response +3 a|b|cd +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] + + + +=== TEST 39: remaining characters are matched by regex (with max) +--- stream_server_config + content_by_lua_block { + local ngx_re = require "ngx.re" + + local subj = "a,b,cd,,," + + for max = 1, 7 do + local res, err = ngx_re.split(subj, ",", nil, nil, max) + if err then + ngx.log(ngx.ERR, "failed: ", err) + return + end + + ngx.say(#res, " ", table.concat(res, "|")) + end + } +--- stream_response +1 a,b,cd,,, +2 a|b,cd,,, +3 a|b|cd,,, +4 a|b|cd|,, +5 a|b|cd||, +6 a|b|cd||| +6 a|b|cd||| +--- error_log eval +qr/\[TRACE\s+\d+/ +--- no_error_log +[error] diff --git a/t/stream/re-sub.t b/t/stream/re-sub.t new file mode 100644 index 000000000..661244661 --- /dev/null +++ b/t/stream/re-sub.t @@ -0,0 +1,278 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4 + 8); + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: sub, no submatch, no jit compile, regex cache +--- stream_server_config + content_by_lua_block { + local m, err + local sub = ngx.re.sub + for i = 1, 350 do + s, n, err = sub("abcbd", "b", "B", "jo") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + } +--- stream_response +s: aBcbd +n: 1 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 2: sub, no submatch, no jit compile, no regex cache +--- stream_server_config + content_by_lua_block { + local m, err + local sub = ngx.re.sub + for i = 1, 400 do + s, n, err = sub("abcbd", "b", "B") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + } +--- stream_response +s: aBcbd +n: 1 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] +bad argument type + + + +=== TEST 3: func + submatches +--- stream_server_config + content_by_lua_block { + local m, err + local function f(m) + return "[" .. m[0] .. "(" .. m[1] .. ")]" + end + local sub = ngx.re.sub + for i = 1, 200 do + s, n, err = sub("abcbd", "b(c)", f, "jo") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + } +--- stream_response +s: a[bc(c)]bd +n: 1 +--- no_error_log eval +[ +"[error]", +"bad argument type", +qr/NYI (?!bytecode 51 at)/, +] + + + +=== TEST 4: replace template + submatches +--- stream_server_config + content_by_lua_block { + local m, err + local sub = ngx.re.sub + for i = 1, 350 do + s, n, err = sub("abcbd", "b(c)", "[$0($1)]", "jo") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + } +--- stream_response +s: a[bc(c)]bd +n: 1 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ + +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 5: replace template + submatches (exceeding buffers) +--- stream_server_config + content_by_lua_block { + local m, err + local gsub = ngx.re.gsub + local subj = string.rep("bcbd", 2048) + for i = 1, 10 do + s, n, err = gsub(subj, "b(c)", "[$0($1)]", "jo") + end + if not s then + ngx.log(ngx.ERR, "failed: ", err) + return + end + ngx.say("s: ", s) + ngx.say("n: ", n) + } +--- stream_response eval +"s: " . ("[bc(c)]bd" x 2048) . +"\nn: 2048\n" + +--- no_error_log +[error] +bad argument type + + + +=== TEST 6: ngx.re.gsub: use of resty.core's API in the user callback +--- stream_config + lua_shared_dict dogs 12k; +--- stream_server_config + content_by_lua_block { + local dogs = ngx.shared.dogs + assert(dogs:set("foo", "bar")) + + local data = [[ + INNER + INNER +]] + + local res = ngx.re.gsub(data, "INNER", function(inner_matches) + assert(dogs:get("foo")) + return "INNER_REPLACED" + end, "s") + + ngx.print(res) + } +--- stream_response + INNER_REPLACED + INNER_REPLACED + +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 7: ngx.re.gsub: recursive calling (github openresty/lua-nginx-module#445) +--- stream_server_config + content_by_lua_block { + function test() + local data = [[ + OUTER {FIRST} +]] + + local p1 = "(OUTER)(.+)" + local p2 = "{([A-Z]+)}" + + ngx.print(data) + + local res = ngx.re.gsub(data, p1, function(m) + -- ngx.say("pre: m[1]: [", m[1], "]") + -- ngx.say("pre: m[2]: [", m[2], "]") + + local res = ngx.re.gsub(m[2], p2, function(_) + return "REPLACED" + end, "") + + -- ngx.say("post: m[1]: [", m[1], "]") + -- ngx.say("post m[2]: [", m[2], "]") + return m[1] .. res + end, "") + + ngx.print(res) + end + + test() + } +--- stream_response + OUTER {FIRST} + OUTER REPLACED +--- no_error_log +[error] +bad argument type +NYI + + + +=== TEST 8: string replace subj is not a string type +--- stream_server_config + content_by_lua_block { + local newstr, n, err = ngx.re.sub(1234, "([0-9])[0-9]", 5, "jo") + + ngx.say(newstr) + } +--- stream_response +534 +--- no_error_log +[error] +attempt to get length of local 'subj' (a number value) + + + +=== TEST 9: func replace return is not a string type (ngx.re.sub) +--- stream_server_config + content_by_lua_block { + local lookup = function(m) + -- note we are returning a number type here + return 5 + end + + local newstr, n, err = ngx.re.sub("hello, 1234", "([0-9])[0-9]", lookup, "jo") + ngx.say(newstr) + } +--- stream_response +hello, 534 +--- no_error_log +[error] +attempt to get length of local 'bit' (a number value) + + + +=== TEST 10: func replace return is not a string type (ngx.re.gsub) +--- stream_server_config + content_by_lua_block { + local lookup = function(m) + -- note we are returning a number type here + return 5 + end + + local newstr, n, err = ngx.re.gsub("hello, 1234", "([0-9])[0-9]", lookup, "jo") + ngx.say(newstr) + } +--- stream_response +hello, 55 +--- no_error_log +[error] +attempt to get length of local 'bit' (a number value) diff --git a/t/stream/request.t b/t/stream/request.t new file mode 100644 index 000000000..4387725e8 --- /dev/null +++ b/t/stream/request.t @@ -0,0 +1,39 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6); + +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.req.start_time() +--- stream_server_config + content_by_lua_block { + local t + for i = 1, 500 do + t = ngx.req.start_time() + end + ngx.sleep(0.10) + local elapsed = ngx.now() - t + ngx.say(t > 1399867351) + ngx.say(">= 0.099: ", elapsed >= 0.099) + ngx.say("< 0.11: ", elapsed < 0.11) + -- ngx.say(t, " ", elapsed) + } +--- stream_response +true +>= 0.099: true +< 0.11: true + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch diff --git a/t/stream/semaphore.t b/t/stream/semaphore.t new file mode 100644 index 000000000..d7e9d7e5c --- /dev/null +++ b/t/stream/semaphore.t @@ -0,0 +1,1163 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +#worker_connections(10140); +#workers(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3 + 2); + +no_long_string(); +#no_diff(); +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::Stream::lua_package_path"; +our $HttpConfig = <<_EOC_; + lua_package_path "$t::TestCore::Stream::lua_package_path"; +_EOC_ + +run_tests(); + +__DATA__ + +=== TEST 1: basic semaphore in uthread +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + + local function sem_wait() + ngx.say("enter waiting") + + local ok, err = sem:wait(1) + if not ok then + ngx.say("err: ", err) + else + ngx.say("wait success") + end + end + + local co = ngx.thread.spawn(sem_wait) + + ngx.say("back in main thread") + + sem:post() + + ngx.say("still in main thread") + + ngx.sleep(0.01) + + ngx.say("main thread end") + } +--- stream_response +enter waiting +back in main thread +still in main thread +wait success +main thread end +--- no_error_log +[error] + + + +=== TEST 2: semaphore wait order +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + + local function sem_wait(id) + ngx.say("enter waiting, id: ", id) + + local ok, err = sem:wait(1) + if not ok then + ngx.say("err: ", err) + else + ngx.say("wait success, id: ", id) + end + end + + local co1 = ngx.thread.spawn(sem_wait, 1) + local co2 = ngx.thread.spawn(sem_wait, 2) + + ngx.say("back in main thread") + + sem:post(2) + + local ok, err = sem:wait(0) + if ok then + ngx.say("wait success in main thread") + else + ngx.say("wait failed in main thread: ", err) -- busy + end + + ngx.say("still in main thread") + + local ok, err = sem:wait(0.01) + if ok then + ngx.say("wait success in main thread") + else + ngx.say("wait failed in main thread: ", err) + end + + ngx.sleep(0.01) + + ngx.say("main thread end") + } +--- stream_response +enter waiting, id: 1 +enter waiting, id: 2 +back in main thread +wait failed in main thread: timeout +still in main thread +wait success, id: 1 +wait success, id: 2 +wait failed in main thread: timeout +main thread end +--- no_error_log +[error] + + + +=== TEST 3: semaphore wait time=0 +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(1) + + local function wait_1s() + ngx.say("enter 1s wait") + + local ok, err = sem:wait(1) + if not ok then + ngx.say("err in wait 1s: ", err) + else + ngx.say("wait success in 1s wait") + end + end + + local function wait_0() + local ok, err = sem:wait(0) + if not ok then + ngx.say("err: ", err) + else + ngx.say("wait success") + end + end + + wait_0() + wait_0() + + local co = ngx.thread.spawn(wait_1s) + + ngx.say("back in main thread") + + wait_0() + + sem:post(2) + + wait_0() + + ngx.say("still in main thread") + + ngx.sleep(0.01) + + wait_0() + + ngx.say("main thread end") + } +--- stream_response +wait success +err: timeout +enter 1s wait +back in main thread +err: timeout +err: timeout +still in main thread +wait success in 1s wait +wait success +main thread end +--- no_error_log +[error] + + + +=== TEST 4: semaphore.new in init_by_lua* (w/o shdict) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + init_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + else + ngx.log(ngx.WARN, "sema created: ", tostring(sem)) + end + sem:post(2) + package.loaded.my_sema = sem + } +--- stream_server_config + content_by_lua_block { + local sem = package.loaded.my_sema + ngx.say("sem count: ", sem:count()) + -- sem:post(1) + local ok, err = sem:wait(0) + if not ok then + ngx.say("failed to wait: ", err) + return + end + ngx.say("waited successfully.") + } +--- stream_response_like +sem count: [12] +waited successfully. +--- grep_error_log eval +qr/\[lua\] init_by_lua:\d+: sema created: table: 0x[a-f0-9]+/ +--- grep_error_log_out eval +[ +qr/\[lua\] init_by_lua:\d+: sema created: table: 0x[a-f0-9]+/, +"", +] + + + +=== TEST 5: semaphore.new in init_by_lua* (with shdict) +--- stream_config + lua_shared_dict dogs 1m; + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + init_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + else + ngx.log(ngx.WARN, "sema created: ", tostring(sem)) + end + sem:post(2) + package.loaded.my_sema = sem + } +--- stream_server_config + content_by_lua_block { + local sem = package.loaded.my_sema + ngx.say("sem count: ", sem:count()) + -- sem:post(1) + local ok, err = sem:wait(0) + if not ok then + ngx.say("failed to wait: ", err) + return + end + ngx.say("waited successfully.") + } +--- stream_response_like +sem count: [12] +waited successfully. +--- grep_error_log eval +qr/\[lua\] init_by_lua:\d+: sema created: table: 0x[a-f0-9]+/ +--- grep_error_log_out eval +[ +qr/\[lua\] init_by_lua:\d+: sema created: table: 0x[a-f0-9]+/, +"", +] + + + +=== TEST 6: semaphore in init_worker_by_lua (wait is not allowed) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + init_worker_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem new: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem count: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem wait: ", err) + end + } +--- stream_server_config + return "ok"; +--- stream_response chop +ok +--- grep_error_log eval: qr/sem \w+: .*?,/ +--- grep_error_log_out eval +[ +"sem count: 1, +sem wait: API disabled in the context of init_worker_by_lua*, +", +"", +] + + + +=== TEST 7: semaphore in init_worker_by_lua (new and post) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + init_worker_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem new: ", err) + end + + sem:post(2) + + local count = sem:count() + ngx.log(ngx.WARN, "sem count: ", count) + + package.loaded.my_sema = sem + } +--- stream_server_config + content_by_lua_block { + local sem = package.loaded.my_sema + local ok, err = sem:wait(0.1) + if not ok then + ngx.say("failed to wait: ", err) + return + end + ngx.say("sem wait successfully.") + } +--- stream_response +sem wait successfully. +--- grep_error_log eval: qr/sem \w+: .*?,/ +--- grep_error_log_out eval +[ +"sem count: 2, +", +"" +] +--- no_error_log +[error] + + + +=== TEST 8: semaphore in preread_by_lua (all allowed) +--- stream_config eval: $::HttpConfig +--- stream_server_config + preread_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + local ok, err = sem:wait(0.01) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } + return "ok"; +--- stream_response chop +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: timeout while prereading client data, +sem: 1 while prereading client data, +", +"sem: timeout while prereading client data, +sem: 1 while prereading client data, +", +] + + + +=== TEST 9: semaphore in content_by_lua (all allowed) +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + local ok, err = sem:wait(0.01) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + else + ngx.say("ok") + end + } +--- stream_response +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: timeout, +sem: 1, +", +"sem: timeout, +sem: 1, +", +] + + + +=== TEST 10: semaphore in log_by_lua (wait not allowed) +--- stream_config eval: $::HttpConfig +--- stream_server_config + return "ok"; + log_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } +--- stream_response chop +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: 1 while returning text, +sem: API disabled in the context of log_by_lua* while returning text, +", +"sem: 1 while returning text, +sem: API disabled in the context of log_by_lua* while returning text, +", +] +--- wait: 0.2 + + + +=== TEST 11: semaphore in ngx.timer (all allowed) +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local function func_sem() + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, "sem: ", err) + end + + local ok, err = sem:wait(0.01) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + + sem:post(1) + + local count = sem:count() + ngx.log(ngx.ERR, "sem: ", count) + + local ok, err = sem:wait(0.1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + end + + local ok, err = ngx.timer.at(0, func_sem) + if ok then + ngx.sleep(0.01) + ngx.say("ok") + end + } +--- stream_response +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: timeout, +sem: 1, +", +"sem: timeout, +sem: 1, +", +] +--- wait: 0.2 + + + +=== TEST 12: semaphore post in all phase (in a request) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + init_worker_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + end + package.loaded.sem = sem + + local function wait() + local i = 0 + while true do + local ok, err = sem:wait(1) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + i = i + 1 + if i % 3 == 0 then + ngx.log(ngx.ERR, "sem: 3 times") + end + end + end + + local ok, err = ngx.timer.at(0, wait) + if not ok then + ngx.log(ngx.ERR, "sem: ", err) + end + } +--- stream_server_config + preread_by_lua_block { + local sem = package.loaded.sem + sem:post() + } + content_by_lua_block { + local sem = package.loaded.sem + sem:post() + ngx.say("ok") + } + log_by_lua_block { + local sem = package.loaded.sem + sem:post() + } +--- stream_response +ok +--- grep_error_log eval: qr/sem: .*?,/ +--- grep_error_log_out eval +[ +"sem: 3 times, +", +"sem: 3 times, +", +] +--- wait: 0.2 + + + +=== TEST 13: semaphore wait post in preread_by_lua +--- stream_config eval: $::HttpConfig +--- stream_server_config + preread_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + + local func_wait = function () + ngx.say("enter wait") + + local ok, err = sem:wait(1) + if ok then + ngx.say("wait success") + end + end + local func_post = function () + ngx.say("enter post") + + sem:post() + ngx.say("post success") + end + + local co1 = ngx.thread.spawn(func_wait) + local co2 = ngx.thread.spawn(func_post) + + ngx.thread.wait(co1) + ngx.thread.wait(co2) + } + + return "done"; +--- stream_response chop +enter wait +enter post +post success +wait success +done +--- no_error_log +[error] + + + +=== TEST 14: semaphore wait in timer.at +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + + local function func_wait(premature) + local ok, err = sem:wait(1) + if not ok then + ngx.log(ngx.ERR, err) + else + ngx.log(ngx.ERR, "wait success") + end + end + + ngx.timer.at(0, func_wait) + + sem:post() + ngx.sleep(0.01) + ngx.say("ok") + } +--- stream_response +ok +--- error_log +wait success + + + +=== TEST 15: two thread wait for each other +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem_A = semaphore.new(0) + local sem_B = semaphore.new(0) + if not sem_A or not sem_B then + error("create failed") + end + + local function th_A() + for i = 1, 11 do + local ok, err = sem_A:wait(1) + if not ok then + ngx.log(ngx.ERR, err) + end + sem_B:post(1) + end + ngx.say("count in A: ", sem_A:count()) + end + local function th_B() + for i = 1, 10 do + local ok, err = sem_B:wait(1) + if not ok then + ngx.log(ngx.ERR, err) + end + sem_A:post(1) + end + ngx.say("count in B: ", sem_B:count()) + end + + local co_A = ngx.thread.spawn(th_A) + local co_B = ngx.thread.spawn(th_B) + + sem_A:post(1) + } +--- log_level: debug +--- stream_response +count in B: 0 +count in A: 0 +--- no_error_log +[error] + + + +=== TEST 16: kill a light thread that is waiting on a semaphore (no resource) +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + + local function func_wait() + sem:wait(1) + end + local co = ngx.thread.spawn(func_wait) + local ok, err = ngx.thread.kill(co) + if ok then + ngx.say("ok") + else + ngx.say(err) + end + } +--- log_level: debug +--- stream_response +ok +--- no_error_log +[error] + + + +=== TEST 17: kill a light thread that is waiting on a semaphore (after post) +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + + local function func_wait() + sem:wait(1) + end + local co = ngx.thread.spawn(func_wait) + + sem:post() + local ok, err = ngx.thread.kill(co) + + if ok then + ngx.say("ok") + else + ngx.say(err) + end + + ngx.sleep(0.01) + + local count = sem:count() + ngx.say("count: ", count) + } +--- log_level: debug +--- stream_response +ok +count: 1 +--- no_error_log +[error] + + + +=== TEST 18: kill a thread that is waiting on another thread that is waiting on semaphore +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + + local function sem_wait() + ngx.say("sem waiting start") + local ok, err = sem:wait(0.1) + if not ok then + ngx.say("sem wait err: ", err) + end + ngx.say("sem waiting done") + end + + local function thread_wait() + local co = ngx.thread.spawn(sem_wait) + + ngx.say("thread waiting start") + local ok, err = ngx.thread.wait(co) + if not ok then + ngx.say("thread wait err: ", err) + end + ngx.say("thread waiting done") + end + + local co2 = ngx.thread.spawn(thread_wait) + ngx.sleep(0.01) + + local ok, err = ngx.thread.kill(co2) + if ok then + ngx.say("thread kill success") + else + ngx.say("kill err: ", err) + end + } +--- log_level: debug +--- stream_response +sem waiting start +thread waiting start +thread kill success +sem wait err: timeout +sem waiting done +--- no_error_log +[error] + + + +=== TEST 19: a light thread that is going to exit is waiting on a semaphore +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + local function func(sem) + ngx.say("sem waiting") + local ok, err = sem:wait(0.1) + if ok then + ngx.say("wait success") + else + ngx.say("err: ", err) + end + end + local co = ngx.thread.spawn(func, sem) + ngx.say("ok") + ngx.exit(200) + } +--- log_level: debug +--- stream_response +sem waiting +ok +--- error_log +stream lua semaphore cleanup + + + +=== TEST 20: main thread wait a light thread that is waiting on a semaphore +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if not sem then + error("create failed") + end + local function func(sem) + local ok, err = sem:wait(0.001) + if ok then + ngx.say("wait success") + else + ngx.say("err: ", err) + end + end + local co = ngx.thread.spawn(func, sem) + ngx.thread.wait(co) + } +--- log_level: debug +--- stream_response +err: timeout +--- no_error_log +[error] + + + +=== TEST 21: multi wait and mult post with one semaphore +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if not sem then + ngx.log(ngx.ERR, err) + ngx.exit(500) + end + + local function func(op, id) + ngx.say(op, ": ", id) + if op == "wait" then + local ok, err = sem:wait(1) + if ok then + ngx.say("wait success: ", id) + end + else + sem:post() + end + end + local tco = {} + + for i = 1, 3 do + tco[#tco + 1] = ngx.thread.spawn(func, "wait", i) + end + + for i = 1, 3 do + tco[#tco + 1] = ngx.thread.spawn(func, "post", i) + end + + for i = 1, #tco do + ngx.thread.wait(tco[i]) + end + } +--- stream_response +wait: 1 +wait: 2 +wait: 3 +post: 1 +post: 2 +post: 3 +wait success: 1 +wait success: 2 +wait success: 3 +--- no_error_log +[error] + + + +=== TEST 22: semaphore wait time is zero +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + local ok, err = sem:wait(0) + if not ok then + ngx.say(err) + end + } +--- stream_response +timeout +--- no_error_log +[error] + + + +=== TEST 23: test semaphore gc +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem, err = semaphore.new(0) + if sem then + ngx.say("success") + end + sem = nil + collectgarbage("collect") + } +--- stream_response +success +--- log_level: debug +--- error_log +in lua gc, semaphore + + + +=== TEST 24: basic semaphore_mm alloc +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(0) + if sem then + ngx.say("ok") + end + } +--- log_level: debug +--- stream_response +ok +--- grep_error_log eval: qr/(new block, alloc semaphore|from head of free queue, alloc semaphore)/ +--- grep_error_log_out eval +[ +"new block, alloc semaphore +", +"from head of free queue, alloc semaphore +", +] + + + +=== TEST 25: basic semaphore_mm free insert tail +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sems = package.loaded.sems or {} + package.loaded.sems = sems + + local num_per_block = 4095 + if not sems[num_per_block] then + for i = 1, num_per_block * 3 do + sems[i] = semaphore.new(0) + end + end + + for i = 1, 2 do + if sems[i] then + sems[i] = nil + ngx.say("ok") + break + end + end + collectgarbage("collect") + } +--- log_level: debug +--- stream_response +ok +--- error_log +add to free queue tail + + + +=== TEST 26: basic semaphore_mm free insert head +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sems = package.loaded.sems or {} + package.loaded.sems = sems + + local num_per_block = 4095 + if not sems[num_per_block] then + for i = 1, num_per_block * 3 do + sems[i] = semaphore.new(0) + end + end + + if sems[#sems] then + sems[#sems] = nil + ngx.say("ok") + end + collectgarbage("collect") + } +--- log_level: debug +--- stream_response +ok +--- error_log +add to free queue head + + + +=== TEST 27: semaphore_mm free block (load <= 50% & the on the older side) +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sems = package.loaded.sems or {} + package.loaded.sems = sems + + local num_per_block = 4095 + if not sems[num_per_block * 3] then + for i = 1, num_per_block * 3 do + sems[i] = semaphore.new(0) + end + + for i = num_per_block + 1, num_per_block * 2 do + sems[i] = nil + end + else + for i = 1, num_per_block do + sems[i] = nil + end + end + + collectgarbage("collect") + ngx.say("ok") + } +--- log_level: debug +--- stream_response +ok +--- grep_error_log eval: qr/free semaphore block/ +--- grep_error_log_out eval +[ +"", +"free semaphore block +", +] +--- timeout: 10 + + + +=== TEST 28: basic semaphore count +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new(10) + local count = sem:count() + ngx.say(count) + + sem:wait(0) + local count = sem:count() + ngx.say(count) + + sem:post(3) + local count = sem:count() + ngx.say(count) + } +--- stream_response +10 +9 +12 +--- no_error_log +[error] + + + +=== TEST 29: basic semaphore count (negative number) +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + local sem = semaphore.new() + local count = sem:count() + ngx.say(count) + + local function wait() + sem:wait(0.01) + end + local co = ngx.thread.spawn(wait) + + local count = sem:count() + ngx.say(count) + } +--- stream_response +0 +-1 +--- no_error_log +[error] + + + +=== TEST 30: bugfix: semaphore instance can't be garbage collected when someone is waiting on it +--- stream_config eval: $::HttpConfig +--- stream_server_config + content_by_lua_block { + local semaphore = require "ngx.semaphore" + + local my_sema = {} + local key = "my key" + + local function my_clean() + print("cleaning up") + + my_sema[key]:post() + my_sema[key] = nil + + collectgarbage() + end + + local ok, err = ngx.timer.at(0.001, my_clean) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + ngx.exit(500) + end + + my_sema[key] = semaphore:new(0) + + local ok, err = my_sema[key]:wait(2) + ngx.say(ok, ", ", err) + } +--- stream_response +true, nil +--- no_error_log +[error] +[crit] diff --git a/t/stream/shdict.t b/t/stream/shdict.t new file mode 100644 index 000000000..87f75018f --- /dev/null +++ b/t/stream/shdict.t @@ -0,0 +1,1395 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +add_block_preprocessor(sub { + my $block = shift; + + my $stream_config = $block->stream_config || ''; + + $stream_config .= <<_EOC_; + lua_shared_dict dogs 1m; + lua_shared_dict cats 16k; + lua_shared_dict birds 100k; + $t::TestCore::Stream::StreamConfig +_EOC_ + + $block->set_value("stream_config", $stream_config); +}); + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: get a string value +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible = dogs:set("foo", "bar", 0, 72) + if not ok then + ngx.say("failed to set: ", err) + return + end + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: string +value: bar +flags: 72 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):11 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 2: get an nonexistent key +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + -- dogs:set("foo", "bar") + for i = 1, 100 do + val, flags = dogs:get("nonexistent") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: nil +value: nil +flags: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 3: get a boolean value (true) +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", true, 0, 5678) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: boolean +value: true +flags: 5678 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 4: get a boolean value (false) +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", false, 0, 777) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: boolean +value: false +flags: 777 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 5: get a number value (int) +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", 51203) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: number +value: 51203 +flags: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 6: get a number value (double) +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", 3.1415926, 0, 78) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: number +value: 3.1415926 +flags: 78 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 7: get a large string value +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:flush_all() + dogs:flush_expired() + dogs:set("foo", string.rep("bbbb", 1024) .. "a", 0, 912) + for i = 1, 100 do + val, flags = dogs:get("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response eval +"value type: string +value: " . ("bbbb" x 1024) . "a +flags: 912 +" +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):9 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 8: get_stale (false) +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags, stale + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:flush_all() + dogs:flush_expired() + dogs:set("foo", "bar", 0, 72) + for i = 1, 100 do + val, flags, stale = dogs:get_stale("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + ngx.say("stale: ", stale) + } +--- stream_response +value type: string +value: bar +flags: 72 +stale: false +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):9 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 9: get_stale (true) +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags, stale + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible = dogs:set("foo", "bar", 0.01, 72) + if not ok then + ngx.say("failed to set: ", err) + return + end + ngx.update_time() + ngx.sleep(0.02) + for i = 1, 100 do + val, flags, stale = dogs:get_stale("foo") + end + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + ngx.say("stale: ", stale) + } +--- stream_response +value type: string +value: bar +flags: 72 +stale: true +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):13 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 10: incr int +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible = dogs:set("foo", 56) + if not ok then + ngx.say("failed to set: ", err) + return + end + for i = 1, 100 do + val, err = dogs:incr("foo", 2) + end + ngx.say("value: ", val) + ngx.say("err: ", err) + } +--- stream_response +value: 256 +err: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):11 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 11: incr double +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, err + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", 56) + for i = 1, 150 do + val, err = dogs:incr("foo", 2.1) + end + ngx.say("value: ", val) + ngx.say("err: ", err) + } +--- stream_response +value: 371 +err: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 12: set a string value +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:set("foo", "bar", 0, 72) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: string +value: bar +flags: 72 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 13: set a boolean value (true) +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:set("foo", true, 0, 5678) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: boolean +value: true +flags: 5678 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 14: set a boolean value (false) +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 100 do + dogs:set("foo", false, 0, 777) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: boolean +value: false +flags: 777 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 15: set a number value (int) +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 100 do + dogs:set("foo", 51203) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: number +value: 51203 +flags: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 16: set a number value (double) +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 100 do + dogs:set("foo", 3.1415926, 0, 78) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: number +value: 3.1415926 +flags: 78 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 17: set a number value and a nil +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 150 do + dogs:set("foo", 3.1415926, 0, 78) + dogs:set("foo", nil) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: nil +value: nil +flags: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 18: safe set a number value +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:flush_all() + dogs:flush_expired() + for i = 1, 100 do + dogs:safe_set("foo", 3.1415926, 0, 78) + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: number +value: 3.1415926 +flags: 78 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):8 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 19: add a string value +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:flush_all() + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:add("foo" .. i, "bar", 0, 72) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo100") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: string +value: bar +flags: 72 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):8 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 20: safe add a string value +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:flush_all() + dogs:flush_expired() + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:safe_add("foo" .. i, "bar", 0, 72) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo100") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: string +value: bar +flags: 72 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):9 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 21: replace a string value +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + dogs:set("foo", "hello") + local ok, err, forcible + for i = 1, 100 do + ok, err, forcible = dogs:replace("foo", "bar" .. i, 0, 72) + end + if not ok then + ngx.say("failed to set: ", err) + return + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: string +value: bar100 +flags: 72 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):8 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 22: set a number value and delete +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + -- local cd = ffi.cast("void *", dogs) + for i = 1, 150 do + dogs:set("foo", 3.1415926, 0, 78) + dogs:delete("foo") + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: nil +value: nil +flags: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/ +--- no_error_log +[error] + -- NYI: +stitch + + + +=== TEST 23: set nil key +--- stream_server_config + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + local ok, err = dogs:set(nil, "bar") + if not ok then + ngx.say("failed to set: ", err) + end + } +--- stream_response +failed to set: nil key +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 24: get nil key +--- stream_server_config + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + local value, err = dogs:get(nil, "bar") + if not ok then + ngx.say("failed to get: ", err) + end + } +--- stream_response +failed to get: nil key +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 25: get stale key +--- stream_server_config + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + local value, err = dogs:get_stale(nil, "bar") + if not ok then + ngx.say("failed to get stale: ", err) + end + } +--- stream_response +failed to get stale: nil key +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 26: incr key +--- stream_server_config + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + local value, err = dogs:incr(nil, 32) + if not value then + ngx.say("failed to incr: ", err) + end + } +--- stream_response +failed to incr: nil key +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 27: flush_all +--- stream_server_config + content_by_lua_block { + local ffi = require "ffi" + local val, flags + local dogs = ngx.shared.dogs + dogs:set("foo", "bah") + -- local cd = ffi.cast("void *", dogs) + for i = 1, 150 do + dogs:flush_all() + end + val, flags = dogs:get("foo") + ngx.say("value type: ", type(val)) + ngx.say("value: ", val) + ngx.say("flags: ", flags) + } +--- stream_response +value type: nil +value: nil +flags: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):7 loop\]/ +--- no_error_log +[error] + -- NYI: +stitch + + + +=== TEST 28: incr, value is not number +--- stream_server_config + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + local value, err = dogs:incr("foo", "bar") + if not value then + ngx.say("failed to incr: ", err) + end + } +--- error_log +cannot convert 'nil' to 'double' +--- no_error_log +[alert] +[crit] + + + +=== TEST 29: incr with init +--- stream_server_config + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + dogs:flush_all() + + local value, err = dogs:incr("foo", 10) + if not value then + ngx.say("failed to incr: ", err) + end + + local value, err, forcible = dogs:incr("foo", 10, 10) + if not value then + ngx.say("failed to incr: ", err) + return + end + + ngx.say("incr ok, value: ", value, ", forcible: ", forcible) + } +--- stream_response +failed to incr: not found +incr ok, value: 20, forcible: false +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 30: incr, init is not number +--- stream_server_config + content_by_lua_block { + local val, flags + local dogs = ngx.shared.dogs + local value, err = dogs:incr("foo", 10, "bar") + if not ok then + ngx.say("failed to incr: ", err) + end + } +--- error_log +number expected, got string +--- no_error_log +[alert] +[crit] + + + +=== TEST 31: capacity +--- stream_server_config + content_by_lua_block { + local cats = ngx.shared.cats + local capacity = cats:capacity() + ngx.say("capacity type: ", type(capacity)) + ngx.say("capacity: ", capacity) + } +--- stream_response +capacity type: number +capacity: 16384 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 32: free_space, empty (16k zone) +--- skip_nginx: 5: < 1.11.7 +--- stream_server_config + content_by_lua_block { + local cats = ngx.shared.cats + cats:flush_all() + cats:flush_expired() + local free_page_bytes = cats:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } +--- stream_response +free_page_bytes type: number +free_page_bytes: 4096 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 33: free_space, empty (100k zone) +--- skip_nginx: 5: < 1.11.7 +--- stream_server_config + content_by_lua_block { + local birds = ngx.shared.birds + birds:flush_all() + birds:flush_expired() + local free_page_bytes = birds:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } +--- stream_response_like chomp +\Afree_page_bytes type: number +free_page_bytes: (?:90112|94208) +\z +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 34: free_space, about half full, one page left +--- skip_nginx: 5: < 1.11.7 +--- stream_server_config + content_by_lua_block { + local cats = ngx.shared.cats + cats:flush_all() + cats:flush_expired() + for i = 1, 31 do + local key = string.format("key%05d", i) + local val = string.format("val%05d", i) + local success, err, forcible = cats:set(key, val) + if err ~= nil then + ngx.say(string.format("got error, i=%d, err=%s", i, err)) + end + if forcible then + ngx.say(string.format("got forcible, i=%d", i)) + end + if not success then + ngx.say(string.format("got not success, i=%d", i)) + end + end + local free_page_bytes = cats:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } +--- stream_response +free_page_bytes type: number +free_page_bytes: 4096 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 35: free_space, about half full, no page left +--- skip_nginx: 5: < 1.11.7 +--- stream_server_config + content_by_lua_block { + local cats = ngx.shared.cats + cats:flush_all() + cats:flush_expired() + for i = 1, 32 do + local key = string.format("key%05d", i) + local val = string.format("val%05d", i) + local success, err, forcible = cats:set(key, val) + if err ~= nil then + ngx.say(string.format("got error, i=%d, err=%s", i, err)) + end + if forcible then + ngx.say(string.format("got forcible, i=%d", i)) + end + if not success then + ngx.say(string.format("got not success, i=%d", i)) + end + end + local free_page_bytes = cats:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } +--- stream_response_like chomp +\Afree_page_bytes type: number +free_page_bytes: (?:0|4096) +\z +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 36: free_space, full +--- skip_nginx: 5: < 1.11.7 +--- stream_server_config + content_by_lua_block { + local cats = ngx.shared.cats + cats:flush_all() + cats:flush_expired() + for i = 1, 63 do + local key = string.format("key%05d", i) + local val = string.format("val%05d", i) + local success, err, forcible = cats:set(key, val) + if err ~= nil then + ngx.say(string.format("got error, i=%d, err=%s", i, err)) + end + if forcible then + ngx.say(string.format("got forcible, i=%d", i)) + end + if not success then + ngx.say(string.format("got not success, i=%d", i)) + end + end + local free_page_bytes = cats:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } +--- stream_response +free_page_bytes type: number +free_page_bytes: 0 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 37: free_space, got forcible +--- skip_nginx: 5: < 1.11.7 +--- stream_server_config + content_by_lua_block { + local cats = ngx.shared.cats + cats:flush_all() + cats:flush_expired() + for i = 1, 64 do + local key = string.format("key%05d", i) + local val = string.format("val%05d", i) + local success, err, forcible = cats:set(key, val) + if err ~= nil then + ngx.say(string.format("got error, i=%d, err=%s", i, err)) + end + if forcible then + ngx.say(string.format("got forcible, i=%d", i)) + end + if not success then + ngx.say(string.format("got not success, i=%d", i)) + end + end + local free_page_bytes = cats:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } +--- stream_response_like chomp +\A(?:got forcible, i=64 +)?free_page_bytes type: number +free_page_bytes: 0 +\z +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 38: free_space, full (100k) +--- skip_nginx: 5: < 1.11.7 +--- stream_server_config + content_by_lua_block { + local birds = ngx.shared.birds + birds:flush_all() + birds:flush_expired() + for i = 1, 1000 do + local key = string.format("key%05d", i) + local val = string.format("val%05d", i) + local ok, err, forcible = birds:set(key, val) + if err ~= nil then + ngx.say(string.format("got error, i=%d, err=%s", i, err)) + end + if forcible then + ngx.say(string.format("got forcible, i=%d", i)) + break + end + if not ok then + ngx.say(string.format("got not ok, i=%d", i)) + break + end + end + local free_page_bytes = birds:free_space() + ngx.say("free_page_bytes type: ", type(free_page_bytes)) + ngx.say("free_page_bytes: ", free_page_bytes) + } +--- stream_response_like chomp +\A(?:got forcible, i=736 +)?free_page_bytes type: number +free_page_bytes: (?:0|32768) +\z +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 39: incr bad init_ttl argument +--- stream_server_config + content_by_lua_block { + local dogs = ngx.shared.dogs + local pok, err = pcall(dogs.incr, dogs, "foo", 1, 0, -1) + if not pok then + ngx.say("not ok: ", err) + return + end + + ngx.say("ok") + } +--- stream_response +not ok: bad "init_ttl" argument +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 40: incr init_ttl argument is not a number +--- stream_server_config + content_by_lua_block { + local dogs = ngx.shared.dogs + local pok, err = pcall(dogs.incr, dogs, "foo", 1, 0, "bar") + if not pok then + ngx.say("not ok: ", err) + return + end + + ngx.say("ok") + } +--- stream_response +not ok: bad init_ttl arg: number expected, got string +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 41: incr init_ttl argument without init +--- stream_server_config + content_by_lua_block { + local dogs = ngx.shared.dogs + local pok, err = pcall(dogs.incr, dogs, "foo", 1, nil, 0.01) + if not pok then + ngx.say("not ok: ", err) + return + end + + ngx.say("ok") + } +--- stream_response +not ok: must provide "init" when providing "init_ttl" +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 42: incr key with init_ttl (key exists) +--- stream_server_config + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:set("foo", 32) + + local res, err = dogs:incr("foo", 10502, 0, 0.01) + ngx.say("incr: ", res, " ", err) + ngx.say("foo = ", dogs:get("foo")) + + ngx.update_time() + ngx.sleep(0.02) + + ngx.say("foo after incr init_ttl = ", dogs:get("foo")) + } +--- stream_response +incr: 10534 nil +foo = 10534 +foo after incr init_ttl = 10534 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 43: incr key with init and init_ttl (key not exists) +--- stream_server_config + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:flush_all() + + local res, err = dogs:incr("foo", 10502, 1, 0.01) + ngx.say("incr: ", res, " ", err) + ngx.say("foo = ", dogs:get("foo")) + + ngx.update_time() + ngx.sleep(0.02) + + ngx.say("foo after init_ttl = ", dogs:get("foo")) + } +--- stream_response +incr: 10503 nil +foo = 10503 +foo after init_ttl = nil +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 44: incr key with init and init_ttl as string (key not exists) +--- stream_server_config + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:flush_all() + + local res, err = dogs:incr("foo", 10502, 1, "0.01") + ngx.say("incr: ", res, " ", err) + ngx.say("foo = ", dogs:get("foo")) + + ngx.update_time() + ngx.sleep(0.02) + + ngx.say("foo after init_ttl = ", dogs:get("foo")) + } +--- stream_response +incr: 10503 nil +foo = 10503 +foo after init_ttl = nil +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 45: incr key with init and init_ttl (key expired and size matched) +--- stream_server_config + content_by_lua_block { + local dogs = ngx.shared.dogs + for i = 1, 100 do + dogs:set("bar" .. i, i, 0.02) + end + dogs:set("foo", 32, 0.02) + ngx.update_time() + ngx.sleep(0.03) + + local res, err = dogs:incr("foo", 10502, 0, 0.01) + ngx.say("incr: ", res, " ", err) + ngx.say("foo = ", dogs:get("foo")) + + ngx.update_time() + ngx.sleep(0.02) + + ngx.say("foo after init_ttl = ", dogs:get("foo")) + } +--- stream_response +incr: 10502 nil +foo = 10502 +foo after init_ttl = nil +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 46: incr key with init and init_ttl (forcibly override other valid entries) +--- stream_server_config + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:flush_all() + + local long_prefix = string.rep("1234567890", 100) + for i = 1, 1000 do + local success, err, forcible = dogs:set(long_prefix .. i, i) + if forcible then + dogs:delete(long_prefix .. i) + break + end + end + + local res, err, forcible = dogs:incr(long_prefix .. "bar", 10502, 0) + ngx.say("incr: ", res, " ", err, " ", forcible) + + local res, err, forcible = dogs:incr(long_prefix .. "foo", 10502, 0, 0.01) + ngx.say("incr: ", res, " ", err, " ", forcible) + ngx.say("foo = ", dogs:get(long_prefix .. "foo")) + + ngx.update_time() + ngx.sleep(0.02) + ngx.say("foo after init_ttl = ", dogs:get("foo")) + } +--- stream_response +incr: 10502 nil false +incr: 10502 nil true +foo = 10502 +foo after init_ttl = nil +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 47: exptime uses long type to avoid overflow in set() + ttl() +--- stream_server_config + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:flush_all() + + local ok, err = dogs:set("huge_ttl", true, 2 ^ 31) + if not ok then + ngx.say("err setting: ", err) + return + end + + local ttl, err = dogs:ttl("huge_ttl") + if not ttl then + ngx.say("err retrieving ttl: ", err) + return + end + + ngx.say("ttl: ", ttl) + } +--- stream_response +ttl: 2147483648 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 48: exptime uses long type to avoid overflow in expire() + ttl() +--- stream_server_config + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:flush_all() + + local ok, err = dogs:set("updated_huge_ttl", true) + if not ok then + ngx.say("err setting: ", err) + return + end + + local ok, err = dogs:expire("updated_huge_ttl", 2 ^ 31) + if not ok then + ngx.say("err expire: ", err) + return + end + + local ttl, err = dogs:ttl("updated_huge_ttl") + if not ttl then + ngx.say("err retrieving ttl: ", err) + return + end + + ngx.say("ttl: ", ttl) + } +--- stream_response +ttl: 2147483648 +--- no_error_log +[error] +[alert] +[crit] + + + +=== TEST 49: init_ttl uses long type to avoid overflow in incr() + ttl() +--- stream_server_config + content_by_lua_block { + local dogs = ngx.shared.dogs + dogs:flush_all() + + local ok, err = dogs:incr("incr_huge_ttl", 1, 0, 2 ^ 31) + if not ok then + ngx.say("err incr: ", err) + return + end + + local ttl, err = dogs:ttl("incr_huge_ttl") + if not ttl then + ngx.say("err retrieving ttl: ", err) + return + end + + ngx.say("ttl: ", ttl) + } +--- stream_response +ttl: 2147483648 +--- no_error_log +[error] +[alert] +[crit] diff --git a/t/stream/ssl.t b/t/stream/ssl.t new file mode 100644 index 000000000..17a881015 --- /dev/null +++ b/t/stream/ssl.t @@ -0,0 +1,2142 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +#worker_connections(10140); +#workers(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6 + 1); + +no_long_string(); +#no_diff(); + +env_to_nginx("PATH=" . $ENV{'PATH'}); +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$t::TestCore::Stream::lua_package_path"; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +run_tests(); + +__DATA__ + +=== TEST 1: clear certs +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + ssl.clear_certs() + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + 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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +failed to do SSL handshake: handshake failed + +--- error_log +lua ssl server name: "test.com" +sslv3 alert handshake failure + +--- no_error_log +[alert] +[emerg] + + + +=== TEST 2: set DER cert and private key +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/test.crt.der")) + local cert_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_cert(cert_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/test.key.der")) + local pkey_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + } + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + 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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 3: read SNI name via ssl.server_name() +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + print("read SNI name from Lua: ", ssl.server_name()) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + 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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +read SNI name from Lua: test.com + +--- no_error_log +[error] +[alert] + + + +=== TEST 4: read SNI name via ssl.server_name() when no SNI name specified +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local name = ssl.server_name(), + print("read SNI name from Lua: ", name, ", type: ", type(name)) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + 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, nil, true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log +read SNI name from Lua: nil, type: nil + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 5: read raw server addr via ssl.raw_server_addr() (unix domain socket) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local addr, addrtyp, err = ssl.raw_server_addr() + if not addr then + ngx.log(ngx.ERR, "failed to fetch raw server addr: ", err) + return + end + if addrtyp == "inet" then -- IPv4 + ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2), + byte(addr, 3), byte(addr, 4)) + print("Using IPv4 address: ", ip) + + elseif addrtyp == "inet6" then -- IPv6 + ip = string.format("%d.%d.%d.%d", byte(addr, 13), byte(addr, 14), + byte(addr, 15), byte(addr, 16)) + print("Using IPv6 address: ", ip) + + else -- unix + print("Using unix socket file ", addr) + end + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + 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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log eval +[ +'lua ssl server name: "test.com"', +qr/Using unix socket file .*?nginx\.sock/ +] + +--- no_error_log +[error] +[alert] +--- no_check_leak + + + +=== TEST 6: read raw server addr via ssl.raw_server_addr() (IPv4) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1 ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local byte = string.byte + + local addr, addrtyp, err = ssl.raw_server_addr() + if not addr then + ngx.log(ngx.ERR, "failed to fetch raw server addr: ", err) + return + end + if addrtyp == "inet" then -- IPv4 + ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2), + byte(addr, 3), byte(addr, 4)) + print("Using IPv4 address: ", ip) + + elseif addrtyp == "inet6" then -- IPv6 + ip = string.format("%d.%d.%d.%d", byte(addr, 13), byte(addr, 14), + byte(addr, 15), byte(addr, 16)) + print("Using IPv6 address: ", ip) + + else -- unix + print("Using unix socket file ", addr) + end + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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)) + + 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() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +Using IPv4 address: 127.0.0.1 + +--- no_error_log +[error] +[alert] +--- no_check_leak + + + +=== TEST 7: read raw server addr via ssl.raw_server_addr() (IPv6) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen [::1]:$TEST_NGINX_RAND_PORT_1 ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local byte = string.byte + + local addr, addrtyp, err = ssl.raw_server_addr() + if not addr then + ngx.log(ngx.ERR, "failed to fetch raw server addr: ", err) + return + end + if addrtyp == "inet" then -- IPv4 + ip = string.format("%d.%d.%d.%d", byte(addr, 1), byte(addr, 2), + byte(addr, 3), byte(addr, 4)) + print("Using IPv4 address: ", ip) + + elseif addrtyp == "inet6" then -- IPv6 + ip = string.format("%d.%d.%d.%d", byte(addr, 13), byte(addr, 14), + byte(addr, 15), byte(addr, 16)) + print("Using IPv6 address: ", ip) + + else -- unix + print("Using unix socket file ", addr) + end + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + local ok, err = sock:connect("[::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)) + + 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() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +Using IPv6 address: 0.0.0.1 + +--- no_error_log +[error] +[alert] +--- skip_eval: 6: system("ping6 -c 1 ::1 >/dev/null 2>&1") ne 0 +--- no_check_leak + + + +=== TEST 8: set DER cert chain +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain.der")) + local cert_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_cert(cert_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.der")) + local pkey_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + } + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + 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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 9: read PEM cert chain but set DER cert chain +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.cert_pem_to_der(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local ok, err = ssl.set_der_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.der")) + local pkey_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER private key: ", err) + return + end + } + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + 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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 10: tls version - SSLv3 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols SSLv3; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + lua_ssl_protocols SSLv3; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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 + 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 + } + +--- stream_response +connected: 1 +ssl handshake: boolean + +--- error_log +got TLS1 version: SSLv3, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 11: tls version - TLSv1 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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 + 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 + } + +--- stream_response +connected: 1 +ssl handshake: boolean + +--- error_log +got TLS1 version: TLSv1, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 12: tls version - TLSv1.1 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.1; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1.1; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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 + 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 + } + +--- stream_response +connected: 1 +ssl handshake: boolean + +--- error_log +got TLS1 version: TLSv1.1, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 13: tls version - TLSv1.2 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.2; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1.2; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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 + 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 + } + +--- stream_response +connected: 1 +ssl handshake: boolean + +--- error_log +got TLS1 version: TLSv1.2, + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 14: ngx.semaphore in ssl_certificate_by_lua* +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.2:$TEST_NGINX_RAND_PORT_1 ssl; + ssl_certificate_by_lua_block { + local semaphore = require "ngx.semaphore" + + local sema = assert(semaphore.new()) + + local function f() + assert(sema:wait(1)) + end + + local t = assert(ngx.thread.spawn(f)) + ngx.sleep(0.25) + + assert(sema:post()) + + assert(ngx.thread.wait(t)) + print("ssl cert by lua done") + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.2; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1.2; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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 + 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 + } + +--- stream_response +connected: 1 +ssl handshake: boolean + +--- grep_error_log eval: qr/stream lua semaphore (?:wait yielding|\w[^:,]*)/ +--- grep_error_log_out +stream lua semaphore new +stream lua semaphore wait +stream lua semaphore wait yielding +stream lua semaphore post +--- error_log +ssl cert by lua done + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 15: read PEM key chain but set DER key chain +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.cert_pem_to_der(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local ok, err = ssl.set_der_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.pem")) + local pkey_data = f:read("*a") + f:close() + + pkey_data, err = ssl.priv_key_pem_to_der(pkey_data) + if not pkey_data then + ngx.log(ngx.ERR, "failed to convert pem key to der key: ", err) + return + end + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + return + end + } + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + 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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 16: parse PEM cert and key to cdata +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = ssl.set_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.pem")) + local pkey_data = f:read("*a") + f:close() + + local pkey, err = ssl.parse_pem_priv_key(pkey_data) + if not pkey then + ngx.log(ngx.ERR, "failed to parse pem key: ", err) + return + end + + local ok, err = ssl.set_priv_key(pkey) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + return + end + } + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + 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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 17: parse PEM cert and key to cdata (bad cert 0 in the chain) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain-bad0.pem")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = ssl.set_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.pem")) + local pkey_data = f:read("*a") + f:close() + + local pkey, err = ssl.parse_pem_priv_key(pkey_data) + if not pkey then + ngx.log(ngx.ERR, "failed to parse pem key: ", err) + return + end + + local ok, err = ssl.set_priv_key(pkey) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + return + end + } + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + 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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +failed to do SSL handshake: handshake failed + +--- error_log eval +qr/\[error\] .*? failed to parse pem cert: PEM_read_bio_X509_AUX\(\) failed/ + +--- no_error_log +[alert] +[emerg] +[crit] + + + +=== TEST 18: parse PEM cert and key to cdata (bad cert 2 in the chain) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain-bad2.pem")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = ssl.set_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com.key.pem")) + local pkey_data = f:read("*a") + f:close() + + local pkey, err = ssl.parse_pem_priv_key(pkey_data) + if not pkey then + ngx.log(ngx.ERR, "failed to parse pem key: ", err) + return + end + + local ok, err = ssl.set_priv_key(pkey) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + return + end + } + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + 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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +failed to do SSL handshake: handshake failed + +--- error_log eval +qr/\[error\] .*? failed to parse pem cert: PEM_read_bio_X509\(\) failed/ + +--- no_error_log +[alert] +[emerg] +[crit] + + + +=== TEST 19: parse PEM cert and key to cdata (bad priv key) +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/chain/chain.pem")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = ssl.set_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set cert: ", err) + return + end + + local f = assert(io.open("t/cert/chain/test-com-bad.key.pem")) + local pkey_data = f:read("*a") + f:close() + + local pkey, err = ssl.parse_pem_priv_key(pkey_data) + if not pkey then + ngx.log(ngx.ERR, "failed to parse pem key: ", err) + return + end + + local ok, err = ssl.set_priv_key(pkey) + if not ok then + ngx.log(ngx.ERR, "failed to set private key: ", err) + return + end + } + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + 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) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +failed to do SSL handshake: handshake failed + +--- error_log eval +qr/\[error\] .*? failed to parse pem key: PEM_read_bio_PrivateKey\(\) failed/ + +--- no_error_log +[alert] +[emerg] +[crit] + + + +=== TEST 20: read client addr via ssl.raw_client_addr() +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1 ssl; + ssl_certificate_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; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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, nil, true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log +client ip: 127.0.0.1 + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 21: yield during doing handshake with client which uses low version OpenSSL +--- no_check_leak +--- stream_config + lua_shared_dict done 16k; + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + server { + listen $TEST_NGINX_RAND_PORT_1 ssl; + ssl_session_tickets off; + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/test.crt.der")) + local cert_data = f:read("*a") + f:close() + + ngx.sleep(0.01) -- yield + + local ok, err = ssl.set_der_cert(cert_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/test.key.der")) + local pkey_data = f:read("*a") + f:close() + + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + } + + content_by_lua_block { + ngx.shared.done:set("handshake", true) + } + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + + content_by_lua_block { + ngx.shared.done:delete("handshake") + local addr = ngx.var.addr; + local req = "'GET / HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n'" + local f, err = io.popen("echo -n " .. req .. " | timeout 3s openssl s_client -connect 127.0.0.1:$TEST_NGINX_RAND_PORT_1") + if not f then + ngx.say(err) + return + end + + local step = 0.001 + while step < 2 do + ngx.sleep(step) + step = step * 2 + + if ngx.shared.done:get("handshake") then + local out = f:read('*a') + ngx.log(ngx.INFO, out) + ngx.say("ok") + f:close() + return + end + end + + ngx.log(ngx.ERR, "openssl client handshake timeout") + } + +--- stream_response +ok +--- error_log eval +[ +qr/content_by_lua\(nginx\.conf:\d+\):\d+: CONNECTED/, +qr/subject=\/?C(?\s?=\s?)US(?\/|,\s)ST\kCalifornia\kL\kSan Francisco\kO\kOpenResty\kOU\kOpenResty\kCN\ktest\.com\kemailAddress\kagentzh\@gmail\.com/, +] + +--- no_error_log +[error] +[alert] +--- timeout: 5 + + + +=== TEST 22: tls version - TLSv1.3 +--- skip_openssl: 6: < 1.1.1 +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.3; + + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ver, err = ssl.get_tls1_version_str(resp) + if not ver then + ngx.log(ngx.ERR, "failed to get TLS1 version: ", err) + return + end + ngx.log(ngx.WARN, "got TLS1 version: ", ver) + } + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/test.crt; + lua_ssl_verify_depth 3; + lua_ssl_protocols TLSv1.3; + + 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(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 + } + +--- stream_response +connected: 1 +ssl handshake: boolean +--- error_log +got TLS1 version: TLSv1.3, +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 23: verify client with CA certificates +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local f = assert(io.open("t/cert/test.crt", "rb")) + local cert_data = f:read("*all") + f:close() + + local cert = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = ssl.verify_client(cert, 1) + if not ok then + ngx.log(ngx.ERR, "failed to verify client: ", err) + return + end + } + + content_by_lua_block { + print('client certificate subject: ', ngx.var.ssl_client_s_dn) + ngx.say(ngx.var.ssl_client_verify) + } + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_certificate ../../cert/test.crt; + proxy_ssl_certificate_key ../../cert/test.key; + proxy_ssl_session_reuse off; + +--- stream_response +SUCCESS + +--- error_log +client certificate subject: emailAddress=agentzh@gmail.com,CN=test.com + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 24: verify client without CA certificates +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local ok, err = ssl.verify_client() + if not ok then + ngx.log(ngx.ERR, "failed to verify client: ", err) + return + end + } + + content_by_lua_block { + print('client certificate subject: ', ngx.var.ssl_client_s_dn) + ngx.say(ngx.var.ssl_client_verify) + } + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_certificate ../../cert/test.crt; + proxy_ssl_certificate_key ../../cert/test.key; + proxy_ssl_session_reuse off; + +--- stream_response +FAILED:self signed certificate + +--- error_log +client certificate subject: emailAddress=agentzh@gmail.com,CN=test.com + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 25: verify client but client provides no certificate +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local f = assert(io.open("t/cert/test.crt", "rb")) + local cert_data = f:read("*all") + f:close() + + local cert = ssl.parse_pem_cert(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to parse pem cert: ", err) + return + end + + local ok, err = ssl.verify_client(cert, 1) + if not ok then + ngx.log(ngx.ERR, "failed to verify client: ", err) + return + end + } + + content_by_lua_block { + print('client certificate subject: ', ngx.var.ssl_client_s_dn) + ngx.say(ngx.var.ssl_client_verify) + } + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_session_reuse off; + +--- stream_response +NONE + +--- error_log +client certificate subject: nil + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 26: private key protected by passphrase +--- stream_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + ssl.clear_certs() + + local f = assert(io.open("t/cert/test_passphrase.crt")) + local cert_data = f:read("*a") + f:close() + + local cert, err = ssl.cert_pem_to_der(cert_data) + if not cert then + ngx.log(ngx.ERR, "failed to convert pem cert to der cert: ", err) + return + end + + local ok, err = ssl.set_der_cert(cert) + if not ok then + ngx.log(ngx.ERR, "failed to set DER cert: ", err) + return + end + + local f = assert(io.open("t/cert/test_passphrase.key")) + local pkey_data = f:read("*a") + f:close() + + pkey_data, err = ssl.priv_key_pem_to_der(pkey_data, "123456") + if not pkey_data then + ngx.log(ngx.ERR, "failed to convert pem key to der key: ", err) + return + end + + local ok, err = ssl.set_der_priv_key(pkey_data) + if not ok then + ngx.log(ngx.ERR, "failed to set DER private key: ", err) + return + end + } + ssl_certificate ../../cert/test2.crt; + ssl_certificate_key ../../cert/test2.key; + + return 'it works!\n'; + } +--- stream_server_config + lua_ssl_trusted_certificate ../../cert/chain/root-ca.crt; + lua_ssl_verify_depth 3; + + 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") + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + 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() + } + +--- stream_response +connected: 1 +ssl handshake: userdata +received: it works! +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] +[emerg] diff --git a/t/stream/time.t b/t/stream/time.t new file mode 100644 index 000000000..5603bb33d --- /dev/null +++ b/t/stream/time.t @@ -0,0 +1,204 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore::Stream; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6); + +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.now() +--- stream_server_config + content_by_lua_block { + local t + for i = 1, 500 do + t = ngx.now() + end + ngx.sleep(0.10) + local elapsed = ngx.now() - t + ngx.say(t > 1399867351) + ngx.say(">= 0.099: ", elapsed >= 0.099) + ngx.say("< 0.11: ", elapsed < 0.11) + -- ngx.say(t, " ", elapsed) + } +--- stream_response +true +>= 0.099: true +< 0.11: true + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 2: ngx.time() +--- stream_server_config + content_by_lua_block { + local t + for i = 1, 500 do + t = ngx.time() + end + ngx.say(t > 1400960598) + local diff = os.time() - t + ngx.say("<= 1: ", diff <= 1) + } +--- stream_response +true +<= 1: true + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 3: ngx.update_time() +--- stream_server_config + content_by_lua_block { + local start = ngx.now() + for _ = 1, 1e5 do + ngx.update_time() + end + ngx.say(ngx.now() - start > 0) + } +--- stream_response +true +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 4: ngx.today() +--- stream_server_config + content_by_lua_block { + local t + for i = 1, 500 do + t = ngx.today() + end + ngx.say(t) + } +--- stream_response_like: ^\d{4}-\d{2}-\d{2} +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 5: ngx.localtime() +--- stream_server_config + content_by_lua_block { + local t + for i = 1, 500 do + t = ngx.localtime() + end + ngx.say(t) + } +--- stream_response_like: ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$ +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 6: ngx.utctime() +--- stream_server_config + content_by_lua_block { + local t + for i = 1, 500 do + t = ngx.utctime() + end + ngx.say(t) + } +--- stream_response_like: ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$ +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 7: "resty.core.time".monotonic_msec +--- stream_server_config + content_by_lua_block { + local cur_msec = require "resty.core.time".monotonic_msec + local proc = io.open("/proc/uptime", "r") + local content = proc:read() + proc:close() + local idx = string.find(content, " ", 1, true) + local uptime = 1000 * tonumber(string.sub(content, 1, idx - 1)) + ngx.update_time() + + local t + for i = 1, 500 do + t = cur_msec() + end + ngx.say(t >= uptime) + local diff = t - uptime + ngx.say("< 10: ", diff < 10) + } +--- stream_response +true +< 10: true + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):11 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 8: "resty.core.time".monotonic_time +--- stream_server_config + content_by_lua_block { + local cur_time = require "resty.core.time".monotonic_time + local proc = io.open("/proc/uptime", "r") + local content = proc:read() + proc:close() + local idx = string.find(content, " ", 1, true) + local uptime = tonumber(string.sub(content, 1, idx - 1)) + ngx.update_time() + + local t + for i = 1, 500 do + t = cur_time() + end + ngx.say(t >= uptime) + local diff = t - uptime + ngx.say("< 0.1: ", diff < 0.1) + } +--- stream_response +true +< 0.1: true + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):11 loop\]/ +--- no_error_log +[error] +bad argument type +stitch diff --git a/t/time.t b/t/time.t new file mode 100644 index 000000000..ba2df01ed --- /dev/null +++ b/t/time.t @@ -0,0 +1,400 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6); + +#no_diff(); +no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.now() +--- config + location = /t { + access_log off; + content_by_lua_block { + local t + for i = 1, 30 do + t = ngx.now() + end + ngx.sleep(0.10) + local elapsed = ngx.now() - t + ngx.say(t > 1399867351) + ngx.say(">= 0.099: ", elapsed >= 0.099) + ngx.say("< 0.11: ", elapsed < 0.11) + -- ngx.say(t, " ", elapsed) + } + } +--- request +GET /t +--- response_body +true +>= 0.099: true +< 0.11: true + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 2: ngx.time() +--- config + location = /t { + access_log off; + content_by_lua_block { + local t + for i = 1, 30 do + t = ngx.time() + end + ngx.say(t > 1400960598) + local diff = os.time() - t + ngx.say("<= 1: ", diff <= 1) + } + } +--- request +GET /t +--- response_body +true +<= 1: true + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 3: ngx.update_time() +--- config + location = /t { + content_by_lua_block { + local start = ngx.now() + for _ = 1, 1e5 do + ngx.update_time() + end + ngx.say(ngx.now() - start > 0) + } + } +--- request +GET /t +--- response_body +true +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 4: ngx.today() +--- config + location = /t { + content_by_lua_block { + local t + for i = 1, 30 do + t = ngx.today() + end + ngx.say(t) + } + } +--- request +GET /t +--- response_body_like: ^\d{4}-\d{2}-\d{2} +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 5: ngx.localtime() +--- config + location = /t { + content_by_lua_block { + local t + for i = 1, 30 do + t = ngx.localtime() + end + ngx.say(t) + } + } +--- request +GET /t +--- response_body_like: ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$ +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 6: ngx.utctime() +--- config + location = /t { + content_by_lua_block { + local t + for i = 1, 30 do + t = ngx.utctime() + end + ngx.say(t) + } + } +--- request +GET /t +--- response_body_like: ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$ +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 7: ngx.cookie_time() +--- config + location /t { + content_by_lua_block { + local t + for i = 1, 30 do + t = ngx.cookie_time(1290079655) + end + ngx.say(t) + ngx.say(ngx.cookie_time(2200000000)) + } + } +--- request +GET /t +--- response_body +Thu, 18-Nov-10 11:27:35 GMT +Sun, 18-Sep-2039 23:06:40 GMT +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 8: ngx.cookie_time() bad argument +--- config + location /t { + content_by_lua_block { + local pok, err = pcall(ngx.cookie_time, "foo") + if not pok then + ngx.say("not ok: ", err) + return + end + + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +not ok: number argument only +--- no_error_log +[error] +[alert] +bad argument type +stitch + + + +=== TEST 9: ngx.http_time() +--- config + location /t { + content_by_lua_block { + local t + for i = 1, 30 do + t = ngx.http_time(1290079655) + end + ngx.say(t) + } + } +--- request +GET /t +--- response_body +Thu, 18 Nov 2010 11:27:35 GMT +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 10: ngx.http_time() bad argument +--- config + location /t { + content_by_lua_block { + t = ngx.http_time(1290079655) + local pok, err = pcall(ngx.http_time, "foo") + if not pok then + ngx.say("not ok: ", err) + return + end + + ngx.say("ok") + + } + } +--- request +GET /t +--- response_body +not ok: number argument only +--- no_error_log +[error] +[alert] +bad argument type +stitch + + + +=== TEST 11: ngx.parse_http_time() +--- config + location /t { + content_by_lua_block { + local t + for i = 1, 30 do + t = ngx.parse_http_time("Thu, 18 Nov 2010 11:27:35 GMT") + end + ngx.say(t) + ngx.say(ngx.parse_http_time("Thu, Nov 2010")) + } + } +--- request +GET /t +--- response_body +1290079655 +nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 12: ngx.parse_http_time() bad argument +--- config + location /t { + content_by_lua_block { + t = ngx.http_time(1290079655) + local pok, err = pcall(ngx.parse_http_time, 123) + if not pok then + ngx.say("not ok: ", err) + return + end + + ngx.say("ok") + + } + } +--- request +GET /t +--- response_body +not ok: string argument only +--- no_error_log +[error] +[alert] +bad argument type +stitch + + + +=== TEST 13: "resty.core.time".monotonic_msec +--- config + location = /t { + access_log off; + content_by_lua_block { + local cur_msec = require "resty.core.time".monotonic_msec + local proc = io.open("/proc/uptime", "r") + local content = proc:read() + proc:close() + local idx = string.find(content, " ", 1, true) + local uptime = 1000 * tonumber(string.sub(content, 1, idx - 1)) + ngx.update_time() + + local t + for i = 1, 30 do + t = cur_msec() + end + ngx.say(t >= uptime) + local diff = t - uptime + ngx.say("< 10: ", diff < 10) + } + } +--- request +GET /t +--- response_body +true +< 10: true + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):11 loop\]/ +--- no_error_log +[error] +bad argument type +stitch + + + +=== TEST 14: "resty.core.time".monotonic_time +--- config + location = /t { + access_log off; + content_by_lua_block { + local cur_time = require "resty.core.time".monotonic_time + local proc = io.open("/proc/uptime", "r") + local content = proc:read() + proc:close() + local idx = string.find(content, " ", 1, true) + local uptime = tonumber(string.sub(content, 1, idx - 1)) + ngx.update_time() + + local t + for i = 1, 30 do + t = cur_time() + end + ngx.say(t >= uptime) + local diff = t - uptime + ngx.say("< 0.01: ", diff < 0.01) + } + } +--- request +GET /t +--- response_body +true +< 0.01: true + +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):11 loop\]/ +--- no_error_log +[error] +bad argument type +stitch diff --git a/t/uri.t b/t/uri.t new file mode 100644 index 000000000..97630f7c7 --- /dev/null +++ b/t/uri.t @@ -0,0 +1,231 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 4); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: unescape_uri (string) +--- config + location = /uri { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.unescape_uri("hello%20world") + end + ngx.say(s) + } + } +--- request +GET /uri +--- response_body +hello world +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 2: unescape_uri (nil) +--- config + location = /uri { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.unescape_uri(nil) + end + ngx.say(s) + } + } +--- request +GET /uri +--- response_body eval: "\n" +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 3: unescape_uri (number) +--- config + location = /uri { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.unescape_uri(3.14) + end + ngx.say(s) + } + } +--- request +GET /uri +--- response_body +3.14 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 4: escape_uri (string, escaped) +--- config + location = /uri { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.escape_uri("hello world") + end + ngx.say(s) + } + } +--- request +GET /uri +--- response_body +hello%20world +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 5: escape_uri (string, no escaped) +--- config + location = /uri { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.escape_uri("helloworld") + end + ngx.say(s) + } + } +--- request +GET /uri +--- response_body +helloworld +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 6: escape_uri (nil) +--- config + location = /uri { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.escape_uri(nil) + end + ngx.say(s) + } + } +--- request +GET /uri +--- response_body eval: "\n" +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 7: escape_uri (number) +--- config + location = /uri { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.escape_uri(3.14) + end + ngx.say(s) + } + } +--- request +GET /uri +--- response_body +3.14 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 8: escape_uri (larger than 4k, nothing to be escaped) +--- config + location = /uri { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.escape_uri(string.rep("a", 4097)) + end + ngx.say(s) + } + } +--- request +GET /uri +--- response_body eval: "a" x 4097 . "\n" +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 9: escape_uri (a little smaller than 4k, need to be escaped) +--- config + location = /uri { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.escape_uri(string.rep(" ", 1365)) + end + ngx.say(s) + } + } +--- request +GET /uri +--- response_body eval: "%20" x 1365 . "\n" +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + + + +=== TEST 10: escape_uri (a little bigger than 4k, need to be escaped) +--- config + location = /uri { + content_by_lua_block { + local s + for i = 1, 30 do + s = ngx.escape_uri(string.rep(" ", 1366)) + end + ngx.say(s) + } + } +--- request +GET /uri +--- response_body eval: "%20" x 1366 . "\n" +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] diff --git a/t/utils.t b/t/utils.t new file mode 100644 index 000000000..88a360aa1 --- /dev/null +++ b/t/utils.t @@ -0,0 +1,115 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +plan tests => repeat_each() * blocks() * 3; + +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"); + } +}); + +no_diff(); +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: utils.str_replace_char() sanity (replaces a single character) +--- config + location /t { + content_by_lua_block { + local utils = require "resty.core.utils" + + local strings = { + "Header_Name", + "_Header_Name_", + "Header__Name", + "Header-Name", + "Hello world", + } + + for i = 1, #strings do + ngx.say(utils.str_replace_char(strings[i], "_", "-")) + end + } + } +--- response_body +Header-Name +-Header-Name- +Header--Name +Header-Name +Hello world + + + +=== TEST 2: utils.str_replace_char() JIT compiles when match +--- config + location /t { + content_by_lua_block { + local utils = require "resty.core.utils" + + for i = 1, $TEST_NGINX_HOTLOOP * 10 do + utils.str_replace_char("Header_Name", "_", "-") + end + } + } +--- ignore_response_body +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/, +--- no_error_log +[error] + + + +=== TEST 3: utils.str_replace_char() JIT compiles when no match +--- config + location /t { + content_by_lua_block { + local utils = require "resty.core.utils" + + for i = 1, $TEST_NGINX_HOTLOOP * 10 do + utils.str_replace_char("Header_Name", "-", "_") + end + } + } +--- ignore_response_body +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/, +--- no_error_log +[error] + + + +=== TEST 4: utils.str_replace_char() replacing more than one character is not supported +--- config + location /t { + content_by_lua_block { + local utils = require "resty.core.utils" + + local strings = { + "Header01Name", + "01Header01Name01", + "Header0Name", + "Header1Name", + "Hello world", + } + + for i = 1, #strings do + ngx.say(utils.str_replace_char(strings[i], "02", "02")) + end + } + } +--- response_body +Header01Name +01Header01Name01 +Header0Name +Header1Name +Hello world diff --git a/t/var.t b/t/var.t new file mode 100644 index 000000000..8364613ae --- /dev/null +++ b/t/var.t @@ -0,0 +1,213 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 5); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: get normal var +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ffi = require "ffi" + local val + for i = 1, 30 do + val = ngx.var.foo + end + ngx.say("value: ", val) + } + } +--- request +GET /t +--- response_body +value: hello +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: (?!return to lower frame) + + + +=== TEST 2: get normal var (case) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ffi = require "ffi" + local val + for i = 1, 30 do + val = ngx.var.FOO + end + ngx.say("value: ", val) + } + } +--- request +GET /t +--- response_body +value: hello +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: (?!return to lower frame) + + + +=== TEST 3: get capturing var (bad) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ffi = require "ffi" + local val + for i = 1, 30 do + val = ngx.var[0] + end + ngx.say("value: ", val) + } + } +--- request +GET /t +--- response_body +value: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 4: get capturing var +--- config + location ~ '^(/t)' { + set $foo hello; + content_by_lua_block { + local ffi = require "ffi" + local val + for i = 1, 30 do + val = ngx.var[1] + end + ngx.say("value: ", val) + } + } +--- request +GET /t +--- response_body +value: /t +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: (?!return to lower frame) + + + +=== TEST 5: set normal var (string value) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ffi = require "ffi" + local val = "hello" + for i = 1, 30 do + ngx.var.foo = val + end + ngx.say("value: ", val) + } + } +--- request +GET /t +--- response_body +value: hello +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 6: set normal var (nil value) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ffi = require "ffi" + for i = 1, 30 do + ngx.var.foo = nil + end + ngx.say("value: ", ngx.var.foo) + } + } +--- request +GET /t +--- response_body +value: nil +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 7: set normal var (number value) +--- config + location = /t { + set $foo hello; + content_by_lua_block { + local ffi = require "ffi" + for i = 1, 30 do + ngx.var.foo = i + end + ngx.say("value: ", ngx.var.foo) + } + } +--- request +GET /t +--- response_body +value: 30 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):3 loop\]/ +--- no_error_log +[error] + -- NYI: + + + +=== TEST 8: error buffer overread +--- config + location = /test { + content_by_lua_block { + local ok1, err1 = pcall(function () ngx.var.foo = 32; end) + local ok2, err2 = pcall(function () ngx.var.server_port = 32; end) + assert(not ok1) + ngx.say(err1) + assert(not ok2) + ngx.say(err2) + } + } +--- request +GET /test +--- response_body_like +content_by_lua\(nginx\.conf:\d+\):\d+: variable "foo" not found for writing; maybe it is a built-in variable that is not changeable or you forgot to use "set \$foo '';" in the config file to define it first +content_by_lua\(nginx\.conf:\d+\):\d+: variable "server_port" not changeable +--- no_error_log +[error] +[alert] + -- NYI: diff --git a/t/worker-count-5.t b/t/worker-count-5.t new file mode 100644 index 000000000..aa801bae3 --- /dev/null +++ b/t/worker-count-5.t @@ -0,0 +1,43 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +workers(5); +#worker_connections(1014); +#master_process_enabled(1); +master_on(); +#log_level('warn'); + +repeat_each(3); + +plan tests => repeat_each() * (blocks() * 6); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.worker.count +--- config + location = /t { + content_by_lua_block { + local v + local count = ngx.worker.count + for i = 1, 30 do + v = count() + end + ngx.say("workers: ", v) + } + } +--- request +GET /t +--- response_body +workers: 5 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + stitch diff --git a/t/worker.t b/t/worker.t new file mode 100644 index 000000000..d0917e32c --- /dev/null +++ b/t/worker.t @@ -0,0 +1,149 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestCore; + +#worker_connections(1014); +#master_process_enabled(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6 - 3); + +#no_diff(); +#no_long_string(); +check_accum_error_log(); +run_tests(); + +__DATA__ + +=== TEST 1: ngx.worker.exiting +--- config + location = /t { + content_by_lua_block { + local v + local exiting = ngx.worker.exiting + for i = 1, 30 do + v = exiting() + end + ngx.say(v) + } + } +--- request +GET /t +--- response_body +false +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + stitch + + + +=== TEST 2: ngx.worker.pid +--- config + location = /t { + content_by_lua_block { + local v + local pid = ngx.worker.pid + for i = 1, 30 do + v = pid() + end + ngx.say(v == tonumber(ngx.var.pid)) + ngx.say(v) + } + } +--- request +GET /t +--- response_body_like chop +^true +\d+$ +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + stitch + + + +=== TEST 3: ngx.worker.id +--- config + location = /t { + content_by_lua_block { + local v + local id = ngx.worker.id + for i = 1, 30 do + v = id() + end + ngx.say("worker id: ", v) + } + } +--- request +GET /t +--- response_body_like chop +^worker id: [0-1]$ +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + stitch +--- skip_nginx: 3: <=1.9.0 + + + +=== TEST 4: ngx.worker.count +--- config + location = /t { + content_by_lua_block { + local v + local count = ngx.worker.count + for i = 1, 30 do + v = count() + end + ngx.say("workers: ", v) + } + } +--- request +GET /t +--- response_body +workers: 1 +--- error_log eval +qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):4 loop\]/ +--- no_error_log +[error] + -- NYI: + stitch + + + +=== TEST 5: ngx.worker.pids +--- config + location /lua { + content_by_lua_block { + 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/valgrind.suppress b/valgrind.suppress new file mode 100644 index 000000000..33be300b0 --- /dev/null +++ b/valgrind.suppress @@ -0,0 +1,115 @@ +{ + + Memcheck:Leak + fun:malloc + fun:ngx_alloc + fun:ngx_event_process_init +} +{ + + Memcheck:Param + epoll_ctl(event) + fun:epoll_ctl + fun:ngx_epoll_add_event +} +{ + + Memcheck:Cond + fun:index + fun:expand_dynamic_string_token + fun:_dl_map_object + fun:map_doit + fun:_dl_catch_error + fun:do_preload + fun:dl_main + fun:_dl_sysdep_start + fun:_dl_start +} +{ + + Memcheck:Param + epoll_ctl(event) + fun:epoll_ctl + fun:ngx_epoll_init + fun:ngx_event_process_init +} +{ + + Memcheck:Param + epoll_ctl(event) + fun:epoll_ctl + fun:ngx_epoll_notify_init + fun:ngx_epoll_init + fun:ngx_event_process_init +} +{ + + Memcheck:Param + epoll_ctl(event) + fun:epoll_ctl + fun:ngx_epoll_add_connection + fun:ngx_event_connect_peer +} +{ + + Memcheck:Param + epoll_ctl(event) + fun:epoll_ctl + fun:ngx_epoll_test_rdhup +} +{ + + Memcheck:Param + epoll_pwait(sigmask) + fun:epoll_pwait +} +{ + + Memcheck:Cond + obj:* +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:ngx_alloc + fun:ngx_set_environment + fun:ngx_single_process_cycle +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:ngx_alloc + fun:ngx_set_environment + fun:ngx_worker_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 +}