Rename controller to nginx

This commit is contained in:
Manuel de Brito Fontes 2016-03-27 22:12:15 -03:00
parent 41c34bd9e8
commit b7dee6f95c
47 changed files with 24 additions and 24 deletions

View file

@ -0,0 +1,67 @@
http = require "resty.http"
def_backend = "upstream-default-backend"
local concat = table.concat
local upstream = require "ngx.upstream"
local get_servers = upstream.get_servers
local get_upstreams = upstream.get_upstreams
local random = math.random
local us = get_upstreams()
function openURL(status)
local httpc = http.new()
local random_backend = get_destination()
local res, err = httpc:request_uri(random_backend, {
path = "/",
method = "GET",
headers = {
["X-Code"] = status or "404",
["X-Format"] = ngx.var.httpAccept or "html",
}
})
if not res then
ngx.log(ngx.ERR, err)
ngx.exit(500)
end
if ngx.var.http_cookie then
ngx.header["Cookie"] = ngx.var.http_cookie
end
ngx.status = tonumber(status)
ngx.say(res.body)
end
function get_destination()
for _, u in ipairs(us) do
if u == def_backend then
local srvs, err = get_servers(u)
local us_table = {}
if not srvs then
return "http://127.0.0.1:8181"
else
for _, srv in ipairs(srvs) do
us_table[srv["name"]] = srv["weight"]
end
end
local destination = random_weight(us_table)
return "http://"..destination
end
end
end
function random_weight(tbl)
local total = 0
for k, v in pairs(tbl) do
total = total + v
end
local offset = random(0, total - 1)
for k1, v1 in pairs(tbl) do
if offset < v1 then
return k1
end
offset = offset - v1
end
end

View file

@ -0,0 +1,78 @@
-- Simple trie for URLs
local _M = {}
local mt = {
__index = _M
}
-- http://lua-users.org/wiki/SplitJoin
local strfind, tinsert, strsub = string.find, table.insert, string.sub
function _M.strsplit(delimiter, text)
local list = {}
local pos = 1
while 1 do
local first, last = strfind(text, delimiter, pos)
if first then -- found?
tinsert(list, strsub(text, pos, first-1))
pos = last+1
else
tinsert(list, strsub(text, pos))
break
end
end
return list
end
local strsplit = _M.strsplit
function _M.new()
local t = { }
return setmetatable(t, mt)
end
function _M.add(t, key, val)
local parts = {}
-- hack for just /
if key == "/" then
parts = { "" }
else
parts = strsplit("/", key)
end
local l = t
for i = 1, #parts do
local p = parts[i]
if not l[p] then
l[p] = {}
end
l = l[p]
end
l.__value = val
end
function _M.get(t, key)
local parts = strsplit("/", key)
local l = t
-- this may be nil
local val = t.__value
for i = 1, #parts do
local p = parts[i]
if l[p] then
l = l[p]
local v = l.__value
if v then
val = v
end
else
break
end
end
-- may be nil
return val
end
return _M

View file

@ -0,0 +1,2 @@
t/servroot/
t/error.log

View file

@ -0,0 +1,23 @@
Copyright (c) 2013, James Hurst
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,20 @@
OPENRESTY_PREFIX=/usr/local/openresty
PREFIX ?= /usr/local
LUA_INCLUDE_DIR ?= $(PREFIX)/include
LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION)
INSTALL ?= install
TEST_FILE ?= t
.PHONY: all test install
all: ;
install: all
$(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty
$(INSTALL) lib/resty/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/
test: all
PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH TEST_NGINX_NO_SHUFFLE=1 prove -I../test-nginx/lib -r $(TEST_FILE)
util/lua-releng

View file

@ -0,0 +1,423 @@
# lua-resty-http
Lua HTTP client cosocket driver for [OpenResty](http://openresty.org/) / [ngx_lua](https://github.com/chaoslawful/lua-nginx-module).
# Status
Ready for testing. Probably production ready in most cases, though not yet proven in the wild. Please check the issues list and let me know if you have any problems / questions.
# Features
* HTTP 1.0 and 1.1
* Streaming interface to reading bodies using coroutines, for predictable memory usage in Lua land.
* Alternative simple interface for singleshot requests without manual connection step.
* Headers treated case insensitively.
* Chunked transfer encoding.
* Keepalive.
* Pipelining.
* Trailers.
# API
* [new](#name)
* [connect](#connect)
* [set_timeout](#set_timeout)
* [ssl_handshake](#ssl_handshake)
* [set_keepalive](#set_keepalive)
* [get_reused_times](#get_reused_times)
* [close](#close)
* [request](#request)
* [request_uri](#request_uri)
* [request_pipeline](#request_pipeline)
* [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
';
}
}
````
# Connection
## new
`syntax: httpc = http.new()`
Creates the http 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 = httpc:connect("unix:/path/to/unix.sock", options_table?)`
Attempts to connect to the web server.
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.
An optional Lua table can be specified as the last argument to this method to specify various connect options:
* `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 `<host>:<port>` or `<unix-socket-path>`.
## set_timeout
`syntax: httpc:set_timeout(time)`
Sets the timeout (in ms) protection for subsequent operations, including the `connect` method.
## ssl_handshake
`syntax: session, err = httpc:ssl_handshake(session, host, verify)`
Performs an SSL handshake on the TCP connection, only availble in ngx_lua > v0.9.11
See docs for [ngx.socket.tcp](https://github.com/openresty/lua-nginx-module#ngxsockettcp) for details.
## set_keepalive
`syntax: ok, err = httpc:set_keepalive(max_idle_timeout, pool_size)`
Attempts to puts the current connection into the ngx_lua cosocket connection pool.
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.
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.
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`.
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`.
## 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.
## 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.
# Requesting
## request
`syntax: res, err = httpc:request(params)`
Returns a `res` table or `nil` and an error message.
The `params` table accepts the following fields:
* `version` The HTTP version number, currently supporting 1.0 or 1.1.
* `method` The HTTP method string.
* `path` The path 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
When the request is successful, `res` will contain the following fields:
* `status` The status code.
* `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
`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.
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.
Additionally there is no ability to stream the response body in this mode. 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.
## 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:
```lua
local responses = httpc:request_pipeline{
{
path = "/b",
},
{
path = "/c",
},
{
path = "/d",
}
}
for i,r in ipairs(responses) do
if r.status then
ngx.say(r.status)
ngx.say(r:read_body())
end
end
```
Due to the nature of pipelining, no responses are actually read until you attempt to use the response fields (status / headers etc). And since the responses are read off in order, you must read the entire body (and any trailers if you have them), before attempting to read the next response.
Note this doesn't preclude the use of the streaming response body reader. Responses can still be streamed, so long as the entire body is streamed before attempting to access the next response.
Be sure to test at least one field (such as status) before trying to use the others, in case a socket read error has occurred.
# Response
## 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
repeat
local chunk, err = reader(8192)
if err then
ngx.log(ngx.ERR, err)
break
end
if chunk then
-- process
end
until not chunk
````
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.
## res:read_body
`syntax: body, err = res:read_body()`
Reads the entire body into a local string.
## res:read_trailers
`syntax: res:read_trailers()`
This merges any trailers underneath the `res.headers` table itself. Must be called after reading the body.
# Proxy
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:
```lua
local http = require "resty.http"
local httpc = http.new()
httpc:set_timeout(500)
local ok, err = httpc:connect(HOST, PORT)
if not ok then
ngx.log(ngx.ERR, err)
return
end
httpc:set_timeout(2000)
httpc:proxy_response(httpc:proxy_request())
httpc:set_keepalive()
```
## proxy_request
`syntax: local res, err = httpc:proxy_request(request_body_chunk_size?)`
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).
## 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).
# Utility
## parse_uri
`syntax: local scheme, host, port, path = unpack(httpc:parse_uri(uri))`
This is a convenience function allowing one to more easily use the generic interface, when the input data is a URI.
## get_client_body_reader
`syntax: reader, err = httpc:get_client_body_reader(chunksize?, sock?)`
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.
Example:
```lua
local req_reader = httpc:get_client_body_reader()
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
```
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.
```lua
local client_body_reader, err = httpc:get_client_body_reader()
local res, err = httpc:request{
path = "/helloworld",
body = client_body_reader,
}
```
If `sock` is specified,
# Author
James Hurst <james@pintsized.co.uk>
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.
# Licence
This module is licensed under the 2-clause BSD license.
Copyright (c) 2013, James Hurst <james@pintsized.co.uk>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,818 @@
local http_headers = require "resty.http_headers"
local ngx_socket_tcp = ngx.socket.tcp
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
local str_sub = string.sub
local str_gsub = string.gsub
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_gsub = ngx.re.gsub
local ngx_log = ngx.log
local ngx_DEBUG = ngx.DEBUG
local ngx_ERR = ngx.ERR
local ngx_NOTICE = ngx.NOTICE
local ngx_var = ngx.var
local co_yield = coroutine.yield
local co_create = coroutine.create
local co_status = coroutine.status
local co_resume = coroutine.resume
-- http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
local HOP_BY_HOP_HEADERS = {
["connection"] = true,
["keep-alive"] = true,
["proxy-authenticate"] = true,
["proxy-authorization"] = true,
["te"] = true,
["trailers"] = true,
["transfer-encoding"] = true,
["upgrade"] = true,
["content-length"] = true, -- Not strictly hop-by-hop, but Nginx will deal
-- with this (may send chunked for example).
}
-- Reimplemented coroutine.wrap, returning "nil, err" if the coroutine cannot
-- be resumed. This protects user code from inifite 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.
-- ...
-- end
-- until not chunk
local co_wrap = function(func)
local co = co_create(func)
if not co then
return nil, "could not create coroutine"
else
return function(...)
if co_status(co) == "suspended" then
return select(2, co_resume(co, ...))
else
return nil, "can't resume a " .. co_status(co) .. " coroutine"
end
end
end
end
local _M = {
_VERSION = '0.07',
}
_M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version
local mt = { __index = _M }
local HTTP = {
[1.0] = " HTTP/1.0\r\n",
[1.1] = " HTTP/1.1\r\n",
}
local DEFAULT_PARAMS = {
method = "GET",
path = "/",
version = 1.1,
}
function _M.new(self)
local sock, err = ngx_socket_tcp()
if not sock then
return nil, err
end
return setmetatable({ sock = sock, keepalive = true }, mt)
end
function _M.set_timeout(self, timeout)
local sock = self.sock
if not sock then
return nil, "not initialized"
end
return sock:settimeout(timeout)
end
function _M.ssl_handshake(self, ...)
local sock = self.sock
if not sock then
return nil, "not initialized"
end
return sock:sslhandshake(...)
end
function _M.connect(self, ...)
local sock = self.sock
if not sock then
return nil, "not initialized"
end
self.host = select(1, ...)
self.keepalive = true
return sock:connect(...)
end
function _M.set_keepalive(self, ...)
local sock = self.sock
if not sock then
return nil, "not initialized"
end
if self.keepalive == true then
return sock:setkeepalive(...)
else
-- The server said we must close the connection, so we cannot setkeepalive.
-- If close() succeeds we return 2 instead of 1, to differentiate between
-- a normal setkeepalive() failure and an intentional close().
local res, err = sock:close()
if res then
return 2, "connection must be closed"
else
return res, err
end
end
end
function _M.get_reused_times(self)
local sock = self.sock
if not sock then
return nil, "not initialized"
end
return sock:getreusedtimes()
end
function _M.close(self)
local sock = self.sock
if not sock then
return nil, "not initialized"
end
return sock:close()
end
local function _should_receive_body(method, code)
if method == "HEAD" then return nil end
if code == 204 or code == 304 then return nil end
if code >= 100 and code < 200 then return nil end
return true
end
function _M.parse_uri(self, uri)
local m, err = ngx_re_match(uri, [[^(http[s]*)://([^:/]+)(?::(\d+))?(.*)]],
"jo")
if not m then
if err then
return nil, "failed to match the uri: " .. err
end
return nil, "bad uri"
else
if not m[3] then
if m[1] == "https" then
m[3] = 443
else
m[3] = 80
end
end
if not m[4] or "" == m[4] then m[4] = "/" end
return m, nil
end
end
local function _format_request(params)
local version = params.version
local headers = params.headers or {}
local query = params.query or ""
if query then
if type(query) == "table" then
query = "?" .. ngx_encode_args(query)
end
end
-- Initialize request
local req = {
str_upper(params.method),
" ",
params.path,
query,
HTTP[version],
-- Pre-allocate slots for minimum headers and carriage return.
true,
true,
true,
}
local c = 6 -- 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"
c = c + 1
end
end
-- Close headers
req[c] = "\r\n"
return tbl_concat(req)
end
local function _receive_status(sock)
local line, err = sock:receive("*l")
if not line then
return nil, nil, err
end
return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8))
end
local function _receive_headers(sock)
local headers = http_headers.new()
repeat
local line, err = sock:receive("*l")
if not line then
return nil, err
end
for key, val in str_gmatch(line, "([^:%s]+):%s*(.+)") do
if headers[key] then
if type(headers[key]) ~= "table" then
headers[key] = { headers[key] }
end
tbl_insert(headers[key], tostring(val))
else
headers[key] = tostring(val)
end
end
until str_find(line, "^%s*$")
return headers, nil
end
local function _chunked_body_reader(sock, default_chunk_size)
return co_wrap(function(max_chunk_size)
local max_chunk_size = max_chunk_size or default_chunk_size
local remaining = 0
local length
repeat
-- If we still have data on this chunk
if max_chunk_size and remaining > 0 then
if remaining > max_chunk_size then
-- Consume up to max_chunk_size
length = max_chunk_size
remaining = remaining - max_chunk_size
else
-- Consume all remaining
length = remaining
remaining = 0
end
else -- This is a fresh chunk
-- Receive the chunk size
local str, err = sock:receive("*l")
if not str then
co_yield(nil, err)
end
length = tonumber(str, 16)
if not length then
co_yield(nil, "unable to read chunksize")
end
if max_chunk_size and length > max_chunk_size then
-- Consume up to max_chunk_size
remaining = length - max_chunk_size
length = max_chunk_size
end
end
if length > 0 then
local str, err = sock:receive(length)
if not str then
co_yield(nil, err)
end
max_chunk_size = co_yield(str) or default_chunk_size
-- If we're finished with this chunk, read the carriage return.
if remaining == 0 then
sock:receive(2) -- read \r\n
end
else
-- Read the last (zero length) chunk's carriage return
sock:receive(2) -- read \r\n
end
until length == 0
end)
end
local function _body_reader(sock, content_length, default_chunk_size)
return co_wrap(function(max_chunk_size)
local max_chunk_size = max_chunk_size or default_chunk_size
if not content_length and max_chunk_size then
-- We have no length, but wish to stream.
-- HTTP 1.0 with no length will close connection, so read chunks to the end.
repeat
local str, err, partial = sock:receive(max_chunk_size)
if not str and err == "closed" then
max_chunk_size = tonumber(co_yield(partial, err) or default_chunk_size)
end
max_chunk_size = tonumber(co_yield(str) or default_chunk_size)
if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end
if not max_chunk_size then
ngx_log(ngx_ERR, "Buffer size not specified, bailing")
break
end
until not str
elseif not content_length then
-- We have no length but don't wish to stream.
-- HTTP 1.0 with no length will close connection, so read to the end.
co_yield(sock:receive("*a"))
elseif not max_chunk_size then
-- We have a length and potentially keep-alive, but want everything.
co_yield(sock:receive(content_length))
else
-- We have a length and potentially a keep-alive, and wish to stream
-- the response.
local received = 0
repeat
local length = max_chunk_size
if received + length > content_length then
length = content_length - received
end
if length > 0 then
local str, err = sock:receive(length)
if not str then
max_chunk_size = tonumber(co_yield(nil, err) or default_chunk_size)
end
received = received + length
max_chunk_size = tonumber(co_yield(str) or default_chunk_size)
if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end
if not max_chunk_size then
ngx_log(ngx_ERR, "Buffer size not specified, bailing")
break
end
end
until length == 0
end
end)
end
local function _no_body_reader()
return nil
end
local function _read_body(res)
local reader = res.body_reader
if not reader then
-- Most likely HEAD or 304 etc.
return nil, "no body to be read"
end
local chunks = {}
local c = 1
local chunk, err
repeat
chunk, err = reader()
if err then
return nil, err, tbl_concat(chunks) -- Return any data so far.
end
if chunk then
chunks[c] = chunk
c = c + 1
end
until not chunk
return tbl_concat(chunks)
end
local function _trailer_reader(sock)
return co_wrap(function()
co_yield(_receive_headers(sock))
end)
end
local function _read_trailers(res)
local reader = res.trailer_reader
if not reader then
return nil, "no trailers"
end
local trailers = reader()
setmetatable(res.headers, { __index = trailers })
end
local function _send_body(sock, body)
if type(body) == 'function' then
repeat
local chunk, err, partial = body()
if chunk then
local ok,err = sock:send(chunk)
if not ok then
return nil, err
end
elseif err ~= nil then
return nil, err, partial
end
until chunk == nil
elseif body ~= nil then
local bytes, err = sock:send(body)
if not bytes then
return nil, err
end
end
return true, nil
end
local function _handle_continue(sock, body)
local status, version, err = _receive_status(sock)
if not status then
return 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
if not ok then
return nil, err
end
_send_body(sock, body)
end
return status, version, err
end
function _M.send_request(self, params)
-- Apply defaults
setmetatable(params, { __index = DEFAULT_PARAMS })
local sock = self.sock
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
end
-- Ensure minimal headers are set
if type(body) == 'string' and not headers["Content-Length"] then
headers["Content-Length"] = #body
end
if not headers["Host"] then
headers["Host"] = self.host
end
if not headers["User-Agent"] then
headers["User-Agent"] = _M._USER_AGENT
end
if params.version == 1.0 and not headers["Connection"] then
headers["Connection"] = "Keep-Alive"
end
params.headers = headers
-- Format and send request
local req = _format_request(params)
ngx_log(ngx_DEBUG, "\n", req)
local bytes, err = sock:send(req)
if not bytes then
return nil, err
end
-- Send the request body, unless we expect: continue, in which case
-- we handle this as part of reading the response.
if headers["Expect"] ~= "100-continue" then
local ok, err, partial = _send_body(sock, body)
if not ok then
return nil, err, partial
end
end
return true
end
function _M.read_response(self, params)
local sock = self.sock
local status, version, err
-- 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)
if not _status then
return nil, _err
elseif _status ~= 100 then
status, version, err = _status, _version, _err
end
end
-- Just read the status as normal.
if not status then
status, version, err = _receive_status(sock)
if not status then
return nil, err
end
end
local res_headers, err = _receive_headers(sock)
if not res_headers then
return nil, err
end
-- Determine if we should keepalive 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
self.keepalive = false
end
end
local body_reader = _no_body_reader
local trailer_reader, err = nil, nil
local has_body = false
-- 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
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
end
end
end
if res_headers["Trailer"] then
trailer_reader, err = _trailer_reader(sock)
end
if err then
return nil, err
else
return {
status = status,
headers = res_headers,
has_body = has_body,
body_reader = body_reader,
read_body = _read_body,
trailer_reader = trailer_reader,
read_trailers = _read_trailers,
}
end
end
function _M.request(self, params)
local res, err = self:send_request(params)
if not res then
return res, err
else
return self:read_response(params)
end
end
function _M.request_pipeline(self, requests)
for i, params in ipairs(requests) do
if params.headers and params.headers["Expect"] == "100-continue" then
return nil, "Cannot pipeline request specifying Expect: 100-continue"
end
local res, err = self:send_request(params)
if not res then
return res, err
end
end
local responses = {}
for i, params in ipairs(requests) do
responses[i] = setmetatable({
params = params,
response_read = false,
}, {
-- Read each actual response lazily, at the point the user tries
-- to access any of the fields.
__index = function(t, k)
local res, err
if t.response_read == false then
res, err = _M.read_response(self, t.params)
t.response_read = true
if not res then
ngx_log(ngx_ERR, err)
else
for rk, rv in pairs(res) do
t[rk] = rv
end
end
end
return rawget(t, k)
end,
})
end
return responses
end
function _M.request_uri(self, uri, params)
if not params then params = {} end
local parsed_uri, err = self:parse_uri(uri)
if not parsed_uri then
return nil, err
end
local scheme, host, port, path = unpack(parsed_uri)
if not params.path then params.path = path end
local c, err = self:connect(host, port)
if not c then
return nil, err
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
end
end
local res, err = self:request(params)
if not res then
return nil, err
end
local body, err = res:read_body()
if not body then
return nil, err
end
res.body = body
local ok, err = self:set_keepalive()
if not ok then
ngx_log(ngx_ERR, err)
end
return res, nil
end
function _M.get_client_body_reader(self, chunksize, sock)
local chunksize = chunksize or 65536
if not sock then
local ok, err
ok, sock, err = pcall(ngx_req_socket)
if not ok then
return nil, sock -- pcall err
end
if not sock then
if err == "no body" then
return nil
else
return nil, err
end
end
end
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
-- Not yet supported by ngx_lua but should just work...
return _chunked_body_reader(sock, chunksize)
else
return nil
end
end
function _M.proxy_request(self, chunksize)
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)
if not response then
ngx_log(ngx_ERR, "no response provided")
return
end
ngx.status = response.status
-- Filter out hop-by-hop headeres
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
end
if chunk then
local res, err = ngx.print(chunk)
if not res then
ngx_log(ngx_ERR, err)
break
end
end
until not chunk
end
return _M

View file

@ -0,0 +1,62 @@
local rawget, rawset, setmetatable =
rawget, rawset, setmetatable
local str_gsub = string.gsub
local str_lower = string.lower
local _M = {
_VERSION = '0.01',
}
-- Returns an empty headers table with internalised case normalisation.
-- Supports the same cases as in ngx_lua:
--
-- headers.content_length
-- headers["content-length"]
-- headers["Content-Length"]
function _M.new(self)
local mt = {
normalised = {},
}
mt.__index = function(t, k)
local k_hyphened = str_gsub(k, "_", "-")
local matched = rawget(t, k)
if matched then
return matched
else
local k_normalised = str_lower(k_hyphened)
return rawget(t, mt.normalised[k_normalised])
end
end
-- First check the normalised table. If there's no match (first time) add an entry for
-- our current case in the normalised table. This is to preserve the human (prettier) case
-- instead of outputting lowercased header names.
--
-- If there's a match, we're being updated, just with a different case for the key. We use
-- the normalised table to give us the original key, and perorm a rawset().
mt.__newindex = function(t, k, v)
-- we support underscore syntax, so always hyphenate.
local k_hyphened = str_gsub(k, "_", "-")
-- lowercase hyphenated is "normalised"
local k_normalised = str_lower(k_hyphened)
if not mt.normalised[k_normalised] then
mt.normalised[k_normalised] = k_hyphened
rawset(t, k_hyphened, v)
else
rawset(t, mt.normalised[k_normalised], v)
end
end
return setmetatable({}, mt)
end
return _M

View file

@ -0,0 +1,33 @@
package = "lua-resty-http"
version = "0.07-0"
source = {
url = "git://github.com/pintsized/lua-resty-http",
tag = "v0.07"
}
description = {
summary = "Lua HTTP client cosocket driver for OpenResty / ngx_lua.",
detailed = [[
Features an HTTP 1.0 and 1.1 streaming interface to reading
bodies using coroutines, for predictable memory usage in Lua
land. Alternative simple interface for singleshot requests
without manual connection step. Supports chunked transfer
encoding, keepalive, pipelining, and trailers. Headers are
treated case insensitively. Probably production ready in most
cases, though not yet proven in the wild.
Recommended by the OpenResty maintainer as a long-term
replacement for internal requests through ngx.location.capture.
]],
homepage = "https://github.com/pintsized/lua-resty-http",
license = "2-clause BSD",
maintainer = "James Hurst <james@pintsized.co.uk>"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
["resty.http"] = "lib/resty/http.lua",
["resty.http_headers"] = "lib/resty/http_headers.lua"
}
}

View file

@ -0,0 +1,231 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket;
use Cwd qw(cwd);
plan tests => repeat_each() * (blocks() * 4) + 1;
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
error_log logs/error.log debug;
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: Simple default get.
--- 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)
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]
=== TEST 2: HTTP 1.0
--- 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)
local res, err = httpc:request{
version = 1.0,
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]
=== TEST 3: Status code
--- 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)
local res, err = httpc:request{
path = "/b"
}
ngx.status = res.status
ngx.print(res:read_body())
httpc:close()
';
}
location = /b {
content_by_lua '
ngx.status = 404
ngx.say("OK")
';
}
--- request
GET /a
--- response_body
OK
--- error_code: 404
--- no_error_log
[error]
[warn]
=== TEST 4: Response headers
--- 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)
local res, err = httpc:request{
path = "/b"
}
ngx.status = res.status
ngx.say(res.headers["X-Test"])
httpc:close()
';
}
location = /b {
content_by_lua '
ngx.header["X-Test"] = "x-value"
ngx.say("OK")
';
}
--- request
GET /a
--- response_body
x-value
--- no_error_log
[error]
[warn]
=== TEST 5: Query
--- 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)
local res, err = httpc:request{
query = {
a = 1,
b = 2,
},
path = "/b"
}
ngx.status = res.status
for k,v in pairs(res.headers) do
ngx.header[k] = v
end
ngx.print(res:read_body())
httpc:close()
';
}
location = /b {
content_by_lua '
for k,v in pairs(ngx.req.get_uri_args()) do
ngx.header["X-Header-" .. string.upper(k)] = v
end
';
}
--- request
GET /a
--- response_headers
X-Header-A: 1
X-Header-B: 2
--- no_error_log
[error]
[warn]
=== TEST 7: HEAD has no body.
--- 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)
local res, err = httpc:request{
method = "HEAD",
path = "/b"
}
local body = res:read_body()
if body then
ngx.print(body)
end
httpc:close()
';
}
location = /b {
echo "OK";
}
--- request
GET /a
--- response_body
--- no_error_log
[error]
[warn]

View file

@ -0,0 +1,158 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket;
use Cwd qw(cwd);
plan tests => repeat_each() * (blocks() * 4);
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: Non chunked.
--- 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)
local res, err = httpc:request{
path = "/b"
}
local body = res:read_body()
ngx.say(#body)
httpc:close()
';
}
location = /b {
chunked_transfer_encoding off;
content_by_lua '
local len = 32768
local t = {}
for i=1,len do
t[i] = 0
end
ngx.print(table.concat(t))
';
}
--- request
GET /a
--- response_body
32768
--- no_error_log
[error]
[warn]
=== TEST 2: Chunked. The number of chunks received when no max size is given proves the response was in fact chunked.
--- 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)
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)
httpc:close()
';
}
location = /b {
content_by_lua '
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
--- no_error_log
[error]
[warn]
=== TEST 3: Chunked using read_body method.
--- 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)
local res, err = httpc:request{
path = "/b"
}
local body = res:read_body()
ngx.say(#body)
httpc:close()
';
}
location = /b {
content_by_lua '
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
--- no_error_log
[error]
[warn]

View file

@ -0,0 +1,185 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket;
use Cwd qw(cwd);
plan tests => repeat_each() * (blocks() * 4);
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: POST form-urlencoded
--- 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)
local res, err = httpc:request{
body = "a=1&b=2&c=3",
path = "/b",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
}
ngx.say(res:read_body())
httpc:close()
';
}
location = /b {
content_by_lua '
ngx.req.read_body()
local args = ngx.req.get_post_args()
ngx.say("a: ", args.a)
ngx.say("b: ", args.b)
ngx.print("c: ", args.c)
';
}
--- request
GET /a
--- response_body
a: 1
b: 2
c: 3
--- no_error_log
[error]
[warn]
=== TEST 2: POST form-urlencoded 1.0
--- 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)
local res, err = httpc:request{
method = "POST",
body = "a=1&b=2&c=3",
path = "/b",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
},
version = 1.0,
}
ngx.say(res:read_body())
httpc:close()
';
}
location = /b {
content_by_lua '
ngx.req.read_body()
local args = ngx.req.get_post_args()
ngx.say(ngx.req.get_method())
ngx.say("a: ", args.a)
ngx.say("b: ", args.b)
ngx.print("c: ", args.c)
';
}
--- request
GET /a
--- response_body
POST
a: 1
b: 2
c: 3
--- no_error_log
[error]
[warn]
=== TEST 3: 100 Continue does not end requset
--- 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)
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",
}
}
ngx.say(res.status)
ngx.say(res:read_body())
httpc:close()
';
}
location = /b {
content_by_lua '
ngx.req.read_body()
local args = ngx.req.get_post_args()
ngx.say("a: ", args.a)
ngx.say("b: ", args.b)
ngx.print("c: ", args.c)
';
}
--- request
GET /a
--- response_body
200
a: 1
b: 2
c: 3
--- no_error_log
[error]
[warn]
=== TEST 4: Return non-100 status to user
--- 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)
local res, err = httpc:request{
path = "/b",
headers = {
["Expect"] = "100-continue",
["Content-Type"] = "application/x-www-form-urlencoded",
}
}
if not res then
ngx.say(err)
end
ngx.say(res.status)
ngx.say(res:read_body())
httpc:close()
';
}
location = /b {
return 417 "Expectation Failed";
}
--- request
GET /a
--- response_body
417
Expectation Failed
--- no_error_log
[error]
[warn]

View file

@ -0,0 +1,151 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket;
use Cwd qw(cwd);
plan tests => repeat_each() * (blocks() * 4);
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: Trailers. Check Content-MD5 generated after the body is sent matches up.
--- 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)
local res, err = httpc:request{
path = "/b",
headers = {
["TE"] = "trailers",
}
}
local body = res:read_body()
local hash = ngx.md5(body)
res:read_trailers()
if res.headers["Content-MD5"] == hash then
ngx.say("OK")
else
ngx.say(res.headers["Content-MD5"])
end
';
}
location = /b {
content_by_lua '
-- We use the raw socket to compose a response, since OpenResty
-- doesnt support trailers natively.
ngx.req.read_body()
local sock, err = ngx.req.socket(true)
if not sock then
ngx.say(err)
end
local res = {}
table.insert(res, "HTTP/1.1 200 OK")
table.insert(res, "Date: " .. ngx.http_time(ngx.time()))
table.insert(res, "Transfer-Encoding: chunked")
table.insert(res, "Trailer: Content-MD5")
table.insert(res, "")
local body = "Hello, World"
table.insert(res, string.format("%x", #body))
table.insert(res, body)
table.insert(res, "0")
table.insert(res, "")
table.insert(res, "Content-MD5: " .. ngx.md5(body))
table.insert(res, "")
table.insert(res, "")
sock:send(table.concat(res, "\\r\\n"))
';
}
--- request
GET /a
--- response_body
OK
--- no_error_log
[error]
[warn]
=== TEST 2: Advertised trailer does not exist, handled gracefully.
--- 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)
local res, err = httpc:request{
path = "/b",
headers = {
["TE"] = "trailers",
}
}
local body = res:read_body()
local hash = ngx.md5(body)
res:read_trailers()
ngx.say("OK")
httpc:close()
';
}
location = /b {
content_by_lua '
-- We use the raw socket to compose a response, since OpenResty
-- doesnt support trailers natively.
ngx.req.read_body()
local sock, err = ngx.req.socket(true)
if not sock then
ngx.say(err)
end
local res = {}
table.insert(res, "HTTP/1.1 200 OK")
table.insert(res, "Date: " .. ngx.http_time(ngx.time()))
table.insert(res, "Transfer-Encoding: chunked")
table.insert(res, "Trailer: Content-MD5")
table.insert(res, "")
local body = "Hello, World"
table.insert(res, string.format("%x", #body))
table.insert(res, body)
table.insert(res, "0")
table.insert(res, "")
table.insert(res, "")
sock:send(table.concat(res, "\\r\\n"))
';
}
--- request
GET /a
--- response_body
OK
--- no_error_log
[error]
[warn]

View file

@ -0,0 +1,566 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket;
use Cwd qw(cwd);
plan tests => repeat_each() * (blocks() * 4) - 1;
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: Chunked streaming body reader returns the right content length.
--- 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)
local res, err = httpc:request{
path = "/b",
}
local chunks = {}
repeat
local chunk = res.body_reader()
if chunk then
table.insert(chunks, chunk)
end
until not chunk
local body = table.concat(chunks)
ngx.say(#body)
ngx.say(res.headers["Transfer-Encoding"])
httpc:close()
';
}
location = /b {
content_by_lua '
local len = 32768
local t = {}
for i=1,len do
t[i] = 0
end
ngx.print(table.concat(t))
';
}
--- request
GET /a
--- response_body
32768
chunked
--- no_error_log
[error]
[warn]
=== TEST 2: Non-Chunked streaming body reader returns the right content length.
--- 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)
local res, err = httpc:request{
path = "/b",
}
local chunks = {}
repeat
local chunk = res.body_reader()
if chunk then
table.insert(chunks, chunk)
end
until not chunk
local body = table.concat(chunks)
ngx.say(#body)
ngx.say(res.headers["Transfer-Encoding"])
ngx.say(#chunks)
httpc:close()
';
}
location = /b {
chunked_transfer_encoding off;
content_by_lua '
local len = 32768
local t = {}
for i=1,len do
t[i] = 0
end
ngx.print(table.concat(t))
';
}
--- request
GET /a
--- response_body
32768
nil
1
--- no_error_log
[error]
[warn]
=== TEST 2b: Non-Chunked streaming body reader, buffer size becomes nil
--- 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)
local res, err = httpc:request{
path = "/b",
}
local chunks = {}
local buffer_size = 16384
repeat
local chunk = res.body_reader(buffer_size)
if chunk then
table.insert(chunks, chunk)
end
buffer_size = nil
until not chunk
local body = table.concat(chunks)
ngx.say(res.headers["Transfer-Encoding"])
httpc:close()
';
}
location = /b {
chunked_transfer_encoding off;
content_by_lua '
local len = 32768
local t = {}
for i=1,len do
t[i] = 0
end
ngx.print(table.concat(t))
';
}
--- request
GET /a
--- response_body
nil
--- error_log
Buffer size not specified, bailing
=== TEST 3: HTTP 1.0 body reader with no max size returns the right content length.
--- 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)
local res, err = httpc:request{
path = "/b",
version = 1.0,
}
local chunks = {}
repeat
local chunk = res.body_reader()
if chunk then
table.insert(chunks, chunk)
end
until not chunk
local body = table.concat(chunks)
ngx.say(#body)
ngx.say(res.headers["Transfer-Encoding"])
ngx.say(#chunks)
httpc:close()
';
}
location = /b {
chunked_transfer_encoding off;
content_by_lua '
local len = 32768
local t = {}
for i=1,len do
t[i] = 0
end
ngx.print(table.concat(t))
';
}
--- request
GET /a
--- response_body
32768
nil
1
--- no_error_log
[error]
[warn]
=== TEST 4: HTTP 1.0 body reader with max chunk size returns the right content length.
--- 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)
local res, err = httpc:request{
path = "/b",
version = 1.0,
}
local chunks = {}
local size = 8192
repeat
local chunk = res.body_reader(size)
if chunk then
table.insert(chunks, chunk)
end
size = size + size
until not chunk
local body = table.concat(chunks)
ngx.say(#body)
ngx.say(res.headers["Transfer-Encoding"])
ngx.say(#chunks)
httpc:close()
';
}
location = /b {
chunked_transfer_encoding off;
content_by_lua '
local len = 32769
local t = {}
for i=1,len do
t[i] = 0
end
ngx.print(table.concat(t))
';
}
--- request
GET /a
--- response_body
32769
nil
3
--- no_error_log
[error]
[warn]
=== TEST 4b: HTTP 1.0 body reader with no content length, stream works as expected.
--- 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)
local res, err = httpc:request{
path = "/b",
version = 1.0,
}
local chunks = {}
local size = 8192
repeat
local chunk = res.body_reader(size)
if chunk then
table.insert(chunks, chunk)
end
size = size + size
until not chunk
local body = table.concat(chunks)
ngx.say(#body)
ngx.say(#chunks)
httpc:close()
';
}
location = /b {
content_by_lua '
ngx.req.read_body()
local sock, err = ngx.req.socket(true)
if not sock then
ngx.say(err)
end
local res = {}
table.insert(res, "HTTP/1.0 200 OK")
table.insert(res, "Date: " .. ngx.http_time(ngx.time()))
table.insert(res, "")
local len = 32769
local t = {}
for i=1,len do
t[i] = 0
end
table.insert(res, table.concat(t))
sock:send(table.concat(res, "\\r\\n"))
';
}
--- request
GET /a
--- response_body
32769
3
--- no_error_log
[error]
[warn]
=== TEST 5: Chunked streaming body reader with max chunk size returns the right content length.
--- 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)
local res, err = httpc:request{
path = "/b",
}
local chunks = {}
local size = 8192
repeat
local chunk = res.body_reader(size)
if chunk then
table.insert(chunks, chunk)
end
size = size + size
until not chunk
local body = table.concat(chunks)
ngx.say(#body)
ngx.say(res.headers["Transfer-Encoding"])
ngx.say(#chunks)
httpc:close()
';
}
location = /b {
content_by_lua '
local len = 32768
local t = {}
for i=1,len do
t[i] = 0
end
ngx.print(table.concat(t))
';
}
--- request
GET /a
--- response_body
32768
chunked
3
--- no_error_log
[error]
[warn]
=== TEST 6: Request reader correctly reads body
--- http_config eval: $::HttpConfig
--- config
location = /a {
lua_need_request_body off;
content_by_lua '
local http = require "resty.http"
local httpc = http.new()
local reader, err = httpc:get_client_body_reader(8192)
repeat
local chunk, err = reader()
if chunk then
ngx.print(chunk)
end
until chunk == nil
';
}
--- request
POST /a
foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
--- response_body: foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
--- no_error_log
[error]
[warn]
=== TEST 7: Request reader correctly reads body in chunks
--- http_config eval: $::HttpConfig
--- config
location = /a {
lua_need_request_body off;
content_by_lua '
local http = require "resty.http"
local httpc = http.new()
local reader, err = httpc:get_client_body_reader(64)
local chunks = 0
repeat
chunks = chunks +1
local chunk, err = reader()
if chunk then
ngx.print(chunk)
end
until chunk == nil
ngx.say("\\n"..chunks)
';
}
--- request
POST /a
foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
--- response_body
foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
3
--- no_error_log
[error]
[warn]
=== TEST 8: Request reader passes into client
--- http_config eval: $::HttpConfig
--- config
location = /a {
lua_need_request_body off;
content_by_lua '
local http = require "resty.http"
local httpc = http.new()
httpc:connect("127.0.0.1", ngx.var.server_port)
local reader, err = httpc:get_client_body_reader(64)
local res, err = httpc:request{
method = POST,
path = "/b",
body = reader,
headers = ngx.req.get_headers(100, true),
}
local body = res:read_body()
ngx.say(body)
httpc:close()
';
}
location = /b {
content_by_lua '
ngx.req.read_body()
local body, err = ngx.req.get_body_data()
ngx.print(body)
';
}
--- request
POST /a
foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
--- response_body
foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
--- no_error_log
[error]
[warn]
=== TEST 9: Body reader is a function returning nil when no body is present.
--- 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)
local res, err = httpc:request{
path = "/b",
method = "HEAD",
}
repeat
local chunk = res.body_reader()
until not chunk
httpc:close()
';
}
location = /b {
content_by_lua '
ngx.exit(200)
';
}
--- request
GET /a
--- no_error_log
[error]
[warn]
=== TEST 10: Issue a notice (but do not error) if trying to read the request body in a subrequest
--- http_config eval: $::HttpConfig
--- config
location = /a {
echo_location /b;
}
location = /b {
lua_need_request_body off;
content_by_lua '
local http = require "resty.http"
local httpc = http.new()
local reader, err = httpc:get_client_body_reader(8192)
if not reader then
ngx.log(ngx.NOTICE, err)
return
end
repeat
local chunk, err = reader()
if chunk then
ngx.print(chunk)
end
until chunk == nil
';
}
--- request
POST /a
foobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbazfoobarbaz
--- response_body:
--- no_error_log
[error]
[warn]
--- error_log
attempt to read the request body in a subrequest

View file

@ -0,0 +1,145 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket;
use Cwd qw(cwd);
plan tests => repeat_each() * (blocks() * 4) + 6;
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
error_log logs/error.log debug;
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: Simple URI interface
--- 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?a=1&b=2")
if not res then
ngx.log(ngx.ERR, err)
end
ngx.status = res.status
ngx.header["X-Header-A"] = res.headers["X-Header-A"]
ngx.header["X-Header-B"] = res.headers["X-Header-B"]
ngx.print(res.body)
';
}
location = /b {
content_by_lua '
for k,v in pairs(ngx.req.get_uri_args()) do
ngx.header["X-Header-" .. string.upper(k)] = v
end
ngx.say("OK")
';
}
--- request
GET /a
--- response_headers
X-Header-A: 1
X-Header-B: 2
--- response_body
OK
--- no_error_log
[error]
[warn]
=== TEST 2: Simple URI interface HTTP 1.0
--- 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?a=1&b=2", {
}
)
ngx.status = res.status
ngx.header["X-Header-A"] = res.headers["X-Header-A"]
ngx.header["X-Header-B"] = res.headers["X-Header-B"]
ngx.print(res.body)
';
}
location = /b {
content_by_lua '
for k,v in pairs(ngx.req.get_uri_args()) do
ngx.header["X-Header-" .. string.upper(k)] = v
end
ngx.say("OK")
';
}
--- request
GET /a
--- response_headers
X-Header-A: 1
X-Header-B: 2
--- response_body
OK
--- no_error_log
[error]
[warn]
=== TEST 3 Simple URI interface, params override
--- 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?a=1&b=2", {
path = "/c",
query = {
a = 2,
b = 3,
},
}
)
ngx.status = res.status
ngx.header["X-Header-A"] = res.headers["X-Header-A"]
ngx.header["X-Header-B"] = res.headers["X-Header-B"]
ngx.print(res.body)
';
}
location = /c {
content_by_lua '
for k,v in pairs(ngx.req.get_uri_args()) do
ngx.header["X-Header-" .. string.upper(k)] = v
end
ngx.say("OK")
';
}
--- request
GET /a
--- response_headers
X-Header-A: 2
X-Header-B: 3
--- response_body
OK
--- no_error_log
[error]
[warn]

View file

@ -0,0 +1,182 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket;
use Cwd qw(cwd);
plan tests => repeat_each() * (blocks() * 4);
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
error_log logs/error.log debug;
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1 Simple interface, Connection: Keep-alive. Test the connection is reused.
--- 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", {
}
)
ngx.say(res.headers["Connection"])
httpc:connect("127.0.0.1", ngx.var.server_port)
ngx.say(httpc:get_reused_times())
';
}
location = /b {
content_by_lua '
ngx.say("OK")
';
}
--- request
GET /a
--- response_body
keep-alive
1
--- no_error_log
[error]
[warn]
=== TEST 2 Simple interface, Connection: 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 '
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", {
version = 1.0,
headers = {
["Connection"] = "close",
},
}
)
httpc:connect("127.0.0.1", ngx.var.server_port)
ngx.say(httpc:get_reused_times())
httpc:set_keepalive()
httpc:connect("127.0.0.1", ngx.var.server_port)
ngx.say(httpc:get_reused_times())
';
}
location = /b {
content_by_lua '
ngx.say("OK")
';
}
--- request
GET /a
--- response_body
0
1
--- no_error_log
[error]
[warn]
=== TEST 3 Generic interface, Connection: Keep-alive. Test the connection is reused.
--- 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)
local res, err = httpc:request{
path = "/b"
}
local body = res:read_body()
ngx.say(res.headers["Connection"])
ngx.say(httpc:set_keepalive())
httpc:connect("127.0.0.1", ngx.var.server_port)
ngx.say(httpc:get_reused_times())
';
}
location = /b {
content_by_lua '
ngx.say("OK")
';
}
--- request
GET /a
--- response_body
keep-alive
1
1
--- no_error_log
[error]
[warn]
=== TEST 4 Generic interface, Connection: 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 '
local http = require "resty.http"
local httpc = http.new()
httpc:connect("127.0.0.1", ngx.var.server_port)
local res, err = httpc:request{
version = 1.0,
headers = {
["Connection"] = "Close",
},
path = "/b"
}
local body = res:read_body()
ngx.say(res.headers["Connection"])
local r, e = httpc:set_keepalive()
ngx.say(r)
ngx.say(e)
httpc:connect("127.0.0.1", ngx.var.server_port)
ngx.say(httpc:get_reused_times())
httpc:set_keepalive()
httpc:connect("127.0.0.1", ngx.var.server_port)
ngx.say(httpc:get_reused_times())
';
}
location = /b {
content_by_lua '
ngx.say("OK")
';
}
--- request
GET /a
--- response_body
close
2
connection must be closed
0
1
--- no_error_log
[error]
[warn]

View file

@ -0,0 +1,143 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket;
use Cwd qw(cwd);
plan tests => repeat_each() * (blocks() * 4);
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
error_log logs/error.log debug;
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1 Test that pipelined reqests 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)
local responses = httpc:request_pipeline{
{
path = "/b",
},
{
path = "/c",
},
{
path = "/d",
}
}
for i,r in ipairs(responses) do
if r.status then
ngx.say(r.status)
ngx.say(r.headers["X-Res"])
ngx.say(r:read_body())
end
end
';
}
location = /b {
content_by_lua '
ngx.status = 200
ngx.header["X-Res"] = "B"
ngx.print("B")
';
}
location = /c {
content_by_lua '
ngx.status = 404
ngx.header["X-Res"] = "C"
ngx.print("C")
';
}
location = /d {
content_by_lua '
ngx.status = 200
ngx.header["X-Res"] = "D"
ngx.print("D")
';
}
--- request
GET /a
--- response_body
200
B
B
404
C
C
200
D
D
--- no_error_log
[error]
[warn]
=== TEST 2: Test we can handle timeouts on reading the pipelined requests.
--- 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:set_timeout(1)
local responses = httpc:request_pipeline{
{
path = "/b",
},
{
path = "/c",
},
}
for i,r in ipairs(responses) do
if r.status then
ngx.say(r.status)
ngx.say(r.headers["X-Res"])
ngx.say(r:read_body())
end
end
';
}
location = /b {
content_by_lua '
ngx.status = 200
ngx.header["X-Res"] = "B"
ngx.print("B")
';
}
location = /c {
content_by_lua '
ngx.status = 404
ngx.header["X-Res"] = "C"
ngx.sleep(1)
ngx.print("C")
';
}
--- request
GET /a
--- response_body
200
B
B
--- no_error_log
[warn]
--- error_log eval
[qr/timeout/]

View file

@ -0,0 +1,59 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket;
use Cwd qw(cwd);
plan tests => repeat_each() * (blocks() * 4);
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
error_log logs/error.log debug;
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: parse_uri returns port 443 for https URIs
--- http_config eval: $::HttpConfig
--- config
location = /a {
content_by_lua '
local http = require "resty.http"
local httpc = http.new()
local parsed = httpc:parse_uri("https://www.google.com/foobar")
ngx.say(parsed[3])
';
}
--- request
GET /a
--- response_body
443
--- no_error_log
[error]
[warn]
=== TEST 2: parse_uri returns port 80 for http URIs
--- http_config eval: $::HttpConfig
--- config
location = /a {
content_by_lua '
local http = require "resty.http"
local httpc = http.new()
local parsed = httpc:parse_uri("http://www.google.com/foobar")
ngx.say(parsed[3])
';
}
--- request
GET /a
--- response_body
80
--- no_error_log
[error]
[warn]

View file

@ -0,0 +1,57 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket;
use Cwd qw(cwd);
plan tests => repeat_each() * (blocks() * 4);
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: Issue a notice (but do not error) if trying to read the request body in a subrequest
--- http_config eval: $::HttpConfig
--- config
location = /a {
echo_location /b;
}
location = /b {
content_by_lua '
local http = require "resty.http"
local httpc = http.new()
httpc:connect("127.0.0.1", ngx.var.server_port)
local res, err = httpc:request{
path = "/c",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
}
if not res then
ngx.say(err)
end
ngx.print(res:read_body())
httpc:close()
';
}
location /c {
echo "OK";
}
--- request
GET /a
--- response_body
OK
--- no_error_log
[error]
[warn]

View file

@ -0,0 +1,152 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket;
use Cwd qw(cwd);
plan tests => repeat_each() * (blocks() * 5);
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
error_log logs/error.log debug;
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: Proxy GET request and response
--- http_config eval: $::HttpConfig
--- config
location = /a_prx {
rewrite ^(.*)_prx$ $1 break;
content_by_lua '
local http = require "resty.http"
local httpc = http.new()
httpc:connect("127.0.0.1", ngx.var.server_port)
httpc:proxy_response(httpc:proxy_request())
httpc:set_keepalive()
';
}
location = /a {
content_by_lua '
ngx.status = 200
ngx.header["X-Test"] = "foo"
ngx.say("OK")
';
}
--- request
GET /a_prx
--- response_body
OK
--- response_headers
X-Test: foo
--- error_code: 200
--- no_error_log
[error]
[warn]
=== TEST 2: Proxy POST request and response
--- http_config eval: $::HttpConfig
--- config
location = /a_prx {
rewrite ^(.*)_prx$ $1 break;
content_by_lua '
local http = require "resty.http"
local httpc = http.new()
httpc:connect("127.0.0.1", ngx.var.server_port)
httpc:proxy_response(httpc:proxy_request())
httpc:set_keepalive()
';
}
location = /a {
lua_need_request_body on;
content_by_lua '
ngx.status = 404
ngx.header["X-Test"] = "foo"
local args, err = ngx.req.get_post_args()
ngx.say(args["foo"])
ngx.say(args["hello"])
';
}
--- request
POST /a_prx
foo=bar&hello=world
--- response_body
bar
world
--- response_headers
X-Test: foo
--- error_code: 404
--- no_error_log
[error]
[warn]
=== TEST 3: Proxy multiple headers
--- http_config eval: $::HttpConfig
--- config
location = /a_prx {
rewrite ^(.*)_prx$ $1 break;
content_by_lua '
local http = require "resty.http"
local httpc = http.new()
httpc:connect("127.0.0.1", ngx.var.server_port)
httpc:proxy_response(httpc:proxy_request())
httpc:set_keepalive()
';
}
location = /a {
content_by_lua '
ngx.status = 200
ngx.header["Set-Cookie"] = { "cookie1", "cookie2" }
ngx.say("OK")
';
}
--- request
GET /a_prx
--- response_body
OK
--- raw_response_headers_like: .*Set-Cookie: cookie1\r\nSet-Cookie: cookie2\r\n
--- error_code: 200
--- no_error_log
[error]
[warn]
=== TEST 4: Proxy still works with spaces in URI
--- http_config eval: $::HttpConfig
--- config
location = "/a_ b_prx" {
rewrite ^(.*)_prx$ $1 break;
content_by_lua '
local http = require "resty.http"
local httpc = http.new()
httpc:connect("127.0.0.1", ngx.var.server_port)
httpc:proxy_response(httpc:proxy_request())
httpc:set_keepalive()
';
}
location = "/a_ b" {
content_by_lua '
ngx.status = 200
ngx.header["X-Test"] = "foo"
ngx.say("OK")
';
}
--- request
GET /a_%20b_prx
--- response_body
OK
--- response_headers
X-Test: foo
--- error_code: 200
--- no_error_log
[error]
[warn]

View file

@ -0,0 +1,160 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket;
use Cwd qw(cwd);
plan tests => repeat_each() * (blocks() * 4);
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
error_log logs/error.log debug;
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: Test header normalisation
--- http_config eval: $::HttpConfig
--- config
location = /a {
content_by_lua '
local http_headers = require "resty.http_headers"
local headers = http_headers.new()
headers.x_a_header = "a"
headers["x-b-header"] = "b"
headers["X-C-Header"] = "c"
headers["X_d-HEAder"] = "d"
ngx.say(headers["X-A-Header"])
ngx.say(headers.x_b_header)
for k,v in pairs(headers) do
ngx.say(k, ": ", v)
end
';
}
--- request
GET /a
--- response_body
a
b
x-b-header: b
x-a-header: a
X-d-HEAder: d
X-C-Header: c
--- no_error_log
[error]
[warn]
=== TEST 2: Test headers can be accessed in all cases
--- 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)
local res, err = httpc:request{
path = "/b"
}
ngx.status = res.status
ngx.say(res.headers["X-Foo-Header"])
ngx.say(res.headers["x-fOo-heaDeR"])
ngx.say(res.headers.x_foo_header)
httpc:close()
';
}
location = /b {
content_by_lua '
ngx.header["X-Foo-Header"] = "bar"
ngx.say("OK")
';
}
--- request
GET /a
--- response_body
bar
bar
bar
--- no_error_log
[error]
[warn]
=== TEST 3: Test request headers are normalised
--- 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)
local res, err = httpc:request{
path = "/b",
headers = {
["uSeR-AgENT"] = "test_user_agent",
x_foo = "bar",
},
}
ngx.status = res.status
ngx.print(res:read_body())
httpc:close()
';
}
location = /b {
content_by_lua '
ngx.say(ngx.req.get_headers()["User-Agent"])
ngx.say(ngx.req.get_headers()["X-Foo"])
';
}
--- request
GET /a
--- response_body
test_user_agent
bar
--- no_error_log
[error]
=== TEST 4: Test that headers remain unique
--- http_config eval: $::HttpConfig
--- config
location = /a {
content_by_lua '
local http_headers = require "resty.http_headers"
local headers = http_headers.new()
headers["x-a-header"] = "a"
headers["X-A-HEAder"] = "b"
for k,v in pairs(headers) do
ngx.log(ngx.DEBUG, k, ": ", v)
ngx.header[k] = v
end
';
}
--- request
GET /a
--- response_headers
x-a-header: b
--- no_error_log
[error]
[warn]
[warn]

View file

@ -0,0 +1,52 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket;
use Cwd qw(cwd);
plan tests => repeat_each() * (blocks() * 3);
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
error_log logs/error.log debug;
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: request_uri (check the default path)
--- http_config eval: $::HttpConfig
--- config
location /lua {
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)
if res and 200 == res.status then
ngx.say("OK")
else
ngx.say("FAIL")
end
';
}
location =/ {
content_by_lua '
ngx.print("OK")
';
}
--- request
GET /lua
--- response_body
OK
--- no_error_log
[error]

View file

@ -0,0 +1,63 @@
#!/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("luac -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});
}
}