mirror of
https://github.com/vimagick/dockerfiles.git
synced 2024-12-27 02:09:06 +02:00
235 lines
8.4 KiB
Lua
235 lines
8.4 KiB
Lua
local ngx_re_gmatch = ngx.re.gmatch
|
|
local ngx_re_sub = ngx.re.sub
|
|
local ngx_re_find = ngx.re.find
|
|
|
|
--[[
|
|
A connection function that incorporates:
|
|
- tcp connect
|
|
- ssl handshake
|
|
- http proxy
|
|
Due to this it will be better at setting up a socket pool where connections can
|
|
be kept alive.
|
|
|
|
|
|
Call it with a single options table as follows:
|
|
|
|
client:connect {
|
|
scheme = "https" -- scheme to use, or nil for unix domain socket
|
|
host = "myhost.com", -- target machine, or a unix domain socket
|
|
port = nil, -- port on target machine, will default to 80/443 based on scheme
|
|
pool = nil, -- connection pool name, leave blank! this function knows best!
|
|
pool_size = nil, -- options as per: https://github.com/openresty/lua-nginx-module#tcpsockconnect
|
|
backlog = nil,
|
|
|
|
-- ssl options as per: https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake
|
|
ssl_server_name = nil,
|
|
ssl_send_status_req = nil,
|
|
ssl_verify = true, -- NOTE: defaults to true
|
|
ctx = nil, -- NOTE: not supported
|
|
|
|
proxy_opts, -- proxy opts, defaults to global proxy options
|
|
}
|
|
]]
|
|
local function connect(self, options)
|
|
local sock = self.sock
|
|
if not sock then
|
|
return nil, "not initialized"
|
|
end
|
|
|
|
local ok, err
|
|
|
|
local request_scheme = options.scheme
|
|
local request_host = options.host
|
|
local request_port = options.port
|
|
|
|
local poolname = options.pool
|
|
local pool_size = options.pool_size
|
|
local backlog = options.backlog
|
|
|
|
if request_scheme and not request_port then
|
|
request_port = (request_scheme == "https" and 443 or 80)
|
|
elseif request_port and not request_scheme then
|
|
return nil, "'scheme' is required when providing a port"
|
|
end
|
|
|
|
-- ssl settings
|
|
local ssl, ssl_server_name, ssl_verify, ssl_send_status_req
|
|
if request_scheme == "https" then
|
|
ssl = true
|
|
ssl_server_name = options.ssl_server_name
|
|
ssl_send_status_req = options.ssl_send_status_req
|
|
ssl_verify = true -- default
|
|
if options.ssl_verify == false then
|
|
ssl_verify = false
|
|
end
|
|
end
|
|
|
|
-- proxy related settings
|
|
local proxy, proxy_uri, proxy_authorization, proxy_host, proxy_port, path_prefix
|
|
proxy = options.proxy_opts or self.proxy_opts
|
|
|
|
if proxy then
|
|
if request_scheme == "https" then
|
|
proxy_uri = proxy.https_proxy
|
|
proxy_authorization = proxy.https_proxy_authorization
|
|
else
|
|
proxy_uri = proxy.http_proxy
|
|
proxy_authorization = proxy.http_proxy_authorization
|
|
-- When a proxy is used, the target URI must be in absolute-form
|
|
-- (RFC 7230, Section 5.3.2.). That is, it must be an absolute URI
|
|
-- to the remote resource with the scheme, host and an optional port
|
|
-- in place.
|
|
--
|
|
-- Since _format_request() constructs the request line by concatenating
|
|
-- params.path and params.query together, we need to modify the path
|
|
-- to also include the scheme, host and port so that the final form
|
|
-- in conformant to RFC 7230.
|
|
path_prefix = "http://" .. request_host .. (request_port == 80 and "" or (":" .. request_port))
|
|
end
|
|
if not proxy_uri then
|
|
proxy = nil
|
|
proxy_authorization = nil
|
|
path_prefix = nil
|
|
end
|
|
end
|
|
|
|
if proxy and proxy.no_proxy then
|
|
-- Check if the no_proxy option matches this host. Implementation adapted
|
|
-- from lua-http library (https://github.com/daurnimator/lua-http)
|
|
if proxy.no_proxy == "*" then
|
|
-- all hosts are excluded
|
|
proxy = nil
|
|
|
|
else
|
|
local host = request_host
|
|
local no_proxy_set = {}
|
|
-- wget allows domains in no_proxy list to be prefixed by "."
|
|
-- e.g. no_proxy=.mit.edu
|
|
for host_suffix in ngx_re_gmatch(proxy.no_proxy, "\\.?([^,]+)") do
|
|
no_proxy_set[host_suffix[1]] = true
|
|
end
|
|
|
|
-- From curl docs:
|
|
-- matched as either a domain which contains the hostname, or the
|
|
-- hostname itself. For example local.com would match local.com,
|
|
-- local.com:80, and www.local.com, but not www.notlocal.com.
|
|
--
|
|
-- Therefore, we keep stripping subdomains from the host, compare
|
|
-- them to the ones in the no_proxy list and continue until we find
|
|
-- a match or until there's only the TLD left
|
|
repeat
|
|
if no_proxy_set[host] then
|
|
proxy = nil
|
|
proxy_uri = nil
|
|
proxy_authorization = nil
|
|
break
|
|
end
|
|
|
|
-- Strip the next level from the domain and check if that one
|
|
-- is on the list
|
|
host = ngx_re_sub(host, "^[^.]+\\.", "")
|
|
until not ngx_re_find(host, "\\.")
|
|
end
|
|
end
|
|
|
|
if proxy then
|
|
local proxy_uri_t, err = self:parse_uri(proxy_uri)
|
|
if not proxy_uri_t then
|
|
return nil, err
|
|
end
|
|
|
|
local proxy_scheme = proxy_uri_t[1]
|
|
if proxy_scheme ~= "http" then
|
|
return nil, "protocol " .. tostring(proxy_scheme) ..
|
|
" not supported for proxy connections"
|
|
end
|
|
proxy_host = proxy_uri_t[2]
|
|
proxy_port = proxy_uri_t[3]
|
|
end
|
|
|
|
-- construct a poolname unique within proxy and ssl info
|
|
if not poolname then
|
|
poolname = (request_scheme or "")
|
|
.. ":" .. request_host
|
|
.. ":" .. tostring(request_port)
|
|
.. ":" .. tostring(ssl)
|
|
.. ":" .. (ssl_server_name or "")
|
|
.. ":" .. tostring(ssl_verify)
|
|
.. ":" .. (proxy_uri or "")
|
|
.. ":" .. (request_scheme == "https" and proxy_authorization or "")
|
|
-- in the above we only add the 'proxy_authorization' as part of the poolname
|
|
-- when the request is https. Because in that case the CONNECT request (which
|
|
-- carries the authorization header) is part of the connect procedure, whereas
|
|
-- with a plain http request the authorization is part of the actual request.
|
|
end
|
|
|
|
-- do TCP level connection
|
|
local tcp_opts = { pool = poolname, pool_size = pool_size, backlog = backlog }
|
|
if proxy then
|
|
-- proxy based connection
|
|
ok, err = sock:connect(proxy_host, proxy_port, tcp_opts)
|
|
if not ok then
|
|
return nil, err
|
|
end
|
|
|
|
if ssl and sock:getreusedtimes() == 0 then
|
|
-- Make a CONNECT request to create a tunnel to the destination through
|
|
-- the proxy. The request-target and the Host header must be in the
|
|
-- authority-form of RFC 7230 Section 5.3.3. See also RFC 7231 Section
|
|
-- 4.3.6 for more details about the CONNECT request
|
|
local destination = request_host .. ":" .. request_port
|
|
local res, err = self:request({
|
|
method = "CONNECT",
|
|
path = destination,
|
|
headers = {
|
|
["Host"] = destination,
|
|
["Proxy-Authorization"] = proxy_authorization,
|
|
}
|
|
})
|
|
|
|
if not res then
|
|
return nil, err
|
|
end
|
|
|
|
if res.status < 200 or res.status > 299 then
|
|
return nil, "failed to establish a tunnel through a proxy: " .. res.status
|
|
end
|
|
end
|
|
|
|
elseif not request_port then
|
|
-- non-proxy, without port -> unix domain socket
|
|
ok, err = sock:connect(request_host, tcp_opts)
|
|
if not ok then
|
|
return nil, err
|
|
end
|
|
|
|
else
|
|
-- non-proxy, regular network tcp
|
|
ok, err = sock:connect(request_host, request_port, tcp_opts)
|
|
if not ok then
|
|
return nil, err
|
|
end
|
|
end
|
|
|
|
-- Now do the ssl handshake
|
|
if ssl and sock:getreusedtimes() == 0 then
|
|
local ok, err = sock:sslhandshake(nil, ssl_server_name, ssl_verify, ssl_send_status_req)
|
|
if not ok then
|
|
self:close()
|
|
return nil, err
|
|
end
|
|
end
|
|
|
|
self.host = request_host
|
|
self.port = request_port
|
|
self.keepalive = true
|
|
self.ssl = ssl
|
|
-- set only for http, https has already been handled
|
|
self.http_proxy_auth = request_scheme ~= "https" and proxy_authorization or nil
|
|
self.path_prefix = path_prefix
|
|
|
|
return true
|
|
end
|
|
|
|
return connect
|