Skip to content

Commit

Permalink
Refactor code to use lua-resty-http 0.17.1
Browse files Browse the repository at this point in the history
  • Loading branch information
tkan145 committed Jan 26, 2024
1 parent df95769 commit ad67268
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 124 deletions.
189 changes: 83 additions & 106 deletions gateway/src/resty/http/proxy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,108 +13,6 @@ local function default_port(uri)
return uri.port or resty_url.default_port(uri.scheme)
end

local function connect_direct(httpc, request)
local uri = request.uri
local host = uri.host
local ip, port = httpc:resolve(host, nil, uri)
-- #TODO: This logic may no longer be needed as of PR#1323 and should be reviewed as part of a refactor
local options = { pool = format('%s:%s', host, port) }
local ok, err = httpc:connect(ip, port or default_port(uri), options)

if not ok then return nil, err end

ngx.log(ngx.DEBUG, 'connection to ', host, ':', httpc.port, ' established',
', reused times: ', httpc:get_reused_times())

if uri.scheme == 'https' then
ok, err = httpc:ssl_handshake(nil, host, request.ssl_verify)
if not ok then return nil, err end
end

-- use correct host header
httpc.host = host

return httpc
end

local function _connect_tls_direct(httpc, request, host, port)

local uri = request.uri

local ok, err = httpc:ssl_handshake(nil, uri.host, request.ssl_verify)
if not ok then return nil, err end

return httpc
end

local function _connect_proxy_https(httpc, request, host, port)
-- When the connection is reused the tunnel is already established, so
-- the second CONNECT request would reach the upstream instead of the proxy.
if httpc:get_reused_times() > 0 then
return httpc, 'already connected'
end

local uri = request.uri

local res, err = httpc:request({
method = 'CONNECT',
path = format('%s:%s', host, port or default_port(uri)),
headers = {
['Host'] = request.headers.host or format('%s:%s', uri.host, default_port(uri)),
['Proxy-Authorization'] = request.proxy_auth or ''
}
})
if not res then return nil, err end

if res.status < 200 or res.status > 299 then
return nil, "failed to establish a tunnel through a proxy: " .. res.status
end

res, err = httpc:ssl_handshake(nil, uri.host, request.ssl_verify)
if not res then return nil, err end

return httpc
end

local function connect_proxy(httpc, request, skip_https_connect)
-- target server requires hostname not IP and DNS resolution is left to the proxy itself as specified in the RFC #7231
-- https://httpwg.org/specs/rfc7231.html#CONNECT
local uri = request.uri
local proxy_uri = request.proxy

if proxy_uri.scheme ~= 'http' then
return nil, 'proxy connection supports only http'
else
proxy_uri.port = default_port(proxy_uri)
end

local port = default_port(uri)

-- TLS tunnel is verified only once, so we need to reuse connections only for the same Host header
local options = { pool = format('%s:%s:%s:%s', proxy_uri.host, proxy_uri.port, uri.host, port) }
local ok, err = httpc:connect(proxy_uri.host, proxy_uri.port, options)
if not ok then return nil, err end

ngx.log(ngx.DEBUG, 'connection to ', proxy_uri.host, ':', proxy_uri.port, ' established',
', pool: ', options.pool, ' reused times: ', httpc:get_reused_times())

ngx.log(ngx.DEBUG, 'targeting server ', uri.host, ':', uri.port)

if uri.scheme == 'http' then
-- http proxy needs absolute URL as the request path
request.path = format('%s://%s:%s%s', uri.scheme, uri.host, uri.port, uri.path or '/')
return httpc
elseif uri.scheme == 'https' and skip_https_connect then
request.path = format('%s://%s:%s%s', uri.scheme, uri.host, uri.port, request.path or '/')
return _connect_tls_direct(httpc, request, uri.host, uri.port)
elseif uri.scheme == 'https' then
return _connect_proxy_https(httpc, request, uri.host, uri.port)

else
return nil, 'invalid scheme'
end
end

local function parse_request_uri(request)
local uri = request.uri or resty_url.parse(request.url)
request.uri = uri
Expand All @@ -133,15 +31,94 @@ end
local function connect(request, skip_https_connect)
local httpc = http.new()
local proxy_uri = find_proxy_url(request)
local uri = request.uri
local scheme = uri.scheme
local host = uri.host
local port = default_port(uri)

request.ssl_verify = request.options and request.options.ssl and request.options.ssl.verify
request.proxy = proxy_uri
-- set ssl_verify: lua-resty-http set ssl_verify to true by default if scheme is https, whereas
-- openresty treat nil as false, so we need to explicitly set ssl_verify to false if nil
local ssl_verify = request.options and request.options.ssl and request.options.ssl.verify or false

local options = {
scheme = scheme,
host = host,
port = port
}
if scheme == 'https' then
options.ssl_server_name = host
options.ssl_verify = ssl_verify
end

-- Connect via proxy
if proxy_uri then
return connect_proxy(httpc, request, skip_https_connect)
if proxy_uri.scheme ~= 'http' then
return nil, 'proxy connection supports only http'

Check warning on line 56 in gateway/src/resty/http/proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/proxy.lua#L56

Added line #L56 was not covered by tests
else
proxy_uri.port = default_port(proxy_uri)
end

-- Resolve the proxy IP/Port
local proxy_host, proxy_port = httpc:resolve(proxy_uri.host, proxy_uri.port)
local proxy_url = format("%s://%s:%s", proxy_uri.scheme, proxy_host, proxy_port)
local proxy_auth = request.proxy_auth

if scheme == 'http' then
-- http proxy needs absolute URL as the request path, lua-resty-http 1.17.1 will
-- construct a path_prefix based on host and port so we only set request path here
--
-- https://github.com/ledgetech/lua-resty-http/blob/master/lib/resty/http_connect.lua#L99
request.path = uri.path or '/'
options.proxy_opts = {
http_proxy = proxy_url,
http_proxy_authorization = proxy_auth
}
elseif scheme == 'https' and skip_https_connect then
options.scheme = proxy_uri.scheme
options.host = proxy_uri.host
options.port = proxy_uri.port
options.pool = format('%s:%s:%s:%s', proxy_uri.host, proxy_uri.port, host, port)
request.path = format('%s://%s:%s%s', scheme, host, port, request.path or '/')

Check warning on line 81 in gateway/src/resty/http/proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/proxy.lua#L76-L81

Added lines #L76 - L81 were not covered by tests

local ok, err = httpc:connect(options)
if not ok then return nil, err end

Check warning on line 84 in gateway/src/resty/http/proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/proxy.lua#L83-L84

Added lines #L83 - L84 were not covered by tests

ngx.log(ngx.DEBUG, 'connection to ', proxy_uri.host, ':', proxy_uri.port, ' established',
', pool: ', httpc.pool, ' reused times: ', httpc:get_reused_times())

Check warning on line 87 in gateway/src/resty/http/proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/proxy.lua#L86-L87

Added lines #L86 - L87 were not covered by tests

ngx.log(ngx.DEBUG, 'targeting server ', host, ':', port)

Check warning on line 89 in gateway/src/resty/http/proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/proxy.lua#L89

Added line #L89 was not covered by tests

local ok, err = httpc:ssl_handshake(nil, host, request.ssl_verify)
if not ok then return nil, err end

Check warning on line 92 in gateway/src/resty/http/proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/proxy.lua#L91-L92

Added lines #L91 - L92 were not covered by tests

return httpc
elseif scheme == 'https' then
options.proxy_opts = {

Check warning on line 96 in gateway/src/resty/http/proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/proxy.lua#L94-L96

Added lines #L94 - L96 were not covered by tests
https_proxy = proxy_url,
https_proxy_authorization = proxy_auth

Check warning on line 98 in gateway/src/resty/http/proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/proxy.lua#L98

Added line #L98 was not covered by tests
}
else
return nil, 'invalid scheme'

Check warning on line 101 in gateway/src/resty/http/proxy.lua

View check run for this annotation

Codecov / codecov/patch

gateway/src/resty/http/proxy.lua#L101

Added line #L101 was not covered by tests
end

-- TLS tunnel is verified only once, so we need to reuse connections only for the same Host header
local ok, err = httpc:connect(options)
if not ok then return nil, err end

ngx.log(ngx.DEBUG, 'connection to ', proxy_uri.host, ':', proxy_uri.port, ' established',
', pool: ', httpc.pool, ' reused times: ', httpc:get_reused_times())
ngx.log(ngx.DEBUG, 'targeting server ', host, ':', port)
else
return connect_direct(httpc, request)
-- Connect direct
local ok, err = httpc:connect(options)
if not ok then return nil, err end

ngx.log(ngx.DEBUG, 'connection to ', httpc.host, ':', httpc.port, ' established',
', pool: ', httpc.pool, ' reused times: ', httpc:get_reused_times())
end


return httpc
end

function _M.env()
Expand Down
25 changes: 12 additions & 13 deletions gateway/src/resty/http_ng/backend/async_resty.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,21 @@ _M.async = function(request)
end
end

local ok, err = httpc:connect(host, port)
local verify = request.options and request.options.ssl and request.options.ssl.verify
if type(verify) == 'nil' then verify = true end

if not ok then
return response.error(request, err)
end

if scheme == 'https' then
local verify = request.options and request.options.ssl and request.options.ssl.verify
if type(verify) == 'nil' then verify = true end
local options = {
scheme = scheme,
host = host,
port = port,
ssl_server_name = host,
ssl_verify = verify
}

local session
session, err = httpc:ssl_handshake(false, host, verify)
local ok, err = httpc:connect(options)

if not session then
return response.error(request, err)
end
if not ok then
return response.error(request, err)
end

local res
Expand Down
24 changes: 20 additions & 4 deletions gateway/src/resty/resolver/http.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,29 @@ function _M:resolve(host, port, options)
return ip, port
end

function _M.connect(self, host, port, ...)
local ip, real_port = self:resolve(host, port)
local ok, err = resty_http.connect(self, ip, real_port, ...)
function _M.connect(self, options, ...)
-- cache the host because we need to resolve host to IP
local host = options.host
local proxy_opts = options.proxy_opts
local proxy = proxy_opts and (proxy_opts.http_proxy or proxy_opts.https_proxy)
local ip, real_port

-- target server requires hostname not IP and DNS resolution is left to the proxy itself as specified in the RFC #7231
-- https://httpwg.org/specs/rfc7231.html#CONNECT
--
-- Therefore, only resolve host IP when not using with proxy
if not proxy then
ip, real_port = self:resolve(options.host, options.port)
options.host = ip
options.port = real_port
end

local ok, err = resty_http.connect(self, options, ...)

if ok then
-- use correct host header
self.host = host
self.port = real_port
self.port = options.port
end

ngx.log(ngx.DEBUG, 'connected to ip:', ip, ' host: ', host, ' port: ', real_port, ' ok: ', ok, ' err: ', err)
Expand Down
2 changes: 1 addition & 1 deletion spec/resty/resolver/http_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('resty.resolver.http', function()
local client = _M.new()
client:set_timeout(1000)
client.resolver.cache:save({ { address = '127.0.0.1', name = 'unknown.', ttl = 1800 } })
assert(client:connect('unknown', 1984))
assert(client:connect({scheme="http", host='unknown', port=1984}))
assert.equal('unknown', client.host)
assert.equal(1984, client.port)
end)
Expand Down

0 comments on commit ad67268

Please sign in to comment.