From e08d4e887a1c838d36cf62f7d26144c3f00aa90c Mon Sep 17 00:00:00 2001 From: Thibault Charbonnier Date: Mon, 18 May 2015 22:13:26 +0200 Subject: [PATCH] feat(resolver): Support for alternate Host header Proxying can now be triggered via a `Host` or a `X-Host-Override` header. Multiple values can be given to each of those headers. Fix #203 --- kong/constants.lua | 5 ++-- kong/kong.lua | 2 +- kong/resolver/access.lua | 33 ++++++++++++++---------- kong/tools/responses.lua | 4 +-- spec/integration/proxy/resolver_spec.lua | 14 ++++++---- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/kong/constants.lua b/kong/constants.lua index 8463fd2ced73..67117b707825 100644 --- a/kong/constants.lua +++ b/kong/constants.lua @@ -26,10 +26,9 @@ return { ID = "id", TIMESTAMP = "timestamp" }, + -- Non standard headers, specific to Kong HEADERS = { - SERVER = "Server", - VIA = "Via", - CONTENT_TYPE = "Content-Type", + HOST_OVERRIDE = "X-Host-Override", PROXY_TIME = "X-Kong-Proxy-Time", API_TIME = "X-Kong-Api-Time", CONSUMER_ID = "X-Consumer-ID", diff --git a/kong/kong.lua b/kong/kong.lua index dce1779c552f..5a0e1507f393 100644 --- a/kong/kong.lua +++ b/kong/kong.lua @@ -191,7 +191,7 @@ function _M.exec_plugins_header_filter() ngx.ctx.proxy_ended_at = timestamp.get_utc() -- Setting a property that will be available for every plugin if not ngx.ctx.stop_phases then - ngx.header[constants.HEADERS.VIA] = constants.NAME.."/"..constants.VERSION + ngx.header["Via"] = constants.NAME.."/"..constants.VERSION for _, plugin in ipairs(plugins) do local conf = ngx.ctx.plugin_conf[plugin.name] diff --git a/kong/resolver/access.lua b/kong/resolver/access.lua index 48377ba3758a..e2e574b24e4d 100644 --- a/kong/resolver/access.lua +++ b/kong/resolver/access.lua @@ -1,6 +1,7 @@ local url = require("socket.url") local cache = require "kong.tools.database_cache" local stringy = require "stringy" +local constants = require "kong.constants" local responses = require "kong.tools.responses" local _M = {} @@ -24,7 +25,7 @@ local function get_host_from_url(val) local port if parsed_url.port then - port = parsed_url.port + port = parsed_url.port elseif parsed_url.scheme == "https" then port = 443 end @@ -37,18 +38,24 @@ local function skip_authentication(headers) return headers["expect"] and stringy.startswith(headers["expect"], "100") end --- Retrieve the API from the Host that has been requested -function _M.execute(conf) - local hosts = ngx.req.get_headers()["host"] -- Multiple "Host" can have been requested - if type(hosts) == "string" then - hosts = { hosts } - elseif not hosts then - hosts = {} +-- Retrieve the API from the Host header that has been requested. +function _M.execute() + -- Search for a Host header in all `Host` and `X-Host-Override` headers + local hosts_headers = {} + for _, header_name in ipairs({"Host", constants.HEADERS.HOST_OVERRIDE}) do + local host = ngx.req.get_headers()[header_name] + if type(host) == "string" then -- single header + table.insert(hosts_headers, host) + elseif type(host) == "table" then -- multiple headers + for _, v in ipairs(host) do + table.insert(hosts_headers, v) + end + end end - -- Find the API - local api = nil - for _, host in ipairs(hosts) do + -- Find the API from one of the given hosts + local api + for _, host in ipairs(hosts_headers) do api = cache.get_and_set(cache.api_key(host), function() local apis, err = dao.apis:find_by_keys { public_dns = host } if err then @@ -61,13 +68,13 @@ function _M.execute(conf) end if not api then - return responses.send_HTTP_NOT_FOUND("API not found with Host: "..table.concat(hosts, ",")) + return responses.send_HTTP_NOT_FOUND("API not found with Host: "..table.concat(hosts_headers, ",")) end -- Setting the backend URL for the proxy_pass directive ngx.var.backend_url = get_backend_url(api)..ngx.var.request_uri - ngx.req.set_header("host", get_host_from_url(ngx.var.backend_url)) + ngx.req.set_header("Host", get_host_from_url(ngx.var.backend_url)) -- There are some requests whose authentication needs to be skipped if not skip_authentication(ngx.req.get_headers()) then diff --git a/kong/tools/responses.lua b/kong/tools/responses.lua index 301ce084585e..19314da8d9cf 100644 --- a/kong/tools/responses.lua +++ b/kong/tools/responses.lua @@ -54,8 +54,8 @@ local function send_response(status_code) end ngx.status = status_code - ngx.header[constants.HEADERS.CONTENT_TYPE] = "application/json; charset=utf-8" - ngx.header[constants.HEADERS.SERVER] = constants.NAME.."/"..constants.VERSION + ngx.header["Content-Type"] = "application/json; charset=utf-8" + ngx.header["Server"] = constants.NAME.."/"..constants.VERSION if type(response_default_content[status_code]) == "function" then content = response_default_content[status_code](content) diff --git a/spec/integration/proxy/resolver_spec.lua b/spec/integration/proxy/resolver_spec.lua index 0d9c7e969625..fd7463b345b8 100644 --- a/spec/integration/proxy/resolver_spec.lua +++ b/spec/integration/proxy/resolver_spec.lua @@ -33,12 +33,12 @@ describe("Resolver", function() describe("Existing API", function() - it("should return Success when the API is in Kong", function() + it("should proxy when the API is in Kong", function() local response, status = http_client.get(STUB_GET_URL, nil, { host = "test4.com"}) assert.are.equal(200, status) end) - it("should return Success when the Host header is not trimmed", function() + it("should proxy when the Host header is not trimmed", function() local response, status = http_client.get(STUB_GET_URL, nil, { host = " test4.com "}) assert.are.equal(200, status) end) @@ -57,7 +57,7 @@ describe("Resolver", function() assert.falsy(headers.via) end) - it("should return Success when the API is in Kong and one Host headers is being sent via plain TCP", function() + it("should proxy when the API is in Kong and one Host header is being sent via plain TCP", function() local parsed_url = url.parse(STUB_GET_URL) local host = parsed_url.host local port = parsed_url.port @@ -76,7 +76,7 @@ describe("Resolver", function() assert.truthy(stringy.startswith(response, "HTTP/1.1 200 OK")) end) - it("should return Success when the API is in Kong and multiple Host headers are being sent via plain TCP", function() + it("should proxy when the API is in Kong and multiple Host headers are being sent via plain TCP", function() local parsed_url = url.parse(STUB_GET_URL) local host = parsed_url.host local port = parsed_url.port @@ -95,6 +95,10 @@ describe("Resolver", function() assert.truthy(stringy.startswith(response, "HTTP/1.1 200 OK")) end) - end) + it("should proxy when the request has no Host header but the X-Override-Host header", function() + local _, status = http_client.get(STUB_GET_URL, nil, { ["X-Host-Override"] = "test4.com"}) + assert.are.equal(200, status) + end) + end) end)