Remove lua and use fastcgi to render errors

This commit is contained in:
Manuel de Brito Fontes 2017-08-03 10:51:39 -04:00
parent 7b38e5a36e
commit a091d3ede7
35 changed files with 251 additions and 4160 deletions

View file

@ -35,7 +35,7 @@ IMAGE = $(REGISTRY)/$(IMGNAME)
MULTI_ARCH_IMG = $(IMAGE)-$(ARCH)
# Set default base image dynamically for each arch
BASEIMAGE?=gcr.io/google_containers/nginx-slim-$(ARCH):0.21
BASEIMAGE?=gcr.io/google_containers/nginx-slim-$(ARCH):0.22
ifeq ($(ARCH),arm)
QEMUARCH=arm

View file

@ -38,6 +38,7 @@ import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress/controllers/nginx/pkg/config"
"k8s.io/ingress/controllers/nginx/pkg/fastcgi"
ngx_template "k8s.io/ingress/controllers/nginx/pkg/template"
"k8s.io/ingress/controllers/nginx/pkg/version"
"k8s.io/ingress/core/pkg/ingress"
@ -54,6 +55,10 @@ const (
defaultStatusModule statusModule = "default"
vtsStatusModule statusModule = "vts"
defUpstreamName = "upstream-default-backend"
fastCGISocket = "/var/run/go-fastcgi.sock"
)
var (
@ -123,6 +128,23 @@ func newNGINXController() ingress.Controller {
}
}()
fcgiListener, err := net.Listen("unix", fastCGISocket)
if err != nil {
glog.Fatalf("%v", err)
}
err = os.Chmod(fastCGISocket, 0777)
if err != nil {
glog.Fatalf("%v", err)
}
go func() {
err = fastcgi.ServeError(fcgiListener)
if err != nil {
glog.Fatalf("%v", err)
}
}()
var onChange func()
onChange = func() {
template, err := ngx_template.NewTemplate(tmplPath, onChange)
@ -523,7 +545,7 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
cfg.SSLDHParam = sslDHParam
content, err := n.t.Write(config.TemplateConfig{
tc := config.TemplateConfig{
ProxySetHeaders: setHeaders,
AddHeaders: addHeaders,
MaxOpenFiles: maxOpenFiles,
@ -537,7 +559,21 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
CustomErrors: len(cfg.CustomHTTPErrors) > 0,
Cfg: cfg,
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
})
}
// We need to extract the endpoints to be used in the fastcgi error handler
for _, b := range ingressCfg.Backends {
if b.Name == defUpstreamName {
eps := []string{}
for _, e := range b.Endpoints {
eps = append(eps, fmt.Sprintf("%v:%v", e.Address, e.Port))
}
tc.DefaultBackendEndpoints = strings.Join(eps, ",")
break
}
}
content, err := n.t.Write(tc)
if err != nil {
return err

View file

@ -406,17 +406,18 @@ func (cfg Configuration) BuildLogFormatUpstream() string {
// TemplateConfig contains the nginx configuration to render the file nginx.conf
type TemplateConfig struct {
ProxySetHeaders map[string]string
AddHeaders map[string]string
MaxOpenFiles int
BacklogSize int
Backends []*ingress.Backend
PassthroughBackends []*ingress.SSLPassthroughBackend
Servers []*ingress.Server
TCPBackends []ingress.L4Service
UDPBackends []ingress.L4Service
HealthzURI string
CustomErrors bool
Cfg Configuration
IsIPV6Enabled bool
DefaultBackendEndpoints string
ProxySetHeaders map[string]string
AddHeaders map[string]string
MaxOpenFiles int
BacklogSize int
Backends []*ingress.Backend
PassthroughBackends []*ingress.SSLPassthroughBackend
Servers []*ingress.Server
TCPBackends []ingress.L4Service
UDPBackends []ingress.L4Service
HealthzURI string
CustomErrors bool
Cfg Configuration
IsIPV6Enabled bool
}

View file

@ -0,0 +1,104 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fastcgi
import (
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/http/fcgi"
"strconv"
"strings"
)
const (
// CodeHeader name of the header that indicates the expected response
// status code
CodeHeader = "X-Code"
// FormatHeader name of the header with the expected Content-Type to be
// sent to the client
FormatHeader = "X-Format"
// EndpointsHeader comma separated header that contains the list of
// endpoints for the default backend
EndpointsHeader = "X-Endpoints"
// ContentTypeHeader returns information about the type of the returned body
ContentTypeHeader = "Content-Type"
)
// ServeError creates a fastcgi handler to serve the custom error pages
func ServeError(l net.Listener) error {
return fcgi.Serve(l, handler())
}
func handler() http.Handler {
r := http.NewServeMux()
r.HandleFunc("/", serveError)
return r
}
func serveError(w http.ResponseWriter, req *http.Request) {
code := req.Header.Get(CodeHeader)
format := req.Header.Get(FormatHeader)
if format == "" || format == "*/*" {
format = "text/html"
}
httpCode, err := strconv.Atoi(code)
if err != nil {
httpCode = 404
}
de := []byte(fmt.Sprintf("default backend - %v", httpCode))
w.Header().Set(ContentTypeHeader, format)
w.WriteHeader(httpCode)
eh := req.Header.Get(EndpointsHeader)
if eh == "" {
w.Write(de)
return
}
eps := strings.Split(eh, ",")
// TODO: add retries in case of errors
ep := eps[rand.Intn(len(eps))]
req, err = http.NewRequest("GET", fmt.Sprintf("http://%v/", ep), nil)
if err != nil {
w.Write(de)
return
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
w.Write(de)
return
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
w.Write(de)
return
}
defer resp.Body.Close()
w.Write(b)
}

View file

@ -0,0 +1,70 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fastcgi
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func TestErrorHandler(t *testing.T) {
tt := []struct {
name string
code int
format string
}{
{name: "404 text/html", code: 404, format: "text/html"},
{name: "503 text/html", code: 503, format: "text/html"},
{name: "404 application/json", code: 404, format: "application/json"},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
req, err := http.NewRequest("GET", "localhost:8080/", nil)
req.Header.Add(CodeHeader, fmt.Sprintf("%v", tc.code))
req.Header.Add(FormatHeader, tc.format)
if err != nil {
t.Fatalf("could not created request: %v", err)
}
w := httptest.NewRecorder()
serveError(w, req)
res := w.Result()
defer res.Body.Close()
b, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatalf("could not read response: %v", err)
}
if res.StatusCode != tc.code {
t.Errorf("expected status %v; got %v", tc.code, res.StatusCode)
}
ct := res.Header.Get(ContentTypeHeader)
if ct != tc.format {
t.Errorf("expected content type %v; got %v", tc.format, ct)
}
if len(b) == 0 {
t.Fatalf("unexpected empty body")
}
})
}
}

View file

@ -25,7 +25,7 @@ RUN curl -sSL -o /tmp/dumb-init.deb http://ftp.us.debian.org/debian/pool/main/d/
dpkg -i /tmp/dumb-init.deb && \
rm /tmp/dumb-init.deb
ENTRYPOINT ["/usr/bin/dumb-init", "-v"]
ENTRYPOINT ["/usr/bin/dumb-init"]
COPY . /

View file

@ -1,67 +0,0 @@
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(original_headers, status)
local httpc = http.new()
original_headers["X-Code"] = status or "404"
original_headers["X-Format"] = original_headers["Accept"] or "text/html"
local random_backend = get_destination()
local res, err = httpc:request_uri(random_backend, {
path = "/",
method = "GET",
headers = original_headers,
})
if not res then
ngx.log(ngx.ERR, err)
ngx.exit(500)
end
for k,v in pairs(res.headers) do
ngx.header[k] = v
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

@ -1,78 +0,0 @@
-- 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

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

View file

@ -1,23 +0,0 @@
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

@ -1,20 +0,0 @@
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

@ -1,424 +0,0 @@
# lua-resty-http
Lua HTTP client cosocket driver for [OpenResty](http://openresty.org/) / [ngx_lua](https://github.com/openresty/lua-nginx-module).
# Status
Production ready.
# 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
* Chunked and non-chunked transfer encodings
* 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.
* `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
`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-2016, 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

@ -1,850 +0,0 @@
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.09',
}
_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
self.ssl = true
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.port = select(2, ...)
-- If port is not a number, this is likely a unix domain socket connection.
if type(self.port) ~= "number" then
self.port = nil
end
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: " .. uri .. ", " .. err
end
return nil, "bad uri: " .. uri
else
if m[3] then
m[3] = tonumber(m[3])
else
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, nil, err
end
return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8)), str_sub(line, 14)
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, reason, err = _receive_status(sock)
if not status then
return 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
if not ok then
return nil, 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
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.
if self.port then
if self.ssl and self.port ~= 443 then
headers["Host"] = self.host .. ":" .. self.port
elseif not self.ssl and self.port ~= 80 then
headers["Host"] = self.host .. ":" .. self.port
else
headers["Host"] = self.host
end
else
headers["Host"] = self.host
end
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, reason, 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, reason, 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
-- 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
self.keepalive = false
end
else
-- no connection header
if version == 1.0 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,
reason = reason,
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

@ -1,62 +0,0 @@
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

@ -1,22 +0,0 @@
package = "lua-resty-http"
version = "0.09-0"
source = {
url = "git://github.com/pintsized/lua-resty-http",
tag = "v0.09"
}
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 <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

@ -1,233 +0,0 @@
# 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 and reason phrase
--- 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.reason)
ngx.print(res:read_body())
httpc:close()
';
}
location = /b {
content_by_lua '
ngx.status = 404
ngx.say("OK")
';
}
--- request
GET /a
--- response_body
Not Found
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

@ -1,158 +0,0 @@
# 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

@ -1,185 +0,0 @@
# 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

@ -1,151 +0,0 @@
# 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

@ -1,566 +0,0 @@
# 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

@ -1,145 +0,0 @@
# 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

@ -1,240 +0,0 @@
# 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]
=== TEST 5: Generic interface, HTTP 1.0, no connection header. 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", 12345)
local res, err = httpc:request{
version = 1.0,
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("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
--- tcp_listen: 12345
--- tcp_reply
HTTP/1.0 200 OK
Date: Fri, 08 Aug 2016 08:12:31 GMT
Server: OpenResty
OK
--- response_body
OK
nil
2
connection must be closed
0
1
--- no_error_log
[error]
[warn]

View file

@ -1,143 +0,0 @@
# 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

@ -1,59 +0,0 @@
# 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

@ -1,57 +0,0 @@
# 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

@ -1,152 +0,0 @@
# 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

@ -1,160 +0,0 @@
# 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

@ -1,52 +0,0 @@
# 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

@ -1,161 +0,0 @@
# 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;
resolver 8.8.8.8;
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
$ENV{TEST_NGINX_PWD} ||= $pwd;
sub read_file {
my $infile = shift;
open my $in, $infile
or die "cannot open $infile for reading: $!";
my $cert = do { local $/; <$in> };
close $in;
$cert;
}
our $TestCertificate = read_file("t/cert/test.crt");
our $TestCertificateKey = read_file("t/cert/test.key");
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: Default HTTP port is not added to Host header
--- 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://www.google.com")
';
}
--- request
GET /lua
--- no_error_log
[error]
--- error_log
Host: www.google.com
=== TEST 2: Default HTTPS port is not added to Host header
--- 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("https://www.google.com:443", { ssl_verify = false })
';
}
--- request
GET /lua
--- no_error_log
[error]
--- error_log
Host: www.google.com
=== TEST 3: Non-default HTTP port is added to Host header
--- 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 '
local http = require "resty.http"
local httpc = http.new()
local res, err = httpc:request_uri("http://127.0.0.1:8080")
';
}
--- request
GET /lua
--- no_error_log
[error]
--- error_log
Host: 127.0.0.1:8080
=== TEST 4: Non-default HTTPS port is added to Host header
--- http_config
lua_package_path "$TEST_NGINX_PWD/lib/?.lua;;";
error_log logs/error.log debug;
resolver 8.8.8.8;
server {
listen *:8080;
listen *:8081 ssl;
ssl_certificate ../html/test.crt;
ssl_certificate_key ../html/test.key;
}
--- config
location /lua {
content_by_lua '
local http = require "resty.http"
local httpc = http.new()
local res, err = httpc:request_uri("https://127.0.0.1:8081", { ssl_verify = false })
';
}
--- user_files eval
">>> test.key
$::TestCertificateKey
>>> test.crt
$::TestCertificate"
--- request
GET /lua
--- no_error_log
[error]
--- error_log
Host: 127.0.0.1:8081
=== TEST 5: No host header on a unix domain socket returns a useful error.
--- http_config eval: $::HttpConfig
--- config
location /a {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
local res, err = httpc:connect("unix:test.sock")
if not res then
ngx.log(ngx.ERR, err)
end
local res, err = httpc:request({ path = "/" })
if not res then
ngx.say(err)
else
ngx.say(res:read_body())
end
}
}
--- tcp_listen: test.sock
--- tcp_reply: OK
--- request
GET /a
--- no_error_log
[error]
--- response_body
Unable to generate a useful Host header for a unix domain socket. Please provide one.

View file

@ -1,24 +0,0 @@
-----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==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----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-----

View file

@ -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("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});
}
}

View file

@ -1,3 +1,4 @@
{{ $all := . }}
{{ $cfg := .Cfg }}
{{ $IsIPV6Enabled := .IsIPV6Enabled }}
{{ $healthzURI := .HealthzURI }}
@ -46,12 +47,6 @@ http {
vhost_traffic_status_filter_by_set_key $geoip_country_code country::*;
{{ end }}
# lua section to return proper error codes when custom pages are used
lua_package_path '.?.lua;/etc/nginx/lua/?.lua;/etc/nginx/lua/vendor/lua-resty-http/lib/?.lua;';
init_by_lua_block {
require("error_page")
}
sendfile on;
aio threads;
tcp_nopush on;
@ -494,7 +489,7 @@ http {
}
{{ end }}
{{ template "CUSTOM_ERRORS" $cfg }}
{{ template "CUSTOM_ERRORS" $all }}
}
{{ end }}
@ -543,7 +538,7 @@ http {
set $proxy_upstream_name "upstream-default-backend";
proxy_pass http://upstream-default-backend;
}
{{ template "CUSTOM_ERRORS" $cfg }}
{{ template "CUSTOM_ERRORS" $all }}
}
# default server for services without endpoints
@ -553,9 +548,11 @@ http {
location / {
{{ if .CustomErrors }}
content_by_lua_block {
openURL(ngx.req.get_headers(0), 503)
}
include /etc/nginx/fastcgi_params;
fastcgi_param HTTP_X_Code 503;
fastcgi_param HTTP_X_Format $http_accept;
fastcgi_param HTTP_X_Endpoints {{ .DefaultBackendEndpoints }};
fastcgi_pass unix:/var/run/go-fastcgi.sock;
{{ else }}
return 503;
{{ end }}
@ -608,12 +605,15 @@ stream {
{{/* definition of templates to avoid repetitions */}}
{{ define "CUSTOM_ERRORS" }}
{{ range $errCode := .CustomHTTPErrors }}
{{ $defaultBackendEndpoints := .DefaultBackendEndpoints }}
{{ range $errCode := .Cfg.CustomHTTPErrors }}
location @custom_{{ $errCode }} {
internal;
content_by_lua_block {
openURL(ngx.req.get_headers(0), {{ $errCode }})
}
include /etc/nginx/fastcgi_params;
fastcgi_param HTTP_X_Code {{ $errCode }};
fastcgi_param HTTP_X_Format $http_accept;
fastcgi_param HTTP_X_Endpoints {{ $defaultBackendEndpoints }};
fastcgi_pass unix:/var/run/go-fastcgi.sock;
}
{{ end }}
{{ end }}

View file

@ -110,6 +110,8 @@ type GenericController struct {
// runningConfig contains the running configuration in the Backend
runningConfig *ingress.Configuration
forceReload bool
}
// Configuration contains all the settings required by an Ingress controller
@ -243,6 +245,7 @@ func newIngressController(config *Configuration) *GenericController {
if mapKey == ic.cfg.ConfigMapName {
glog.V(2).Infof("adding configmap %v to backend", mapKey)
ic.cfg.Backend.SetConfig(upCmap)
ic.forceReload = true
}
},
UpdateFunc: func(old, cur interface{}) {
@ -252,6 +255,7 @@ func newIngressController(config *Configuration) *GenericController {
if mapKey == ic.cfg.ConfigMapName {
glog.V(2).Infof("updating configmap backend (%v)", mapKey)
ic.cfg.Backend.SetConfig(upCmap)
ic.forceReload = true
}
// updates to configuration configmaps can trigger an update
if mapKey == ic.cfg.ConfigMapName || mapKey == ic.cfg.TCPConfigMapName || mapKey == ic.cfg.UDPConfigMapName {
@ -406,7 +410,7 @@ func (ic *GenericController) syncIngress(key interface{}) error {
PassthroughBackends: passUpstreams,
}
if ic.runningConfig != nil && ic.runningConfig.Equal(&pcfg) {
if !ic.forceReload || ic.runningConfig != nil && ic.runningConfig.Equal(&pcfg) {
glog.V(3).Infof("skipping backend reload (no changes detected)")
return nil
}
@ -425,6 +429,7 @@ func (ic *GenericController) syncIngress(key interface{}) error {
setSSLExpireTime(servers)
ic.runningConfig = &pcfg
ic.forceReload = false
return nil
}

View file

@ -8,7 +8,6 @@ import (
"net/http/pprof"
"os"
"syscall"
"time"
"github.com/golang/glog"
"github.com/prometheus/client_golang/prometheus/promhttp"
@ -65,8 +64,8 @@ func NewIngressController(backend ingress.Controller) *GenericController {
service with the format namespace/serviceName and the port of the service could be a
number of the name of the port.`)
resyncPeriod = flags.Duration("sync-period", 60*time.Second,
`Relist and confirm cloud resources this often.`)
resyncPeriod = flags.Duration("sync-period", 0,
`Relist and confirm cloud resources this often. Default is 0 (no resync)`)
watchNamespace = flags.String("watch-namespace", api.NamespaceAll,
`Namespace to watch for Ingress. Default is to watch all namespaces`)