Skip to content

Commit

Permalink
Merge pull request #983 from 3scale/routing-policy-configurable-host-…
Browse files Browse the repository at this point in the history
…header

Routing policy: allow to modify the Host header
  • Loading branch information
davidor authored Jan 22, 2019
2 parents a82dcea + a399634 commit 432ec30
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 4 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added

- "Matches" operation that can be used when defining conditionals [PR #975](https://github.com/3scale/apicast/pull/975)
- New routing policy that selects an upstream based on the request path, a header, a query argument, or a jwt claim [PR #976](https://github.com/3scale/apicast/pull/976), [THREESCALE-1709](https://issues.jboss.org/browse/THREESCALE-1709)
- New routing policy that selects an upstream based on the request path, a header, a query argument, or a jwt claim [PR #976](https://github.com/3scale/apicast/pull/976), [PR #983](https://github.com/3scale/apicast/pull/983), [THREESCALE-1709](https://issues.jboss.org/browse/THREESCALE-1709)
- Added "last" attribute in the mapping rules. When set to true indicates that, if the rule matches, APIcast should not try to match the rules placed after this one [PR #982](https://github.com/3scale/apicast/pull/982), [THREESCALE-1344](https://issues.jboss.org/browse/THREESCALE-1344)

### Changed
Expand Down
2 changes: 1 addition & 1 deletion gateway/src/apicast/http_proxy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ function _M.request(upstream, proxy_uri)
local uri = upstream.uri

if uri.scheme == 'http' then -- rewrite the request to use http_proxy
upstream.host = uri.host -- to keep correct Host header in case we need to resolve it to IP
upstream:use_host_header(uri.host) -- to keep correct Host header in case we need to resolve it to IP
upstream.servers = resolve_servers(proxy_uri)
upstream.uri.path = absolute_url(uri)
upstream:rewrite_request()
Expand Down
30 changes: 30 additions & 0 deletions gateway/src/apicast/policy/routing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,33 @@ configuration that uses that value to route the request:
}
}
```

## Set the host used in the Host header

By default, when a request is routed, the policy sets the Host header using the
host of the URL of the rule that matched. However, it is possible to specify a
different host with the `host_header` attribute. As an example, this is a
config that specifies `some_host.com` as the host of the Host header:
```json
{
"name": "routing",
"version": "builtin",
"configuration": {
"rules": [
{
"url": "http://example.com",
"host_header": "some_host.com",
"condition": {
"operations": [
{
"match": "path",
"op": "==",
"value": "/"
}
]
}
}
]
}
}
```
4 changes: 4 additions & 0 deletions gateway/src/apicast/policy/routing/apicast-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@
"url": {
"type": "string"
},
"host_header": {
"description": "Host for the Host header. When not specified, defaults to the host of the URL.",
"type": "string"
},
"condition": {
"type": "object",
"properties": {
Expand Down
1 change: 1 addition & 0 deletions gateway/src/apicast/policy/routing/rule.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ function _M.new_from_config_rule(config_rule)

if upstream then
self.url = config_rule.url
self.host_header = config_rule.host_header
self.condition = init_condition(config_rule.condition)
return self
else
Expand Down
8 changes: 7 additions & 1 deletion gateway/src/apicast/policy/routing/upstream_selector.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ function _M.select(_, rules, context)
local cond_is_true = rule.condition:evaluate(context)

if cond_is_true then
return Upstream.new(rule.url)
local upstream = Upstream.new(rule.url)

if rule.host_header and rule.host_header ~= '' then
upstream:use_host_header(rule.host_header)
end

return upstream
end
end

Expand Down
2 changes: 1 addition & 1 deletion gateway/src/apicast/proxy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ function _M.get_upstream(service)
return nil, err
end

upstream.host = service.hostname_rewrite
upstream:use_host_header(service.hostname_rewrite)

return upstream
end
Expand Down
4 changes: 4 additions & 0 deletions gateway/src/apicast/upstream.lua
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ local function host_header(uri)
end
end

function _M:use_host_header(host)
self.host = host
end

--- Rewrite request Host header to what is provided in the argument or in the URL.
function _M:rewrite_request()
local uri = self.uri
Expand Down
32 changes: 32 additions & 0 deletions spec/policy/routing/upstream_selector_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,37 @@ describe('UpstreamSelector', function()
assert.is_nil(upstream)
end)
end)

describe('when a rule that matches has a host for the Host header', function()
describe('and it is not empty', function()
it('sets the host for the header', function()
local rule = {
url = 'http://example.com',
condition = true_condition,
host_header = 'some_host.com'
}

local upstream_selector = UpstreamSelector.new()
local upstream = upstream_selector:select({ rule }, {})

assert.equals(rule.host_header, upstream.host)
end)
end)

describe('and it is empty', function()
it('does not set the host for the header', function()
local rule = {
url = 'http://example.com',
condition = true_condition,
host_header = ''
}

local upstream_selector = UpstreamSelector.new()
local upstream = upstream_selector:select({ rule }, {})

assert.is_nil(upstream.host)
end)
end)
end)
end)
end)
14 changes: 14 additions & 0 deletions spec/upstream_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -271,4 +271,18 @@ describe('Upstream', function()
end)
end)
end)

describe('.use_host_header', function()
before_each(stub_ngx_request)

it('allows to set the host used when setting the Host Header', function()
local upstream = Upstream.new('http://example.com')
local host = 'http://another_host'

upstream:use_host_header(host)

upstream:rewrite_request()
assert.spy(ngx.req.set_header).was_called_with('Host', host)
end)
end)
end)
58 changes: 58 additions & 0 deletions t/apicast-policy-routing.t
Original file line number Diff line number Diff line change
Expand Up @@ -1337,3 +1337,61 @@ yay, api backend
--- error_code: 200
--- no_error_log
[error]
=== TEST 20: choosing a Host Header in a routing rule
--- configuration
{
"services": [
{
"id": 42,
"proxy": {
"policy_chain": [
{
"name": "apicast.policy.routing",
"configuration": {
"rules": [
{
"url": "http://test:$TEST_NGINX_SERVER_PORT",
"host_header": "example.com",
"condition": {
"operations": [
{
"match": "path",
"op": "==",
"value": "/a_path"
}
]
}
}
]
}
},
{
"name": "apicast.policy.echo"
}
]
}
}
]
}
--- upstream
# We need to put the 'host_header' of the rule here.
server_name example.com;
location /a_path {
content_by_lua_block {
-- Check that the Host header was rewritten as indicated in the policy
-- config.
local host_header = ngx.req.get_headers()['Host']
require('luassert').equals('example.com', host_header)
ngx.say('yay, api backend');
}
}
--- request
GET /a_path
--- response_body
yay, api backend
--- error_code: 200
--- no_error_log
[error]

0 comments on commit 432ec30

Please sign in to comment.