Skip to content

Commit 607ef6b

Browse files
committed
Merge branch 'release/0.12'
2 parents 09492c8 + c39cf8c commit 607ef6b

File tree

6 files changed

+511
-9
lines changed

6 files changed

+511
-9
lines changed

Diff for: README.md

+34-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Production ready.
2222

2323
* [new](#new)
2424
* [connect](#connect)
25+
* [connect_proxy](#connect_proxy)
26+
* [set_proxy_options](#set_proxy_options)
2527
* [set_timeout](#set_timeout)
2628
* [set_timeouts](#set_timeouts)
2729
* [ssl_handshake](#ssl_handshake)
@@ -158,6 +160,24 @@ An optional Lua table can be specified as the last argument to this method to sp
158160
* `pool`
159161
: 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>`.
160162

163+
## connect_proxy
164+
165+
`syntax: ok, err = httpc:connect_proxy(proxy_uri, scheme, host, port)`
166+
167+
Attempts to connect to the web server through the given proxy server. The method accepts the following arguments:
168+
169+
* `proxy_uri` - Full URI of the proxy server to use (e.g. `http://proxy.example.com:3128/`). Note: Only `http` protocol is supported.
170+
* `scheme` - The protocol to use between the proxy server and the remote host (`http` or `https`). If `https` is specified as the scheme, `connect_proxy()` makes a `CONNECT` request to establish a TCP tunnel to the remote host through the proxy server.
171+
* `host` - The hostname of the remote host to connect to.
172+
* `port` - The port of the remote host to connect to.
173+
174+
If an error occurs during the connection attempt, this method returns `nil` with a string describing the error. If the connection was successfully established, the method returns `1`.
175+
176+
There's a few key points to keep in mind when using this api:
177+
178+
* If the scheme is `https`, you need to perform the TLS handshake with the remote server manually using the `ssl_handshake()` method before sending any requests through the proxy tunnel.
179+
* If the scheme is `http`, you need to ensure that the requests you send through the connections conforms to [RFC 7230](https://tools.ietf.org/html/rfc7230) and especially [Section 5.3.2.](https://tools.ietf.org/html/rfc7230#section-5.3.2) which states that the request target must be in absolute form. In practice, this means that when you use `send_request()`, the `path` must be an absolute URI to the resource (e.g. `http://example.com/index.html` instead of just `/index.html`).
180+
161181
## set_timeout
162182

163183
`syntax: httpc:set_timeout(time)`
@@ -192,6 +212,18 @@ Note that calling this instead of `close` is "safe" in that it will conditionall
192212

193213
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`.
194214

215+
## set_proxy_options
216+
217+
`syntax: httpc:set_proxy_options(opts)`
218+
219+
Configure an http proxy to be used with this client instance. The `opts` is a table that accepts the following fields:
220+
221+
* `http_proxy` - an URI to a proxy server to be used with http requests
222+
* `https_proxy` - an URI to a proxy server to be used with https requests
223+
* `no_proxy` - a comma separated list of hosts that should not be proxied.
224+
225+
Note that proxy options are only applied when using the high-level `request_uri()` API.
226+
195227
## get_reused_times
196228

197229
`syntax: times, err = httpc:get_reused_times()`
@@ -232,7 +264,7 @@ When the request is successful, `res` will contain the following fields:
232264
* `status` The status code.
233265
* `reason` The status reason phrase.
234266
* `headers` A table of headers. Multiple headers with the same field name will be presented as a table of values.
235-
* `has_body` A boolean flag indicating if there is a body to be read.
267+
* `has_body` A boolean flag indicating if there is a body to be read.
236268
* `body_reader` An iterator function for reading the body in a streaming fashion.
237269
* `read_body` A method to read the entire body into a string.
238270
* `read_trailers` A method to merge any trailers underneath the headers, after reading the body.
@@ -408,7 +440,7 @@ local res, err = httpc:request{
408440
}
409441
```
410442

411-
If `sock` is specified,
443+
If `sock` is specified,
412444

413445
# Author
414446

Diff for: lib/resty/http.lua

+149-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ local tbl_concat = table.concat
1515
local tbl_insert = table.insert
1616
local ngx_encode_args = ngx.encode_args
1717
local ngx_re_match = ngx.re.match
18+
local ngx_re_gmatch = ngx.re.gmatch
19+
local ngx_re_sub = ngx.re.sub
1820
local ngx_re_gsub = ngx.re.gsub
1921
local ngx_re_find = ngx.re.find
2022
local ngx_log = ngx.log
@@ -98,7 +100,7 @@ end
98100

99101

100102
local _M = {
101-
_VERSION = '0.11',
103+
_VERSION = '0.12',
102104
}
103105
_M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version
104106

@@ -787,7 +789,6 @@ function _M.request_pipeline(self, requests)
787789
return responses
788790
end
789791

790-
791792
function _M.request_uri(self, uri, params)
792793
params = tbl_copy(params or {}) -- Take by value
793794

@@ -800,11 +801,55 @@ function _M.request_uri(self, uri, params)
800801
if not params.path then params.path = path end
801802
if not params.query then params.query = query end
802803

803-
local c, err = self:connect(host, port)
804+
-- See if we should use a proxy to make this request
805+
local proxy_uri = self:get_proxy_uri(scheme, host)
806+
807+
-- Make the connection either through the proxy or directly
808+
-- to the remote host
809+
local c, err
810+
811+
if proxy_uri then
812+
c, err = self:connect_proxy(proxy_uri, scheme, host, port)
813+
else
814+
c, err = self:connect(host, port)
815+
end
816+
804817
if not c then
805818
return nil, err
806819
end
807820

821+
if proxy_uri then
822+
if scheme == "http" then
823+
-- When a proxy is used, the target URI must be in absolute-form
824+
-- (RFC 7230, Section 5.3.2.). That is, it must be an absolute URI
825+
-- to the remote resource with the scheme, host and an optional port
826+
-- in place.
827+
--
828+
-- Since _format_request() constructs the request line by concatenating
829+
-- params.path and params.query together, we need to modify the path
830+
-- to also include the scheme, host and port so that the final form
831+
-- in conformant to RFC 7230.
832+
if port == 80 then
833+
params.path = scheme .. "://" .. host .. path
834+
else
835+
params.path = scheme .. "://" .. host .. ":" .. port .. path
836+
end
837+
end
838+
839+
if scheme == "https" then
840+
-- don't keep this connection alive as the next request could target
841+
-- any host and re-using the proxy tunnel for that is not possible
842+
self.keepalive = false
843+
end
844+
845+
-- self:connect_uri() set the host and port to point to the proxy server. As
846+
-- the connection to the proxy has been established, set the host and port
847+
-- to point to the actual remote endpoint at the other end of the tunnel to
848+
-- ensure the correct Host header added to the requests.
849+
self.host = host
850+
self.port = port
851+
end
852+
808853
if scheme == "https" then
809854
local verify = true
810855
if params.ssl_verify == false then
@@ -914,5 +959,106 @@ function _M.proxy_response(self, response, chunksize)
914959
until not chunk
915960
end
916961

962+
function _M.set_proxy_options(self, opts)
963+
self.proxy_opts = tbl_copy(opts) -- Take by value
964+
end
965+
966+
function _M.get_proxy_uri(self, scheme, host)
967+
if not self.proxy_opts then
968+
return nil
969+
end
970+
971+
-- Check if the no_proxy option matches this host. Implementation adapted
972+
-- from lua-http library (https://github.com/daurnimator/lua-http)
973+
if self.proxy_opts.no_proxy then
974+
if self.proxy_opts.no_proxy == "*" then
975+
-- all hosts are excluded
976+
return nil
977+
end
978+
979+
local no_proxy_set = {}
980+
-- wget allows domains in no_proxy list to be prefixed by "."
981+
-- e.g. no_proxy=.mit.edu
982+
for host_suffix in ngx_re_gmatch(self.proxy_opts.no_proxy, "\\.?([^,]+)") do
983+
no_proxy_set[host_suffix[1]] = true
984+
end
985+
986+
-- From curl docs:
987+
-- matched as either a domain which contains the hostname, or the
988+
-- hostname itself. For example local.com would match local.com,
989+
-- local.com:80, and www.local.com, but not www.notlocal.com.
990+
--
991+
-- Therefore, we keep stripping subdomains from the host, compare
992+
-- them to the ones in the no_proxy list and continue until we find
993+
-- a match or until there's only the TLD left
994+
repeat
995+
if no_proxy_set[host] then
996+
return nil
997+
end
998+
999+
-- Strip the next level from the domain and check if that one
1000+
-- is on the list
1001+
host = ngx_re_sub(host, "^[^.]+\\.", "")
1002+
until not ngx_re_find(host, "\\.")
1003+
end
1004+
1005+
if scheme == "http" and self.proxy_opts.http_proxy then
1006+
return self.proxy_opts.http_proxy
1007+
end
1008+
1009+
if scheme == "https" and self.proxy_opts.https_proxy then
1010+
return self.proxy_opts.https_proxy
1011+
end
1012+
1013+
return nil
1014+
end
1015+
1016+
1017+
function _M.connect_proxy(self, proxy_uri, scheme, host, port)
1018+
-- Parse the provided proxy URI
1019+
local parsed_proxy_uri, err = self:parse_uri(proxy_uri, false)
1020+
if not parsed_proxy_uri then
1021+
return nil, err
1022+
end
1023+
1024+
-- Check that the scheme is http (https is not supported for
1025+
-- connections between the client and the proxy)
1026+
local proxy_scheme = parsed_proxy_uri[1]
1027+
if proxy_scheme ~= "http" then
1028+
return nil, "protocol " .. proxy_scheme .. " not supported for proxy connections"
1029+
end
1030+
1031+
-- Make the connection to the given proxy
1032+
local proxy_host, proxy_port = parsed_proxy_uri[2], parsed_proxy_uri[3]
1033+
local c, err = self:connect(proxy_host, proxy_port)
1034+
if not c then
1035+
return nil, err
1036+
end
1037+
1038+
if scheme == "https" then
1039+
-- Make a CONNECT request to create a tunnel to the destination through
1040+
-- the proxy. The request-target and the Host header must be in the
1041+
-- authority-form of RFC 7230 Section 5.3.3. See also RFC 7231 Section
1042+
-- 4.3.6 for more details about the CONNECT request
1043+
local destination = host .. ":" .. port
1044+
local res, err = self:request({
1045+
method = "CONNECT",
1046+
path = destination,
1047+
headers = {
1048+
["Host"] = destination
1049+
}
1050+
})
1051+
1052+
if not res then
1053+
return nil, err
1054+
end
1055+
1056+
if res.status < 200 or res.status > 299 then
1057+
return nil, "failed to establish a tunnel through a proxy: " .. res.status
1058+
end
1059+
end
1060+
1061+
return c, nil
1062+
end
9171063

9181064
return _M

Diff for: lib/resty/http_headers.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ local rawget, rawset, setmetatable =
44
local str_lower = string.lower
55

66
local _M = {
7-
_VERSION = '0.11',
7+
_VERSION = '0.12',
88
}
99

1010

Diff for: lua-resty-http-0.11-0.rockspec renamed to lua-resty-http-0.12-0.rockspec

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package = "lua-resty-http"
2-
version = "0.11-0"
2+
version = "0.12-0"
33
source = {
44
url = "git://github.com/pintsized/lua-resty-http",
5-
tag = "v0.11"
5+
tag = "v0.12"
66
}
77
description = {
88
summary = "Lua HTTP client cosocket driver for OpenResty / ngx_lua.",

Diff for: t/14-host-header.t

+28-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ $ENV{TEST_COVERAGE} ||= 0;
1212
our $HttpConfig = qq{
1313
lua_package_path "$pwd/lib/?.lua;/usr/local/share/lua/5.1/?.lua;;";
1414
error_log logs/error.log debug;
15-
resolver 8.8.8.8;
15+
resolver 8.8.8.8 ipv6=off;
1616
1717
init_by_lua_block {
1818
if $ENV{TEST_COVERAGE} == 1 then
@@ -165,3 +165,30 @@ GET /a
165165
[error]
166166
--- response_body
167167
Unable to generate a useful Host header for a unix domain socket. Please provide one.
168+
169+
=== TEST 6: Host header is correct when http_proxy is used
170+
--- http_config
171+
lua_package_path "$TEST_NGINX_PWD/lib/?.lua;;";
172+
error_log logs/error.log debug;
173+
resolver 8.8.8.8;
174+
server {
175+
listen *:8080;
176+
}
177+
178+
--- config
179+
location /lua {
180+
content_by_lua '
181+
local http = require "resty.http"
182+
local httpc = http.new()
183+
httpc:set_proxy_options({
184+
http_proxy = "http://127.0.0.1:8080"
185+
})
186+
local res, err = httpc:request_uri("http://127.0.0.1:8081")
187+
';
188+
}
189+
--- request
190+
GET /lua
191+
--- no_error_log
192+
[error]
193+
--- error_log
194+
Host: 127.0.0.1:8081

0 commit comments

Comments
 (0)