doc(admin api): route. (#593)

feature: supported new fields hosts, remote_addrs and vars for router.

fix: #592
This commit is contained in:
YuanSheng Wang 2019-09-26 17:47:08 +08:00 committed by GitHub
parent 4abb47ba98
commit 3f700c5dc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 493 additions and 47 deletions

68
doc/admin-api-cn.md Normal file
View File

@ -0,0 +1,68 @@
目录
===
* [Route](#route)
## Route
*地址*/apisix/admin/routes/{id}
*说明*Route 字面意思就是路由,通过定义一些规则来匹配客户端的请求,然后根据匹配结果加载并执行相应的
插件,并把请求转发给到指定 Upstream。
> 请求方法:
|名字 |请求 uri|请求 body|说明 |
|---------|-------------------------|--|------|
|GET |/apisix/admin/routes/{id}|无|获取资源|
|PUT |/apisix/admin/routes/{id}|{...}|根据 id 创建资源|
|POST |/apisix/admin/routes |{...}|创建资源id 由后台服务自动生成|
|DELETE |/apisix/admin/routes/{id}|无|删除资源|
|PATCH |/apisix/admin/routes/{id}/{path}|{...}|修改已有 Route 的部分内容,其他不涉及部分会原样保留。|
> 请求参数:
|名字 |可选项 |类型 |说明 |示例|
|---------|---------|----|-----------|----|
|desc |可选 |辅助 |标识路由名称、使用场景等。|客户 xxxx|
|uri |必须 |匹配规则|除了如 `/foo/bar`、`/foo/gloo` 这种全量匹配外,使用不同 [Router](architecture-design-cn.md#router) 还允许更高级匹配,更多见 [Router](architecture-design-cn.md#router)。|"/hello"|
|host |可选 |匹配规则|当前请求域名,比如 `foo.com`;也支持泛域名,比如 `*.foo.com`。|"foo.com"|
|hosts |可选 |匹配规则|列表形态的 `host`,表示允许有多个不同 `host`,匹配其中任意一个即可。|{"foo.com", "*.bar.com"}|
|remote_addr|可选 |匹配规则|客户端请求 IP 地址: `192.168.1.101`、`192.168.1.102` 以及 CIDR 格式的支持 `192.168.1.0/24`。特别的APISIX 也完整支持 IPv6 地址匹配:`::1``fe80::1`, `fe80::1/64` 等。|"192.168.1.0/24"|
|remote_addrs|可选 |匹配规则|列表形态的 `remote_addr`,表示允许有多个不同 IP 地址,符合其中任意一个即可。|{"127.0.0.1", "192.0.0.0/8", "::1"}|
|methods |可选 |匹配规则|如果为空或没有该选项,代表没有任何 `method` 限制,也可以是一个或多个的组合:`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS``CONNECT``TRACE`。|{"GET", "POST"}|
|vars |可选 |匹配规则(仅支持 `radixtree` 路由)|由一个或多个`{var, operator, val}`元素组成的列表,类似这样:`{{var, operator, val}, {var, operator, val}, ...}`。例如:`{"arg_name", "==", "json"}`,表示当前请求参数 `name``json`。这里的 `var` 与 NGINX 内部自身变量命名是保持一致,所以也可以使用 `request_uri`、`host` 等;对于 `operator` 部分,目前已支持的运算符有 `==`、`~=`、`>`和`<`,特别的对于后面两个运算符,会把结果先转换成 number 然后再做比较。|{{"arg_name", "==", "json"}, {"arg_age", ">", 18}}|
|plugins |可选 |Plugin|详见 [Plugin](architecture-design-cn.md#plugin) ||
|upstream |可选 |Upstream|启用的 Upstream 配置,详见 [Upstream](architecture-design-cn.md#upstream)||
|upstream_id|可选 |Upstream|启用的 upstream id详见 [Upstream](architecture-design-cn.md#upstream)||
|service_id|可选 |Service|绑定的 Service 配置,详见 [Service](architecture-design-cn.md#service)||
|service_protocol|可选|上游协议类型|只可以是 "grpc", "http" 二选一。|默认 "http"|
对于同一类参数比如 `host``hosts``remote_addr` 与 `remote_addrs`,是不能同时存在,二者只能选择其一。如果同时启用,接口会报错。
示例:
```shell
$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -i -d '
{
"uri": "/index.html",
"hosts": ["foo.com", "*.bar.com"],
"remote_addrs": ["127.0.0.0/8"],
"methods": ["PUT", "GET"],
"upstream": {
"type": "roundrobin",
"nodes": {
"39.97.63.215:80": 1
}
}
}'
HTTP/1.1 201 Created
Date: Sat, 31 Aug 2019 01:17:15 GMT
...
```
> 应答参数
目前是直接返回与 etcd 交互后的结果。

View File

@ -84,21 +84,7 @@ Server: APISIX web server
当我们接收到成功应答,表示该 Route 已成功创建。
#### Route 选项
除了 uri 匹配条件外,还支持更多过滤条件。
|名字 |可选项 |类型 |说明 |
|---------|---------|----|-----------|
|uri |必须 |匹配规则|除了如 `/foo/bar`、`/foo/gloo` 这种全量匹配外,使用不同 [Router](#router) 还允许更高级匹配,更多见 [Router](#router)。|
|host |可选 |匹配规则|当前请求域名,比如 `foo.com`;也支持泛域名,比如 `*.foo.com`|
|remote_addr|可选 |匹配规则|客户端请求 IP 地址,比如 `192.168.1.101`、`192.168.1.102`,也支持 CIDR 格式如 `192.168.1.0/24`。特别的APISIX 也完整支持 IPv6 地址匹配,比如:`::1``fe80::1`, `fe80::1/64` 等。|
|methods |可选 |匹配规则|如果为空或没有该选项,代表没有任何 `method` 限制也可以是一个或多个组合GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONSCONNECTTRACE。|
|plugins |可选 |Plugin|详见 [Plugin](#plugin) |
|upstream |可选 |Upstream|启用的 Upstream 配置,详见 [Upstream](#upstream)|
|upstream_id|可选 |Upstream|启用的 upstream id详见 [Upstream](#upstream)|
|service_id|可选 |Service|绑定的 Service 配置,详见 [Service](#service)|
有关 Route 的具体选项,可具体查阅 [Admin API 之 Route](admin-api-cn.md#route)。
[返回目录](#目录)

View File

@ -13,6 +13,7 @@ Reference document
* [r3](router-r3.md)
* [Stand Alone Model](stand-alone.md): Supports to load route rules from local yaml file, it is more friendly such as under the kubernetes(k8s).
* [Stream Proxy](stream-proxy.md)
* [Admin API](admin-api-cn.md)
* [Changelog](../CHANGELOG.md)
* [Code Style](../CODE_STYLE.md)
* [FAQ](../FAQ.md)

View File

@ -95,19 +95,19 @@ end
local uri_route = {
{
path = [[/apisix/admin/*]],
paths = [[/apisix/admin/*]],
methods = {"GET", "PUT", "POST", "DELETE", "PATCH"},
handler = run,
method = {"GET", "PUT", "POST", "DELETE", "PATCH"},
},
{
path = [[/apisix/admin/plugins/list]],
paths = [[/apisix/admin/plugins/list]],
methods = {"GET", "PUT", "POST", "DELETE"},
handler = get_plugins_list,
method = {"GET", "PUT", "POST", "DELETE"},
},
{
path = reload_event,
paths = reload_event,
methods = {"PUT"},
handler = post_reload_plugins,
method = {"PUT"},
},
}

View File

@ -34,6 +34,19 @@ local function check_conf(id, conf, need_id)
return nil, {error_msg = "invalid configuration: " .. err}
end
if conf.host and conf.hosts then
return nil, {error_msg = "only one of host or hosts is allowed"}
end
if conf.remote_addr and conf.remote_addrs then
return nil, {error_msg = "only one of remote_addr or remote_addrs is "
.. "allowed"}
end
if conf.host and conf.hosts then
return nil, {error_msg = "only one of host or hosts is allowed"}
end
local upstream_id = conf.upstream_id
if upstream_id then
local key = "/upstreams/" .. upstream_id

View File

@ -41,7 +41,7 @@ local function create_router(ssl_items)
local sni = ssl.value.sni:reverse()
idx = idx + 1
route_items[idx] = {
path = sni,
paths = sni,
handler = function (api_ctx)
if not api_ctx then
return

View File

@ -25,9 +25,9 @@ local function create_radixtree_router(routes)
for _, route in ipairs(api_routes) do
if type(route) == "table" then
core.table.insert(uri_routes, {
path = route.uri,
paths = route.uris or route.uri,
methods = route.methods,
handler = route.handler,
method = route.methods,
})
end
end
@ -35,10 +35,11 @@ local function create_radixtree_router(routes)
for _, route in ipairs(routes) do
if type(route) == "table" then
core.table.insert(uri_routes, {
path = route.value.uri,
method = route.value.methods,
host = route.value.host,
remote_addr = route.value.remote_addr,
paths = route.value.uris or route.value.uri,
methods = route.value.methods,
hosts = route.value.hosts or route.value.host,
remote_addrs = route.value.remote_addrs
or route.value.remote_addr,
vars = route.value.vars,
handler = function (api_ctx)
api_ctx.matched_params = nil

View File

@ -179,7 +179,8 @@ local valid_ip_fmts = {
{pattern = "^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$"},
{pattern = "^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}"
.. "/[0-9]{1,2}$"},
{pattern = "^([a-f0-9]{0,4}:){0,8}(:[a-f0-9]{0,4}){0,8}$"}
{pattern = "^([a-f0-9]{0,4}:){0,8}(:[a-f0-9]{0,4}){0,8}$"},
{pattern = "^([a-f0-9]{0,4}:){0,8}(:[a-f0-9]{0,4}){0,8}/[0-9]{1,3}$"},
}
@ -254,46 +255,69 @@ local upstream_schema = {
additionalProperties = false,
}
local host = {
type = "string",
pattern = "^\\*?[0-9a-zA-Z-.]+$",
}
local remote_addr = {
description = "client IP",
type = "string",
anyOf = valid_ip_fmts,
}
local route = {
type = "object",
properties = {
uri = {type = "string", minLength = 1, maxLength = 4096},
desc = {type = "string", maxLength = 256},
methods = {
type = "array",
items = {
description = "HTTP method",
type = "string",
enum = {"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD",
"OPTIONS", "CONNECT", "TRACE"}
"OPTIONS", "CONNECT", "TRACE"}
},
uniqueItems = true,
},
service_protocol = {
enum = {"grpc", "http"}
host = host,
hosts = {
type = "array",
items = host,
uniqueItems = true,
},
desc = {type = "string", maxLength = 256},
plugins = plugins_schema,
upstream = upstream_schema,
uri = {
type = "string",
},
host = {
type = "string",
pattern = "^\\*?[0-9a-zA-Z-.]+$",
remote_addr = remote_addr,
remote_addrs = {
type = "array",
items = remote_addr,
uniqueItems = true,
},
vars = {
type = "array",
items = {
description = "Nginx builtin variable name and value",
type = "array",
items = {
maxItems = 3,
mixItems = 2,
anyOf = {
{type = "string",},
{type = "number",},
}
}
}
},
remote_addr = {
description = "client IP",
type = "string",
anyOf = valid_ip_fmts,
},
plugins = plugins_schema,
upstream = upstream_schema,
service_id = id_schema,
upstream_id = id_schema,
service_protocol = {
enum = {"grpc", "http"}
},
id = id_schema,
},
anyOf = {

View File

@ -26,7 +26,7 @@ dependencies = {
"lua-resty-cookie = 0.1.0",
"lua-resty-session = 2.24",
"opentracing-openresty = 0.1",
"lua-resty-radixtree = 1.1",
"lua-resty-radixtree = 1.2",
"lua-protobuf = 0.3.1",
"lua-resty-openidc = 1.7.2-1",
"luafilesystem = 1.7.0-2",

View File

@ -1088,3 +1088,158 @@ GET /t
passed
--- no_error_log
[error]
=== TEST 31: multiple hosts
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/index.html",
"hosts": ["foo.com", "*.bar.com"],
"upstream": {
"nodes": {
"127.0.0.1:8080": 1
},
"type": "roundrobin"
},
"desc": "new route"
}]],
[[{
"node": {
"value": {
"hosts": ["foo.com", "*.bar.com"]
}
}
}]]
)
ngx.status = code
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 32: enable hosts and host together
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/index.html",
"host": "xxx.com",
"hosts": ["foo.com", "*.bar.com"],
"upstream": {
"nodes": {
"127.0.0.1:8080": 1
},
"type": "roundrobin"
},
"desc": "new route"
}]]
)
ngx.status = code
ngx.print(body)
}
}
--- request
GET /t
--- error_code: 400
--- response_body
{"error_msg":"only one of host or hosts is allowed"}
--- no_error_log
[error]
=== TEST 33: multiple remote_addrs
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/index.html",
"remote_addrs": ["127.0.0.1", "192.0.0.1/8", "::1", "fe80::/32"],
"upstream": {
"nodes": {
"127.0.0.1:8080": 1
},
"type": "roundrobin"
},
"desc": "new route"
}]],
[[{
"node": {
"value": {
"remote_addrs": ["127.0.0.1", "192.0.0.1/8", "::1", "fe80::/32"]
}
}
}]]
)
ngx.status = code
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 34: multiple vars
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[=[{
"uri": "/index.html",
"vars": [["arg_name", "==", "json"], ["arg_age", ">", 18]],
"upstream": {
"nodes": {
"127.0.0.1:8080": 1
},
"type": "roundrobin"
},
"desc": "new route"
}]=],
[=[{
"node": {
"value": {
"vars": [["arg_name", "==", "json"], ["arg_age", ">", 18]]
}
}
}]=]
)
ngx.status = code
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]

91
t/node/hosts.t Normal file
View File

@ -0,0 +1,91 @@
use t::APISIX 'no_plan';
repeat_each(1);
log_level('info');
worker_connections(256);
no_root_location();
no_shuffle();
run_tests();
__DATA__
=== TEST 1: set route(id: 1)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"hosts": ["foo.com", "*.bar.com"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 2: /not_found
--- request
GET /not_found
--- error_code: 404
--- response_body eval
qr/404 Not Found/
--- no_error_log
[error]
=== TEST 3: /not_found
--- request
GET /hello
--- more_headers
Host: not_found.com
--- error_code: 404
--- response_body eval
qr/404 Not Found/
--- no_error_log
[error]
=== TEST 4: hit routes
--- request
GET /hello
--- more_headers
Host: foo.com
--- response_body
hello world
--- no_error_log
[error]
=== TEST 5: hit routes
--- request
GET /hello
--- more_headers
Host: www.bar.com
--- response_body
hello world
--- no_error_log
[error]

107
t/node/remote_addrs.t Normal file
View File

@ -0,0 +1,107 @@
use t::APISIX 'no_plan';
repeat_each(1);
log_level('info');
worker_connections(256);
no_root_location();
no_shuffle();
run_tests();
__DATA__
=== TEST 1: set route(id: 1)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"remote_addrs": ["192.0.0.0/8", "127.0.0.3"],
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 2: /not_found
--- http_config
real_ip_header X-Real-IP;
set_real_ip_from 127.0.0.1;
set_real_ip_from unix:;
--- request
GET /not_found
--- error_code: 404
--- response_body eval
qr/404 Not Found/
--- no_error_log
[error]
=== TEST 3: /not_found
--- http_config
real_ip_header X-Real-IP;
set_real_ip_from 127.0.0.1;
set_real_ip_from unix:;
--- request
GET /hello
--- more_headers
Host: not_found.com
--- error_code: 404
--- response_body eval
qr/404 Not Found/
--- no_error_log
[error]
=== TEST 4: hit routes
--- http_config
real_ip_header X-Real-IP;
set_real_ip_from 127.0.0.1;
set_real_ip_from unix:;
--- request
GET /hello
--- more_headers
X-Real-IP: 192.168.1.100
--- response_body
hello world
--- no_error_log
[error]
=== TEST 5: hit routes
--- http_config
real_ip_header X-Real-IP;
set_real_ip_from 127.0.0.1;
set_real_ip_from unix:;
--- request
GET /hello
--- more_headers
X-Real-IP: 127.0.0.3
--- response_body
hello world
--- no_error_log
[error]