diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..5f95a108 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: pintsized diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..1fe8a0b4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,66 @@ +name: Test + +on: [push, pull_request] + +jobs: + luacheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: leafo/gh-actions-lua@v8 + with: + luaVersion: "luajit-openresty" + - uses: leafo/gh-actions-luarocks@v4 + - run: luarocks install luacheck + - run: luacheck lib + + run_tests: + strategy: + matrix: + openresty_version: + - 1.17.8.2 + - 1.19.9.1 + - 1.21.4.3 + - 1.25.3.1 + + runs-on: ubuntu-latest + container: + image: openresty/openresty:${{ matrix.openresty_version }}-alpine-fat + # --init runs tinit as PID 1 and prevents the 'WARNING: killing the child process' spam from the test suite + options: --init + + steps: + - uses: actions/checkout@v2 + - name: Install deps + run: | + apk add --no-cache curl perl bash wget git perl-dev libarchive-tools nodejs + ln -s /usr/bin/bsdtar /usr/bin/tar + + - name: Install CPAN + run: curl -s -L http://xrl.us/cpanm > /bin/cpanm && chmod +x /bin/cpanm + + - name: Cache + uses: actions/cache@v2 + with: + path: | + ~/.cpan + ~/.cache + key: ${{ runner.os }}-${{ matrix.openresty_version }}-cache + + - name: Install Test::Nginx + run: cpanm -q -n Test::Nginx + + - name: Install Luacov + run: | + /usr/local/openresty/luajit/bin/luarocks install luacov + /usr/local/openresty/luajit/bin/luarocks install lua-resty-openssl + + - uses: actions/checkout@v2 + + - name: Run tests + run: make coverage + + - name: Coverage + run: | + luacov + tail -n 8 luacov.report.out diff --git a/.gitignore b/.gitignore index 51b26b94..44834f8e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ t/servroot/ t/error.log -lua-resty-http* luacov* +lua-resty-http-*/ +lua-resty-http-*.src.rock +lua-resty-http-*.tar.gz diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 00000000..77ab5dfd --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,2 @@ +std = "ngx_lua" +redefined = false diff --git a/.luacov b/.luacov new file mode 100644 index 00000000..cd070eeb --- /dev/null +++ b/.luacov @@ -0,0 +1,5 @@ +modules = { + ["http"] = "lib/resty/http.lua", + ["http_headers"] = "lib/resty/http_headers.lua", + ["http_connect"] = "lib/resty/http_connect.lua", +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e098e949..00000000 --- a/.travis.yml +++ /dev/null @@ -1,67 +0,0 @@ -sudo: required -dist: trusty - -os: linux - -language: c - -compiler: - - gcc - - clang - -services: - - redis-server - -cache: - directories: - - download-cache - -env: - global: - - JOBS=3 - - 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 - - OPENSSL_PREFIX=/opt/ssl - - OPENSSL_LIB=$OPENSSL_PREFIX/lib - - OPENSSL_INC=$OPENSSL_PREFIX/include - - OPENSSL_VER=1.0.2h - - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH - - TEST_NGINX_SLEEP=0.006 - matrix: - - NGINX_VERSION=1.9.15 -# - NGINX_VERSION=1.10.0 - -install: - - if [ ! -d download-cache ]; then mkdir download-cache; fi - - if [ ! -f download-cache/openssl-$OPENSSL_VER.tar.gz ]; then wget -O download-cache/openssl-$OPENSSL_VER.tar.gz https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz; fi - - sudo apt-get install -qq -y cpanminus axel - - sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1) - - wget http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz - - git clone https://github.com/openresty/openresty.git ../openresty - - git clone https://github.com/openresty/nginx-devel-utils.git - - git clone https://github.com/openresty/lua-cjson.git - - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module - - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module - - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx - - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git - -script: - - cd luajit2/ - - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT' > 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 ../lua-cjson && make && sudo PATH=$PATH make install && cd .. - - tar zxf download-cache/openssl-$OPENSSL_VER.tar.gz - - cd openssl-$OPENSSL_VER/ - - ./config shared --prefix=$OPENSSL_PREFIX -DPURIFY > build.log 2>&1 || (cat build.log && exit 1) - - make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1) - - sudo make PATH=$PATH install_sw > build.log 2>&1 || (cat build.log && exit 1) - - cd .. - - export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH - - export NGX_BUILD_CC=$CC - - ngx-build $NGINX_VERSION --with-ipv6 --with-http_realip_module --with-http_ssl_module --add-module=../echo-nginx-module --add-module=../lua-nginx-module --with-debug - - nginx -V - - ldd `which nginx`|grep -E 'luajit|ssl|pcre' - - prove -r t diff --git a/Makefile b/Makefile index d9539f2a..95894718 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,16 @@ OPENRESTY_PREFIX=/usr/local/openresty -PREFIX ?= /usr/local +PREFIX ?= /usr/local LUA_INCLUDE_DIR ?= $(PREFIX)/include -LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) -INSTALL ?= install -TEST_FILE ?= t +LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) +INSTALL ?= install +TEST_FILE ?= t + +export PATH := $(OPENRESTY_PREFIX)/nginx/sbin:$(PATH) +PROVE=TEST_NGINX_NO_SHUFFLE=1 prove -I../test-nginx/lib -r $(TEST_FILE) + +# Keep in sync with .luacov, so that we show the right amount of output +LUACOV_NUM_MODULES ?= 3 .PHONY: all test install @@ -15,12 +21,13 @@ install: all $(INSTALL) lib/resty/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/ test: all - util/lua-releng - PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH TEST_NGINX_NO_SHUFFLE=1 prove -I../test-nginx/lib -r $(TEST_FILE) + $(PROVE) coverage: all - -@echo "Cleaning stats" @rm -f luacov.stats.out - PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH TEST_NGINX_NO_SHUFFLE=1 TEST_COVERAGE=1 prove -I../test-nginx/lib -r $(TEST_FILE) + TEST_COVERAGE=1 $(PROVE) @luacov - @tail -10 luacov.report.out + @tail -$$(( $(LUACOV_NUM_MODULES) + 5)) luacov.report.out + +check: + luacheck lib diff --git a/README.md b/README.md index aee6028d..59c02f5a 100644 --- a/README.md +++ b/README.md @@ -1,213 +1,232 @@ # lua-resty-http -Lua HTTP client cosocket driver for [OpenResty](http://openresty.org/) / [ngx_lua](https://github.com/openresty/lua-nginx-module). +Lua HTTP client cosocket driver for [OpenResty](http://openresty.org/) / [ngx\_lua](https://github.com/openresty/lua-nginx-module). -# Status +## Status Production ready. -# Features +[![Test](https://github.com/ledgetech/lua-resty-http/actions/workflows/test.yml/badge.svg)](https://github.com/ledgetech/lua-resty-http/actions) + +## Features * HTTP 1.0 and 1.1 * SSL * Streaming interface to the response body, for predictable memory usage -* Alternative simple interface for singleshot requests without manual connection step +* Alternative simple interface for single-shot requests without a manual connection step * Chunked and non-chunked transfer encodings -* Keepalive -* Pipelining +* Connection keepalives +* Request pipelining * Trailers +* HTTP proxy connections +* mTLS (requires `ngx_lua_http_module` >= v0.10.23) -# API +## API * [new](#new) * [connect](#connect) -* [set_timeout](#set_timeout) -* [set_timeouts](#set_timeouts) -* [ssl_handshake](#ssl_handshake) -* [set_keepalive](#set_keepalive) -* [get_reused_times](#get_reused_times) +* [set\_proxy\_options](#set_proxy_options) +* [set\_timeout](#set_timeout) +* [set\_timeouts](#set_timeouts) +* [set\_keepalive](#set_keepalive) +* [get\_reused\_times](#get_reused_times) * [close](#close) * [request](#request) -* [request_uri](#request_uri) -* [request_pipeline](#request_pipeline) +* [request\_uri](#request_uri) +* [request\_pipeline](#request_pipeline) +* [parse\_uri](#parse_uri) +* [get\_client\_body\_reader](#get_client_body_reader) * [Response](#response) - * [body_reader](#resbody_reader) - * [read_body](#resread_body) - * [read_trailes](#resread_trailers) -* [Proxy](#proxy) - * [proxy_request](#proxy_request) - * [proxy_response](#proxy_response) -* [Utility](#utility) - * [parse_uri](#parse_uri) - * [get_client_body_reader](#get_client_body_reader) - - -## Synopsis - -```` lua -lua_package_path "/path/to/lua-resty-http/lib/?.lua;;"; - -server { - - - location /simpleinterface { - resolver 8.8.8.8; # use Google's open DNS server for an example - - content_by_lua ' - - -- For simple singleshot requests, use the URI interface. - local http = require "resty.http" - local httpc = http.new() - local res, err = httpc:request_uri("http://example.com/helloworld", { - method = "POST", - body = "a=1&b=2", - headers = { - ["Content-Type"] = "application/x-www-form-urlencoded", - } - }) - - if not res then - ngx.say("failed to request: ", err) - return - end - - -- In this simple form, there is no manual connection step, so the body is read - -- all in one go, including any trailers, and the connection closed or keptalive - -- for you. - - ngx.status = res.status - - for k,v in pairs(res.headers) do - -- - end - - ngx.say(res.body) - '; - } - - - location /genericinterface { - content_by_lua ' - - local http = require "resty.http" - local httpc = http.new() - - -- The generic form gives us more control. We must connect manually. - httpc:set_timeout(500) - httpc:connect("127.0.0.1", 80) - - -- And request using a path, rather than a full URI. - local res, err = httpc:request({ - path = "/helloworld", - headers = { - ["Host"] = "example.com", - }, - }) - - if not res then - ngx.say("failed to request: ", err) - return - end - - -- Now we can use the body_reader iterator, to stream the body according to our desired chunk size. - local reader = res.body_reader - - repeat - local chunk, err = reader(8192) - if err then - ngx.log(ngx.ERR, err) - break - end - - if chunk then - -- process - end - until not chunk - - local ok, err = httpc:set_keepalive() - if not ok then - ngx.say("failed to set keepalive: ", err) - return - end - '; - } -} + * [body\_reader](#resbody_reader) + * [read\_body](#resread_body) + * [read\_trailers](#resread_trailers) + +### Deprecated + +These methods may be removed in future versions. + +* [connect\_proxy](#connect_proxy) +* [ssl\_handshake](#ssl_handshake) +* [proxy\_request](#proxy_request) +* [proxy\_response](#proxy_response) + +## Usage + +There are two basic modes of operation: + +1. **Simple single-shot requests** which require no manual connection management but which buffer the entire response and leave the connection either closed or back in the connection pool. + +2. **Streamed requests** where the connection is established separately, then the request is sent, the body stream read in chunks, and finally the connection is manually closed or kept alive. This technique requires a little more code but provides the ability to discard potentially large response bodies on the Lua side, as well as pipelining multiple requests over a single connection. + +### Single-shot request + +```lua +local httpc = require("resty.http").new() + +-- Single-shot requests use the `request_uri` interface. +local res, err = httpc:request_uri("http://example.com/helloworld", { + method = "POST", + body = "a=1&b=2", + headers = { + ["Content-Type"] = "application/x-www-form-urlencoded", + }, +}) +if not res then + ngx.log(ngx.ERR, "request failed: ", err) + return +end + +-- At this point, the entire request / response is complete and the connection +-- will be closed or back on the connection pool. + +-- The `res` table contains the expeected `status`, `headers` and `body` fields. +local status = res.status +local length = res.headers["Content-Length"] +local body = res.body +``` + +### Streamed request + +```lua +local httpc = require("resty.http").new() + +-- First establish a connection +local ok, err, ssl_session = httpc:connect({ + scheme = "https", + host = "127.0.0.1", + port = 8080, +}) +if not ok then + ngx.log(ngx.ERR, "connection failed: ", err) + return +end + +-- Then send using `request`, supplying a path and `Host` header instead of a +-- full URI. +local res, err = httpc:request({ + path = "/helloworld", + headers = { + ["Host"] = "example.com", + }, +}) +if not res then + ngx.log(ngx.ERR, "request failed: ", err) + return +end + +-- At this point, the status and headers will be available to use in the `res` +-- table, but the body and any trailers will still be on the wire. + +-- We can use the `body_reader` iterator, to stream the body according to our +-- desired buffer size. +local reader = res.body_reader +local buffer_size = 8192 + +repeat + local buffer, err = reader(buffer_size) + if err then + ngx.log(ngx.ERR, err) + break + end + + if buffer then + -- process + end +until not buffer + +local ok, err = httpc:set_keepalive() +if not ok then + ngx.say("failed to set keepalive: ", err) + return +end + +-- At this point, the connection will either be safely back in the pool, or closed. ```` # Connection ## new -`syntax: httpc = http.new()` +`syntax: httpc, err = http.new()` -Creates the http object. In case of failures, returns `nil` and a string describing the error. +Creates the HTTP connection object. In case of failures, returns `nil` and a string describing the error. ## connect -`syntax: ok, err = httpc:connect(host, port, options_table?)` +`syntax: ok, err, ssl_session = httpc:connect(options)` -`syntax: ok, err = httpc:connect("unix:/path/to/unix.sock", options_table?)` +Attempts to connect to the web server while incorporating the following activities: -Attempts to connect to the web server. +- TCP connection +- SSL handshake +- HTTP proxy configuration -Before actually resolving the host name and connecting to the remote backend, this method will always look up the connection pool for matched idle connections created by previous calls of this method. +In doing so it will create a distinct connection pool name that is safe to use with SSL and / or proxy based connections, and as such this syntax is strongly recommended over the original (now deprecated) [TCP only connection syntax](#TCP-only-connect). -An optional Lua table can be specified as the last argument to this method to specify various connect options: +The options table has the following fields: -* `pool` -: Specifies a custom name for the connection pool being used. If omitted, then the connection pool name will be generated from the string template `:` or ``. +* `scheme`: scheme to use, or nil for unix domain socket +* `host`: target host, or path to a unix domain socket +* `port`: port on target host, will default to `80` or `443` based on the scheme +* `pool`: custom connection pool name. Option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect), except that the default will become a pool name constructed using the SSL / proxy properties, which is important for safe connection reuse. When in doubt, leave it blank! +* `pool_size`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect) +* `backlog`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect) +* `proxy_opts`: sub-table, defaults to the global proxy options set, see [set\_proxy\_options](#set_proxy_options). +* `ssl_reused_session`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake) +* `ssl_verify`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake), except that it defaults to `true`. +* `ssl_server_name`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake) +* `ssl_send_status_req`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake) +* `ssl_client_cert`: will be passed to `tcpsock:setclientcert`. Requires `ngx_lua_http_module` >= v0.10.23. +* `ssl_client_priv_key`: as above. -## set_timeout +## set\_timeout `syntax: httpc:set_timeout(time)` -Sets the timeout (in ms) protection for subsequent operations, including the `connect` method. +Sets the socket timeout (in ms) for subsequent operations. See [set\_timeouts](#set_timeouts) below for a more declarative approach. -## set_timeouts +## set\_timeouts `syntax: httpc:set_timeouts(connect_timeout, send_timeout, read_timeout)` -Sets the connect timeout thresold, send timeout threshold, and read timeout threshold, respetively, in milliseconds, for subsequent socket operations (connect, send, receive, and iterators returned from receiveuntil). - -## ssl_handshake +Sets the connect timeout threshold, send timeout threshold, and read timeout threshold, respectively, in milliseconds, for subsequent socket operations (connect, send, receive, and iterators returned from receiveuntil). -`syntax: session, err = httpc:ssl_handshake(session, host, verify)` +## set\_keepalive -Performs an SSL handshake on the TCP connection, only availble in ngx_lua > v0.9.11 +`syntax: ok, err = httpc:set_keepalive(max_idle_timeout, pool_size)` -See docs for [ngx.socket.tcp](https://github.com/openresty/lua-nginx-module#ngxsockettcp) for details. +Either places the current connection into the pool for future reuse, or closes the connection. Calling this instead of [close](#close) is "safe" in that it will conditionally close depending on the type of request. Specifically, a `1.0` request without `Connection: Keep-Alive` will be closed, as will a `1.1` request with `Connection: Close`. -## set_keepalive +In case of success, returns `1`. In case of errors, returns `nil, err`. In the case where the connection is conditionally closed as described above, returns `2` and the error string `connection must be closed`, so as to distinguish from unexpected errors. -`syntax: ok, err = httpc:set_keepalive(max_idle_timeout, pool_size)` +See [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksetkeepalive) for parameter documentation. -Attempts to puts the current connection into the ngx_lua cosocket connection pool. +## set\_proxy\_options -You can specify the max idle timeout (in ms) when the connection is in the pool and the maximal size of the pool every nginx worker process. +`syntax: httpc:set_proxy_options(opts)` -Only call this method in the place you would have called the `close` method instead. Calling this method will immediately turn the current http object into the `closed` state. Any subsequent operations other than `connect()` on the current objet will return the `closed` error. +Configure an HTTP proxy to be used with this client instance. The `opts` table expects the following fields: -Note that calling this instead of `close` is "safe" in that it will conditionally close depending on the type of request. Specifically, a `1.0` request without `Connection: Keep-Alive` will be closed, as will a `1.1` request with `Connection: Close`. +* `http_proxy`: an URI to a proxy server to be used with HTTP requests +* `http_proxy_authorization`: a default `Proxy-Authorization` header value to be used with `http_proxy`, e.g. `Basic ZGVtbzp0ZXN0`, which will be overriden if the `Proxy-Authorization` request header is present. +* `https_proxy`: an URI to a proxy server to be used with HTTPS requests +* `https_proxy_authorization`: as `http_proxy_authorization` but for use with `https_proxy` (since with HTTPS the authorisation is done when connecting, this one cannot be overridden by passing the `Proxy-Authorization` request header). +* `no_proxy`: a comma separated list of hosts that should not be proxied. -In case of success, returns `1`. In case of errors, returns `nil, err`. In the case where the conneciton is conditionally closed as described above, returns `2` and the error string `connection must be closed`. +Note that this method has no effect when using the deprecated [TCP only connect](#TCP-only-connect) connection syntax. -## get_reused_times +## get\_reused\_times `syntax: times, err = httpc:get_reused_times()` -This method returns the (successfully) reused times for the current connection. In case of error, it returns `nil` and a string describing the error. - -If the current connection does not come from the built-in connection pool, then this method always returns `0`, that is, the connection has never been reused (yet). If the connection comes from the connection pool, then the return value is always non-zero. So this method can also be used to determine if the current connection comes from the pool. +See [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsockgetreusedtimes). ## close -`syntax: ok, err = http:close()` - -Closes the current connection and returns the status. - -In case of success, returns `1`. In case of errors, returns `nil` with a string describing the error. +`syntax: ok, err = httpc:close()` +See [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsockclose). # Requesting @@ -215,67 +234,69 @@ In case of success, returns `1`. In case of errors, returns `nil` with a string `syntax: res, err = httpc:request(params)` -Returns a `res` table or `nil` and an error message. +Sends an HTTP request over an already established connection. Returns a `res` table or `nil` and an error message. -The `params` table accepts the following fields: +The `params` table expects the following fields: -* `version` The HTTP version number, currently supporting 1.0 or 1.1. -* `method` The HTTP method string. -* `path` The path string. -* `query` The query string. -* `headers` A table of request headers. -* `body` The request body as a string, or an iterator function (see [get_client_body_reader](#get_client_body_reader)). -* `ssl_verify` Verify SSL cert matches hostname +* `version`: The HTTP version number. Defaults to `1.1`. +* `method`: The HTTP method string. Defaults to `GET`. +* `path`: The path string. Defaults to `/`. +* `query`: The query string, presented as either a literal string or Lua table.. +* `headers`: A table of request headers. +* `body`: The request body as a string, a table of strings, or an iterator function yielding strings until nil when exhausted. Note that you must specify a `Content-Length` for the request body, or specify `Transfer-Encoding: chunked` and have your function implement the encoding. See also: [get\_client\_body\_reader](#get_client_body_reader)). When the request is successful, `res` will contain the following fields: -* `status` The status code. -* `reason` The status reason phrase. -* `headers` A table of headers. Multiple headers with the same field name will be presented as a table of values. -* `has_body` A boolean flag indicating if there is a body to be read. -* `body_reader` An iterator function for reading the body in a streaming fashion. -* `read_body` A method to read the entire body into a string. -* `read_trailers` A method to merge any trailers underneath the headers, after reading the body. +* `status`: The status code. +* `reason`: The status reason phrase. +* `headers`: A table of headers. Multiple headers with the same field name will be presented as a table of values. +* `has_body`: A boolean flag indicating if there is a body to be read. +* `body_reader`: An iterator function for reading the body in a streaming fashion. +* `read_body`: A method to read the entire body into a string. +* `read_trailers`: A method to merge any trailers underneath the headers, after reading the body. -## request_uri +If the response has a body, then before the same connection can be used for another request, you must read the body using `read_body` or `body_reader`. + +## request\_uri `syntax: res, err = httpc:request_uri(uri, params)` -The simple interface. Options supplied in the `params` table are the same as in the generic interface, and will override components found in the uri itself. +The single-shot interface (see [usage](#Usage)). Since this method performs an entire end-to-end request, options specified in the `params` can include anything found in both [connect](#connect) and [request](#request) documented above. Note also that fields `path`, and `query`, in `params` will override relevant components of the `uri` if specified (`scheme`, `host`, and `port` will always be taken from the `uri`). + +There are 3 additional parameters for controlling keepalives: -In this mode, there is no need to connect manually first. The connection is made on your behalf, suiting cases where you simply need to grab a URI without too much hassle. +* `keepalive`: Set to `false` to disable keepalives and immediately close the connection. Defaults to `true`. +* `keepalive_timeout`: The maximal idle timeout (ms). Defaults to `lua_socket_keepalive_timeout`. +* `keepalive_pool`: The maximum number of connections in the pool. Defaults to `lua_socket_pool_size`. -Additionally there is no ability to stream the response body in this mode. If the request is successful, `res` will contain the following fields: +If the request is successful, `res` will contain the following fields: -* `status` The status code. -* `headers` A table of headers. -* `body` The response body as a string. +* `status`: The status code. +* `headers`: A table of headers. +* `body`: The entire response body as a string. -## request_pipeline +## request\_pipeline `syntax: responses, err = httpc:request_pipeline(params)` -This method works as per the [request](#request) method above, but `params` is instead a table of param tables. Each request is sent in order, and `responses` is returned as a table of response handles. For example: +This method works as per the [request](#request) method above, but `params` is instead a nested table of parameter tables. Each request is sent in order, and `responses` is returned as a table of response handles. For example: ```lua -local responses = httpc:request_pipeline{ - { - path = "/b", - }, - { - path = "/c", - }, - { - path = "/d", - } -} - -for i,r in ipairs(responses) do - if r.status then +local responses = httpc:request_pipeline({ + { path = "/b" }, + { path = "/c" }, + { path = "/d" }, +}) + +for _, r in ipairs(responses) do + if not r.status then + ngx.log(ngx.ERR, "socket read error") + break + end + ngx.say(r.status) ngx.say(r:read_body()) - end end ``` @@ -287,141 +308,161 @@ Be sure to test at least one field (such as status) before trying to use the oth # Response -## res.body_reader +## res.body\_reader The `body_reader` iterator can be used to stream the response body in chunk sizes of your choosing, as follows: ````lua local reader = res.body_reader +local buffer_size = 8192 repeat - local chunk, err = reader(8192) - if err then - ngx.log(ngx.ERR, err) - break - end - - if chunk then - -- process - end -until not chunk + local buffer, err = reader(buffer_size) + if err then + ngx.log(ngx.ERR, err) + break + end + + if buffer then + -- process + end +until not buffer ```` If the reader is called with no arguments, the behaviour depends on the type of connection. If the response is encoded as chunked, then the iterator will return the chunks as they arrive. If not, it will simply return the entire body. -Note that the size provided is actually a **maximum** size. So in the chunked transfer case, you may get chunks smaller than the size you ask, as a remainder of the actual HTTP chunks. +Note that the size provided is actually a **maximum** size. So in the chunked transfer case, you may get buffers smaller than the size you ask, as a remainder of the actual encoded chunks. -## res:read_body +## res:read\_body `syntax: body, err = res:read_body()` Reads the entire body into a local string. - -## res:read_trailers +## res:read\_trailers `syntax: res:read_trailers()` This merges any trailers underneath the `res.headers` table itself. Must be called after reading the body. +# Utility + +## parse\_uri + +`syntax: local scheme, host, port, path, query? = unpack(httpc:parse_uri(uri, query_in_path?))` + +This is a convenience function allowing one to more easily use the generic interface, when the input data is a URI. + +As of version `0.10`, the optional `query_in_path` parameter was added, which specifies whether the querystring is to be included in the `path` return value, or separately as its own return value. This defaults to `true` in order to maintain backwards compatibility. When set to `false`, `path` will only include the path, and `query` will contain the URI args, not including the `?` delimiter. + + +## get\_client\_body\_reader + +`syntax: reader, err = httpc:get_client_body_reader(chunksize?, sock?)` -# Proxy +Returns an iterator function which can be used to read the downstream client request body in a streaming fashion. You may also specify an optional default chunksize (default is `65536`), or an already established socket in +place of the client request. -There are two convenience methods for when one simply wishes to proxy the current request to the connected upstream, and safely send it downstream to the client, as a reverse proxy. A complete example: +Example: ```lua -local http = require "resty.http" -local httpc = http.new() +local req_reader = httpc:get_client_body_reader() +local buffer_size = 8192 + +repeat + local buffer, err = req_reader(buffer_size) + if err then + ngx.log(ngx.ERR, err) + break + end + + if buffer then + -- process + end +until not buffer +``` -httpc:set_timeout(500) -local ok, err = httpc:connect(HOST, PORT) +This iterator can also be used as the value for the body field in request params, allowing one to stream the request body into a proxied upstream request. -if not ok then - ngx.log(ngx.ERR, err) - return -end +```lua +local client_body_reader, err = httpc:get_client_body_reader() -httpc:set_timeout(2000) -httpc:proxy_response(httpc:proxy_request()) -httpc:set_keepalive() +local res, err = httpc:request({ + path = "/helloworld", + body = client_body_reader, +}) ``` +# Deprecated -## proxy_request +These features remain for backwards compatability, but may be removed in future releases. -`syntax: local res, err = httpc:proxy_request(request_body_chunk_size?)` +### TCP only connect -Performs a request using the current client request arguments, effectively proxying to the connected upstream. The request body will be read in a streaming fashion, according to `request_body_chunk_size` (see [documentation on the client body reader](#get_client_body_reader) below). +*The following versions of the `connect` method signature are deprecated in favour of the single `table` argument [documented above](#connect).* +`syntax: ok, err = httpc:connect(host, port, options_table?)` -## proxy_response +`syntax: ok, err = httpc:connect("unix:/path/to/unix.sock", options_table?)` -`syntax: httpc:proxy_response(res, chunksize?)` +NOTE: the default pool name will only incorporate IP and port information so is unsafe to use in case of SSL and/or Proxy connections. Specify your own pool or, better still, do not use these signatures. -Sets the current response based on the given `res`. Ensures that hop-by-hop headers are not sent downstream, and will read the response according to `chunksize` (see [documentation on the body reader](#resbody_reader) above). +## connect\_proxy +`syntax: ok, err = httpc:connect_proxy(proxy_uri, scheme, host, port, proxy_authorization)` -# Utility +*Calling this method manually is no longer necessary since it is incorporated within [connect](#connect). It is retained for now for compatibility with users of the older [TCP only connect](#TCP-only-connect) syntax.* -## parse_uri +Attempts to connect to the web server through the given proxy server. The method accepts the following arguments: -`syntax: local scheme, host, port, path, query? = unpack(httpc:parse_uri(uri, query_in_path?))` +* `proxy_uri` - Full URI of the proxy server to use (e.g. `http://proxy.example.com:3128/`). Note: Only `http` protocol is supported. +* `scheme` - The protocol to use between the proxy server and the remote host (`http` or `https`). If `https` is specified as the scheme, `connect_proxy()` makes a `CONNECT` request to establish a TCP tunnel to the remote host through the proxy server. +* `host` - The hostname of the remote host to connect to. +* `port` - The port of the remote host to connect to. +* `proxy_authorization` - The `Proxy-Authorization` header value sent to the proxy server via `CONNECT` when the `scheme` is `https`. -This is a convenience function allowing one to more easily use the generic interface, when the input data is a URI. +If an error occurs during the connection attempt, this method returns `nil` with a string describing the error. If the connection was successfully established, the method returns `1`. -As of version `0.10`, the optional `query_in_path` parameter was added, which specifies whether the querystring is to be included in the `path` return value, or separately as its own return value. This defaults to `true` in order to maintain backwards compatability. When set to `false`, `path` will only include the path, and `query` will contain the URI args, not inluding the `?` delimeter. +There's a few key points to keep in mind when using this api: +* If the scheme is `https`, you need to perform the TLS handshake with the remote server manually using the `ssl_handshake()` method before sending any requests through the proxy tunnel. +* If the scheme is `http`, you need to ensure that the requests you send through the connections conforms to [RFC 7230](https://tools.ietf.org/html/rfc7230) and especially [Section 5.3.2.](https://tools.ietf.org/html/rfc7230#section-5.3.2) which states that the request target must be in absolute form. In practice, this means that when you use `send_request()`, the `path` must be an absolute URI to the resource (e.g. `http://example.com/index.html` instead of just `/index.html`). -## get_client_body_reader +## ssl\_handshake -`syntax: reader, err = httpc:get_client_body_reader(chunksize?, sock?)` +`syntax: session, err = httpc:ssl_handshake(session, host, verify)` -Returns an iterator function which can be used to read the downstream client request body in a streaming fashion. You may also specify an optional default chunksize (default is `65536`), or an already established socket in -place of the client request. +*Calling this method manually is no longer necessary since it is incorporated within [connect](#connect). It is retained for now for compatibility with users of the older [TCP only connect](#TCP-only-connect) syntax.* -Example: +See [OpenResty docs](https://github.com/openresty/lua-nginx-module#ngxsockettcp). -```lua -local req_reader = httpc:get_client_body_reader() +## proxy\_request / proxy\_response -repeat - local chunk, err = req_reader(8192) - if err then - ngx.log(ngx.ERR, err) - break - end - - if chunk then - -- process - end -until not chunk -``` +*These two convenience methods were intended simply to demonstrate a common use case of implementing reverse proxying, and the author regrets their inclusion in the module. Users are encouraged to roll their own rather than depend on these functions, which may be removed in a subsequent release.* -This iterator can also be used as the value for the body field in request params, allowing one to stream the request body into a proxied upstream request. +### proxy\_request -```lua -local client_body_reader, err = httpc:get_client_body_reader() +`syntax: local res, err = httpc:proxy_request(request_body_chunk_size?)` -local res, err = httpc:request{ - path = "/helloworld", - body = client_body_reader, -} -``` +Performs a request using the current client request arguments, effectively proxying to the connected upstream. The request body will be read in a streaming fashion, according to `request_body_chunk_size` (see [documentation on the client body reader](#get_client_body_reader) below). -If `sock` is specified, -# Author +### proxy\_response + +`syntax: httpc:proxy_response(res, chunksize?)` + +Sets the current response based on the given `res`. Ensures that hop-by-hop headers are not sent downstream, and will read the response according to `chunksize` (see [documentation on the body reader](#resbody_reader) above). -James Hurst -Originally started life based on https://github.com/bakins/lua-resty-http-simple. Cosocket docs and implementation borrowed from the other lua-resty-* cosocket modules. +# Author +James Hurst , with contributions from @hamishforbes, @Tieske, @bungle et al. # Licence This module is licensed under the 2-clause BSD license. -Copyright (c) 2013-2016, James Hurst +Copyright (c) James Hurst All rights reserved. diff --git a/dist.ini b/dist.ini index 342193d0..c00bda88 100644 --- a/dist.ini +++ b/dist.ini @@ -5,5 +5,5 @@ is_original=yes license=2bsd lib_dir=lib doc_dir=lib -repo_link=https://github.com/pintsized/lua-resty-http +repo_link=https://github.com/ledgetech/lua-resty-http main_module=lib/resty/http.lua diff --git a/lib/resty/http.lua b/lib/resty/http.lua index 49490255..a85f85af 100644 --- a/lib/resty/http.lua +++ b/lib/resty/http.lua @@ -6,7 +6,6 @@ local ngx_req = ngx.req local ngx_req_socket = ngx_req.socket local ngx_req_get_headers = ngx_req.get_headers local ngx_req_get_method = ngx_req.get_method -local str_gmatch = string.gmatch local str_lower = string.lower local str_upper = string.upper local str_find = string.find @@ -15,6 +14,8 @@ local tbl_concat = table.concat local tbl_insert = table.insert local ngx_encode_args = ngx.encode_args local ngx_re_match = ngx.re.match +local ngx_re_gmatch = ngx.re.gmatch +local ngx_re_sub = ngx.re.sub local ngx_re_gsub = ngx.re.gsub local ngx_re_find = ngx.re.find local ngx_log = ngx.log @@ -54,8 +55,15 @@ local HOP_BY_HOP_HEADERS = { } +local EXPECTING_BODY = { + POST = true, + PUT = true, + PATCH = true, +} + + -- Reimplemented coroutine.wrap, returning "nil, err" if the coroutine cannot --- be resumed. This protects user code from inifite loops when doing things like +-- be resumed. This protects user code from infinite loops when doing things like -- repeat -- local chunk, err = res.body_reader() -- if chunk then -- <-- This could be a string msg in the core wrap function. @@ -98,7 +106,7 @@ end local _M = { - _VERSION = '0.10', + _VERSION = '0.17.2', } _M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version @@ -110,6 +118,7 @@ local HTTP = { [1.1] = " HTTP/1.1\r\n", } + local DEFAULT_PARAMS = { method = "GET", path = "/", @@ -117,7 +126,10 @@ local DEFAULT_PARAMS = { } -function _M.new(self) +local DEBUG = false + + +function _M.new(_) local sock, err = ngx_socket_tcp() if not sock then return nil, err @@ -126,6 +138,11 @@ function _M.new(self) end +function _M.debug(d) + DEBUG = (d == true) +end + + function _M.set_timeout(self, timeout) local sock = self.sock if not sock then @@ -145,20 +162,26 @@ function _M.set_timeouts(self, connect_timeout, send_timeout, read_timeout) return sock:settimeouts(connect_timeout, send_timeout, read_timeout) end - -function _M.ssl_handshake(self, ...) - local sock = self.sock - if not sock then - return nil, "not initialized" +do + local aio_connect = require "resty.http_connect" + -- Function signatures to support: + -- ok, err, ssl_session = httpc:connect(options_table) + -- ok, err = httpc:connect(host, port, options_table?) + -- ok, err = httpc:connect("unix:/path/to/unix.sock", options_table?) + function _M.connect(self, options, ...) + if type(options) == "table" then + -- all-in-one interface + return aio_connect(self, options) + else + -- backward compatible + return self:tcp_only_connect(options, ...) + end end - - self.ssl = true - - return sock:sslhandshake(...) end +function _M.tcp_only_connect(self, ...) + ngx_log(ngx_DEBUG, "Use of deprecated `connect` method signature") -function _M.connect(self, ...) local sock = self.sock if not sock then return nil, "not initialized" @@ -173,6 +196,7 @@ function _M.connect(self, ...) end self.keepalive = true + self.ssl = false return sock:connect(...) end @@ -228,10 +252,14 @@ local function _should_receive_body(method, code) end -function _M.parse_uri(self, uri, query_in_path) +function _M.parse_uri(_, uri, query_in_path) if query_in_path == nil then query_in_path = true end - local m, err = ngx_re_match(uri, [[^(?:(http[s]?):)?//([^:/\?]+)(?::(\d+))?([^\?]*)\??(.*)]], "jo") + local m, err = ngx_re_match( + uri, + [[^(?:(http[s]?):)?//((?:[^\[\]:/\?]+)|(?:\[.+\]))(?::(\d+))?([^\?]*)\??(.*)]], + "jo" + ) if not m then if err then @@ -243,6 +271,13 @@ function _M.parse_uri(self, uri, query_in_path) -- If the URI is schemaless (i.e. //example.com) try to use our current -- request scheme. if not m[1] then + -- Schema-less URIs can occur in client side code, implying "inherit + -- the schema from the current request". We support it for a fairly + -- specific case; if for example you are using the ESI parser in + -- ledge (https://github.com/ledgetech/ledge) to perform in-flight + -- sub requests on the edge based on instructions found in markup, + -- those URIs may also be schemaless with the intention that the + -- subrequest would inherit the schema just like JavaScript would. local scheme = ngx_var.scheme if scheme == "http" or scheme == "https" then m[1] = scheme @@ -272,14 +307,16 @@ function _M.parse_uri(self, uri, query_in_path) end -local function _format_request(params) +local function _format_request(self, params) local version = params.version local headers = params.headers or {} local query = params.query or "" if type(query) == "table" then - query = "?" .. ngx_encode_args(query) - elseif query ~= "" and str_sub(query, 1, 1) ~= "?" then + query = ngx_encode_args(query) + end + + if query ~= "" and str_sub(query, 1, 1) ~= "?" then query = "?" .. query end @@ -287,6 +324,7 @@ local function _format_request(params) local req = { str_upper(params.method), " ", + self.path_prefix or "", params.path, query, HTTP[version], @@ -295,17 +333,20 @@ local function _format_request(params) true, true, } - local c = 6 -- req table index it's faster to do this inline vs table.insert + local c = 7 -- req table index it's faster to do this inline vs table.insert -- Append headers for key, values in pairs(headers) do - if type(values) ~= "table" then - values = {values} - end - key = tostring(key) - for _, value in pairs(values) do - req[c] = key .. ": " .. tostring(value) .. "\r\n" + + if type(values) == "table" then + for _, value in pairs(values) do + req[c] = key .. ": " .. tostring(value) .. "\r\n" + c = c + 1 + end + + else + req[c] = key .. ": " .. tostring(values) .. "\r\n" c = c + 1 end end @@ -323,7 +364,21 @@ local function _receive_status(sock) return nil, nil, nil, err end - return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8)), str_sub(line, 14) + local version = tonumber(str_sub(line, 6, 8)) + if not version then + return nil, nil, nil, + "couldn't parse HTTP version from response status line: " .. line + end + + local status = tonumber(str_sub(line, 10, 12)) + if not status then + return nil, nil, nil, + "couldn't parse status code from response status line: " .. line + end + + local reason = str_sub(line, 14) + + return status, version, reason end @@ -336,7 +391,9 @@ local function _receive_headers(sock) return nil, err end - local m, err = ngx_re_match(line, "([^:\\s]+):\\s*(.+)", "jo") + local m, err = ngx_re_match(line, "([^:\\s]+):\\s*(.*)", "jo") + if err then ngx_log(ngx_ERR, err) end + if not m then break end @@ -357,6 +414,23 @@ local function _receive_headers(sock) end +local function transfer_encoding_is_chunked(headers) + local te = headers["Transfer-Encoding"] + if not te then + return false + end + + -- Handle duplicate headers + -- This shouldn't happen but can in the real world + if type(te) ~= "string" then + te = tbl_concat(te, ",") + end + + return str_find(str_lower(te), "chunked", 1, true) ~= nil +end +_M.transfer_encoding_is_chunked = transfer_encoding_is_chunked + + local function _chunked_body_reader(sock, default_chunk_size) return co_wrap(function(max_chunk_size) local remaining = 0 @@ -534,7 +608,7 @@ end local function _send_body(sock, body) - if type(body) == 'function' then + if type(body) == "function" then repeat local chunk, err, partial = body() @@ -561,20 +635,25 @@ end local function _handle_continue(sock, body) - local status, version, reason, err = _receive_status(sock) + local status, version, reason, err = _receive_status(sock) --luacheck: no unused if not status then - return nil, nil, err + return nil, nil, nil, err end -- Only send body if we receive a 100 Continue if status == 100 then - local ok, err = sock:receive("*l") -- Read carriage return + -- Read headers + local headers, err = _receive_headers(sock) + if not headers then + return nil, nil, nil, err + end + + local ok, err = _send_body(sock, body) if not ok then - return nil, nil, err + return nil, nil, nil, err end - _send_body(sock, body) end - return status, version, err + return status, version, reason, err end @@ -586,25 +665,61 @@ function _M.send_request(self, params) local body = params.body local headers = http_headers.new() - local params_headers = params.headers - if params_headers then - -- We assign one by one so that the metatable can handle case insensitivity - -- for us. You can blame the spec for this inefficiency. - for k,v in pairs(params_headers) do - headers[k] = v - end + -- We assign one-by-one so that the metatable can handle case insensitivity + -- for us. You can blame the spec for this inefficiency. + local params_headers = params.headers or {} + for k, v in pairs(params_headers) do + headers[k] = v + end + + if not headers["Proxy-Authorization"] then + -- TODO: next major, change this to always override the provided + -- header. Can't do that yet because it would be breaking. + -- The connect method uses self.http_proxy_auth in the poolname so + -- that should be leading. + headers["Proxy-Authorization"] = self.http_proxy_auth end - -- Ensure minimal headers are set - if type(body) == 'string' and not headers["Content-Length"] then - headers["Content-Length"] = #body + -- Ensure we have appropriate message length or encoding. + do + local is_chunked = transfer_encoding_is_chunked(headers) + + if is_chunked then + -- If we have both Transfer-Encoding and Content-Length we MUST + -- drop the Content-Length, to help prevent request smuggling. + -- https://tools.ietf.org/html/rfc7230#section-3.3.3 + headers["Content-Length"] = nil + + elseif not headers["Content-Length"] then + -- A length was not given, try to calculate one. + + local body_type = type(body) + + if body_type == "function" then + return nil, "Request body is a function but a length or chunked encoding is not specified" + + elseif body_type == "table" then + local length = 0 + for _, v in ipairs(body) do + length = length + #tostring(v) + end + headers["Content-Length"] = length + + elseif body == nil and EXPECTING_BODY[str_upper(params.method)] then + headers["Content-Length"] = 0 + + elseif body ~= nil then + headers["Content-Length"] = #tostring(body) + end + end end + if not headers["Host"] then if (str_sub(self.host, 1, 5) == "unix:") then return nil, "Unable to generate a useful Host header for a unix domain socket. Please provide one." end -- If we have a port (i.e. not connected to a unix domain socket), and this - -- port is non-standard, append it to the Host heaer. + -- port is non-standard, append it to the Host header. if self.port then if self.ssl and self.port ~= 443 then headers["Host"] = self.host .. ":" .. self.port @@ -627,8 +742,8 @@ function _M.send_request(self, params) params.headers = headers -- Format and send request - local req = _format_request(params) - ngx_log(ngx_DEBUG, "\n", req) + local req = _format_request(self, params) + if DEBUG then ngx_log(ngx_DEBUG, "\n", req) end local bytes, err = sock:send(req) if not bytes then @@ -656,11 +771,11 @@ function _M.read_response(self, params) -- If we expect: continue, we need to handle this, sending the body if allowed. -- If we don't get 100 back, then status is the actual status. if params.headers["Expect"] == "100-continue" then - local _status, _version, _err = _handle_continue(sock, params.body) + local _status, _version, _reason, _err = _handle_continue(sock, params.body) if not _status then return nil, _err elseif _status ~= 100 then - status, version, err = _status, _version, _err + status, version, reason, err = _status, _version, _reason, _err -- luacheck: no unused end end @@ -681,8 +796,8 @@ function _M.read_response(self, params) -- keepalive is true by default. Determine if this is correct or not. local ok, connection = pcall(str_lower, res_headers["Connection"]) if ok then - if (version == 1.1 and connection == "close") or - (version == 1.0 and connection ~= "keep-alive") then + if (version == 1.1 and str_find(connection, "close", 1, true)) or + (version == 1.0 and not str_find(connection, "keep-alive", 1, true)) then self.keepalive = false end else @@ -698,17 +813,18 @@ function _M.read_response(self, params) -- Receive the body_reader if _should_receive_body(params.method, status) then - local ok, encoding = pcall(str_lower, res_headers["Transfer-Encoding"]) - if ok and version == 1.1 and encoding == "chunked" then + has_body = true + + if version == 1.1 and transfer_encoding_is_chunked(res_headers) then body_reader, err = _chunked_body_reader(sock) - has_body = true else - local ok, length = pcall(tonumber, res_headers["Content-Length"]) - if ok then - body_reader, err = _body_reader(sock, length) - has_body = true + if not ok then + -- No content-length header, read until connection is closed by server + length = nil end + + body_reader, err = _body_reader(sock, length) end end @@ -734,7 +850,7 @@ end function _M.request(self, params) - params = tbl_copy(params) -- Take by value + params = tbl_copy(params) -- Take by value local res, err = self:send_request(params) if not res then return res, err @@ -745,7 +861,7 @@ end function _M.request_pipeline(self, requests) - requests = tbl_copy(requests) -- Take by value + requests = tbl_copy(requests) -- Take by value for _, params in ipairs(requests) do if params.headers and params.headers["Expect"] == "100-continue" then @@ -789,55 +905,70 @@ end function _M.request_uri(self, uri, params) - params = tbl_copy(params or {}) -- Take by value - - local parsed_uri, err = self:parse_uri(uri, false) - if not parsed_uri then - return nil, err + params = tbl_copy(params or {}) -- Take by value + if self.proxy_opts then + params.proxy_opts = tbl_copy(self.proxy_opts or {}) end - local scheme, host, port, path, query = unpack(parsed_uri) - if not params.path then params.path = path end - if not params.query then params.query = query end + do + local parsed_uri, err = self:parse_uri(uri, false) + if not parsed_uri then + return nil, err + end - local c, err = self:connect(host, port) - if not c then - return nil, err + local path, query + params.scheme, params.host, params.port, path, query = unpack(parsed_uri) + params.path = params.path or path + params.query = params.query or query + params.ssl_server_name = params.ssl_server_name or params.host end - if scheme == "https" then - local verify = true - if params.ssl_verify == false then - verify = false - end - local ok, err = self:ssl_handshake(nil, host, verify) - if not ok then - return nil, err + do + local proxy_auth = (params.headers or {})["Proxy-Authorization"] + if proxy_auth and params.proxy_opts then + params.proxy_opts.https_proxy_authorization = proxy_auth + params.proxy_opts.http_proxy_authorization = proxy_auth end end + local ok, err = self:connect(params) + if not ok then + return nil, err + end + local res, err = self:request(params) if not res then + self:close() return nil, err end local body, err = res:read_body() if not body then + self:close() return nil, err end res.body = body - local ok, err = self:set_keepalive() - if not ok then - ngx_log(ngx_ERR, err) + if params.keepalive == false then + local ok, err = self:close() + if not ok then + ngx_log(ngx_ERR, err) + end + + else + local ok, err = self:set_keepalive(params.keepalive_timeout, params.keepalive_pool) + if not ok then + ngx_log(ngx_ERR, err) + end + end return res, nil end -function _M.get_client_body_reader(self, chunksize, sock) +function _M.get_client_body_reader(_, chunksize, sock) chunksize = chunksize or 65536 if not sock then @@ -859,29 +990,160 @@ function _M.get_client_body_reader(self, chunksize, sock) local headers = ngx_req_get_headers() local length = headers.content_length - local encoding = headers.transfer_encoding if length then return _body_reader(sock, tonumber(length), chunksize) - elseif encoding and str_lower(encoding) == 'chunked' then + elseif transfer_encoding_is_chunked(headers) then -- Not yet supported by ngx_lua but should just work... return _chunked_body_reader(sock, chunksize) else - return nil + return nil + end +end + + +function _M.set_proxy_options(self, opts) + -- TODO: parse and cache these options, instead of parsing them + -- on each request over and over again (lru-cache on module level) + self.proxy_opts = tbl_copy(opts) -- Take by value +end + + +function _M.get_proxy_uri(self, scheme, host) + if not self.proxy_opts then + return nil + end + + -- Check if the no_proxy option matches this host. Implementation adapted + -- from lua-http library (https://github.com/daurnimator/lua-http) + if self.proxy_opts.no_proxy then + if self.proxy_opts.no_proxy == "*" then + -- all hosts are excluded + return nil + end + + local no_proxy_set = {} + -- wget allows domains in no_proxy list to be prefixed by "." + -- e.g. no_proxy=.mit.edu + for host_suffix in ngx_re_gmatch(self.proxy_opts.no_proxy, "\\.?([^,]+)", "jo") do + no_proxy_set[host_suffix[1]] = true + end + + -- From curl docs: + -- matched as either a domain which contains the hostname, or the + -- hostname itself. For example local.com would match local.com, + -- local.com:80, and www.local.com, but not www.notlocal.com. + -- + -- Therefore, we keep stripping subdomains from the host, compare + -- them to the ones in the no_proxy list and continue until we find + -- a match or until there's only the TLD left + repeat + if no_proxy_set[host] then + return nil + end + + -- Strip the next level from the domain and check if that one + -- is on the list + host = ngx_re_sub(host, "^[^.]+\\.", "", "jo") + until not ngx_re_find(host, "\\.", "jo") + end + + if scheme == "http" and self.proxy_opts.http_proxy then + return self.proxy_opts.http_proxy + end + + if scheme == "https" and self.proxy_opts.https_proxy then + return self.proxy_opts.https_proxy + end + + return nil +end + + +-- ---------------------------------------------------------------------------- +-- The following functions are considered DEPRECATED and may be REMOVED in +-- future releases. Please see the notes in `README.md`. +-- ---------------------------------------------------------------------------- + +function _M.ssl_handshake(self, ...) + ngx_log(ngx_DEBUG, "Use of deprecated function `ssl_handshake`") + + local sock = self.sock + if not sock then + return nil, "not initialized" end + + self.ssl = true + + return sock:sslhandshake(...) +end + + +function _M.connect_proxy(self, proxy_uri, scheme, host, port, proxy_authorization) + ngx_log(ngx_DEBUG, "Use of deprecated function `connect_proxy`") + + -- Parse the provided proxy URI + local parsed_proxy_uri, err = self:parse_uri(proxy_uri, false) + if not parsed_proxy_uri then + return nil, err + end + + -- Check that the scheme is http (https is not supported for + -- connections between the client and the proxy) + local proxy_scheme = parsed_proxy_uri[1] + if proxy_scheme ~= "http" then + return nil, "protocol " .. proxy_scheme .. " not supported for proxy connections" + end + + -- Make the connection to the given proxy + local proxy_host, proxy_port = parsed_proxy_uri[2], parsed_proxy_uri[3] + local c, err = self:tcp_only_connect(proxy_host, proxy_port) + if not c then + return nil, err + end + + if scheme == "https" then + -- Make a CONNECT request to create a tunnel to the destination through + -- the proxy. The request-target and the Host header must be in the + -- authority-form of RFC 7230 Section 5.3.3. See also RFC 7231 Section + -- 4.3.6 for more details about the CONNECT request + local destination = host .. ":" .. port + local res, err = self:request({ + method = "CONNECT", + path = destination, + headers = { + ["Host"] = destination, + ["Proxy-Authorization"] = proxy_authorization, + } + }) + + if not res then + return nil, err + end + + if res.status < 200 or res.status > 299 then + return nil, "failed to establish a tunnel through a proxy: " .. res.status + end + end + + return c, nil end function _M.proxy_request(self, chunksize) - return self:request{ + ngx_log(ngx_DEBUG, "Use of deprecated function `proxy_request`") + + return self:request({ method = ngx_req_get_method(), path = ngx_re_gsub(ngx_var.uri, "\\s", "%20", "jo") .. ngx_var.is_args .. (ngx_var.query_string or ""), body = self:get_client_body_reader(chunksize), headers = ngx_req_get_headers(), - } + }) end -function _M.proxy_response(self, response, chunksize) +function _M.proxy_response(_, response, chunksize) + ngx_log(ngx_DEBUG, "Use of deprecated function `proxy_response`") + if not response then ngx_log(ngx_ERR, "no response provided") return @@ -890,27 +1152,32 @@ function _M.proxy_response(self, response, chunksize) ngx.status = response.status -- Filter out hop-by-hop headeres - for k,v in pairs(response.headers) do + for k, v in pairs(response.headers) do if not HOP_BY_HOP_HEADERS[str_lower(k)] then ngx_header[k] = v end end local reader = response.body_reader + repeat - local chunk, err = reader(chunksize) - if err then - ngx_log(ngx_ERR, err) - break + local chunk, ok, read_err, print_err + + chunk, read_err = reader(chunksize) + if read_err then + ngx_log(ngx_ERR, read_err) end if chunk then - local res, err = ngx_print(chunk) - if not res then - ngx_log(ngx_ERR, err) - break + ok, print_err = ngx_print(chunk) + if not ok then + ngx_log(ngx_ERR, print_err) end end + + if read_err or print_err then + break + end until not chunk end diff --git a/lib/resty/http_connect.lua b/lib/resty/http_connect.lua new file mode 100644 index 00000000..83b6d2f4 --- /dev/null +++ b/lib/resty/http_connect.lua @@ -0,0 +1,341 @@ +local ffi = require "ffi" +local ngx_re_gmatch = ngx.re.gmatch +local ngx_re_sub = ngx.re.sub +local ngx_re_find = ngx.re.find +local ngx_log = ngx.log +local ngx_WARN = ngx.WARN +local ngx_DEBUG = ngx.DEBUG +local to_hex = require("resty.string").to_hex +local ffi_gc = ffi.gc +local ffi_cast = ffi.cast +local type = type + +local lib_chain, lib_x509, lib_pkey +local openssl_available, res = xpcall(function() + lib_chain = require("resty.openssl.x509.chain") + lib_x509 = require("resty.openssl.x509") + lib_pkey = require("resty.openssl.pkey") +end, debug.traceback) + +if not openssl_available then + ngx_log(ngx_WARN, "failed to load module `resty.openssl.*`, \z + mTLS isn't supported without lua-resty-openssl:\n", res) +end + +--[[ +A connection function that incorporates: + - tcp connect + - ssl handshake + - http proxy +Due to this it will be better at setting up a socket pool where connections can +be kept alive. + + +Call it with a single options table as follows: + +client:connect { + scheme = "https" -- scheme to use, or nil for unix domain socket + host = "myhost.com", -- target machine, or a unix domain socket + port = nil, -- port on target machine, will default to 80/443 based on scheme + pool = nil, -- connection pool name, leave blank! this function knows best! + pool_size = nil, -- options as per: https://github.com/openresty/lua-nginx-module#tcpsockconnect + backlog = nil, + + -- ssl options as per: https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake + ssl_reused_session = nil + ssl_server_name = nil, + ssl_send_status_req = nil, + ssl_verify = true, -- NOTE: defaults to true + ctx = nil, -- NOTE: not supported + + -- mTLS options: These require support for mTLS in cosockets, which first + -- appeared in `ngx_http_lua_module` v0.10.23. + ssl_client_cert = nil, + ssl_client_priv_key = nil, + + proxy_opts, -- proxy opts, defaults to global proxy options +} +]] +local function connect(self, options) + local sock = self.sock + if not sock then + return nil, "not initialized" + end + + local ok, err + + local request_scheme = options.scheme + local request_host = options.host + local request_port = options.port + + local poolname = options.pool + local pool_size = options.pool_size + local backlog = options.backlog + + if request_scheme and not request_port then + request_port = (request_scheme == "https" and 443 or 80) + elseif request_port and not request_scheme then + return nil, "'scheme' is required when providing a port" + end + + -- ssl settings + local ssl, ssl_reused_session, ssl_server_name + local ssl_verify, ssl_send_status_req, ssl_client_cert, ssl_client_priv_key + if request_scheme == "https" then + ssl = true + ssl_reused_session = options.ssl_reused_session + ssl_server_name = options.ssl_server_name + ssl_send_status_req = options.ssl_send_status_req + ssl_verify = true -- default + if options.ssl_verify == false then + ssl_verify = false + end + ssl_client_cert = options.ssl_client_cert + ssl_client_priv_key = options.ssl_client_priv_key + end + + -- proxy related settings + local proxy, proxy_uri, proxy_authorization, proxy_host, proxy_port, path_prefix + proxy = options.proxy_opts or self.proxy_opts + + if proxy then + if request_scheme == "https" then + proxy_uri = proxy.https_proxy + proxy_authorization = proxy.https_proxy_authorization + else + proxy_uri = proxy.http_proxy + proxy_authorization = proxy.http_proxy_authorization + -- When a proxy is used, the target URI must be in absolute-form + -- (RFC 7230, Section 5.3.2.). That is, it must be an absolute URI + -- to the remote resource with the scheme, host and an optional port + -- in place. + -- + -- Since _format_request() constructs the request line by concatenating + -- params.path and params.query together, we need to modify the path + -- to also include the scheme, host and port so that the final form + -- in conformant to RFC 7230. + path_prefix = "http://" .. request_host .. (request_port == 80 and "" or (":" .. request_port)) + end + if not proxy_uri then + proxy = nil + proxy_authorization = nil + path_prefix = nil + end + end + + if proxy and proxy.no_proxy then + -- Check if the no_proxy option matches this host. Implementation adapted + -- from lua-http library (https://github.com/daurnimator/lua-http) + if proxy.no_proxy == "*" then + -- all hosts are excluded + proxy = nil + + else + local host = request_host + local no_proxy_set = {} + -- wget allows domains in no_proxy list to be prefixed by "." + -- e.g. no_proxy=.mit.edu + for host_suffix in ngx_re_gmatch(proxy.no_proxy, "\\.?([^,]+)") do + no_proxy_set[host_suffix[1]] = true + end + + -- From curl docs: + -- matched as either a domain which contains the hostname, or the + -- hostname itself. For example local.com would match local.com, + -- local.com:80, and www.local.com, but not www.notlocal.com. + -- + -- Therefore, we keep stripping subdomains from the host, compare + -- them to the ones in the no_proxy list and continue until we find + -- a match or until there's only the TLD left + repeat + if no_proxy_set[host] then + proxy = nil + proxy_uri = nil + proxy_authorization = nil + break + end + + -- Strip the next level from the domain and check if that one + -- is on the list + host = ngx_re_sub(host, "^[^.]+\\.", "") + until not ngx_re_find(host, "\\.") + end + end + + if proxy then + local proxy_uri_t + proxy_uri_t, err = self:parse_uri(proxy_uri) + if not proxy_uri_t then + return nil, "uri parse error: " .. err + end + + local proxy_scheme = proxy_uri_t[1] + if proxy_scheme ~= "http" then + return nil, "protocol " .. tostring(proxy_scheme) .. + " not supported for proxy connections" + end + proxy_host = proxy_uri_t[2] + proxy_port = proxy_uri_t[3] + end + + local cert_hash + if ssl and ssl_client_cert and ssl_client_priv_key then + local cert_type = type(ssl_client_cert) + local key_type = type(ssl_client_priv_key) + + if cert_type ~= "cdata" then + return nil, "bad ssl_client_cert: cdata expected, got " .. cert_type + end + + if key_type ~= "cdata" then + return nil, "bad ssl_client_priv_key: cdata expected, got " .. key_type + end + + if not openssl_available then + return nil, "module `resty.openssl.*` not available, mTLS isn't supported without lua-resty-openssl" + end + + -- convert from `void*` to `OPENSSL_STACK*` + local cert_chain, err = lib_chain.dup(ffi_cast("OPENSSL_STACK*", ssl_client_cert)) + if not cert_chain then + return nil, "failed to dup the ssl_client_cert: " .. err + end + + if #cert_chain < 1 then + return nil, "no cert in ssl_client_cert" + end + + local cert, err = lib_x509.dup(cert_chain[1].ctx) + if not cert then + return nil, "failed to dup the x509: " .. err + end + + -- convert from `void*` to `EVP_PKEY*` + local key, err = lib_pkey.new(ffi_cast("EVP_PKEY*", ssl_client_priv_key)) + if not key then + return nil, "failed to new the pkey: " .. err + end + + -- should not free the cdata passed in + ffi_gc(key.ctx, nil) + + -- check the private key in order to make sure the caller is indeed the holder of the cert + ok, err = cert:check_private_key(key) + if not ok then + return nil, "the private key doesn't match the cert: " .. err + end + + cert_hash, err = cert:digest("sha256") + if not cert_hash then + return nil, "failed to calculate the digest of the cert: " .. err + end + + cert_hash = to_hex(cert_hash) -- convert to hex so that it's printable + end + + -- construct a poolname unique within proxy and ssl info + if not poolname then + poolname = (request_scheme or "") + .. ":" .. request_host + .. ":" .. tostring(request_port) + .. ":" .. tostring(ssl) + .. ":" .. (ssl_server_name or "") + .. ":" .. tostring(ssl_verify) + .. ":" .. (proxy_uri or "") + .. ":" .. (request_scheme == "https" and proxy_authorization or "") + .. ":" .. (cert_hash or "") + -- in the above we only add the 'proxy_authorization' as part of the poolname + -- when the request is https. Because in that case the CONNECT request (which + -- carries the authorization header) is part of the connect procedure, whereas + -- with a plain http request the authorization is part of the actual request. + end + + ngx_log(ngx_DEBUG, "poolname: ", poolname) + + -- do TCP level connection + local tcp_opts = { pool = poolname, pool_size = pool_size, backlog = backlog } + if proxy then + -- proxy based connection + ok, err = sock:connect(proxy_host, proxy_port, tcp_opts) + if not ok then + return nil, "failed to connect to: " .. (proxy_host or "") .. + ":" .. (proxy_port or "") .. + ": " .. err + end + + if ssl and sock:getreusedtimes() == 0 then + -- Make a CONNECT request to create a tunnel to the destination through + -- the proxy. The request-target and the Host header must be in the + -- authority-form of RFC 7230 Section 5.3.3. See also RFC 7231 Section + -- 4.3.6 for more details about the CONNECT request + local destination = request_host .. ":" .. request_port + local res + res, err = self:request({ + method = "CONNECT", + path = destination, + headers = { + ["Host"] = destination, + ["Proxy-Authorization"] = proxy_authorization, + } + }) + + if not res then + return nil, "failed to issue CONNECT to proxy: " .. err + end + + if res.status < 200 or res.status > 299 then + return nil, "failed to establish a tunnel through a proxy: " .. res.status + end + end + + elseif not request_port then + -- non-proxy, without port -> unix domain socket + ok, err = sock:connect(request_host, tcp_opts) + if not ok then + return nil, err + end + + else + -- non-proxy, regular network tcp + ok, err = sock:connect(request_host, request_port, tcp_opts) + if not ok then + return nil, err + end + end + + local ssl_session + -- Now do the ssl handshake + if ssl and sock:getreusedtimes() == 0 then + + -- Experimental mTLS support + if ssl_client_cert and ssl_client_priv_key then + if type(sock.setclientcert) ~= "function" then + return nil, "cannot use SSL client cert and key without mTLS support" + + else + ok, err = sock:setclientcert(ssl_client_cert, ssl_client_priv_key) + if not ok then + return nil, "could not set client certificate: " .. err + end + end + end + + ssl_session, err = sock:sslhandshake(ssl_reused_session, ssl_server_name, ssl_verify, ssl_send_status_req) + if not ssl_session then + self:close() + return nil, err + end + end + + self.host = request_host + self.port = request_port + self.keepalive = true + self.ssl = ssl + -- set only for http, https has already been handled + self.http_proxy_auth = request_scheme ~= "https" and proxy_authorization or nil + self.path_prefix = path_prefix + + return true, nil, ssl_session +end + +return connect diff --git a/lib/resty/http_headers.lua b/lib/resty/http_headers.lua index 663ade6a..6394e619 100644 --- a/lib/resty/http_headers.lua +++ b/lib/resty/http_headers.lua @@ -4,7 +4,7 @@ local rawget, rawset, setmetatable = local str_lower = string.lower local _M = { - _VERSION = '0.10', + _VERSION = '0.17.2', } diff --git a/lua-resty-http-0.10-0.rockspec b/lua-resty-http-0.10-0.rockspec deleted file mode 100644 index 95cb42ab..00000000 --- a/lua-resty-http-0.10-0.rockspec +++ /dev/null @@ -1,22 +0,0 @@ -package = "lua-resty-http" -version = "0.10-0" -source = { - url = "git://github.com/pintsized/lua-resty-http", - tag = "v0.10" -} -description = { - summary = "Lua HTTP client cosocket driver for OpenResty / ngx_lua.", - homepage = "https://github.com/pintsized/lua-resty-http", - license = "2-clause BSD", - maintainer = "James Hurst " -} -dependencies = { - "lua >= 5.1" -} -build = { - type = "builtin", - modules = { - ["resty.http"] = "lib/resty/http.lua", - ["resty.http_headers"] = "lib/resty/http_headers.lua" - } -} diff --git a/lua-resty-http-0.17.2-0.rockspec b/lua-resty-http-0.17.2-0.rockspec new file mode 100644 index 00000000..c1d81136 --- /dev/null +++ b/lua-resty-http-0.17.2-0.rockspec @@ -0,0 +1,23 @@ +package = "lua-resty-http" +version = "0.17.2-0" +source = { + url = "git://github.com/ledgetech/lua-resty-http", + tag = "v0.17.2" +} +description = { + summary = "Lua HTTP client cosocket driver for OpenResty / ngx_lua.", + homepage = "https://github.com/ledgetech/lua-resty-http", + license = "2-clause BSD", + maintainer = "James Hurst " +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + ["resty.http"] = "lib/resty/http.lua", + ["resty.http_headers"] = "lib/resty/http_headers.lua", + ["resty.http_connect"] = "lib/resty/http_connect.lua" + } +} diff --git a/t/01-basic.t b/t/01-basic.t index 495f0526..472bed15 100644 --- a/t/01-basic.t +++ b/t/01-basic.t @@ -1,8 +1,6 @@ -use Test::Nginx::Socket; +use Test::Nginx::Socket 'no_plan'; use Cwd qw(cwd); -plan tests => repeat_each() * (blocks() * 4) + 1; - my $pwd = cwd(); $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; @@ -33,7 +31,11 @@ __DATA__ content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect{ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + } local res, err = httpc:request{ path = "/b" @@ -64,7 +66,11 @@ OK content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect{ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + } local res, err = httpc:request{ version = 1.0, @@ -96,7 +102,11 @@ OK content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + local ok, err = httpc:connect{ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + } local res, err = httpc:request{ path = "/b" @@ -133,7 +143,11 @@ OK content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect{ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + } local res, err = httpc:request{ path = "/b" @@ -167,7 +181,11 @@ x-value content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect{ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + } local res, err = httpc:request{ query = { @@ -212,7 +230,11 @@ X-Header-B: 2 content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect{ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + } local res, err = httpc:request{ method = "HEAD", @@ -245,15 +267,16 @@ GET /a content_by_lua ' local http = require "resty.http" - local res, err = http:connect("127.0.0.1", 1984) + local res, err = http:connect{ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + } if not res then ngx.say(err) end local res, err = http:set_timeout(500) if not res then ngx.say(err) end - local res, err = http:ssl_handshake() - if not res then ngx.say(err) end - local res, err = http:set_keepalive() if not res then ngx.say(err) end @@ -272,102 +295,139 @@ not initialized not initialized not initialized not initialized -not initialized --- no_error_log [error] [warn] -=== TEST 9: Parse URI errors if malformed +=== TEST 12: Allow empty HTTP header values (RFC7230) --- http_config eval: $::HttpConfig --- config location = /a { - content_by_lua ' - local http = require("resty.http").new() - local parts, err = http:parse_uri("http:///example.com") - if not parts then ngx.say(err) end - '; + content_by_lua_block { + local httpc = require("resty.http").new() + + -- Create a TCP connection and return an raw HTTP-response because + -- there is no way to set an empty header value in nginx. + assert(httpc:connect{ + scheme = "http", + host = "127.0.0.1", + port = 12345, + }, "connect should return positively") + + local res = httpc:request({ path = "/b" }) + if res.headers["X-Header-Empty"] == "" then + ngx.say("Empty") + end + ngx.say(res.headers["X-Header-Test"]) + ngx.print(res:read_body()) + } } +--- tcp_listen: 12345 +--- tcp_reply +HTTP/1.0 200 OK +Date: Mon, 23 Jul 2018 13:00:00 GMT +X-Header-Empty: +X-Header-Test: Test +Server: OpenResty + +OK --- request GET /a --- response_body -bad uri: http:///example.com +Empty +Test +OK --- no_error_log [error] [warn] - -=== TEST 10: Parse URI fills in defaults correctly +=== TEST 13: Should return error on invalid HTTP version in response status line --- http_config eval: $::HttpConfig --- config location = /a { - content_by_lua ' - local http = require("resty.http").new() + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:12345") - function test_uri(uri) - local scheme, host, port, path, query = unpack(http:parse_uri(uri, false)) - ngx.say("scheme: ", scheme, ", host: ", host, ", port: ", port, ", path: ", path, ", query: ", query) - end + assert(err == "couldn't parse HTTP version from response status line: TEAPOT/1.1 OMG") + } + } +--- tcp_listen: 12345 +--- tcp_reply +TEAPOT/1.1 OMG +Server: Teapot - test_uri("http://example.com") - test_uri("http://example.com/") - test_uri("https://example.com/foo/bar") - test_uri("https://example.com/foo/bar?a=1&b=2") - test_uri("http://example.com?a=1&b=2") - test_uri("//example.com") - test_uri("//example.com?a=1&b=2") - test_uri("//example.com/foo/bar?a=1&b=2") - '; +OK +--- request +GET /a +--- no_error_log +[error] +[warn] + +=== TEST 14: Should return error on invalid status code in response status line +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:12345") + + assert(err == "couldn't parse status code from response status line: HTTP/1.1 OMG") + } } +--- tcp_listen: 12345 +--- tcp_reply +HTTP/1.1 OMG +Server: Teapot + +OK --- request GET /a ---- response_body -scheme: http, host: example.com, port: 80, path: /, query: -scheme: http, host: example.com, port: 80, path: /, query: -scheme: https, host: example.com, port: 443, path: /foo/bar, query: -scheme: https, host: example.com, port: 443, path: /foo/bar, query: a=1&b=2 -scheme: http, host: example.com, port: 80, path: /, query: a=1&b=2 -scheme: http, host: example.com, port: 80, path: /, query: -scheme: http, host: example.com, port: 80, path: /, query: a=1&b=2 -scheme: http, host: example.com, port: 80, path: /foo/bar, query: a=1&b=2 --- no_error_log [error] [warn] -=== TEST 11: Parse URI fills in defaults correctly, using backwards compatible mode + +=== TEST 14: Empty query --- http_config eval: $::HttpConfig --- config location = /a { content_by_lua ' - local http = require("resty.http").new() + local http = require "resty.http" + local httpc = http.new() + httpc:connect{ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + } - function test_uri(uri) - local scheme, host, port, path, query = unpack(http:parse_uri(uri)) - ngx.say("scheme: ", scheme, ", host: ", host, ", port: ", port, ", path: ", path) - end + local res, err = httpc:request{ + query = {}, + path = "/b" + } + + ngx.status = res.status + + ngx.print(ngx.header.test) - test_uri("http://example.com") - test_uri("http://example.com/") - test_uri("https://example.com/foo/bar") - test_uri("https://example.com/foo/bar?a=1&b=2") - test_uri("http://example.com?a=1&b=2") - test_uri("//example.com") - test_uri("//example.com?a=1&b=2") - test_uri("//example.com/foo/bar?a=1&b=2") + httpc:close() + '; + } + location = /b { + content_by_lua ' + ngx.header.test = ngx.var.request_uri '; } --- request GET /a ---- response_body -scheme: http, host: example.com, port: 80, path: / -scheme: http, host: example.com, port: 80, path: / -scheme: https, host: example.com, port: 443, path: /foo/bar -scheme: https, host: example.com, port: 443, path: /foo/bar?a=1&b=2 -scheme: http, host: example.com, port: 80, path: /?a=1&b=2 -scheme: http, host: example.com, port: 80, path: / -scheme: http, host: example.com, port: 80, path: /?a=1&b=2 -scheme: http, host: example.com, port: 80, path: /foo/bar?a=1&b=2 +--- response_headers +/b --- no_error_log [error] [warn] + + diff --git a/t/02-chunked.t b/t/02-chunked.t index 4c013aac..26b75f23 100644 --- a/t/02-chunked.t +++ b/t/02-chunked.t @@ -1,8 +1,6 @@ -use Test::Nginx::Socket; +use Test::Nginx::Socket 'no_plan'; use Cwd qw(cwd); -plan tests => repeat_each() * (blocks() * 4); - my $pwd = cwd(); $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; @@ -33,7 +31,11 @@ __DATA__ content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b" @@ -72,7 +74,11 @@ GET /a content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b" @@ -128,7 +134,11 @@ GET /a content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b" @@ -163,3 +173,152 @@ GET /a --- no_error_log [error] [warn] + + +=== TEST 4: Chunked. multiple-headers, mixed case +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) + + local res, err = httpc:request{ + path = "/b" + } + + local chunks = {} + local c = 1 + repeat + local chunk, err = res.body_reader() + if chunk then + chunks[c] = chunk + c = c + 1 + end + until not chunk + + local body = table.concat(chunks) + + ngx.say(#body) + ngx.say(#chunks) + ngx.say(type(res.headers["Transfer-Encoding"])) + httpc:close() + } + } + location = /b { + header_filter_by_lua_block { + ngx.header["Transfer-Encoding"] = {"chUnked", "CHunked"} + } + content_by_lua_block { + local len = 32768 + local t = {} + for i=1,len do + t[i] = 0 + end + ngx.print(table.concat(t)) + local len = 32768 + local t = {} + for i=1,len do + t[i] = 0 + end + ngx.print(table.concat(t)) + } + } +--- request +GET /a +--- response_body +65536 +2 +table +--- no_error_log +[error] +[warn] + + +=== TEST 5: transfer_encoding_is_chunked utility. +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local http_headers = require("resty.http_headers") + local http = require("resty.http") + + local headers = http_headers:new() + assert(http.transfer_encoding_is_chunked(headers) == false, + "empty headers should return false") + + headers["Transfer-Encoding"] = "chunked" + assert(http.transfer_encoding_is_chunked(headers) == true, + "te set to `chunked` should return true`") + + headers["Transfer-Encoding"] = " ChuNkEd " + assert(http.transfer_encoding_is_chunked(headers) == true, + "te set to ` ChuNkEd ` should return true`") + + headers["Transfer-Encoding"] = { "chunked", " ChuNkEd " } + assert(http.transfer_encoding_is_chunked(headers) == true, + "te set to table values containing `chunked` should return true`") + + headers["Transfer-Encoding"] = "chunked" + headers["Content-Length"] = 10 + assert(http.transfer_encoding_is_chunked(headers) == true, + "transfer encoding should override content-length`") + } + } +--- request +GET /a +--- no_error_log +[error] +[warn] + + +=== TEST 6: Don't send Content-Length if Transfer-Encoding is specified +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local httpc = require("resty.http").new() + local yield = coroutine.yield + + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/b" + + local res, err = assert(httpc:request_uri(uri, { + body = coroutine.wrap(function() + yield("3\r\n") + yield("foo\r\n") + + yield("3\r\n") + yield("bar\r\n") + + yield("0\r\n") + yield("\r\n") + end), + headers = { + ["Transfer-Encoding"] = "chunked", + ["Content-Length"] = 42, + }, + })) + + ngx.say(res.body) + } + } + location = /b { + content_by_lua_block { + ngx.req.read_body() + ngx.say(ngx.req.get_headers()["Content-Length"]) + ngx.print(ngx.req.get_body_data()) + } + } +--- request +GET /a +--- response_body +nil +foobar +--- no_error_log +[error] +[warn] diff --git a/t/03-requestbody.t b/t/03-requestbody.t index f0168327..50b876ba 100644 --- a/t/03-requestbody.t +++ b/t/03-requestbody.t @@ -1,8 +1,6 @@ -use Test::Nginx::Socket; +use Test::Nginx::Socket 'no_plan'; use Cwd qw(cwd); -plan tests => repeat_each() * (blocks() * 4); - my $pwd = cwd(); $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; @@ -33,7 +31,11 @@ __DATA__ content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ body = "a=1&b=2&c=3", @@ -74,7 +76,11 @@ c: 3 content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ method = "POST", @@ -119,7 +125,11 @@ c: 3 content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ body = "a=1&b=2&c=3", @@ -161,7 +171,11 @@ c: 3 content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b", @@ -189,3 +203,307 @@ Expectation Failed --- no_error_log [error] [warn] + + +=== TEST 5: Return 100 Continue with headers +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) + + local res, err = httpc:request{ + body = "a=1&b=2&c=3", + path = "/b", + headers = { + ["Expect"] = "100-continue", + ["Content-Type"] = "application/x-www-form-urlencoded", + } + } + + if not res then + ngx.log(ngx.ERR, "httpc:request failed: ", err) + end + + ngx.say(res.status) + ngx.say(res:read_body()) + httpc:close() + } + } + location = /b { + content_by_lua_block { + local len = ngx.req.get_headers()["Content-Length"] + + local sock, err = ngx.req.socket(true) + if not sock then + ngx.log(ngx.ERR, "server: failed to get raw req socket: ", err) + return + end + + -- with additional header + local ok, err = sock:send("HTTP/1.1 100 Continue\r\nConnection: keep-alive\r\n\r\n") + if not ok then + ngx.log(ngx.ERR, "failed to send 100 response: ", err) + end + + local data, err = sock:receive(len) + if not data then + ngx.log(ngx.ERR, "failed to receive: ", err) + return + end + + local ok, err = sock:send("HTTP/1.1 200 OK\r\n" .. + "Content-Length: " .. len .. "\r\n" .. + "Content-Type: application/x-www-form-urlencoded\r\n\r\n" .. + data) + if not ok then + ngx.log(ngx.ERR, "failed to send 200 response: ", err) + return + end + } + } +--- request +GET /a +--- response_body +200 +a=1&b=2&c=3 +--- no_error_log +[error] +[warn] + + +=== TEST 6: Return 100 Continue without headers +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) + + local res, err = httpc:request{ + body = "a=1&b=2&c=3", + path = "/b", + headers = { + ["Expect"] = "100-continue", + ["Content-Type"] = "application/x-www-form-urlencoded", + } + } + + if not res then + ngx.log(ngx.ERR, "httpc:request failed: ", err) + end + + ngx.say(res.status) + ngx.say(res:read_body()) + httpc:close() + } + } + location = /b { + content_by_lua_block { + local len = ngx.req.get_headers()["Content-Length"] + + local sock, err = ngx.req.socket(true) + if not sock then + ngx.log(ngx.ERR, "server: failed to get raw req socket: ", err) + return + end + + -- without additional headers + local ok, err = sock:send("HTTP/1.1 100 Continue\r\n\r\n") + if not ok then + ngx.log(ngx.ERR, "failed to send 100 response: ", err) + end + + local data, err = sock:receive(len) + if not data then + ngx.log(ngx.ERR, "failed to receive: ", err) + return + end + + local ok, err = sock:send("HTTP/1.1 200 OK\r\n" .. + "Content-Length: " .. len .. "\r\n" .. + "Content-Type: application/x-www-form-urlencoded\r\n\r\n" .. + data) + if not ok then + ngx.log(ngx.ERR, "failed to send 200 response: ", err) + return + end + } + } +--- request +GET /a +--- response_body +200 +a=1&b=2&c=3 +--- no_error_log +[error] +[warn] + + +=== TEST 7: Non string request bodies are converted with correct length +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua ' + local httpc = require("resty.http").new() + + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/b" + + for _, body in ipairs({ 12345, + true, + "string", + { "tab", "le" }, + { "mix", 123, "ed", "tab", "le" } }) do + + local res, err = assert(httpc:request_uri(uri, { + body = body, + })) + + ngx.say(res.body) + end + '; + } + location = /b { + content_by_lua ' + ngx.req.read_body() + ngx.print(ngx.req.get_body_data()) + '; + } +--- request +GET /a +--- response_body +12345 +true +string +table +mix123edtable +--- no_error_log +[error] +[warn] + + +=== TEST 8: Request body as iterator +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua ' + local httpc = require("resty.http").new() + + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/b" + + local res, err = assert(httpc:request_uri(uri, { + body = coroutine.wrap(function() + coroutine.yield("foo") + coroutine.yield("bar") + end), + headers = { + ["Content-Length"] = 6 + } + })) + + ngx.say(res.body) + '; + } + location = /b { + content_by_lua ' + ngx.req.read_body() + ngx.print(ngx.req.get_body_data()) + '; + } +--- request +GET /a +--- response_body +foobar +--- no_error_log +[error] +[warn] + + +=== TEST 9: Request body as iterator, errors with missing length +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua ' + local httpc = require("resty.http").new() + + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/b" + + local res, err = httpc:request_uri(uri, { + body = coroutine.wrap(function() + coroutine.yield("foo") + coroutine.yield("bar") + end), + }) + + assert(not res) + ngx.say(err) + '; + } + location = /b { + content_by_lua ' + ngx.req.read_body() + ngx.print(ngx.req.get_body_data()) + '; + } +--- request +GET /a +--- response_body +Request body is a function but a length or chunked encoding is not specified +--- no_error_log +[error] +[warn] + + +=== TEST 10: Request body as iterator with chunked encoding +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local httpc = require("resty.http").new() + local yield = coroutine.yield + + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/b" + + local res, err = assert(httpc:request_uri(uri, { + body = coroutine.wrap(function() + yield("3\r\n") + yield("foo\r\n") + + yield("3\r\n") + yield("bar\r\n") + + yield("0\r\n") + yield("\r\n") + end), + headers = { + ["Transfer-Encoding"] = "chunked" + } + })) + + ngx.say(res.body) + } + } + location = /b { + content_by_lua ' + ngx.req.read_body() + ngx.print(ngx.req.get_body_data()) + '; + } +--- request +GET /a +--- response_body +foobar +--- no_error_log +[error] +[warn] diff --git a/t/04-trailers.t b/t/04-trailers.t index 7b6a6e98..bb9d8009 100644 --- a/t/04-trailers.t +++ b/t/04-trailers.t @@ -1,8 +1,6 @@ -use Test::Nginx::Socket; +use Test::Nginx::Socket 'no_plan'; use Cwd qw(cwd); -plan tests => repeat_each() * (blocks() * 4); - my $pwd = cwd(); $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; @@ -33,7 +31,11 @@ __DATA__ content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b", @@ -101,7 +103,11 @@ OK content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b", diff --git a/t/05-stream.t b/t/05-stream.t index 9228bbf8..7b90d2b2 100644 --- a/t/05-stream.t +++ b/t/05-stream.t @@ -1,8 +1,6 @@ -use Test::Nginx::Socket; +use Test::Nginx::Socket 'no_plan'; use Cwd qw(cwd); -plan tests => repeat_each() * (blocks() * 4) - 1; - my $pwd = cwd(); $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; @@ -33,7 +31,11 @@ __DATA__ content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b", @@ -81,7 +83,11 @@ chunked content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b", @@ -132,7 +138,11 @@ nil content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b", @@ -181,7 +191,11 @@ Buffer size not specified, bailing content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b", @@ -233,7 +247,11 @@ nil content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b", @@ -287,7 +305,11 @@ nil content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b", @@ -350,7 +372,11 @@ GET /a content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b", @@ -466,7 +492,11 @@ foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarba content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local reader, err = httpc:get_client_body_reader(64) @@ -509,7 +539,11 @@ foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarba content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b", diff --git a/t/06-simpleinterface.t b/t/06-simpleinterface.t index bee21d32..c828c2d5 100644 --- a/t/06-simpleinterface.t +++ b/t/06-simpleinterface.t @@ -1,8 +1,6 @@ -use Test::Nginx::Socket; +use Test::Nginx::Socket 'no_plan'; use Cwd qw(cwd); -plan tests => repeat_each() * (blocks() * 6); - my $pwd = cwd(); $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; @@ -232,3 +230,109 @@ OK --- no_error_log [error] [warn] + +=== TEST 6: Connection is closed on error +--- http_config eval: $::HttpConfig +--- config + lua_socket_read_timeout 100ms; + location = /a { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:"..ngx.var.server_port.."/b?a=1&b=2") + + if not res then + ngx.log(ngx.ERR, err) + else + return ngx.say("BAD") + end + + local ok, err = httpc.sock:close() + ngx.say(ok, " ", err) + + } + } + location = /b { + content_by_lua_block { + ngx.say("1") + ngx.flush(true) + ngx.sleep(0.5) + ngx.say("2") + + } + } +--- request +GET /a +--- response_body +nil closed +--- error_log +lua tcp socket read timed out + + +=== TEST 7: Content-Length is set on POST/PUT/PATCH requests when body is absent +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua ' + for i, method in ipairs({ "POST", "PUT", "PATCH" }) do + local http = require "resty.http" + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:"..ngx.var.server_port.."/b", { method = method }) + + if not res then + ngx.log(ngx.ERR, err) + end + + if i == 1 then + ngx.status = res.status + end + + ngx.print(res.body) + end + '; + } + location = /b { + content_by_lua ' + ngx.say(ngx.req.get_method(), " Content-Length: ", ngx.req.get_headers()["Content-Length"]) + '; + } +--- request +GET /a +--- response_body +POST Content-Length: 0 +PUT Content-Length: 0 +PATCH Content-Length: 0 +--- no_error_log +[error] +[warn] + + +=== TEST 8: Content-Length is not set on GET requests when body is absent +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua ' + local http = require "resty.http" + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:"..ngx.var.server_port.."/b") + + if not res then + ngx.log(ngx.ERR, err) + end + ngx.status = res.status + ngx.print(res.body) + '; + } + location = /b { + content_by_lua ' + ngx.say("Content-Length: ", type(ngx.req.get_headers()["Content-Length"])) + '; + } +--- request +GET /a +--- response_body +Content-Length: nil +--- no_error_log +[error] +[warn] + diff --git a/t/07-keepalive.t b/t/07-keepalive.t index 9a29d255..573bbf0d 100644 --- a/t/07-keepalive.t +++ b/t/07-keepalive.t @@ -1,8 +1,6 @@ -use Test::Nginx::Socket; +use Test::Nginx::Socket 'no_plan'; use Cwd qw(cwd); -plan tests => repeat_each() * (blocks() * 4); - my $pwd = cwd(); $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; @@ -34,13 +32,15 @@ __DATA__ local http = require "resty.http" local httpc = http.new() local res, err = httpc:request_uri( - "http://127.0.0.1:"..ngx.var.server_port.."/b", { - } + "http://127.0.0.1:" .. ngx.var.server_port.."/b", {} ) - ngx.say(res.headers["Connection"]) - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect { + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + } ngx.say(httpc:get_reused_times()) '; } @@ -75,12 +75,20 @@ keep-alive } ) - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) ngx.say(httpc:get_reused_times()) httpc:set_keepalive() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) ngx.say(httpc:get_reused_times()) '; } @@ -106,7 +114,11 @@ GET /a content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ path = "/b" @@ -117,7 +129,11 @@ GET /a ngx.say(res.headers["Connection"]) ngx.say(httpc:set_keepalive()) - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) ngx.say(httpc:get_reused_times()) '; } @@ -144,7 +160,11 @@ keep-alive content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local res, err = httpc:request{ version = 1.0, @@ -161,12 +181,20 @@ keep-alive ngx.say(r) ngx.say(e) - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) ngx.say(httpc:get_reused_times()) httpc:set_keepalive() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) ngx.say(httpc:get_reused_times()) '; } @@ -195,7 +223,11 @@ connection must be closed content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", 12345) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = 12345, + }) local res, err = httpc:request{ version = 1.0, @@ -211,12 +243,20 @@ connection must be closed ngx.say(r) ngx.say(e) - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) ngx.say(httpc:get_reused_times()) httpc:set_keepalive() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) ngx.say(httpc:get_reused_times()) '; } @@ -244,3 +284,151 @@ connection must be closed --- no_error_log [error] [warn] + +=== TEST 6: Simple interface, override settings +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + local res, err = httpc:request_uri( + "http://127.0.0.1:"..ngx.var.server_port.."/b", + { + keepalive = false + } + ) + + ngx.say(res.headers["Connection"]) + + httpc:connect { + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + } + ngx.say(httpc:get_reused_times()) + httpc:close() + + local res, err = httpc:request_uri( + "http://127.0.0.1:"..ngx.var.server_port.."/b", + { + keepalive_timeout = 10 + } + ) + + ngx.say(res.headers["Connection"]) + + httpc:connect { + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + } + ngx.say(httpc:get_reused_times()) + httpc:close() + + local res, err = httpc:request_uri( + "http://127.0.0.1:"..ngx.var.server_port.."/b", + { + keepalive_timeout = 1 + } + ) + + ngx.say(res.headers["Connection"]) + + ngx.sleep(1.1) + + httpc:connect { + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + } + ngx.say(httpc:get_reused_times()) + httpc:close() + } + } + location = /b { + content_by_lua_block { + ngx.say("OK") + } + } +--- request +GET /a +--- response_body +keep-alive +0 +keep-alive +1 +keep-alive +0 +--- no_error_log +[error] +[warn] + +=== TEST 7: Generic interface, HTTP 1.1, Connection: Upgrade, close. Test we don't try to keepalive, but also that subsequent connections can keepalive. +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + + -- Create a TCP connection and return an raw HTTP-response because + -- there is no way to set an "Connection: Upgrade, close" header in nginx. + assert(httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = 12345, + }), "connect should return positively") + + local res = httpc:request({ + version = 1.1, + path = "/b", + }) + + local body = res:read_body() + ngx.print(body) + + ngx.say(res.headers["Connection"]) + + local r, e = httpc:set_keepalive() + ngx.say(r) + ngx.say(e) + + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) + ngx.say(httpc:get_reused_times()) + + httpc:set_keepalive() + + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) + ngx.say(httpc:get_reused_times()) + } + } +--- tcp_listen: 12345 +--- tcp_reply +HTTP/1.1 200 OK +Date: Wed, 08 Aug 2018 17:00:00 GMT +Server: Apache/2 +Upgrade: h2,h2c +Connection: Upgrade, close + +OK +--- request +GET /a +--- response_body +OK +Upgrade, close +2 +connection must be closed +0 +1 +--- no_error_log +[error] +[warn] diff --git a/t/08-pipeline.t b/t/08-pipeline.t index a8b3b735..4384bc8c 100644 --- a/t/08-pipeline.t +++ b/t/08-pipeline.t @@ -1,8 +1,6 @@ -use Test::Nginx::Socket; +use Test::Nginx::Socket 'no_plan'; use Cwd qw(cwd); -plan tests => repeat_each() * (blocks() * 4); - my $pwd = cwd(); $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; @@ -26,14 +24,18 @@ no_long_string(); run_tests(); __DATA__ -=== TEST 1 Test that pipelined reqests can be read correctly. +=== TEST 1 Test that pipelined requests can be read correctly. --- http_config eval: $::HttpConfig --- config location = /a { content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) local responses = httpc:request_pipeline{ { @@ -101,7 +103,11 @@ D content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port + }) httpc:set_timeout(1) local responses = httpc:request_pipeline{ diff --git a/t/09-ssl.t b/t/09-ssl.t index bac29d06..7c1e5da0 100644 --- a/t/09-ssl.t +++ b/t/09-ssl.t @@ -1,8 +1,6 @@ -use Test::Nginx::Socket; +use Test::Nginx::Socket 'no_plan'; use Cwd qw(cwd); -plan tests => repeat_each() * (blocks() * 4); - my $pwd = cwd(); $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; diff --git a/t/10-clientbodyreader.t b/t/10-clientbodyreader.t index 15542346..6380c4a0 100644 --- a/t/10-clientbodyreader.t +++ b/t/10-clientbodyreader.t @@ -1,8 +1,6 @@ -use Test::Nginx::Socket; +use Test::Nginx::Socket 'no_plan'; use Cwd qw(cwd); -plan tests => repeat_each() * (blocks() * 4); - my $pwd = cwd(); $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; @@ -36,7 +34,11 @@ __DATA__ content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port, + }) local res, err = httpc:request{ path = "/c", @@ -62,3 +64,59 @@ OK [error] [warn] + +=== TEST 2: Read request body +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local httpc = require("resty.http").new() + + local reader, err = assert(httpc:get_client_body_reader()) + + repeat + local buffer, err = reader() + if err then + ngx.log(ngx.ERR, err) + end + + if buffer then + ngx.print(buffer) + end + until not buffer + } + } +--- request +POST /a +foobar +--- response_body: foobar +--- no_error_log +[error] +[warn] + + +=== TEST 2: Read chunked request body, errors as not yet supported +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua_block { + local httpc = require("resty.http").new() + local _, err = httpc:get_client_body_reader() + ngx.log(ngx.ERR, err) + } + } +--- more_headers +Transfer-Encoding: chunked +--- request eval +"POST /a +3\r +foo\r +3\r +bar\r +0\r +\r +" +--- error_log +chunked request bodies not supported yet +--- no_error_log +[warn] diff --git a/t/11-proxy.t b/t/11-proxy.t index fb40252d..a56203c9 100644 --- a/t/11-proxy.t +++ b/t/11-proxy.t @@ -1,8 +1,6 @@ -use Test::Nginx::Socket; +use Test::Nginx::Socket 'no_plan'; use Cwd qw(cwd); -plan tests => repeat_each() * (blocks() * 5); - my $pwd = cwd(); $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; @@ -35,7 +33,11 @@ __DATA__ content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port, + }) httpc:proxy_response(httpc:proxy_request()) httpc:set_keepalive() '; @@ -57,6 +59,8 @@ X-Test: foo --- no_error_log [error] [warn] +--- error_log +[debug] === TEST 2: Proxy POST request and response @@ -67,7 +71,11 @@ X-Test: foo content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port, + }) httpc:proxy_response(httpc:proxy_request()) httpc:set_keepalive() '; @@ -93,7 +101,10 @@ X-Test: foo --- error_code: 404 --- no_error_log [error] +--- error_log [warn] +--- error_log +[debug] === TEST 3: Proxy multiple headers @@ -104,7 +115,11 @@ X-Test: foo content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port, + }) httpc:proxy_response(httpc:proxy_request()) httpc:set_keepalive() '; @@ -125,6 +140,8 @@ OK --- no_error_log [error] [warn] +--- error_log +[debug] === TEST 4: Proxy still works with spaces in URI @@ -135,7 +152,11 @@ OK content_by_lua ' local http = require "resty.http" local httpc = http.new() - httpc:connect("127.0.0.1", ngx.var.server_port) + httpc:connect({ + scheme = "http", + host = "127.0.0.1", + port = ngx.var.server_port, + }) httpc:proxy_response(httpc:proxy_request()) httpc:set_keepalive() '; @@ -157,3 +178,5 @@ X-Test: foo --- no_error_log [error] [warn] +--- error_log +[debug] diff --git a/t/13-default-path.t b/t/13-default-path.t index fe68ac95..b5bb52e9 100644 --- a/t/13-default-path.t +++ b/t/13-default-path.t @@ -1,8 +1,6 @@ -use Test::Nginx::Socket; +use Test::Nginx::Socket 'no_plan'; use Cwd qw(cwd); -plan tests => repeat_each() * (blocks() * 3); - my $pwd = cwd(); $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; diff --git a/t/14-host-header.t b/t/14-host-header.t index 62a6164c..39e4d3ad 100644 --- a/t/14-host-header.t +++ b/t/14-host-header.t @@ -1,8 +1,6 @@ -use Test::Nginx::Socket; +use Test::Nginx::Socket 'no_plan'; use Cwd qw(cwd); -plan tests => repeat_each() * (blocks() * 3); - my $pwd = cwd(); $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; @@ -12,13 +10,15 @@ $ENV{TEST_COVERAGE} ||= 0; our $HttpConfig = qq{ lua_package_path "$pwd/lib/?.lua;/usr/local/share/lua/5.1/?.lua;;"; error_log logs/error.log debug; - resolver 8.8.8.8; + resolver 8.8.8.8 ipv6=off; init_by_lua_block { if $ENV{TEST_COVERAGE} == 1 then jit.off() require("luacov.runner").init() end + + require("resty.http").debug(true) } }; @@ -67,6 +67,7 @@ Host: www.google.com local http = require "resty.http" local httpc = http.new() + httpc:set_timeouts(300, 1000, 1000) local res, err = httpc:request_uri("https://www.google.com:443", { ssl_verify = false }) '; } @@ -89,6 +90,7 @@ Host: www.google.com --- config location /lua { content_by_lua ' + require("resty.http").debug(true) local http = require "resty.http" local httpc = http.new() @@ -117,6 +119,7 @@ Host: 127.0.0.1:8080 --- config location /lua { content_by_lua ' + require("resty.http").debug(true) local http = require "resty.http" local httpc = http.new() @@ -165,3 +168,31 @@ GET /a [error] --- response_body Unable to generate a useful Host header for a unix domain socket. Please provide one. + +=== TEST 6: Host header is correct when http_proxy is used +--- http_config + lua_package_path "$TEST_NGINX_PWD/lib/?.lua;;"; + error_log logs/error.log debug; + resolver 8.8.8.8; + server { + listen *:8080; + } + +--- config + location /lua { + content_by_lua ' + require("resty.http").debug(true) + local http = require "resty.http" + local httpc = http.new() + httpc:set_proxy_options({ + http_proxy = "http://127.0.0.1:8080" + }) + local res, err = httpc:request_uri("http://127.0.0.1:8081") + '; + } +--- request +GET /lua +--- no_error_log +[error] +--- error_log +Host: 127.0.0.1:8081 diff --git a/t/16-http-proxy.t b/t/16-http-proxy.t new file mode 100644 index 00000000..d5912181 --- /dev/null +++ b/t/16-http-proxy.t @@ -0,0 +1,520 @@ +use Test::Nginx::Socket 'no_plan'; +use Cwd qw(cwd); + +my $pwd = cwd(); + +$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; +$ENV{TEST_NGINX_PWD} ||= $pwd; +$ENV{TEST_COVERAGE} ||= 0; + +our $HttpConfig = qq{ + lua_package_path "$pwd/lib/?.lua;/usr/local/share/lua/5.1/?.lua;;"; + error_log logs/error.log debug; + resolver 8.8.8.8; + + init_by_lua_block { + if $ENV{TEST_COVERAGE} == 1 then + jit.off() + require("luacov.runner").init() + end + } +}; + +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: get_proxy_uri returns nil if proxy is not configured +--- http_config eval: $::HttpConfig +--- config + location /lua { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + ngx.say(httpc:get_proxy_uri("http", "example.com")) + } + } +--- request +GET /lua +--- response_body +nil +--- no_error_log +[error] +[warn] + + + +=== TEST 2: get_proxy_uri matches no_proxy hosts correctly +--- http_config eval: $::HttpConfig +--- config + location /lua { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + + -- helper that verifies get_proxy_uri works correctly with the given + -- scheme, host and no_proxy list + local function test_no_proxy(scheme, host, no_proxy) + httpc:set_proxy_options({ + http_proxy = "http://http_proxy.example.com", + https_proxy = "http://https_proxy.example.com", + no_proxy = no_proxy + }) + + local proxy_uri = httpc:get_proxy_uri(scheme, host) + ngx.say("scheme: ", scheme, ", host: ", host, ", no_proxy: ", no_proxy, ", proxy_uri: ", proxy_uri) + end + + -- All these match the no_proxy list + test_no_proxy("http", "example.com", nil) + test_no_proxy("http", "example.com", "*") + test_no_proxy("http", "example.com", "example.com") + test_no_proxy("http", "sub.example.com", "example.com") + test_no_proxy("http", "example.com", "example.com,example.org") + test_no_proxy("http", "example.com", "example.org,example.com") + + -- Same for https for good measure + test_no_proxy("https", "example.com", nil) + test_no_proxy("https", "example.com", "*") + test_no_proxy("https", "example.com", "example.com") + test_no_proxy("https", "sub.example.com", "example.com") + test_no_proxy("https", "example.com", "example.com,example.org") + test_no_proxy("https", "example.com", "example.org,example.com") + + -- Edge cases + + -- example.com should match .example.com in the no_proxy list (legacy behavior of wget) + test_no_proxy("http", "example.com", ".example.com") + + -- notexample.com should not match example.com in the no_proxy list (not a subdomain) + test_no_proxy("http", "notexample.com", "example.com") + } + } +--- request +GET /lua +--- response_body +scheme: http, host: example.com, no_proxy: nil, proxy_uri: http://http_proxy.example.com +scheme: http, host: example.com, no_proxy: *, proxy_uri: nil +scheme: http, host: example.com, no_proxy: example.com, proxy_uri: nil +scheme: http, host: sub.example.com, no_proxy: example.com, proxy_uri: nil +scheme: http, host: example.com, no_proxy: example.com,example.org, proxy_uri: nil +scheme: http, host: example.com, no_proxy: example.org,example.com, proxy_uri: nil +scheme: https, host: example.com, no_proxy: nil, proxy_uri: http://https_proxy.example.com +scheme: https, host: example.com, no_proxy: *, proxy_uri: nil +scheme: https, host: example.com, no_proxy: example.com, proxy_uri: nil +scheme: https, host: sub.example.com, no_proxy: example.com, proxy_uri: nil +scheme: https, host: example.com, no_proxy: example.com,example.org, proxy_uri: nil +scheme: https, host: example.com, no_proxy: example.org,example.com, proxy_uri: nil +scheme: http, host: example.com, no_proxy: .example.com, proxy_uri: nil +scheme: http, host: notexample.com, no_proxy: example.com, proxy_uri: http://http_proxy.example.com +--- no_error_log +[error] +[warn] + + + +=== TEST 3: get_proxy_uri returns correct proxy URIs for http and https URIs +--- http_config eval: $::HttpConfig +--- config + location /lua { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + + -- helper that configures the proxy opts as proived and checks what + -- get_proxy_uri says for the given scheme / host pair + local function test_get_proxy_uri(scheme, host, http_proxy, https_proxy) + httpc:set_proxy_options({ + http_proxy = http_proxy, + https_proxy = https_proxy + }) + + local proxy_uri = httpc:get_proxy_uri(scheme, host) + ngx.say( + "scheme: ", scheme, + ", host: ", host, + ", http_proxy: ", http_proxy, + ", https_proxy: ", https_proxy, + ", proxy_uri: ", proxy_uri + ) + end + + -- http + test_get_proxy_uri("http", "example.com", "http_proxy", "https_proxy") + test_get_proxy_uri("http", "example.com", nil, "https_proxy") + + -- https + test_get_proxy_uri("https", "example.com", "http_proxy", "https_proxy") + test_get_proxy_uri("https", "example.com", "http_proxy", nil) + } + } +--- request +GET /lua +--- response_body +scheme: http, host: example.com, http_proxy: http_proxy, https_proxy: https_proxy, proxy_uri: http_proxy +scheme: http, host: example.com, http_proxy: nil, https_proxy: https_proxy, proxy_uri: nil +scheme: https, host: example.com, http_proxy: http_proxy, https_proxy: https_proxy, proxy_uri: https_proxy +scheme: https, host: example.com, http_proxy: http_proxy, https_proxy: nil, proxy_uri: nil +--- no_error_log +[error] +[warn] + + + +=== TEST 4: request_uri uses http_proxy correctly for non-standard destination ports +--- http_config + lua_package_path "$TEST_NGINX_PWD/lib/?.lua;;"; + error_log logs/error.log debug; + resolver 8.8.8.8; + server { + listen *:8080; + + location / { + content_by_lua_block { + ngx.print(ngx.req.raw_header()) + } + } + } +--- config + location /lua { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + httpc:set_proxy_options({ + http_proxy = "http://127.0.0.1:8080", + https_proxy = "http://127.0.0.1:8080" + }) + + -- request should go to the proxy server + local res, err = httpc:request_uri("http://127.0.0.1:1234/target?a=1&b=2") + + if not res then + ngx.log(ngx.ERR, err) + return + end + ngx.status = res.status + ngx.say(res.body) + } + } +--- request +GET /lua +--- response_body_like +^GET http://127.0.0.1:1234/target\?a=1&b=2 HTTP/.+\r\nHost: 127.0.0.1:1234.+ +--- no_error_log +[error] +[warn] + + + +=== TEST 5: request_uri uses http_proxy correctly for standard destination port +--- http_config + lua_package_path "$TEST_NGINX_PWD/lib/?.lua;;"; + error_log logs/error.log debug; + resolver 8.8.8.8; + server { + listen *:8080; + + location / { + content_by_lua_block { + ngx.print(ngx.req.raw_header()) + } + } + } +--- config + location /lua { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + httpc:set_proxy_options({ + http_proxy = "http://127.0.0.1:8080", + https_proxy = "http://127.0.0.1:8080" + }) + + -- request should go to the proxy server + local res, err = httpc:request_uri("http://127.0.0.1/target?a=1&b=2") + + if not res then + ngx.log(ngx.ERR, err) + return + end + + -- the proxy echoed the raw request header and we shall pass it onwards + -- to the test harness + ngx.status = res.status + ngx.say(res.body) + } + } +--- request +GET /lua +--- response_body_like +^GET http://127.0.0.1/target\?a=1&b=2 HTTP/.+\r\nHost: 127.0.0.1.+ +--- no_error_log +[error] +[warn] + + + +=== TEST 6: request_uri makes a proper CONNECT request when proxying https resources +--- http_config eval: $::HttpConfig +--- config + location /lua { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + httpc:set_proxy_options({ + http_proxy = "http://127.0.0.1:12345", + https_proxy = "http://127.0.0.1:12345" + }) + + -- Slight Hack: temporarily change the module global user agent to make it + -- predictable for this test case + local ua = http._USER_AGENT + http._USER_AGENT = "test_ua" + local res, err = httpc:request_uri("https://127.0.0.1/target?a=1&b=2") + http._USER_AGENT = ua + + if not err then + -- The proxy request should fail as the TCP server listening returns + -- 403 response. We cannot really test the success case here as that + -- would require an actual reverse proxy to be implemented through + -- the limited functionality we have available in the raw TCP sockets + ngx.log(ngx.ERR, "unexpected success") + return + end + + ngx.status = 403 + ngx.say(err) + } + } +--- tcp_listen: 12345 +--- tcp_query eval +qr/CONNECT 127.0.0.1:443 HTTP\/1.1\r\n.*Host: 127.0.0.1:443\r\n.*/s + +# The reply cannot be successful or otherwise the client would start +# to do a TLS handshake with the proxied host and that we cannot +# do with these sockets +--- tcp_reply +HTTP/1.1 403 Forbidden +Connection: close + +--- request +GET /lua +--- error_code: 403 +--- no_error_log +[error] +[warn] + + + +=== TEST 7: request_uri uses http_proxy_authorization option +--- http_config + lua_package_path "$TEST_NGINX_PWD/lib/?.lua;;"; + error_log logs/error.log debug; + resolver 8.8.8.8; + server { + listen *:8080; + + location / { + content_by_lua_block { + ngx.print(ngx.var.http_proxy_authorization or "no-header") + } + } + } +--- config + location /lua { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + httpc:set_proxy_options({ + http_proxy = "http://127.0.0.1:8080", + http_proxy_authorization = "Basic ZGVtbzp0ZXN0", + https_proxy = "http://127.0.0.1:8080", + https_proxy_authorization = "Basic ZGVtbzpwYXNz" + }) + + -- request should go to the proxy server + local res, err = httpc:request_uri("http://127.0.0.1/") + if not res then + ngx.log(ngx.ERR, err) + return + end + + -- the proxy echoed the proxy authorization header + -- to the test harness + ngx.status = res.status + ngx.say(res.body) + } + } +--- request +GET /lua +--- response_body +Basic ZGVtbzp0ZXN0 +--- no_error_log +[error] +[warn] + + + +=== TEST 8: request_uri uses https_proxy_authorization option +--- http_config eval: $::HttpConfig +--- config + location /lua { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + httpc:set_proxy_options({ + http_proxy = "http://127.0.0.1:12345", + http_proxy_authorization = "Basic ZGVtbzp0ZXN0", + https_proxy = "http://127.0.0.1:12345", + https_proxy_authorization = "Basic ZGVtbzpwYXNz" + }) + + -- Slight Hack: temporarily change the module global user agent to make it + -- predictable for this test case + local ua = http._USER_AGENT + http._USER_AGENT = "test_ua" + local res, err = httpc:request_uri("https://127.0.0.1/target?a=1&b=2") + http._USER_AGENT = ua + + if not err then + -- The proxy request should fail as the TCP server listening returns + -- 403 response. We cannot really test the success case here as that + -- would require an actual reverse proxy to be implemented through + -- the limited functionality we have available in the raw TCP sockets + ngx.log(ngx.ERR, "unexpected success") + return + end + + ngx.status = 403 + ngx.say(err) + } + } +--- tcp_listen: 12345 +--- tcp_query eval +qr/CONNECT 127.0.0.1:443 HTTP\/1.1\r\n.*Proxy-Authorization: Basic ZGVtbzpwYXNz\r\n.*/s + +# The reply cannot be successful or otherwise the client would start +# to do a TLS handshake with the proxied host and that we cannot +# do with these sockets +--- tcp_reply +HTTP/1.1 403 Forbidden +Connection: close + +--- request +GET /lua +--- error_code: 403 +--- no_error_log +[error] +[warn] + + + +=== TEST 9: request_uri does not use http_proxy_authorization option when overridden +--- http_config + lua_package_path "$TEST_NGINX_PWD/lib/?.lua;;"; + error_log logs/error.log debug; + resolver 8.8.8.8; + server { + listen *:8080; + + location / { + content_by_lua_block { + ngx.print(ngx.var.http_proxy_authorization or "no-header") + } + } + } +--- config + location /lua { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + httpc:set_proxy_options({ + http_proxy = "http://127.0.0.1:8080", + http_proxy_authorization = "Basic ZGVtbzp0ZXN0", + https_proxy = "http://127.0.0.1:8080", + https_proxy_authorization = "Basic ZGVtbzpwYXNz" + }) + + -- request should go to the proxy server + local res, err = httpc:request_uri("http://127.0.0.1/", { + headers = { + ["Proxy-Authorization"] = "Basic ZGVtbzp3b3Jk" + } + }) + if not res then + ngx.log(ngx.ERR, err) + return + end + + -- the proxy echoed the proxy authorization header + -- to the test harness + ngx.status = res.status + ngx.say(res.body) + } + } +--- request +GET /lua +--- response_body +Basic ZGVtbzp3b3Jk +--- no_error_log +[error] +[warn] + + + +=== TEST 10: request_uri does not use https_proxy_authorization option when overridden +--- http_config eval: $::HttpConfig +--- config + location /lua { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + httpc:set_proxy_options({ + http_proxy = "http://127.0.0.1:12345", + http_proxy_authorization = "Basic ZGVtbzp0ZXN0", + https_proxy = "http://127.0.0.1:12345", + https_proxy_authorization = "Basic ZGVtbzpwYXNz" + }) + + -- Slight Hack: temporarily change the module global user agent to make it + -- predictable for this test case + local ua = http._USER_AGENT + http._USER_AGENT = "test_ua" + local res, err = httpc:request_uri("https://127.0.0.1/target?a=1&b=2", { + headers = { + ["Proxy-Authorization"] = "Basic ZGVtbzp3b3Jk" + } + }) + http._USER_AGENT = ua + + if not err then + -- The proxy request should fail as the TCP server listening returns + -- 403 response. We cannot really test the success case here as that + -- would require an actual reverse proxy to be implemented through + -- the limited functionality we have available in the raw TCP sockets + ngx.log(ngx.ERR, "unexpected success") + return + end + + ngx.status = 403 + ngx.say(err) + } + } +--- tcp_listen: 12345 +--- tcp_query eval +qr/CONNECT 127.0.0.1:443 HTTP\/1.1\r\n.*Proxy-Authorization: Basic ZGVtbzp3b3Jk\r\n.*/s + +# The reply cannot be successful or otherwise the client would start +# to do a TLS handshake with the proxied host and that we cannot +# do with these sockets +--- tcp_reply +HTTP/1.1 403 Forbidden +Connection: close + +--- request +GET /lua +--- error_code: 403 +--- no_error_log +[error] +[warn] diff --git a/t/17-deprecated.t b/t/17-deprecated.t new file mode 100644 index 00000000..4a0156c4 --- /dev/null +++ b/t/17-deprecated.t @@ -0,0 +1,58 @@ +use Test::Nginx::Socket 'no_plan'; +use Cwd qw(cwd); + +my $pwd = cwd(); + +$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; +$ENV{TEST_NGINX_PWD} ||= $pwd; +$ENV{TEST_COVERAGE} ||= 0; + +our $HttpConfig = qq{ + lua_package_path "$pwd/lib/?.lua;/usr/local/share/lua/5.1/?.lua;;"; + error_log logs/error.log debug; + resolver 8.8.8.8; + + init_by_lua_block { + if $ENV{TEST_COVERAGE} == 1 then + jit.off() + require("luacov.runner").init() + end + } +}; + +no_long_string(); +run_tests(); + +__DATA__ +=== TEST 1: Old connect syntax still works +--- http_config eval: $::HttpConfig +--- config + location /a { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + local ok, err = httpc:connect("127.0.0.1", ngx.var.server_port) + assert(ok, err) + + local res, err = httpc:request{ + path = "/b" + } + + ngx.status = res.status + ngx.print(res:read_body()) + + httpc:close() + } + } + location = /b { + echo "OK"; + } +--- request +GET /a +--- response_body +OK +--- no_error_log +[error] +[warn] +--- error_log +[debug] diff --git a/t/18-parse_uri.t b/t/18-parse_uri.t new file mode 100644 index 00000000..2343d9b8 --- /dev/null +++ b/t/18-parse_uri.t @@ -0,0 +1,157 @@ +use Test::Nginx::Socket 'no_plan'; +use Cwd qw(cwd); + +my $pwd = cwd(); + +$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; +$ENV{TEST_COVERAGE} ||= 0; + +our $HttpConfig = qq{ + lua_package_path "$pwd/lib/?.lua;/usr/local/share/lua/5.1/?.lua;;"; + error_log logs/error.log debug; + + init_by_lua_block { + if $ENV{TEST_COVERAGE} == 1 then + jit.off() + require("luacov.runner").init() + end + } +}; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ +=== TEST 1: Parse URI errors if malformed +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua ' + local http = require("resty.http").new() + local parts, err = http:parse_uri("http:///example.com") + if not parts then ngx.say(err) end + '; + } +--- request +GET /a +--- response_body +bad uri: http:///example.com +--- no_error_log +[error] +[warn] + + +=== TEST 2: Parse URI fills in defaults correctly +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua ' + local http = require("resty.http").new() + + local function test_uri(uri) + local scheme, host, port, path, query = unpack(http:parse_uri(uri, false)) + ngx.say("scheme: ", scheme, ", host: ", host, ", port: ", port, ", path: ", path, ", query: ", query) + end + + test_uri("http://example.com") + test_uri("http://example.com/") + test_uri("https://example.com/foo/bar") + test_uri("https://example.com/foo/bar?a=1&b=2") + test_uri("http://example.com?a=1&b=2") + test_uri("//example.com") + test_uri("//example.com?a=1&b=2") + test_uri("//example.com/foo/bar?a=1&b=2") + '; + } +--- request +GET /a +--- response_body +scheme: http, host: example.com, port: 80, path: /, query: +scheme: http, host: example.com, port: 80, path: /, query: +scheme: https, host: example.com, port: 443, path: /foo/bar, query: +scheme: https, host: example.com, port: 443, path: /foo/bar, query: a=1&b=2 +scheme: http, host: example.com, port: 80, path: /, query: a=1&b=2 +scheme: http, host: example.com, port: 80, path: /, query: +scheme: http, host: example.com, port: 80, path: /, query: a=1&b=2 +scheme: http, host: example.com, port: 80, path: /foo/bar, query: a=1&b=2 +--- no_error_log +[error] +[warn] + + +=== TEST 3: Parse URI fills in defaults correctly, using backwards compatible mode +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua ' + local http = require("resty.http").new() + + local function test_uri(uri) + local scheme, host, port, path, query = unpack(http:parse_uri(uri)) + ngx.say("scheme: ", scheme, ", host: ", host, ", port: ", port, ", path: ", path) + end + + test_uri("http://example.com") + test_uri("http://example.com/") + test_uri("https://example.com/foo/bar") + test_uri("https://example.com/foo/bar?a=1&b=2") + test_uri("http://example.com?a=1&b=2") + test_uri("//example.com") + test_uri("//example.com?a=1&b=2") + test_uri("//example.com/foo/bar?a=1&b=2") + '; + } +--- request +GET /a +--- response_body +scheme: http, host: example.com, port: 80, path: / +scheme: http, host: example.com, port: 80, path: / +scheme: https, host: example.com, port: 443, path: /foo/bar +scheme: https, host: example.com, port: 443, path: /foo/bar?a=1&b=2 +scheme: http, host: example.com, port: 80, path: /?a=1&b=2 +scheme: http, host: example.com, port: 80, path: / +scheme: http, host: example.com, port: 80, path: /?a=1&b=2 +scheme: http, host: example.com, port: 80, path: /foo/bar?a=1&b=2 +--- no_error_log +[error] +[warn] + + +=== TEST 4: IPv6 notation +--- http_config eval: $::HttpConfig +--- config + location = /a { + content_by_lua ' + local http = require("resty.http").new() + + local function test_uri(uri) + local scheme, host, port, path, query = unpack(http:parse_uri(uri, false)) + ngx.say("scheme: ", scheme, ", host: ", host, ", port: ", port, ", path: ", path, ", query: ", query) + end + + test_uri("http://[::1]") + test_uri("http://[::1]/") + test_uri("https://[::1]/foo/bar") + test_uri("https://[::1]/foo/bar?a=1&b=2") + test_uri("http://[::1]?a=1&b=2") + test_uri("//[0:0:0:0:0:0:0:0]") + test_uri("//[0:0:0:0:0:0:0:0]?a=1&b=2") + test_uri("//[0:0:0:0:0:0:0:0]/foo/bar?a=1&b=2") + '; + } +--- request +GET /a +--- response_body +scheme: http, host: [::1], port: 80, path: /, query: +scheme: http, host: [::1], port: 80, path: /, query: +scheme: https, host: [::1], port: 443, path: /foo/bar, query: +scheme: https, host: [::1], port: 443, path: /foo/bar, query: a=1&b=2 +scheme: http, host: [::1], port: 80, path: /, query: a=1&b=2 +scheme: http, host: [0:0:0:0:0:0:0:0], port: 80, path: /, query: +scheme: http, host: [0:0:0:0:0:0:0:0], port: 80, path: /, query: a=1&b=2 +scheme: http, host: [0:0:0:0:0:0:0:0], port: 80, path: /foo/bar, query: a=1&b=2 +--- no_error_log +[error] +[warn] diff --git a/t/19-ssl_reused_session.t b/t/19-ssl_reused_session.t new file mode 100644 index 00000000..d9b69236 --- /dev/null +++ b/t/19-ssl_reused_session.t @@ -0,0 +1,136 @@ +use Test::Nginx::Socket::Lua 'no_plan'; +use Cwd qw(abs_path realpath); +use File::Basename; + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; +$ENV{TEST_NGINX_CERT_DIR} ||= dirname(realpath(abs_path(__FILE__))); +$ENV{TEST_COVERAGE} ||= 0; + +my $realpath = realpath(); + +our $HttpConfig = qq{ + lua_package_path "$realpath/lib/?.lua;/usr/local/share/lua/5.1/?.lua;;"; + + init_by_lua_block { + if $ENV{TEST_COVERAGE} == 1 then + jit.off() + require("luacov.runner").init() + end + + TEST_SERVER_SOCK = "unix:/$ENV{TEST_NGINX_HTML_DIR}/nginx.sock" + + num_handshakes = 0 + } + + server { + listen unix:$ENV{TEST_NGINX_HTML_DIR}/nginx.sock ssl; + server_name example.com; + ssl_certificate $ENV{TEST_NGINX_CERT_DIR}/cert/test.crt; + ssl_certificate_key $ENV{TEST_NGINX_CERT_DIR}/cert/test.key; + ssl_session_tickets off; + + server_tokens off; + } +}; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: connect returns session userdata +--- http_config eval: $::HttpConfig +--- config + server_tokens off; + resolver $TEST_NGINX_RESOLVER ipv6=off; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + + location /t { + content_by_lua_block { + local httpc = assert(require("resty.http").new()) + local ok, err, session = assert(httpc:connect { + scheme = "https", + host = TEST_SERVER_SOCK, + }) + + assert(type(session) == "userdata" or type(session) == "cdata", "expected session to be userdata or cdata") + assert(httpc:close()) + } + } + +--- request +GET /t +--- no_error_log +[error] +[alert] + + +=== TEST 2: ssl_reused_session false does not return session userdata +--- http_config eval: $::HttpConfig +--- config + server_tokens off; + resolver $TEST_NGINX_RESOLVER ipv6=off; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + + location /t { + content_by_lua_block { + local httpc = assert(require("resty.http").new()) + local ok, err, session = assert(httpc:connect { + scheme = "https", + host = TEST_SERVER_SOCK, + ssl_reused_session = false, + }) + + assert(type(session) == "boolean", "expected session to be a boolean") + assert(session == true, "expected session to be true") + assert(httpc:close()) + } + } + +--- request +GET /t +--- no_error_log +[error] +[alert] + + +=== TEST 3: ssl_reused_session accepts userdata +--- http_config eval: $::HttpConfig +--- config + server_tokens off; + resolver $TEST_NGINX_RESOLVER ipv6=off; + lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + + location /t { + content_by_lua_block { + local httpc = assert(require("resty.http").new()) + local ok, err, session = assert(httpc:connect { + scheme = "https", + host = TEST_SERVER_SOCK, + }) + + assert(type(session) == "userdata" or type(session) == "cdata", "expected session to be userdata or cdata") + + local httpc2 = assert(require("resty.http").new()) + local ok, err, session2 = assert(httpc2:connect { + scheme = "https", + host = TEST_SERVER_SOCK, + ssl_reused_session = session, + }) + + assert(type(session2) == "userdata" or type(session2) == "cdata", "expected session2 to be userdata or cdata") + + assert(httpc:close()) + assert(httpc2:close()) + } + } + +--- request +GET /t +--- no_error_log +[error] +[alert] diff --git a/t/20-mtls.t b/t/20-mtls.t new file mode 100644 index 00000000..e5b6df6a --- /dev/null +++ b/t/20-mtls.t @@ -0,0 +1,316 @@ +use Test::Nginx::Socket::Lua 'no_plan'; + + +#$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; +#$ENV{TEST_COVERAGE} ||= 0; + +log_level 'debug'; + +no_long_string(); +#no_diff(); + +sub read_file { + my $infile = shift; + open my $in, $infile + or die "cannot open $infile for reading: $!"; + my $cert = do { local $/; <$in> }; + close $in; + $cert; +} + +our $MTLSCA = read_file("t/cert/mtls_ca.crt"); +our $MTLSClient = read_file("t/cert/mtls_client.crt"); +our $MTLSClientKey = read_file("t/cert/mtls_client.key"); +our $TestCert = read_file("t/cert/test.crt"); +our $TestKey = read_file("t/cert/test.key"); + +our $HtmlDir = html_dir; + +use Cwd qw(cwd); +my $pwd = cwd(); + +our $mtls_http_config = <<"_EOC_"; + lua_package_path "$pwd/lib/?.lua;/usr/local/share/lua/5.1/?.lua;;"; +server { + listen unix:$::HtmlDir/mtls.sock ssl; + + ssl_certificate $::HtmlDir/test.crt; + ssl_certificate_key $::HtmlDir/test.key; + ssl_client_certificate $::HtmlDir/mtls_ca.crt; + ssl_verify_client on; + server_tokens off; + server_name example.com; + + location / { + echo -n "hello, \$ssl_client_s_dn"; + } +} +_EOC_ + +our $mtls_user_files = <<"_EOC_"; +>>> mtls_ca.crt +$::MTLSCA +>>> mtls_client.key +$::MTLSClientKey +>>> mtls_client.crt +$::MTLSClient +>>> test.crt +$::TestCert +>>> test.key +$::TestKey +_EOC_ + +run_tests(); + +__DATA__ + +=== TEST 1: Connection fails during handshake without client cert and key +--- http_config eval: $::mtls_http_config +--- config eval +" +lua_ssl_trusted_certificate $::HtmlDir/test.crt; +location /t { + content_by_lua_block { + local httpc = assert(require('resty.http').new()) + + local ok, err = httpc:connect { + scheme = 'https', + host = 'unix:$::HtmlDir/mtls.sock', + } + + if ok and not err then + local res, err = assert(httpc:request { + method = 'GET', + path = '/', + headers = { + ['Host'] = 'example.com', + }, + }) + + ngx.status = res.status -- expect 400 + end + + httpc:close() + } +} +" +--- user_files eval: $::mtls_user_files +--- request +GET /t +--- error_code: 400 +--- no_error_log +[error] +[warn] + + +=== TEST 2: Connection fails during handshake with not priv_key +--- http_config eval: $::mtls_http_config +--- config eval +" +lua_ssl_trusted_certificate $::HtmlDir/test.crt; +location /t { + content_by_lua_block { + local f = assert(io.open('$::HtmlDir/mtls_client.crt')) + local cert_data = f:read('*a') + f:close() + + local ssl = require('ngx.ssl') + + local cert = assert(ssl.parse_pem_cert(cert_data)) + + local httpc = assert(require('resty.http').new()) + + local ok, err = httpc:connect { + scheme = 'https', + host = 'unix:$::HtmlDir/mtls.sock', + ssl_client_cert = cert, + ssl_client_priv_key = 'foo', + } + + if ok and not err then + local res, err = assert(httpc:request { + method = 'GET', + path = '/', + headers = { + ['Host'] = 'example.com', + }, + }) + + ngx.say(res:read_body()) + + else + ngx.say('failed to connect: ' .. (err or '')) + end + + httpc:close() + } +} +" +--- user_files eval: $::mtls_user_files +--- request +GET /t +--- error_code: 200 +--- no_error_log +[error] +[warn] +--- response_body +failed to connect: bad ssl_client_priv_key: cdata expected, got string +--- skip_nginx +4: < 1.21.4 + + +=== TEST 3: Connection succeeds with client cert and key. +--- http_config eval: $::mtls_http_config +--- config eval +" +lua_ssl_trusted_certificate $::HtmlDir/test.crt; + +location /t { + content_by_lua_block { + local f = assert(io.open('$::HtmlDir/mtls_client.crt')) + local cert_data = f:read('*a') + f:close() + + f = assert(io.open('$::HtmlDir/mtls_client.key')) + local key_data = f:read('*a') + f:close() + + local ssl = require('ngx.ssl') + + local cert = assert(ssl.parse_pem_cert(cert_data)) + local key = assert(ssl.parse_pem_priv_key(key_data)) + + local httpc = assert(require('resty.http').new()) + + local ok, err = httpc:connect { + scheme = 'https', + host = 'unix:$::HtmlDir/mtls.sock', + ssl_client_cert = cert, + ssl_client_priv_key = key, + } + + if ok and not err then + local res, err = assert(httpc:request { + method = 'GET', + path = '/', + headers = { + ['Host'] = 'example.com', + }, + }) + + ngx.say(res:read_body()) + end + + httpc:close() + } +} +" +--- user_files eval: $::mtls_user_files +--- request +GET /t +--- no_error_log +[error] +[warn] +--- response_body +hello, CN=foo@example.com,O=OpenResty,ST=California,C=US +--- skip_nginx +4: < 1.21.4 + +=== TEST 4: users with different client certs should not share the same pool. +--- http_config eval: $::mtls_http_config +--- config eval +" +lua_ssl_trusted_certificate $::HtmlDir/test.crt; + +location /t { + content_by_lua_block { + local f = assert(io.open('$::HtmlDir/mtls_client.crt')) + local cert_data = f:read('*a') + f:close() + + f = assert(io.open('$::HtmlDir/mtls_client.key')) + local key_data = f:read('*a') + f:close() + + local ssl = require('ngx.ssl') + + local cert = assert(ssl.parse_pem_cert(cert_data)) + local key = assert(ssl.parse_pem_priv_key(key_data)) + + f = assert(io.open('$::HtmlDir/test.crt')) + local invalid_cert_data = f:read('*a') + f:close() + + f = assert(io.open('$::HtmlDir/test.key')) + local invalid_key_data = f:read('*a') + f:close() + + local invalid_cert = assert(ssl.parse_pem_cert(invalid_cert_data)) + local invalid_key = assert(ssl.parse_pem_priv_key(invalid_key_data)) + + local httpc = assert(require('resty.http').new()) + + local ok, err = httpc:connect { + scheme = 'https', + host = 'unix:$::HtmlDir/mtls.sock', + ssl_client_cert = cert, + ssl_client_priv_key = key, + } + + if ok and not err then + local res, err = assert(httpc:request { + method = 'GET', + path = '/', + headers = { + ['Host'] = 'example.com', + }, + }) + + ngx.say(res:read_body()) + end + + httpc:set_keepalive() + + local httpc = assert(require('resty.http').new()) + + local ok, err = httpc:connect { + scheme = 'https', + host = 'unix:$::HtmlDir/mtls.sock', + ssl_client_cert = invalid_cert, + ssl_client_priv_key = invalid_key, + } + + ngx.say(httpc:get_reused_times()) + ngx.say(ok) + ngx.say(err) + + if ok and not err then + local res, err = assert(httpc:request { + method = 'GET', + path = '/', + headers = { + ['Host'] = 'example.com', + }, + }) + + ngx.say(res.status) -- expect 400 + end + + httpc:close() + } +} +" +--- user_files eval: $::mtls_user_files +--- request +GET /t +--- no_error_log +[error] +[warn] +--- response_body +hello, CN=foo@example.com,O=OpenResty,ST=California,C=US +0 +true +nil +400 +--- skip_nginx +4: < 1.21.4 diff --git a/t/cert/mtls_ca.crt b/t/cert/mtls_ca.crt new file mode 100644 index 00000000..99c8ec83 --- /dev/null +++ b/t/cert/mtls_ca.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUfTh89NyxxmbVwNZ/YFddssWc+WkwDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoM +CU9wZW5SZXN0eTEiMCAGA1UEAwwZT3BlblJlc3R5IFRlc3RpbmcgUm9vdCBDQTAe +Fw0xOTA5MTMyMjI4MTJaFw0zOTA5MDgyMjI4MTJaMFoxCzAJBgNVBAYTAlVTMRMw +EQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQKDAlPcGVuUmVzdHkxIjAgBgNVBAMM +GU9wZW5SZXN0eSBUZXN0aW5nIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDcMg2DeV8z+0E2ZiXUax111lKzhAbMCK0RJlV9tAi+YdcsDR/t +zvvAZNGONUoewUuz/7E88oweh+Xi1GJtvd0DjB70y7tgpf5PUXovstWVwy7s5jZo +kgn62yi9ZOOZpjwnYTBviirtRTnZRwkzL6wF0xMyJjAbKBJuPMrMiyFdh82lt7wI +NS4mhyEdM0UiVVxfC2uzsddTOcOJURfGbW7UZm4Xohzq4QZ8geQj2OT5YTqw7dZ7 +Xxre5H7IcNcAh+vIk5SEBV1WE+S5MnFly7gaLYNc49OSfz5Hcpv59Vr+4bZ+olbW +nQ/uU8BQovtkW6pjuT8nC4OKs2e8osoAZuk0rFS1uC501C+yES48mzaU8ttAidu6 +nb/JgsdkrnJQeTc5rAoER4M2ne5kqtEXN8wzf3/sazo2PLywbfrUXUTV6kJilrGr +RkBN+fr6HTBkf+ooQMBOQPTojUdwbR86CLCyiJov2bzmBfGcOgSakv59S+uvUZFp +FLTiahuzLfcgYsG3UKQA47pYlNdUqP8vCCaf1nwmqjx2KS3Z/YFnO/gQgtY+f0Bh +UpnUDv+zBxpVFfVCyxByEsDPdwDkqLSwB6+YZINl36S48iXpoPhNXIYmO6GnhNWV +k2/RyCDTxEO+MbXHVg6iyIVHJWth7m18vl4uuSK/LbJHV9Q9Z7G99DQ0NwIDAQAB +o2MwYTAdBgNVHQ4EFgQUuoo+ehdlDFcQU+j5qONMKh0NtFQwHwYDVR0jBBgwFoAU +uoo+ehdlDFcQU+j5qONMKh0NtFQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQELBQADggIBAM7c9q41K8306lfAVLqtbtaeETy+xxYG +XE2HfFW1IuukrXQ8d/JH4stL/HcHJzzhHPf5p3ja3Snu9zPmTk3pgUPDYPZf57tR +NCqwxjn6blwXWlzQqSavto9KAx3IWHuj0OTrZz/a1KPb9NGvatBhgthyRCRTbvhL +OA5tveuYSHb724cp3NZ1xaTQmDZsSgHCoCJ/7RnlbcJ7RsKCOzCWNFRomH410vdv +TajkUBlEC4OC1RIvxuVePHHb1ogbbe93SA/9mzw/E5SfoeF3mvByN4Ay8awXbNlH +26RfuIdGc4fZRc/87s4yPwhYScZBG+pHO0gn42E0FyiG6Jp3rhHMH5Sa2hNlPMpn +hYMaA6zQI4n/3AeFNM0VGxA+Yg/Al2WpXEJARrZqMW/qcrdMcPj5WeY6Tb6er04S +kfImwhMIajl3nNc9tHoad8r2VuMWMltH/dnWuEdo+pPdIY3fdJdyQeoLQDDLEQwL +AYrFy4uzKfQogfQBIHRdIMZTJh5v3mAFDpK59I5yzSt1GtUnFMC5MVOg+LbOo5UW +FCtwaW5EZiTszmakvvWMMZe9HwZMYNCeSGGtiPA/GA2zNci/n2TEcB11HgiY52y2 +E/40nS61oL81zMwhV7l5psgJxQ2ORsKRJPHjADwvwh3xyCEJgVyBRCDX7J3PpAUO +79DprjVU8t7p +-----END CERTIFICATE----- diff --git a/t/cert/mtls_client.crt b/t/cert/mtls_client.crt new file mode 100644 index 00000000..c6ba07cc --- /dev/null +++ b/t/cert/mtls_client.crt @@ -0,0 +1,111 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4096 (0x1000) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=California, O=OpenResty, CN=OpenResty Testing Root CA + Validity + Not Before: Sep 13 22:30:49 2019 GMT + Not After : Sep 10 22:30:49 2029 GMT + Subject: C=US, ST=California, O=OpenResty, CN=foo@example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:d0:8c:1e:2c:25:7f:00:9d:8a:3d:8a:f3:b5:1d: + 6b:24:f5:ac:35:7d:cd:b7:d0:af:db:88:7c:ee:82: + 46:16:47:f3:43:08:a1:04:6e:0d:3e:ce:69:fa:d5: + 89:14:82:20:f1:47:f2:38:c8:ab:ea:2f:1e:f0:15: + 04:c0:f4:8b:3c:c3:d4:78:56:87:4c:f1:70:ac:11: + 86:2e:c4:6a:6d:10:84:27:81:ca:2a:8b:85:3e:62: + 13:5e:40:6c:19:e4:49:3d:f3:de:aa:e8:5e:11:a1: + f2:66:83:6a:40:d1:34:c5:bf:b8:cb:97:7c:6a:ea: + 46:bf:17:be:32:8d:a8:31:56:e5:8b:6d:08:03:d0: + 44:69:b9:af:1e:15:1d:a5:64:9e:12:84:83:db:d9: + c6:71:90:3b:c2:7b:41:21:57:af:70:15:0b:56:59: + 21:a6:4e:46:71:66:90:f1:ef:bc:b2:48:f9:8b:ea: + e5:72:4a:ba:4a:ae:2d:74:0b:33:03:f6:2e:47:0f: + 56:a4:00:e8:1e:62:cb:b8:af:9c:98:1a:89:7c:d0: + a3:7a:5a:e1:84:50:64:e4:5d:a5:70:a4:69:54:c4: + f4:76:44:a2:be:1b:ef:dc:a3:d8:1d:0d:30:a2:d4: + 79:fb:39:76:ab:b7:18:f2:f7:92:f8:81:83:94:b8: + 11:b1 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Cert Type: + SSL Client, S/MIME + Netscape Comment: + OpenSSL Generated Client Certificate + X509v3 Subject Key Identifier: + D2:E4:F5:21:1C:17:A4:FF:13:F4:1A:28:A8:A7:DC:C6:DE:89:A0:31 + X509v3 Authority Key Identifier: + keyid:BA:8A:3E:7A:17:65:0C:57:10:53:E8:F9:A8:E3:4C:2A:1D:0D:B4:54 + + X509v3 Key Usage: critical + Digital Signature, Non Repudiation, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Client Authentication, E-mail Protection + X509v3 Subject Alternative Name: + email:foo@example.com, email:bar@example.com + Signature Algorithm: sha256WithRSAEncryption + 47:77:51:37:00:f7:28:da:c4:d9:4e:38:c4:ec:ea:24:c9:83: + 36:4c:90:93:a7:b2:2b:10:bf:75:df:0b:72:d8:e7:4f:4f:68: + e5:32:2f:35:89:17:95:5c:bb:43:fc:70:89:46:08:43:61:ac: + 41:62:84:01:94:88:d1:dc:8a:bd:30:2c:18:eb:51:79:0b:b7: + 1b:b6:49:df:c9:85:55:f6:73:9f:b7:83:99:52:23:fe:e6:bd: + 09:da:90:b9:e2:9b:68:c4:df:bd:fe:23:94:55:34:be:0d:7d: + 84:0c:53:69:2a:0f:3c:47:68:34:3f:2a:3f:89:3f:3e:d3:26: + ce:b7:58:bc:d0:6f:ee:f8:bd:5d:c6:48:ae:a0:6c:1f:6d:e0: + 66:93:7d:db:3c:07:e6:15:ae:aa:e3:d0:3d:ef:04:b6:dd:53: + 16:93:61:70:e9:af:c0:e9:1d:ff:2b:e5:0a:03:56:48:3f:1c: + dc:fe:1b:a6:6d:f6:54:ab:41:e5:3b:5b:ab:f5:81:10:46:26: + bb:ea:d7:0e:33:b1:5e:30:4d:81:86:63:9a:4a:4f:1e:44:b9: + c2:c6:08:4e:da:fa:3a:55:da:96:7c:01:f6:d5:e8:3b:ba:e9: + 31:3b:1c:51:39:1a:59:f0:e0:c7:17:2e:f6:18:9d:ec:a7:48: + 30:b8:4c:6d:e5:4a:4f:43:41:cb:0e:6b:ac:ad:87:44:90:76: + 85:23:2b:eb:8f:97:4b:22:13:60:20:3a:37:a4:dc:74:7d:85: + 3d:a1:f5:1a:03:f6:d5:78:c7:bc:9b:09:f2:c8:05:27:43:2a: + ac:50:21:3a:ee:83:2d:db:02:6f:c7:91:de:63:d6:36:7d:7a: + 9f:1f:fb:48:62:f4:fb:8e:3a:ea:61:9b:3c:03:f9:f8:a5:df: + 1b:02:14:2c:de:e6:e3:47:d2:44:65:94:1a:c6:e1:fd:ba:8d: + b6:f8:93:a9:46:46:26:79:b0:bf:57:a8:a2:20:66:56:7e:c9: + f5:a4:0b:5e:76:70:0a:47:a4:db:45:2e:15:99:69:f9:6b:14: + 93:2a:0a:b6:ee:53:a6:b9:02:9b:a2:25:37:1e:37:70:a2:7c: + 7f:c3:ce:98:17:2f:9b:5b:fa:6f:ae:d8:0e:d4:6a:b2:03:5a: + fe:ba:4b:7f:f6:98:20:ea:cb:be:17:34:e0:43:74:d1:0c:e5: + d4:cc:5d:13:41:d3:5e:a4:f6:94:f7:15:b8:15:a9:65:f8:28: + 3f:da:ef:b2:30:34:6d:96:3a:7a:f4:20:ec:9e:62:13:36:f1: + a7:04:e1:7a:d2:33:20:f6:61:4a:68:44:cb:92:d7:62:f0:e4: + 70:f0:a5:e3:dd:2f:e2:a3 +-----BEGIN CERTIFICATE----- +MIIFGTCCAwGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEiMCAGA1UE +AwwZT3BlblJlc3R5IFRlc3RpbmcgUm9vdCBDQTAeFw0xOTA5MTMyMjMwNDlaFw0y +OTA5MTAyMjMwNDlaMFAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh +MRIwEAYDVQQKDAlPcGVuUmVzdHkxGDAWBgNVBAMMD2Zvb0BleGFtcGxlLmNvbTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANCMHiwlfwCdij2K87UdayT1 +rDV9zbfQr9uIfO6CRhZH80MIoQRuDT7OafrViRSCIPFH8jjIq+ovHvAVBMD0izzD +1HhWh0zxcKwRhi7Eam0QhCeByiqLhT5iE15AbBnkST3z3qroXhGh8maDakDRNMW/ +uMuXfGrqRr8XvjKNqDFW5YttCAPQRGm5rx4VHaVknhKEg9vZxnGQO8J7QSFXr3AV +C1ZZIaZORnFmkPHvvLJI+Yvq5XJKukquLXQLMwP2LkcPVqQA6B5iy7ivnJgaiXzQ +o3pa4YRQZORdpXCkaVTE9HZEor4b79yj2B0NMKLUefs5dqu3GPL3kviBg5S4EbEC +AwEAAaOB8jCB7zAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAzBglghkgB +hvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgQ2xpZW50IENlcnRpZmljYXRlMB0G +A1UdDgQWBBTS5PUhHBek/xP0Giiop9zG3omgMTAfBgNVHSMEGDAWgBS6ij56F2UM +VxBT6Pmo40wqHQ20VDAOBgNVHQ8BAf8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUH +AwIGCCsGAQUFBwMEMCsGA1UdEQQkMCKBD2Zvb0BleGFtcGxlLmNvbYEPYmFyQGV4 +YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4ICAQBHd1E3APco2sTZTjjE7OokyYM2 +TJCTp7IrEL913wty2OdPT2jlMi81iReVXLtD/HCJRghDYaxBYoQBlIjR3Iq9MCwY +61F5C7cbtknfyYVV9nOft4OZUiP+5r0J2pC54ptoxN+9/iOUVTS+DX2EDFNpKg88 +R2g0Pyo/iT8+0ybOt1i80G/u+L1dxkiuoGwfbeBmk33bPAfmFa6q49A97wS23VMW +k2Fw6a/A6R3/K+UKA1ZIPxzc/humbfZUq0HlO1ur9YEQRia76tcOM7FeME2BhmOa +Sk8eRLnCxghO2vo6VdqWfAH21eg7uukxOxxRORpZ8ODHFy72GJ3sp0gwuExt5UpP +Q0HLDmusrYdEkHaFIyvrj5dLIhNgIDo3pNx0fYU9ofUaA/bVeMe8mwnyyAUnQyqs +UCE67oMt2wJvx5HeY9Y2fXqfH/tIYvT7jjrqYZs8A/n4pd8bAhQs3ubjR9JEZZQa +xuH9uo22+JOpRkYmebC/V6iiIGZWfsn1pAtednAKR6TbRS4VmWn5axSTKgq27lOm +uQKboiU3Hjdwonx/w86YFy+bW/pvrtgO1GqyA1r+ukt/9pgg6su+FzTgQ3TRDOXU +zF0TQdNepPaU9xW4Fall+Cg/2u+yMDRtljp69CDsnmITNvGnBOF60jMg9mFKaETL +ktdi8ORw8KXj3S/iow== +-----END CERTIFICATE----- diff --git a/t/cert/mtls_client.key b/t/cert/mtls_client.key new file mode 100644 index 00000000..34b301f0 --- /dev/null +++ b/t/cert/mtls_client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA0IweLCV/AJ2KPYrztR1rJPWsNX3Nt9Cv24h87oJGFkfzQwih +BG4NPs5p+tWJFIIg8UfyOMir6i8e8BUEwPSLPMPUeFaHTPFwrBGGLsRqbRCEJ4HK +KouFPmITXkBsGeRJPfPequheEaHyZoNqQNE0xb+4y5d8aupGvxe+Mo2oMVbli20I +A9BEabmvHhUdpWSeEoSD29nGcZA7wntBIVevcBULVlkhpk5GcWaQ8e+8skj5i+rl +ckq6Sq4tdAszA/YuRw9WpADoHmLLuK+cmBqJfNCjelrhhFBk5F2lcKRpVMT0dkSi +vhvv3KPYHQ0wotR5+zl2q7cY8veS+IGDlLgRsQIDAQABAoIBAEpoY++OZVT74LH6 +nN+XIn5qZUokm7yk6cnjVefndUhH3aSiNIkXFwS8sxV7ENDPaR+NcwANoUEKFPjG +Fw8dcXx5xpo1DUtHrdLG4eBX1j0Zsn1CErbBVwYeChkL1UYbrII9O8ow5DdYV9t5 +sfR0cGbJ9A43+31OH3XY69SvtD39xItgxK0Wg4Ciz475kCvG51Q1iBWAkm4koXhz +VCha4wghs81wJ28HRMFZAFf2C+72rk6EypMUX2dYirvW/+7zONirk298NDMAOSBh +mRWyPV8qipYx42hBQ9vSVm0UVb0ZbqVomKKUZfj11LL6Ad/OzCyVAiNLXyZREV6r +d324Bu0CgYEA/YPsE6p6H3MTPIoVTnsyyQw0pNmXtIAuYTgSveeuPoTYn3ZBWoGN +iLpbnW4EH3xNKfrdMjbqqLls1iwm7/ZAP5klAuL4s10onrcjMt65fyfa3Lw1gavG +SUFFdsueH2k3FohqNsbQUSXZILVQnXsRoldi38b7NKrAqABcEMAIqXMCgYEA0pde +nt4aMmrGBRPLnjCs1UlC5PbXzCE8XxxQ7HZKx4Sy5ErQ0EW1wzF6c0fEYI7i+j1/ +ESKqekzc5ue0T8acoioB+VUybO1oxQZsZUPY7roqXOYwZH9LQOdPYUOh9k33CZHw +6KFfx8bKCpdXn7FkwR2UUtCSp/6CZcyYr89Qn0sCgYAQ0L5I86bUDTL6cgJFyWAt ++7RGNvScEWCCLFD57bMeDHu93/8nvK4hopLPF2wIlpsbrLsdSI06EcqJTjZq9j9+ +uG6/CUULyKMYG/emuSU+rOsUdxtpdXZah4zO+2SKmtT/lp7M8VUB/OuxArXNLEuY +JAm35B/nd2f9/MAekE5CxwKBgQCV660w7G0590mB09XhiEWCkeVNm22FpSOVklMK +BCy4XX/9hkWh//6mN1M1PqJPG2n7PEx5pnQ3HQEmYU28fWiFCeLd3glIArvTh/8j +GGoXifEescFByl2IlyOr2roy3s4/weX/tuK5Fow/ff6jcWaJFMXDLzk437d1QXJx +tuVugQKBgByfr2eakXFQvAVGJUfVXA3M2BoBODZEPYTgryVMoEEduFy0HZiw4xKi +Dngwewy6/UJMAGA+8ak9Ca367FxnegZU9knm6ujYVyhU5WzbKpR8v7OaUP8d5icq +rCZZtglG0c8XfVpJjR4FsKA/qrFvKZpu5NdEw3o5/LSrV4HjqZQ6 +-----END RSA PRIVATE KEY----- diff --git a/t/cert/mtls_server.crt b/t/cert/mtls_server.crt new file mode 100644 index 00000000..c4d320f4 --- /dev/null +++ b/t/cert/mtls_server.crt @@ -0,0 +1,112 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4097 (0x1001) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=California, O=OpenResty, CN=OpenResty Testing Root CA + Validity + Not Before: Sep 13 22:32:03 2019 GMT + Not After : Sep 10 22:32:03 2029 GMT + Subject: C=US, ST=California, O=OpenResty, CN=example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:bb:69:2b:30:43:b0:b4:cc:84:3f:22:39:65:65: + 6a:bd:75:a4:2b:7d:f7:ec:e4:12:e8:d1:c4:ce:7e: + 4e:54:5c:22:cc:d2:18:7f:3b:9e:cb:70:d9:7d:79: + 8f:05:93:b6:9f:2f:d5:33:d7:98:a2:ed:c5:00:93: + e4:ca:bc:cb:f0:e1:63:3e:07:6b:38:6f:4d:09:45: + f1:a1:3b:a3:ca:c0:47:c1:a1:0a:f8:c9:bb:c7:da: + 26:9d:d3:0b:35:24:01:3e:16:14:2e:44:38:8c:c9: + 09:02:41:9e:b6:fb:0c:aa:fc:d6:44:5e:27:ab:aa: + d5:c3:68:e1:dd:57:06:6c:4f:f6:24:33:a8:2b:49: + 60:82:0e:15:aa:55:9f:61:cc:74:39:7e:9f:a6:4f: + 71:4a:8b:eb:43:dd:c2:f7:90:38:df:a6:a6:a8:f6: + 77:bc:9e:54:69:30:83:4c:2a:eb:b8:62:7c:c7:14: + 84:9e:f3:e1:4a:15:33:51:65:a3:af:9d:09:c6:b8: + 89:30:a3:d2:18:e9:dc:5d:6b:ea:68:ca:8b:5c:e4: + 3b:fe:32:7f:48:c3:4c:f0:b5:06:f6:23:97:3e:f2: + 50:90:68:26:39:6d:b2:e2:53:89:71:6a:48:f0:f1: + fc:89:3c:6d:db:87:6c:79:23:ed:87:5d:c5:fa:8a: + 0d:b9 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Cert Type: + SSL Server + Netscape Comment: + OpenSSL Generated Server Certificate + X509v3 Subject Key Identifier: + 8C:8E:18:2C:13:84:C9:2A:61:6B:73:3F:18:76:A4:85:55:5F:5C:5F + X509v3 Authority Key Identifier: + keyid:BA:8A:3E:7A:17:65:0C:57:10:53:E8:F9:A8:E3:4C:2A:1D:0D:B4:54 + DirName:/C=US/ST=California/O=OpenResty/CN=OpenResty Testing Root CA + serial:7D:38:7C:F4:DC:B1:C6:66:D5:C0:D6:7F:60:57:5D:B2:C5:9C:F9:69 + + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication + Signature Algorithm: sha256WithRSAEncryption + 80:9f:3e:f5:8b:50:ee:cb:e4:c2:f0:16:01:07:a1:af:76:bc: + da:c8:cd:24:e9:63:df:d3:47:28:8c:7f:58:14:5d:d4:fd:44: + 16:c3:06:15:be:90:ec:1c:8f:78:34:11:e7:cc:86:d8:2a:a2: + e5:99:70:83:76:4a:65:a4:e1:9a:68:20:29:c0:7a:c6:4a:08: + b3:74:c3:53:b5:7b:79:92:f1:99:b5:a1:a3:90:ce:9a:cb:26: + a5:a6:33:de:74:98:99:ec:18:d1:1e:41:be:f8:c3:d2:8d:aa: + 07:de:9a:97:28:0d:bf:70:ac:2b:cf:b7:ff:bc:ac:e4:16:0c: + 1c:03:a7:5a:2d:64:0d:90:16:bd:97:c3:1f:f5:bf:a9:fa:15: + d1:e0:d4:0d:f7:b3:51:23:ce:ad:16:4f:41:72:17:aa:01:d5: + 44:e2:9e:d5:ce:ea:54:98:04:43:14:2e:51:4b:c7:d9:21:4f: + e1:a4:fa:dd:e0:f0:82:ec:6f:9f:be:a2:3c:3b:85:f7:6d:96: + ee:0d:e6:08:2b:1b:be:06:a4:b7:5f:a3:f2:f2:b9:d0:5a:8f: + 90:86:1a:f4:7a:9f:c8:ae:09:1d:60:a2:8b:e0:0b:f6:00:21: + d9:df:33:4b:39:75:b6:64:9b:c7:df:e4:85:7a:ae:df:72:8c: + 8b:7e:98:8e:47:0a:27:1f:8e:2c:11:7f:7b:fc:a0:db:1b:6e: + f6:de:4e:85:ac:30:e6:e8:6a:7a:e6:f9:f4:18:0a:c6:ad:1c: + e1:0c:dd:e0:e0:8d:5a:d7:08:34:e7:22:b4:44:bd:99:39:b1: + 71:74:3f:7c:aa:65:f5:37:46:85:d3:79:f7:a8:35:8d:2b:30: + 99:d2:47:ce:a6:74:eb:f3:9f:d3:9a:4e:99:96:50:7b:ba:22: + c8:72:47:d4:da:6e:9a:73:01:3c:89:e9:3f:56:17:b7:ba:22: + 71:db:66:a2:d2:fb:33:51:36:f6:b6:f2:5b:32:70:9d:e7:e3: + 36:d6:ae:cb:9b:62:ef:69:c7:f7:ba:95:49:16:f5:7c:d9:29: + bb:0a:02:b1:6b:72:15:ab:2c:27:7b:c8:bc:f6:15:1f:fa:ae: + 08:fd:e0:11:36:b1:ab:9c:c8:11:d1:d3:0d:7d:49:4e:ca:e6: + 73:ee:0d:c3:8d:6f:f5:a4:fe:a1:af:6b:91:f7:53:fd:10:df: + 77:dd:ef:ec:7b:cf:32:75:df:04:8a:d1:a1:f7:36:68:ee:65: + e3:43:90:37:43:e8:d1:a8:e2:90:5c:1c:75:0a:29:94:4a:6a: + 9b:89:28:43:bd:85:56:0d:f1:2b:44:bd:e6:7a:4c:b7:85:10: + 77:b7:a8:0f:33:29:a7:26 +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEiMCAGA1UE +AwwZT3BlblJlc3R5IFRlc3RpbmcgUm9vdCBDQTAeFw0xOTA5MTMyMjMyMDNaFw0y +OTA5MTAyMjMyMDNaMEwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh +MRIwEAYDVQQKDAlPcGVuUmVzdHkxFDASBgNVBAMMC2V4YW1wbGUuY29tMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu2krMEOwtMyEPyI5ZWVqvXWkK333 +7OQS6NHEzn5OVFwizNIYfzuey3DZfXmPBZO2ny/VM9eYou3FAJPkyrzL8OFjPgdr +OG9NCUXxoTujysBHwaEK+Mm7x9omndMLNSQBPhYULkQ4jMkJAkGetvsMqvzWRF4n +q6rVw2jh3VcGbE/2JDOoK0lggg4VqlWfYcx0OX6fpk9xSovrQ93C95A436amqPZ3 +vJ5UaTCDTCrruGJ8xxSEnvPhShUzUWWjr50JxriJMKPSGOncXWvqaMqLXOQ7/jJ/ +SMNM8LUG9iOXPvJQkGgmOW2y4lOJcWpI8PH8iTxt24dseSPth13F+ooNuQIDAQAB +o4IBNTCCATEwCQYDVR0TBAIwADARBglghkgBhvhCAQEEBAMCBkAwMwYJYIZIAYb4 +QgENBCYWJE9wZW5TU0wgR2VuZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAdBgNV +HQ4EFgQUjI4YLBOEySpha3M/GHakhVVfXF8wgZcGA1UdIwSBjzCBjIAUuoo+ehdl +DFcQU+j5qONMKh0NtFShXqRcMFoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp +Zm9ybmlhMRIwEAYDVQQKDAlPcGVuUmVzdHkxIjAgBgNVBAMMGU9wZW5SZXN0eSBU +ZXN0aW5nIFJvb3QgQ0GCFH04fPTcscZm1cDWf2BXXbLFnPlpMA4GA1UdDwEB/wQE +AwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAgJ8+ +9YtQ7svkwvAWAQehr3a82sjNJOlj39NHKIx/WBRd1P1EFsMGFb6Q7ByPeDQR58yG +2Cqi5Zlwg3ZKZaThmmggKcB6xkoIs3TDU7V7eZLxmbWho5DOmssmpaYz3nSYmewY +0R5BvvjD0o2qB96alygNv3CsK8+3/7ys5BYMHAOnWi1kDZAWvZfDH/W/qfoV0eDU +DfezUSPOrRZPQXIXqgHVROKe1c7qVJgEQxQuUUvH2SFP4aT63eDwguxvn76iPDuF +922W7g3mCCsbvgakt1+j8vK50FqPkIYa9HqfyK4JHWCii+AL9gAh2d8zSzl1tmSb +x9/khXqu33KMi36YjkcKJx+OLBF/e/yg2xtu9t5Ohaww5uhqeub59BgKxq0c4Qzd +4OCNWtcINOcitES9mTmxcXQ/fKpl9TdGhdN596g1jSswmdJHzqZ06/Of05pOmZZQ +e7oiyHJH1NpumnMBPInpP1YXt7oicdtmotL7M1E29rbyWzJwnefjNtauy5ti72nH +97qVSRb1fNkpuwoCsWtyFassJ3vIvPYVH/quCP3gETaxq5zIEdHTDX1JTsrmc+4N +w41v9aT+oa9rkfdT/RDfd93v7HvPMnXfBIrRofc2aO5l40OQN0Po0ajikFwcdQop +lEpqm4koQ72FVg3xK0S95npMt4UQd7eoDzMppyY= +-----END CERTIFICATE----- diff --git a/t/cert/mtls_server.key b/t/cert/mtls_server.key new file mode 100644 index 00000000..3509beb6 --- /dev/null +++ b/t/cert/mtls_server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAu2krMEOwtMyEPyI5ZWVqvXWkK3337OQS6NHEzn5OVFwizNIY +fzuey3DZfXmPBZO2ny/VM9eYou3FAJPkyrzL8OFjPgdrOG9NCUXxoTujysBHwaEK ++Mm7x9omndMLNSQBPhYULkQ4jMkJAkGetvsMqvzWRF4nq6rVw2jh3VcGbE/2JDOo +K0lggg4VqlWfYcx0OX6fpk9xSovrQ93C95A436amqPZ3vJ5UaTCDTCrruGJ8xxSE +nvPhShUzUWWjr50JxriJMKPSGOncXWvqaMqLXOQ7/jJ/SMNM8LUG9iOXPvJQkGgm +OW2y4lOJcWpI8PH8iTxt24dseSPth13F+ooNuQIDAQABAoIBABnT/KfCLHA+X1t0 +FATtXTCPLfjwe2KibBi6EC2FKrZlnEYuDkI6rT/MZaztO9DA8sItjWx/ogGSUzwp +JbbrHhAsf8jkrNoyPKOyiAJ4fbJLnZgJ4cE3zDFW10uY8kp4k9NCp7VYoZKFgkBV +WtJM9wn5nm39q+n0uVEc+0PN4oy6m54Aqb1HqVCyXFp+/pVhL6PtgaClbqJd3oKV +0/HLWfWaI3nvV6ltAphUfPoCmYIUtSl90sRgSeEaJ61UZXh0OkhhtD7Iw/JUlHDk +a0J7owrh0Wf1kDsaSn+j1ba8MELsspFYYVm0gAMKAvRXbVrYgUMdb+HVdZ0odULl +ezFWeAECgYEA9nnoZs+PzKWNxTzYPtgvvrSLmpXLzNrs/p41JUhy6GjQIixTSBFy +WHkjwu0k2fvRgODfcaetAyK6sV4uTpRgqUhtFSeyNMelZ2yiIEqvhUrtHoVov1C+ +BqwwlUnmkQZNQODXOpKCvnqnOaPwMILKLtxDGmPtW0tCTR2dVVaht6ECgYEAwqb/ +h0Fh3YtykOnhV8wOZRrpVr8jS1RIgg/hklt2xh6+OYtL16sKFaLBF/BhzZRBapqd +fB2Cx3B6rxZ5PLTse8yjEvjt6Ly7TusYWpaKbYKFnnEbmdsm5sBepuLUv4AoMYbk +99ZejFcQI2gNbzX7eIrFitCQGxT+Wu7Gncv+vxkCgYBvAYCVrS2KcZVkG38Y7qyy +KwYk3QoofQD3u7Eb1YFLAsmaWnQ3pQPmrMhaZguO0UcN0DlSKr5VBzMl5tDcOx89 +noziVjqAYtovtlFeUcSzN4eLk3IVl/u9bZeD5QCemEP60Eie7JVNzFe8MgVfE8iT +Skg+fnrL/x0hNhFB+f5jgQKBgDgOEX4o5P8A3nA++gbnm6mgE1xI1OgnkG3sFuCn ++E9boRo/NAsalV/fq82yCuhB7oi9l+abNQMsMBhl12oVDBkmuDuJdjHUz/gNGclU +mu6obMRQ/ErVYqGG+nsCzZOMW4bPuvZoRHgTxnD70QqauB1hkTvFjgpOhGU5Z/cf +PPBZAoGBAJQK7NF6VoF9nm5CT8vufrQ2vvp2aiLdOLLx5JXt/6seEnPZWtEvjp8/ ++ExIsfOIaU5elhv8ze8iKmRP9f04XdWpbRm6k6AR5cOkkQQ1oO7N9abU7KbD/gqX +pJIWOlaUrbKO4Dprx7HyMYYPs9mu/UoF0Dvd/+bYXM5ZKiFrQ3Ly +-----END RSA PRIVATE KEY----- diff --git a/t/cert/test.crt b/t/cert/test.crt index ae113446..6efde069 100644 --- a/t/cert/test.crt +++ b/t/cert/test.crt @@ -1,24 +1,21 @@ -----BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIJALL9eJPZ6neGMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV -BAYTAkdCMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU -ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwR0ZXN0MB4XDTE1MTAyMTE2MjQ1 -NloXDTE1MTEyMDE2MjQ1NlowWDELMAkGA1UEBhMCR0IxDTALBgNVBAgTBFRlc3Qx -DTALBgNVBAcTBFRlc3QxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QxDTAL -BgNVBAMTBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDz/AoE -c+TPdm+Aqcchq8fLNWksFQZqbsCBGnq8rUG1b6MsVlAOkDUQGRlNPs9v0/+pzgX7 -IYXPCFcV7YONNsTUfvBYTq43mfOycmAdb3SX6kBygxdhYsDRZR+vCAIkjoRmRB20 -meh1motqM58spq3IcT8VADTRJl1OI48VTnxmXdCtmkOymU948DcauMoxm03eL/hU -6eniNEujbnbB305noNG0W5c3h6iz9CvqUAD1kwyjick+f1atB2YYn1bymA+db6YN -3iTo0v2raWmIc7D+qqpkNaCRxgMb2HN6X3/SfkijtNJidjqHMbs2ftlKJ5/lODPZ -rCPQOcYK6TT8MIZ1AgMBAAGjgbwwgbkwHQYDVR0OBBYEFFUC1GrAhUp7IvJH5iyf -+fJQliEIMIGJBgNVHSMEgYEwf4AUVQLUasCFSnsi8kfmLJ/58lCWIQihXKRaMFgx -CzAJBgNVBAYTAkdCMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYD -VQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwR0ZXN0ggkAsv14k9nq -d4YwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAtaUQOr3Qn87KXmmP -GbSvCLSl+bScE09VYZsYaB6iq0pGN9y+Vh4/HjBUUsFexopw1dY25MEEJXEVi1xV -2krLYAsfKCM6c1QBVmdqfVuxUvxpXwr+CNRNAlzz6PhjkeY/Ds/j4sg7EqN8hMmT -gu8GuogX7+ZCgrzRSMMclWej+W8D1xSIuCC+rqv4w9SZdtVb3XGpCyizpTNsQAuV -ACXvq9KXkEEj+XNvKrNdWd4zG715RdMnVm+WM53d9PLp63P+4/kwhwHULYhXygQ3 -DzzVPaojBBdw3VaHbbPHnv73FtAzOb7ky6zJ01DlmEPxEahCFpklMkY9T2uCdpj9 -oOzaNA== +MIIDYzCCAkugAwIBAgIUXCmnoPKJ60jFkycVZ04mVj3B8aswDQYJKoZIhvcNAQEL +BQAwQTELMAkGA1UEBhMCUFQxDjAMBgNVBAgMBVBvcnRvMQ4wDAYDVQQHDAVQb3J0 +bzESMBAGA1UECgwJbGVkZ2V0ZWNoMB4XDTIyMTIxNTEyMDQzMVoXDTMyMDkxMzEy +MDQzMVowQTELMAkGA1UEBhMCUFQxDjAMBgNVBAgMBVBvcnRvMQ4wDAYDVQQHDAVQ +b3J0bzESMBAGA1UECgwJbGVkZ2V0ZWNoMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAqzJINgQtchR799ahPpu2gweHCmx7cytQ59ZFW4J3QBRIB+DFnLSM +fJa797s0Sl6f+t9pT3QLYjbdNK+R60HGd5tM31cEIa518AcDokfkzGo/clY+dbs4 +OcFe10HxAlXpu6S5/yiQ0u4CIf1uQSsS53kNpaRWcEz6Z/sw80ksCEnYSfD3YXEh +sJm41i5Hd4F2/Y1WrMg82YGZG7Y81cM9LgUxKcikTm4JnEn9G1yg56hSbKs1a0E0 +J9Gk84lufWpiUqpX9ASsUPttnYzgljo24x7zeNEaXsVOKgu+88Cc+bBn7AORGXM4 +YtYbxVGhAgPf0vOalNl/kDwfE1jwBz0r6QIDAQABo1MwUTAdBgNVHQ4EFgQUxuo7 +FdlD/iQ7cMnORpKFQ0JW9I4wHwYDVR0jBBgwFoAUxuo7FdlD/iQ7cMnORpKFQ0JW +9I4wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAfINyfrlE/f3W +cTNFiVMzqCa+oeNtvlkaCKgtjJdow8xwAc/Mxae76dkN3Mh8yegKV9+QRY6rw6q0 +dfma/Rg0tJgrI9El64XdGRcMpwGdtujZbE/bCGTJwLpcIM051cr/NCtYYduM2RU8 +i5uc8z5sQbMIJHdwDDm4PevA4WrqteUo5bXFQp9jYessDIkjIg7n5hGNvSNtfpVV +0fGDYPD9yNydgRMe6EwqQ0Z9p6yfq4o60JceYt4MfbbcGxzwrQc41ou2wKM/iKnQ +FBwCOaa+imgn01Qno/PdisV05KM7uSv/dK1v33nrk4xPSVG8u0aGe440U1CcGXbB +jy3ACMw/ZQ== -----END CERTIFICATE----- diff --git a/t/cert/test.key b/t/cert/test.key index 4fcd0dae..7d61246a 100644 --- a/t/cert/test.key +++ b/t/cert/test.key @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEA8/wKBHPkz3ZvgKnHIavHyzVpLBUGam7AgRp6vK1BtW+jLFZQ -DpA1EBkZTT7Pb9P/qc4F+yGFzwhXFe2DjTbE1H7wWE6uN5nzsnJgHW90l+pAcoMX -YWLA0WUfrwgCJI6EZkQdtJnodZqLajOfLKatyHE/FQA00SZdTiOPFU58Zl3QrZpD -splPePA3GrjKMZtN3i/4VOnp4jRLo252wd9OZ6DRtFuXN4eos/Qr6lAA9ZMMo4nJ -Pn9WrQdmGJ9W8pgPnW+mDd4k6NL9q2lpiHOw/qqqZDWgkcYDG9hzel9/0n5Io7TS -YnY6hzG7Nn7ZSief5Tgz2awj0DnGCuk0/DCGdQIDAQABAoIBAGjKc7L94+SHRdTJ -FtILacCJrCZW0W6dKulIajbnYzV+QWMlnzTiEyha31ciBw5My54u8rqt5z7Ioj60 -yK+6OkfaTXhgMsuGv/iAz29VE4q7/fow+7XEKHTHLhiLJAB3hb42u1t6TzFTs1Vl -3pPa8wEIQsPOVuENzT1mYGoST7PW+LBIMr9ScMnRHfC0MNdV/ntQiXideOAd5PkA -4O7fNgYZ8CTAZ8rOLYTMFF76/c/jLiqfeghqbIhqMykk36kd7Lud//FRykVsn1aJ -REUva/SjVEth5kITot1hpMC4SIElWpha2YxiiZFoSXSaUbtHpymiUGV01cYtMWk0 -MZ5HN3ECgYEA/74U8DpwPxd4up9syKyNqOqrCrYnhEEC/tdU/W5wECi4y5kppjdd -88lZzICVPzk2fezYXlCO9HiSHU1UfcEsY3u16qNCvylK7Qz1OqXV/Ncj59891Q5Z -K0UBcbnrv+YD6muZuhlHEbyDPqYO091G9Gf/BbL5JIBDzg1qFO9Dh9cCgYEA9Drt -O9PJ5Sjz3mXQVtVHpwyhOVnd7CUv8a1zkUQCK5uQeaiF5kal1FIo7pLOr3KAvG0C -pXbm/TobwlfAfcERQN88aPN8Z/l1CB0oKV6ipBMD2/XLzDRtx8lpTeh/BB8jIhrz -+FDJY54HCzLfW0P5kT+Cyw51ofjziPnFdO/Z6pMCgYEAon17gEchGnUnWCwDSl2Y -hELV+jBSW02TQag/b+bDfQDiqTnfpKR5JXRBghYQveL0JH5f200EB4C0FboUfPJH -6c2ogDTLK/poiMU66tCDbeqj/adx+fTr4votOL0QdRUIV+GWAxAcf8BvA1cvBJ4L -fy60ckKM2gxFCJ6tUC/VkHECgYBoMDNAUItSnXPbrmeAg5/7naGxy6qmsP6RBUPF -9tNOMyEhJUlqAT2BJEOd8zcFFb3hpEd6uwyzfnSVJcZSX2iy2gj1ZNnvqTXJ7lZR -v7N2dz4wOd1lEgC7OCsaN1LoOThNtl3Z0uz2+FVc66jpUEhJNGThpxt7q66JArS/ -vAqkzQKBgFkzqA6QpnH5KhOCoZcuLQ4MtvnNHOx1xSm2B0gKDVJzGkHexTmOJvwM -ZhHXRl9txS4icejS+AGUXNBzCWEusfhDaZpZqS6zt6UxEjMsLj/Te7z++2KQn4t/ -aI77jClydW1pJvICtqm5v+sukVZvQTTJza9ujta6fj7u2s671np9 ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrMkg2BC1yFHv3 +1qE+m7aDB4cKbHtzK1Dn1kVbgndAFEgH4MWctIx8lrv3uzRKXp/632lPdAtiNt00 +r5HrQcZ3m0zfVwQhrnXwBwOiR+TMaj9yVj51uzg5wV7XQfECVem7pLn/KJDS7gIh +/W5BKxLneQ2lpFZwTPpn+zDzSSwISdhJ8PdhcSGwmbjWLkd3gXb9jVasyDzZgZkb +tjzVwz0uBTEpyKRObgmcSf0bXKDnqFJsqzVrQTQn0aTziW59amJSqlf0BKxQ+22d +jOCWOjbjHvN40RpexU4qC77zwJz5sGfsA5EZczhi1hvFUaECA9/S85qU2X+QPB8T +WPAHPSvpAgMBAAECggEADrAtoUwCQpRYWIv9LkJ5sJjmPX8DDn9Fnaz099d9qy1L +5GuLRbLF9PCRdKO55k5jenvnOXs5RZym2QNEfHaA9GuKKmvF8N/awmFpSE46M4Gp +xOhHMdG1qFroOLhkv1cNfhB+XTjzrm1GS8VGZU5jr+5Jo4/lUxUZTO+XCq1mAXK0 +io58x7K3Z7P26/46yeuuqNggbFCxeHfHSK8SXYg6QxgjlZyA/A8tHDuvhDCH81q/ +AISHxIFmly8ZQ2KxuVCSyHXQkPwODPh2F/4UpOq5EdN781CuV/W4e73WKBwcDUQM +aEX5KF8D2C0nXjk592SS9SFmXqw9ZD77sAL2JmbE4wKBgQDGoKT8i3xn3x4nAju0 +yTxK6jaV72Lrdn5HmOZyJb8irAUr9B0t33zaubzXlM1LpuS/JAQwtppShGo1jPlt +T8iE2+JR9UKi6siHV/3EKC15i5UT3p30txdR2KS+hAasb7qXvM6sgM0nwzoS9PUy +LbKnRN8UYTvJmEKU1vuyeFQAQwKBgQDcpUQpNhtf4GrT7EoBh8E24yOO2k9XludW +yhaEddTYe2FXmqf9MGuPPX+dBj3G1SY6RNXvjmVjr3uvFN5qz+mtOuAkm7g99KkI +bKjkVuCgx6mpiH89dt7d/3uTcKXEkai0F7JgSPd1mcSxjRybhyna4qC4CuCHjICU +Ug00LAqGYwKBgGjX6N6JPgySABd1HVDrG9ErWc7AwkUpkbR3J8S+2eoSRNSTkUdi +fUPy4JQmrkqteHbQKwoPiNvfmzRTCmHByEUgz5CVViwqo9iVAJUm5AIRRIptapD+ +h+ei5CrQA7nHbAWmGq2Be0juytuwwzBOYMvcFahrPqTFovdvlwH4c9aDAoGAYAk6 +4qkfPxrhxH3rNEFPUsGIX4wbzqbq6DarmFnlG5iQJN420hf6KO1+luz5hIqPyfre +Fxemf74Imor9yAXY0sJ2fticV7Mew4Dv/frmaHSfHyA/KZSMqpmhwunb7PPtNv29 +cPUxaClWmGUwF228RP4xMAnj8nuwF16jSpsEtbsCgYEAxEaEzpuknqV4AOnlJFBQ +7JqSkvW7w4Zy8hnUNmqhOOX1KwB0YudfirHceGtOpjxWQmHqjCHZqau0nEU02+ev +qUQsdiGKeJvWHYyqEldOdNd49qaux19IPQBeKRJQGWheGHDWGlg1aSDw3jY0sESS +3VqdbL5Z3XRXozQdwmNyfbw= +-----END PRIVATE KEY----- diff --git a/util/lua-releng b/util/lua-releng deleted file mode 100755 index e3dea2d6..00000000 --- a/util/lua-releng +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env perl - -use strict; -use warnings; - -sub file_contains ($$); - -my $version; -for my $file (map glob, qw{ *.lua lib/*.lua lib/*/*.lua lib/*/*/*.lua lib/*/*/*/*.lua lib/*/*/*/*/*.lua }) { - # Check the sanity of each .lua file - open my $in, $file or - die "ERROR: Can't open $file for reading: $!\n"; - my $found_ver; - while (<$in>) { - my ($ver, $skipping); - if (/(?x) (?:_VERSION) \s* = .*? ([\d\.]*\d+) (.*? SKIP)?/) { - my $orig_ver = $ver = $1; - $found_ver = 1; - # $skipping = $2; - $ver =~ s{^(\d+)\.(\d{3})(\d{3})$}{join '.', int($1), int($2), int($3)}e; - warn "$file: $orig_ver ($ver)\n"; - - } elsif (/(?x) (?:_VERSION) \s* = \s* ([a-zA-Z_]\S*)/) { - warn "$file: $1\n"; - $found_ver = 1; - last; - } - - if ($ver and $version and !$skipping) { - if ($version ne $ver) { - # die "$file: $ver != $version\n"; - } - } elsif ($ver and !$version) { - $version = $ver; - } - } - if (!$found_ver) { - warn "WARNING: No \"_VERSION\" or \"version\" field found in `$file`.\n"; - } - close $in; - - #print "Checking use of Lua global variables in file $file ...\n"; - system("luac5.1 -p -l $file | grep ETGLOBAL | grep -vE '(require|type|tostring|error|ngx|ndk|jit|setmetatable|getmetatable|string|table|io|os|print|tonumber|math|pcall|xpcall|unpack|pairs|ipairs|assert|module|package|coroutine|[gs]etfenv|next|select|rawset|rawget|debug)\$'"); - #file_contains($file, "attempt to write to undeclared variable"); - system("grep -H -n -E --color '.{120}' $file"); -} - -sub file_contains ($$) { - my ($file, $regex) = @_; - open my $in, $file - or die "Cannot open $file fo reading: $!\n"; - my $content = do { local $/; <$in> }; - close $in; - #print "$content"; - return scalar ($content =~ /$regex/); -} - -if (-d 't') { - for my $file (map glob, qw{ t/*.t t/*/*.t t/*/*/*.t }) { - system(qq{grep -H -n --color -E '\\--- ?(ONLY|LAST)' $file}); - } -} -