diff --git a/apisix/balancer.lua b/apisix/balancer.lua index 3696fa41ef2f..d480356b9b47 100644 --- a/apisix/balancer.lua +++ b/apisix/balancer.lua @@ -134,10 +134,19 @@ end -- set_balancer_opts will be called in balancer phase and before any tries -local function set_balancer_opts(ctx) +local function set_balancer_opts(route, ctx) local up_conf = ctx.upstream_conf - if up_conf.timeout then - local timeout = up_conf.timeout + + -- If the matched route has timeout config, prefer to use the route config. + local timeout = nil + if route and route.value and route.value.timeout then + timeout = route.value.timeout + else + if up_conf.timeout then + timeout = up_conf.timeout + end + end + if timeout then local ok, err = set_timeouts(timeout.connect, timeout.send, timeout.read) if not ok then @@ -252,7 +261,7 @@ function _M.run(route, ctx) server = ctx.picked_server ctx.picked_server = nil - set_balancer_opts(ctx) + set_balancer_opts(route, ctx) else -- retry diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua index 0fb75739586a..d9837a7ca12d 100644 --- a/apisix/schema_def.lua +++ b/apisix/schema_def.lua @@ -111,6 +111,17 @@ local desc_def = { } +local timeout_def = { + type = "object", + properties = { + connect = {type = "number", exclusiveMinimum = 0}, + send = {type = "number", exclusiveMinimum = 0}, + read = {type = "number", exclusiveMinimum = 0}, + }, + required = {"connect", "send", "read"}, +} + + local health_checker = { type = "object", properties = { @@ -342,15 +353,7 @@ local upstream_schema = { type = "integer", minimum = 0, }, - timeout = { - type = "object", - properties = { - connect = {type = "number", exclusiveMinimum = 0}, - send = {type = "number", exclusiveMinimum = 0}, - read = {type = "number", exclusiveMinimum = 0}, - }, - required = {"connect", "send", "read"}, - }, + timeout = timeout_def, tls = { type = "object", properties = { @@ -491,6 +494,7 @@ _M.route = { minItems = 1, uniqueItems = true, }, + timeout = timeout_def, vars = { type = "array", }, diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md index 4e85a49fdc5c..af9724409f9e 100644 --- a/docs/en/latest/admin-api.md +++ b/docs/en/latest/admin-api.md @@ -91,6 +91,7 @@ Note: When the `Admin API` is enabled, it will occupy the API prefixed with `/ap | service_id | False | Service | Binded Service configuration, see [Service](architecture-design/service.md) for more | | | plugin_config_id | False, can't be used with `script` | Plugin | Binded plugin config object, see [Plugin Config](architecture-design/plugin-config.md) for more | | | labels | False | Match Rules | Key/value pairs to specify attributes | {"version":"v2","build":"16","env":"production"} | +| timeout | False | Auxiliary | Set the upstream timeout for connecting, sending and receiving messages of the route. This option will overwrite the [timeout](#upstream) option which set in upstream configuration. | {"connect": 3, "send": 3, "read": 3} | | enable_websocket | False | Auxiliary | enable `websocket`(boolean), default `false`. | | | status | False | Auxiliary | enable this route, default `1`. | `1` to enable, `0` to disable | | create_time | False | Auxiliary | epoch timestamp in second, will be created automatically if missing | 1602883670 | @@ -117,6 +118,11 @@ Config Example: "vars": [["http_user", "==", "ios"]], # A list of one or more `[var, operator, val]` elements "upstream_id": "1", # upstream id, recommended "upstream": {}, # upstream, not recommended + "timeout": { # Set the upstream timeout for connecting, sending and receiving messages of the route. + "connect": 3, + "send": 3, + "read": 3 + }, "filter_func": "", # User-defined filtering function } ``` @@ -539,7 +545,7 @@ In addition to the basic complex equalization algorithm selection, APISIX's Upst |key |optional|This option is only valid if the `type` is `chash`. Find the corresponding node `id` according to `hash_on` and `key`. When `hash_on` is set as `vars`, `key` is the required parameter, for now, it support nginx built-in variables like `uri, server_name, server_addr, request_uri, remote_port, remote_addr, query_string, host, hostname, arg_***`, `arg_***` is arguments in the request line, [Nginx variables list](http://nginx.org/en/docs/varindex.html). When `hash_on` is set as `header`, `key` is the required parameter, and `header name` is customized. When `hash_on` is set to `cookie`, `key` is the required parameter, and `cookie name` is customized. When `hash_on` is set to `consumer`, `key` does not need to be set. In this case, the `key` adopted by the hash algorithm is the `consumer_name` authenticated. If the specified `hash_on` and `key` can not fetch values, it will be fetch `remote_addr` by default.| |checks |optional|Configure the parameters of the health check. For details, refer to [health-check](health-check.md).| |retries |optional|Pass the request to the next upstream using the underlying Nginx retry mechanism, the retry mechanism is enabled by default and set the number of retries according to the number of available backend nodes. If `retries` option is explicitly set, it will override the default value. `0` means disable retry mechanism.| -|timeout |optional| Set the timeout for connection, sending and receiving messages. | +|timeout |optional| Set the timeout for connecting, sending and receiving messages. | |name |optional|Identifies upstream names| |desc |optional|upstream usage scenarios, and more.| |pass_host |optional| `host` option when the request is sent to the upstream, can be one of [`pass`, `node`, `rewrite`], the default option is `pass`. `pass`: Pass the client's host transparently to the upstream; `node`: Use the host configured in the node of `upstream`; `rewrite`: Use the value of the configuration `upstream_host`.| @@ -577,7 +583,7 @@ This feature requires APISIX to run on [APISIX-OpenResty](../how-to-build.md#6-b { "id": "1", # id "retries": 1, # retry times - "timeout": { # Set the timeout for connection, sending and receiving messages. + "timeout": { # Set the timeout for connecting, sending and receiving messages. "connect":15, "send":15, "read":15, diff --git a/docs/zh/latest/admin-api.md b/docs/zh/latest/admin-api.md index eb43f812a7b6..d8c591aa16f4 100644 --- a/docs/zh/latest/admin-api.md +++ b/docs/zh/latest/admin-api.md @@ -92,6 +92,7 @@ Admin API 是为 Apache APISIX 服务的一组 API,我们可以将参数传递 | vars | 可选 | 匹配规则 | 由一个或多个`{var, operator, val}`元素组成的列表,类似这样:`{{var, operator, val}, {var, operator, val}, ...}}`。例如:`{"arg_name", "==", "json"}`,表示当前请求参数 `name` 是 `json`。这里的 `var` 与 Nginx 内部自身变量命名是保持一致,所以也可以使用 `request_uri`、`host` 等。更多细节请参考[lua-resty-expr](https://github.com/api7/lua-resty-expr) | {{"arg_name", "==", "json"}, {"arg_age", ">", 18}} | | filter_func | 可选 | 匹配规则 | 用户自定义的过滤函数。可以使用它来实现特殊场景的匹配要求实现。该函数默认接受一个名为 vars 的输入参数,可以用它来获取 Nginx 变量。 | function(vars) return vars["arg_name"] == "json" end | | labels | 可选 | 匹配规则 | 标识附加属性的键值对 | {"version":"v2","build":"16","env":"production"} | +| timeout | 可选 | 辅助 | 为 route 设置 upstream 的连接、发送消息、接收消息的超时时间。这个配置将会覆盖在 upstream 中 配置的 [timeout](#upstream) 选项 | {"connect": 3, "send": 3, "read": 3} | | enable_websocket | 可选 | 辅助 | 是否启用 `websocket`(boolean), 缺省 `false`. | | | status | 可选 | 辅助 | 是否启用此路由, 缺省 `1`。 | `1` 表示启用,`0` 表示禁用 | | create_time | 可选 | 辅助 | 单位为秒的 epoch 时间戳,如果不指定则自动创建 | 1602883670 | @@ -118,6 +119,11 @@ route 对象 json 配置内容: "vars": [["http_user", "==", "ios"]], # 由一个或多个 [var, operator, val] 元素组成的列表 "upstream_id": "1", # upstream 对象在 etcd 中的 id ,建议使用此值 "upstream": {}, # upstream 信息对象,建议尽量不要使用 + "timeout": { # 为 route 设置 upstream 的连接、发送消息、接收消息的超时时间。 + "connect": 3, + "send": 3, + "read": 3 + }, "filter_func": "", # 用户自定义的过滤函数,非必填 } ``` diff --git a/t/admin/routes2.t b/t/admin/routes2.t index 9d76c940e75c..c472ddb49099 100644 --- a/t/admin/routes2.t +++ b/t/admin/routes2.t @@ -650,3 +650,40 @@ GET /t {"error_msg":"failed to fetch plugin config info by plugin config id [not_found], response code: 404"} --- no_error_log [error] + + + +=== TEST 18: valid route with timeout +--- 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, + [[{ + "methods": ["GET"], + "upstream": { + "nodes": { + "127.0.0.1:8080": 1 + }, + "type": "roundrobin" + }, + "timeout": { + "connect": 3, + "send": 3, + "read": 3 + }, + "uri": "/index.html" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] diff --git a/t/node/timeout-upstream.t b/t/node/timeout-upstream.t index d76f4369d950..38a4a9023114 100644 --- a/t/node/timeout-upstream.t +++ b/t/node/timeout-upstream.t @@ -73,3 +73,58 @@ GET /sleep1 qr/504 Gateway Time-out/ --- error_log timed out) while reading response header from upstream + + + +=== TEST 3: set custom timeout for route(overwrite upstream timeout) +--- 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, + [[{ + "methods": ["GET"], + "timeout": { + "connect": 0.5, + "send": 0.5, + "read": 0.5 + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin", + "timeout": { + "connect": 2, + "send": 2, + "read": 2 + } + }, + "uri": "/sleep1" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 4: hit routes (timeout) +--- request +GET /sleep1 +--- error_code: 504 +--- response_body eval +qr/504 Gateway Time-out/ +--- error_log +timed out) while reading response header from upstream