Skip to content

Commit

Permalink
feat: allow route to inherit hosts from service
Browse files Browse the repository at this point in the history
Signed-off-by: spacewander <[email protected]>
  • Loading branch information
spacewander committed Sep 3, 2021
1 parent 6c6ad38 commit 035899f
Show file tree
Hide file tree
Showing 12 changed files with 614 additions and 15 deletions.
15 changes: 14 additions & 1 deletion apisix/http/route.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
local require = require
local radixtree = require("resty.radixtree")
local router = require("apisix.utils.router")
local service_fetch = require("apisix.http.service").get
local core = require("apisix.core")
local expr = require("resty.expr.v1")
local plugin_checker = require("apisix.plugin").plugin_checker
Expand Down Expand Up @@ -56,13 +57,25 @@ function _M.create_radixtree_uri_router(routes, uri_routes, with_parameter)
filter_fun = filter_fun()
end

local hosts = route.value.hosts or route.value.host
if not hosts and route.value.service_id then
local service = service_fetch(route.value.service_id)
if not service then
core.log.error("failed to fetch service configuration by ",
"id: ", route.value.service_id)
-- we keep the behavior that missing service won't affect the route matching
else
hosts = service.value.hosts
end
end

core.log.info("insert uri route: ",
core.json.delay_encode(route.value, true))
core.table.insert(uri_routes, {
paths = route.value.uris or route.value.uri,
methods = route.value.methods,
priority = route.value.priority,
hosts = route.value.hosts or route.value.host,
hosts = hosts,
remote_addrs = route.value.remote_addrs
or route.value.remote_addr,
vars = route.value.vars,
Expand Down
32 changes: 27 additions & 5 deletions apisix/http/router/radixtree_host_uri.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@
local require = require
local router = require("apisix.utils.router")
local core = require("apisix.core")
local get_services = require("apisix.http.service").services
local service_fetch = require("apisix.http.service").get
local ipairs = ipairs
local type = type
local tab_insert = table.insert
local loadstring = loadstring
local pairs = pairs
local cached_version
local cached_router_version
local cached_service_version
local host_router
local only_uri_router

Expand All @@ -49,7 +52,21 @@ local function push_host_router(route, host_routes, only_uri_routes)
filter_fun = filter_fun()
end

local hosts = route.value.hosts or {route.value.host}
local hosts = route.value.hosts
if not hosts then
if route.value.host then
hosts = {route.value.host}
elseif route.value.service_id then
local service = service_fetch(route.value.service_id)
if not service then
core.log.error("failed to fetch service configuration by ",
"id: ", route.value.service_id)
-- we keep the behavior that missing service won't affect the route matching
else
hosts = service.value.hosts
end
end
end

local radixtree_route = {
paths = route.value.uris or route.value.uri,
Expand All @@ -66,7 +83,7 @@ local function push_host_router(route, host_routes, only_uri_routes)
end
}

if #hosts == 0 then
if hosts == nil then
core.table.insert(only_uri_routes, radixtree_route)
return
end
Expand Down Expand Up @@ -124,11 +141,16 @@ end
local match_opts = {}
function _M.match(api_ctx)
local user_routes = _M.user_routes
if not cached_version or cached_version ~= user_routes.conf_version then
local _, service_version = get_services()
if not cached_router_version or cached_router_version ~= user_routes.conf_version
or not cached_service_version or cached_service_version ~= service_version
then
create_radixtree_router(user_routes.values)
cached_version = user_routes.conf_version
cached_router_version = user_routes.conf_version
cached_service_version = service_version
end


core.table.clear(match_opts)
match_opts.method = api_ctx.var.request_method
match_opts.remote_addr = api_ctx.var.remote_addr
Expand Down
12 changes: 9 additions & 3 deletions apisix/http/router/radixtree_uri.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
local require = require
local core = require("apisix.core")
local base_router = require("apisix.http.route")
local cached_version
local get_services = require("apisix.http.service").services
local cached_router_version
local cached_service_version


local _M = {version = 0.2}
Expand All @@ -28,10 +30,14 @@ local _M = {version = 0.2}
local match_opts = {}
function _M.match(api_ctx)
local user_routes = _M.user_routes
if not cached_version or cached_version ~= user_routes.conf_version then
local _, service_version = get_services()
if not cached_router_version or cached_router_version ~= user_routes.conf_version
or not cached_service_version or cached_service_version ~= service_version
then
uri_router = base_router.create_radixtree_uri_router(user_routes.values,
uri_routes, false)
cached_version = user_routes.conf_version
cached_router_version = user_routes.conf_version
cached_service_version = service_version
end

if not uri_router then
Expand Down
12 changes: 9 additions & 3 deletions apisix/http/router/radixtree_uri_with_parameter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
local require = require
local core = require("apisix.core")
local base_router = require("apisix.http.route")
local cached_version
local get_services = require("apisix.http.service").services
local cached_router_version
local cached_service_version


local _M = {}
Expand All @@ -28,10 +30,14 @@ local _M = {}
local match_opts = {}
function _M.match(api_ctx)
local user_routes = _M.user_routes
if not cached_version or cached_version ~= user_routes.conf_version then
local _, service_version = get_services()
if not cached_router_version or cached_router_version ~= user_routes.conf_version
or not cached_service_version or cached_service_version ~= service_version
then
uri_router = base_router.create_radixtree_uri_router(user_routes.values,
uri_routes, true)
cached_version = user_routes.conf_version
cached_router_version = user_routes.conf_version
cached_service_version = service_version
end

if not uri_router then
Expand Down
7 changes: 7 additions & 0 deletions apisix/plugin.lua
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,13 @@ local function merge_service_route(service_conf, route_conf)
new_conf.value.name = nil
end

if route_conf.value.hosts then
new_conf.value.hosts = route_conf.value.hosts
end
if not new_conf.value.hosts and route_conf.value.host then
new_conf.value.host = route_conf.value.host
end

-- core.log.info("merged conf : ", core.json.delay_encode(new_conf))
return new_conf
end
Expand Down
7 changes: 6 additions & 1 deletion apisix/schema_def.lua
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,12 @@ _M.service = {
description = "enable websocket for request",
type = "boolean",
},

hosts = {
type = "array",
items = host_def,
minItems = 1,
uniqueItems = true,
},
},
}

Expand Down
4 changes: 3 additions & 1 deletion docs/en/latest/admin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,13 @@ Return response from etcd currently.
| desc | False | Auxiliary | service usage scenarios, and more. | service xxxx |
| labels | False | Match Rules | Key/value pairs to specify attributes | {"version":"v2","build":"16","env":"production"} |
| enable_websocket | False | Auxiliary | enable `websocket`(boolean), default `false`. | |
| hosts | False | Match Rules | The `host` in the form of a non-empty list means that multiple different hosts are allowed, and match any one of them.| ["foo.com", "*.bar.com"] |
| create_time | False | Auxiliary | epoch timestamp in second, will be created automatically if missing | 1602883670 |
| update_time | False | Auxiliary | epoch timestamp in second, will be created automatically if missing | 1602883670 |

Config Example:

```shell
```json
{
"id": "1", # id
"plugins": {}, # Bound plugin
Expand All @@ -337,6 +338,7 @@ Config Example:
"name": "service-test",
"desc": "hello world",
"enable_websocket": true,
"hosts": ["foo.com"]
}
```

Expand Down
4 changes: 3 additions & 1 deletion docs/zh/latest/admin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,13 @@ HTTP/1.1 200 OK
| desc | 可选 | 辅助 | 服务描述、使用场景等。 | |
| labels | 可选 | 匹配规则 | 标识附加属性的键值对 | {"version":"v2","build":"16","env":"production"} |
| enable_websocket | 可选 | 辅助 | 是否启用 `websocket`(boolean), 缺省 `false`. | |
| hosts | 可选 | 匹配规则 | 非空列表形态的 `host`,表示允许有多个不同 `host`,匹配其中任意一个即可。| ["foo.com", "\*.bar.com"] |
| create_time | 可选 | 辅助 | 单位为秒的 epoch 时间戳,如果不指定则自动创建 | 1602883670 |
| update_time | 可选 | 辅助 | 单位为秒的 epoch 时间戳,如果不指定则自动创建 | 1602883670 |

service 对象 json 配置内容:

```shell
```json
{
"id": "1", # id
"plugins": {}, # 指定 service 绑定的插件
Expand All @@ -339,6 +340,7 @@ service 对象 json 配置内容:
"name": "测试svc", # service 名称
"desc": "hello world", # service 描述
"enable_websocket": true, #启动 websocket 功能
"hosts": ["foo.com"]
}
```

Expand Down
36 changes: 36 additions & 0 deletions t/router/radixtree-host-uri2.t
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,39 @@ GET /server_port
Host: test.com
--- no_error_log
[error]
=== TEST 14: inherit hosts from services
--- yaml_config eval: $::yaml_config
--- apisix_yaml
services:
- id: 1
hosts:
- bar.com
upstreams:
- id: 1
nodes:
"127.0.0.1:1980": 1
type: roundrobin
routes:
-
service_id: 1
upstream_id: 1
uri: /hello
plugins:
proxy-rewrite:
uri: /hello1
-
upstream_id: 1
uri: /hello
priority: -1
#END
--- more_headers
Host: www.foo.com
--- request
GET /hello
--- response_body
hello world
--- no_error_log
[error]
124 changes: 124 additions & 0 deletions t/router/radixtree-host-uri3.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';

our $yaml_config = <<_EOC_;
apisix:
node_listen: 1984
router:
http: 'radixtree_host_uri'
admin_key: null
_EOC_

add_block_preprocessor(sub {
my ($block) = @_;

$block->set_value("yaml_config", $yaml_config);

if (!$block->request) {
$block->set_value("request", "GET /t");
}

if (!$block->error_log && !$block->no_error_log &&
(defined $block->error_code && $block->error_code != 502))
{
$block->set_value("no_error_log", "[error]");
}

$block;
});

run_tests();

__DATA__
=== TEST 1: change hosts in services
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
.. "/hello"
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/services/1',
ngx.HTTP_PUT,
[[{
"hosts": ["foo.com"]
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.1)
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"service_id": "1",
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local httpc = http.new()
local res, err = httpc:request_uri(uri, {headers = {Host = "foo.com"}})
if not res then
ngx.say(err)
return
end
ngx.print(res.body)
local code, body = t('/apisix/admin/services/1',
ngx.HTTP_PUT,
[[{
"hosts": ["bar.com"]
}]]
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
ngx.sleep(0.1)
local httpc = http.new()
local res, err = httpc:request_uri(uri, {headers = {Host = "foo.com"}})
if not res then
ngx.say(err)
return
end
ngx.say(res.status)
}
}
--- response_body
hello world
404
Loading

0 comments on commit 035899f

Please sign in to comment.