diff --git a/CHANGELOG.md b/CHANGELOG.md index 573d01163..54d3e4bbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Prometheus metrics for the 3scale batching policy [PR #902](https://github.com/3scale/apicast/pull/902) +- Support for path in the upstream URL [PR #905](https://github.com/3scale/apicast/pull/905) ## [3.3.0-cr1] - 2018-09-14 diff --git a/gateway/src/apicast/http_proxy.lua b/gateway/src/apicast/http_proxy.lua index 5efa09f31..dfaf140bc 100644 --- a/gateway/src/apicast/http_proxy.lua +++ b/gateway/src/apicast/http_proxy.lua @@ -76,7 +76,7 @@ local function absolute_url(uri) uri.scheme, host, port, - uri.path or ngx.var.uri or '' + uri.path or '' ) end diff --git a/gateway/src/apicast/upstream.lua b/gateway/src/apicast/upstream.lua index 34632d7d9..39105725a 100644 --- a/gateway/src/apicast/upstream.lua +++ b/gateway/src/apicast/upstream.lua @@ -117,6 +117,21 @@ function _M:port() return self.uri.port or resty_url.default_port(self.uri.scheme) end +local root_uri = { + ['/'] = true, + [''] = true, +} + +local function prefix_path(prefix) + local uri = ngx.var.uri or '' + + if root_uri[uri] then return prefix end + + uri = resty_url.join(prefix, uri) + + return uri +end + --- Rewrite request Host header to what is provided in the argument or in the URL. function _M:rewrite_request() local uri = self.uri @@ -126,7 +141,7 @@ function _M:rewrite_request() ngx.req.set_header('Host', self.host or uri.host) if uri.path then - ngx.req.set_uri(uri.path) + ngx.req.set_uri(prefix_path(uri.path)) end if uri.query then diff --git a/gateway/src/resty/http_ng/backend/ngx.lua b/gateway/src/resty/http_ng/backend/ngx.lua index 65916e9df..1cd5902ab 100644 --- a/gateway/src/resty/http_ng/backend/ngx.lua +++ b/gateway/src/resty/http_ng/backend/ngx.lua @@ -60,6 +60,7 @@ function backend.resolver() ngx.req.set_header(name, headers[name]) end + ngx.req.set_uri('/') -- reset the original PROXY_LOCATION path ngx.var.connection_header = headers.connection ngx.var.host_header = headers.host ngx.var.options = headers['3scale-options'] diff --git a/spec/upstream_spec.lua b/spec/upstream_spec.lua index a55b3c940..d07374598 100644 --- a/spec/upstream_spec.lua +++ b/spec/upstream_spec.lua @@ -95,6 +95,38 @@ describe('Upstream', function() assert.spy(ngx.req.set_uri).was_called_with('/test-path') end) + for url, path in pairs({ + ['http://example.com'] = '/', + ['http://example.com/path'] = '/path', + ['http://example.com/path/'] = '/path/', + ['http://example.com/path/foo'] = '/path/foo', + ['http://example.com/path/foo/'] = '/path/foo/', + ['http://example.com/path?test=value/'] = '/path', + }) do + it(('/ uses path %s for upstream %s'):format(path, url), function() + ngx.var.uri = '/' + local upstream = Upstream.new(url) + upstream:rewrite_request() + assert.same(path, ngx.var.uri) + end) + end + + for url, path in pairs({ + ['http://example.com'] = '/test', + ['http://example.com/path'] = '/path/test', + ['http://example.com/path/'] = '/path/test', + ['http://example.com/path/foo'] = '/path/foo/test', + ['http://example.com/path/foo/'] = '/path/foo/test', + ['http://example.com/path?test=value/'] = '/path/test', + }) do + it(('/test uses path %s for upstream %s'):format(path, url), function() + ngx.var.uri = '/test' + local upstream = Upstream.new(url) + upstream:rewrite_request() + assert.same(path, ngx.var.uri) + end) + end + it('sets request query params to the upstream query params', function() assert(Upstream.new('http://example.com/?query=param')):rewrite_request() diff --git a/t/apicast-blackbox.t b/t/apicast-blackbox.t index 36ea51cf6..b8d66ae0d 100644 --- a/t/apicast-blackbox.t +++ b/t/apicast-blackbox.t @@ -136,3 +136,37 @@ location /transactions/authrep.xml { --- no_error_log [error] --- user_files fixture=tls.pl eval + + + +=== TEST 4: api backend gets the request on its subpath +The request url is concatenated with the api backend url. +--- configuration +{ + "services": [ + { + "id": 42, + "backend_version": 1, + "proxy": { + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/foo", + "proxy_rules": [ + { "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ] + } + } + ] +} +--- backend +location /transactions/authrep.xml { + echo 'ok'; +} +--- upstream +location / { + echo 'path: $uri'; +} +--- request +GET /bar?user_key=value +--- response_body +path: /foo/bar +--- no_error_log +[error] diff --git a/t/apicast-caching.t b/t/apicast-caching.t index 01341e662..67e8afe84 100644 --- a/t/apicast-caching.t +++ b/t/apicast-caching.t @@ -76,7 +76,7 @@ Two services can exist together and are split by their hostname. backend_authentication_type = 'service_token', backend_authentication_value = 'service-one', proxy = { - api_backend = "http://127.0.0.1:$TEST_NGINX_SERVER_PORT/api-backend/one/", + api_backend = "http://127.0.0.1:$TEST_NGINX_SERVER_PORT/api-backend/foo/", hosts = { 'one' }, proxy_rules = { { pattern = '/', http_method = 'GET', metric_system_name = 'hits', delta = 1 } @@ -89,7 +89,7 @@ Two services can exist together and are split by their hostname. backend_authentication_type = 'service_token', backend_authentication_value = 'service-two', proxy = { - api_backend = "http://127.0.0.1:$TEST_NGINX_SERVER_PORT/api-backend/two/", + api_backend = "http://127.0.0.1:$TEST_NGINX_SERVER_PORT/api-backend/bar/", hosts = { 'two' }, proxy_rules = { { pattern = '/', http_method = 'GET', metric_system_name = 'hits', delta = 2 } @@ -135,8 +135,8 @@ Two services can exist together and are split by their hostname. --- request GET /t --- response_body -yay, api backend: /one/ -yay, api backend: /two/ +yay, api backend: /foo/one +yay, api backend: /bar/two --- error_code: 200 --- no_error_log [error] diff --git a/t/apicast-path-routing.t b/t/apicast-path-routing.t index 4060a9079..1d9d99afc 100644 --- a/t/apicast-path-routing.t +++ b/t/apicast-path-routing.t @@ -22,7 +22,7 @@ env APICAST_PATH_ROUTING; id = 42, backend_version = 1, proxy = { - api_backend = "http://127.0.0.1:$TEST_NGINX_SERVER_PORT/api-backend/one/", + api_backend = "http://127.0.0.1:$TEST_NGINX_SERVER_PORT/api-backend/foo/", hosts = { 'same' }, backend_authentication_type = 'service_token', backend_authentication_value = 'service-one', @@ -35,7 +35,7 @@ env APICAST_PATH_ROUTING; id = 21, backend_version = 2, proxy = { - api_backend = "http://127.0.0.1:$TEST_NGINX_SERVER_PORT/api-backend/two/", + api_backend = "http://127.0.0.1:$TEST_NGINX_SERVER_PORT/api-backend/bar/", hosts = { 'same' }, backend_authentication_type = 'service_token', backend_authentication_value = 'service-two', @@ -71,8 +71,8 @@ env APICAST_PATH_ROUTING; --- request GET /t --- response_body -yay, api backend: /one/ -yay, api backend: /two/ +yay, api backend: /foo/one +yay, api backend: /bar/two --- error_code: 200 --- grep_error_log eval: qr/apicast cache (?:hit|miss|write) key: [^,\s]+/ --- grep_error_log_out diff --git a/t/apicast-policy-upstream.t b/t/apicast-policy-upstream.t index c7b2d0120..427feffaf 100644 --- a/t/apicast-policy-upstream.t +++ b/t/apicast-policy-upstream.t @@ -215,16 +215,12 @@ yay, api backend } --- upstream location /path_in_the_rule { - content_by_lua_block { - require('luassert').are.equal('GET /path_in_the_rule?user_key=uk&a_param=a_value HTTP/1.1', - ngx.var.request) - ngx.say('yay, api backend'); - } + echo $request; } --- request GET /some_path?user_key=uk&a_param=a_value --- response_body -yay, api backend +GET /path_in_the_rule/some_path?user_key=uk&a_param=a_value HTTP/1.1 --- error_code: 200 --- no_error_log [error] diff --git a/t/http-proxy.t b/t/http-proxy.t index 686802a93..b283b66e3 100644 --- a/t/http-proxy.t +++ b/t/http-proxy.t @@ -381,7 +381,7 @@ apicast cache write key: 42:value:usage%5Bhits%5D=2, ttl: nil, context: ngx.time { "backend_version": 1, "proxy": { - "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT", "proxy_rules": [ { "pattern": "/test", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } ] @@ -420,7 +420,7 @@ proxy request: GET http://127.0.0.1:$TEST_NGINX_SERVER_PORT/test?user_key=value { "backend_version": 1, "proxy": { - "api_backend": "https://test:$TEST_NGINX_RANDOM_PORT/", + "api_backend": "https://test:$TEST_NGINX_RANDOM_PORT", "proxy_rules": [ { "pattern": "/test", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } ] @@ -685,3 +685,78 @@ proxy request: CONNECT 127.0.0.1:$TEST_NGINX_RANDOM_PORT HTTP/1.1 --- no_error_log [error] --- user_files fixture=tls.pl eval + + + +=== TEST 14: upstream API connection uses proxy and correctly routes to a path. +--- env eval +("http_proxy" => $ENV{TEST_NGINX_HTTP_PROXY}) +--- configuration +{ + "services": [ + { + "backend_version": 1, + "proxy": { + "api_backend": "http://test:$TEST_NGINX_SERVER_PORT/foo", + "proxy_rules": [ + { "pattern": "/test", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } + ] + } + } + ] +} +--- backend + location /transactions/authrep.xml { + content_by_lua_block { + ngx.exit(ngx.OK) + } + } +--- upstream + location / { + echo $request; + } +--- request +GET /test?user_key=value +--- response_body +GET /foo/test?user_key=value HTTP/1.1 +--- error_code: 200 +--- error_log env +proxy request: GET http://127.0.0.1:$TEST_NGINX_SERVER_PORT/foo/test?user_key=value HTTP/1.1 +--- no_error_log +[error] + + + +=== TEST 15: Upstream Policy connection uses proxy and correctly routes to a path. +--- env eval +("http_proxy" => $ENV{TEST_NGINX_HTTP_PROXY}) +--- configuration +{ + "services": [ + { + "proxy": { + "policy_chain": [ + { "name": "apicast.policy.upstream", + "configuration": + { + "rules": [ { "regex": "/test", "url": "http://test/foo" } ] + } + } + ] + } + } + ] +} +--- upstream + location / { + echo $request; + } +--- request +GET /test?user_key=value +--- response_body +GET /foo/test?user_key=value HTTP/1.1 +--- error_code: 200 +--- error_log env +proxy request: GET http://127.0.0.1:$TEST_NGINX_SERVER_PORT/foo/test?user_key=value HTTP/1.1 +--- no_error_log +[error]