Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[resty.http_ng] use http/s proxy for backend and upstream connections #800

Merged
merged 1 commit into from
Aug 22, 2018

Conversation

mikz
Copy link
Contributor

@mikz mikz commented Jul 4, 2018

Fixes #709

Builds on top of #835

By configuring standard environment variables http_proxy, HTTP_PROXY, no_proxy, NO_PROXY, http_proxy, HTTPS_PROXY you can instruct APIcast to use those instead of direct connection.

Proxy does not support any authentication yet.

Tests

  • test http_ng.backend.ngx
  • test http_ng.backend.ngx + SSL
  • test http_ng.backend.resty
  • test http_ng.backend.resty + SSL
  • test nginx upstream ngx.exec(@upstream)
  • test nginx upstream ngx.exec(@upstream) + SSL
  • test upstream policy
  • test upstream policy + SSL
  • test downloading configuration
  • test downloading configuration + SSL
  • test forwarding request body

@mikz mikz requested a review from a team as a code owner July 4, 2018 08:48
@mikz mikz force-pushed the use-http-proxy branch 2 times, most recently from ec4f24f to 049fd9d Compare July 11, 2018 12:58
local resty_env = require 'resty.env'
local http = require "resty.http"
local resty_url = require "resty.url"
local balancer = require 'apicast.balancer'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused variable 'balancer'

end

function _M.request(url)
local httpc = http.new()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accessing undefined variable 'http'

_M.content = function()
if not ngx.headers_sent then
ngx.exec("@upstream")
_M.content = function(_, context)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused argument 'context'

@mikz mikz force-pushed the use-http-proxy branch 2 times, most recently from 02a8e9e to 1a9d8d6 Compare July 12, 2018 09:36
data, err, partial = readline()

local match = tab_new(1,3)
match, err = re_match(data, [[^(?<method>[A-Z]+)\s(?<uri>\S+)\s(?<http>\S+)$]], 'oj', nil, match)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value assigned to variable 'err' is unused


local readline = client:receiveuntil(cr_lf)
local data, partial
data, err, partial = readline()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value assigned to variable 'err' is overwritten on line 183 before use

if not client then return nil, err end

local readline = client:receiveuntil(cr_lf)
local data, partial
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

variable 'partial' is never accessed

resolver local=on;


# define a TCP server listening on the port 1234:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be 8099 instead of 1234.

end
end

function _M.preread()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed to override the default behavior?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. I'll remove this.


local upstream = ngx.socket.tcp()

if match.method == 'CONNECT' then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check whether there was an error first. Otherwise, this could fail.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same in the ngx_re_match below.

request.proxy = resty_url.parse(proxy_url)
request.uri = uri

local ok, err
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused variable 'ok'

request.proxy = resty_url.parse(proxy_url)
request.uri = uri

local ok, err
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused variable 'err'


local function v1()
ngx.ctx.upstream_server = upstream
ngx.ctx.upstream = resty_resolver:instance():get_servers(upstream.server, { port = upstream.port })
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accessing undefined variable 'upstream'

url.host, { port = url.port })

ngx.var.proxy_pass = proxy_pass(url)
ngx.req.set_header('Host', url.host)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accessing undefined variable 'url'

@@ -80,18 +70,14 @@ function _M:rewrite(context)
end

function _M.content(_, context)
local new_upstream = context.new_upstream
local upstream = context[self]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accessing undefined variable 'self'

ngx.ctx.upstream = resty_resolver:instance():get_servers(
url.host, { port = url.port })

ngx.var.proxy_pass = proxy_pass(url)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accessing undefined variable 'url'


local function v2()
ngx.ctx.upstream = resty_resolver:instance():get_servers(
url.host, { port = url.port })
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accessing undefined variable 'url'

@mikz mikz force-pushed the use-http-proxy branch from 81263a3 to a639185 Compare July 19, 2018 17:34
@@ -15,11 +15,22 @@ local function exit_service_unavailable()
ngx.exit(ngx.status)
end

function _M.call(_, _, balancer)
balancer = balancer or _M.default_balancer
function _M:call(context, bal)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cyclomatic complexity of method '_M.call' is too high (8 > 7)

@@ -15,11 +15,22 @@ local function exit_service_unavailable()
ngx.exit(ngx.status)
end

function _M.call(_, _, balancer)
balancer = balancer or _M.default_balancer
function _M:call(context, bal)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused argument 'self'

@@ -0,0 +1,6 @@
local _M = require('apicast.upstream')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused variable '_M'

local function exit_service_unavailable()
ngx.status = ngx.HTTP_SERVICE_UNAVAILABLE
ngx.exit(ngx.status)
end

function _M.call(_, _, balancer)
balancer = balancer or _M.default_balancer
function _M:call(context, bal)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cyclomatic complexity of method '_M.call' is too high (9 > 7)


ok, err = balancer.set_current_peer(address, port)
local peer, err = self:select_peer(peers)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value assigned to variable 'err' is unused

b.peers = function() return { { '127.0.0.2' } } end
ngx.var.proxy_pass = 'https://example.com'
local balancer = setmetatable({
set_current_peer = spy.new(function(...) return true end),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused variable length argument

end

local function contains(state, arguments, level)
local level = (level or 1) + 1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

variable 'level' was previously defined as an argument on line 90

describe(':set_current_peer', function()
it('returns ok when it sets the peer', function()
local test = resty_balancer.new(function(peers) return peers[1] end)
local set_current_peer = stub.new(test.balancer, 'set_current_peer', function(...) return true end)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused variable length argument

balancer = balancer or _M.default_balancer
local host = ngx.var.proxy_host -- NYI: return to lower frame
local peers = balancer:peers(ngx.ctx[host])
function _M:call(context, bal)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cyclomatic complexity of method '_M.call' is too high (10 > 7)

balancer = balancer or _M.default_balancer
local host = ngx.var.proxy_host -- NYI: return to lower frame
local peers = balancer:peers(ngx.ctx[host])
function _M:call(context, bal)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused argument 'self'

@mikz mikz mentioned this pull request Jul 23, 2018
@mikz mikz force-pushed the use-http-proxy branch 2 times, most recently from 6d94e88 to 2376525 Compare July 24, 2018 11:22
request.proxy = resty_url.parse(proxy_url)
request.uri = uri

local ok, err
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused variable 'ok'

request.proxy = resty_url.parse(proxy_url)
request.uri = uri

local ok, err
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused variable 'err'

end

local match = tab_new(1,3)
match, err = ngx_re_match(data, [[^(?<method>[A-Z]+)\s(?<uri>\S+)\s(?<http>\S+)$]], 'oj', nil, match)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value assigned to variable 'err' is unused

return socket:send(data)
end

local function read_stream(name, input, output)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cyclomatic complexity of function 'read_stream' is too high (8 > 7)

if ngx.config.debug then
if ngx.ctx.upstream == socket then
-- ngx.log(ngx.DEBUG, 'sending: ', data , ' to upstream ', ngx.ctx.upstream_host, ':', ngx.ctx.upstream_port)
else
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty if branch


local function send(socket, data)
if ngx.config.debug then
if ngx.ctx.upstream == socket then
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty if branch

end

local match = tab_new(1,3)
match, err = ngx_re_match(data, [[^(?<method>[A-Z]+)\s(?<uri>\S+)\s(?<http>\S+)$]], 'oj', nil, match)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value assigned to variable 'err' is unused

return socket:send(data)
end

local function read_stream(name, input, output)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cyclomatic complexity of function 'read_stream' is too high (8 > 7)

if ngx.config.debug then
if ngx.ctx.upstream == socket then
-- ngx.log(ngx.DEBUG, 'sending: ', data , ' to upstream ', ngx.ctx.upstream_host, ':', ngx.ctx.upstream_port)
else
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty if branch


local function send(socket, data)
if ngx.config.debug then
if ngx.ctx.upstream == socket then
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty if branch

end

local match = tab_new(1,3)
match, err = ngx_re_match(data, [[^(?<method>[A-Z]+)\s(?<uri>\S+)\s(?<http>\S+)$]], 'oj', nil, match)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value assigned to variable 'err' is unused

return socket:send(data)
end

local function read_stream(name, input, output)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cyclomatic complexity of function 'read_stream' is too high (8 > 7)

if ngx.config.debug then
if ngx.ctx.upstream == socket then
-- ngx.log(ngx.DEBUG, 'sending: ', data , ' to upstream ', ngx.ctx.upstream_host, ':', ngx.ctx.upstream_port)
else
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty if branch


local function send(socket, data)
if ngx.config.debug then
if ngx.ctx.upstream == socket then
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty if branch

t/http-proxy.t Outdated

require("t/http_proxy.pl");

# Can't run twice because one of the test checks the contents of the cache, and
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

return ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
end

local res, err = httpc:request(request)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

variable 'err' was previously defined on line 130

@@ -40,7 +43,15 @@ _M.async = function(request)
end
end

local ok, err = httpc:connect(host, port)

local proxy_uri = httpc:get_proxy_uri(scheme, host)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Async resty is not used

@mikz mikz changed the title [wip][resty.http_ng] use http/s proxy \[resty.http_ng] use http/s proxy Aug 20, 2018
@mikz mikz changed the title \[resty.http_ng] use http/s proxy [resty.http_ng] use http/s proxy for backend and upstream connections Aug 20, 2018
@mikz mikz requested a review from davidor August 21, 2018 11:48
@mikz mikz force-pushed the use-http-proxy branch 2 times, most recently from 74725a8 to 42029d6 Compare August 21, 2018 12:42
@@ -22,7 +22,7 @@ local function init_config(config)
local upstream, err = Upstream.new(rule.url)

if upstream then
tab_insert(res, { regex = rule.regex, upstream = upstream })
tab_insert(res, { regex = rule.regex, upstream = upstream, url = rule.url })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that because of the change below we no longer need to store upstream here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned up.

@@ -49,7 +49,8 @@ function _M:rewrite(context)
for _, rule in ipairs(self.rules) do
if match(req_uri, rule.regex) then
ngx.log(ngx.DEBUG, 'upstream policy uri: ', req_uri, ' regex: ', rule.regex, ' match: true')
context[self] = rule.upstream
-- better to allocate new object for each request as it is going to get mutated
context[self] = Upstream.new(rule.url)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, it doesn't matter much because we're just storing one object, but it might be a better practice to namespace what we store in the context: context[self].url = ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option would be to store just the url instead of the Upstream instance.
The Upstream can be instantiated in content(). Not a big difference, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will have to try to reduce the allocations in the future, so I'm trying to not create objects when it is not absolutely necessary. In this case doing a namespace would require another table.

I'm parsing the upstream here, so potential failure will show early in the rewrite phase before other calls are made (like auth).

@@ -61,7 +62,7 @@ function _M:content(context)
local upstream = context[self]

if upstream then
upstream:set_request_host()
upstream:rewrite_request()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit confusing because the function in the call below, upstream:call(), already calls rewrite_request() at least in some cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed now.

end

local mt = {
__index = _M
}

local function split_path(path)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not something for now, but to take into account in future refactorings. I think we're already doing this in other modules, so it might be a good idea to extract methods like this one to a Request module that can be reused between policies.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, this is probably the most common helper function. We should offer performance optimized version and reuse it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

local function split_path(path)
if not path then return end

local start = str_find(path, '?', 1, true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the find + subs be replaced with a single "split by ?" ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some benchmarking, but I don't have the results and I think this was fully JIT compatible and faster.

end)
end

describe(':rewrite_request', function()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add tests for this part of the code too:

    if uri.query then
        ngx.req.set_uri_args(uri.query)
    end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@davidor davidor merged commit fb579bc into master Aug 22, 2018
@davidor davidor deleted the use-http-proxy branch August 22, 2018 13:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants