diff --git a/apisix/upstream.lua b/apisix/upstream.lua index 8a376f1b9110..8dde8f2756d0 100644 --- a/apisix/upstream.lua +++ b/apisix/upstream.lua @@ -184,9 +184,14 @@ function _M.set_by_route(route, api_ctx) return 503, err end - local new_nodes, err = dis.nodes(up_conf.service_name, up_conf.discovery_args) + local service_name, err = core.utils.resolve_var(up_conf.service_name, api_ctx.var) + if not service_name or service_name == "" then + return 503, "resolve_var resolves to empty string: " .. (err or "nil") + end + + local new_nodes, err = dis.nodes(service_name, up_conf.discovery_args) if not new_nodes then - return HTTP_CODE_UPSTREAM_UNAVAILABLE, "no valid upstream node: " .. (err or "nil") + return HTTP_CODE_UPSTREAM_UNAVAILABLE, "no valid upstream node for service '" .. service_name .. "': " .. (err or "nil") end local same = upstream_util.compare_upstream_node(up_conf, new_nodes) diff --git a/docs/en/latest/discovery.md b/docs/en/latest/discovery.md index 6844ede9f87a..04125b1396e2 100644 --- a/docs/en/latest/discovery.md +++ b/docs/en/latest/discovery.md @@ -216,6 +216,35 @@ Server: APISIX web server {"node":{"value":{"uri":"\/user\/*","upstream": {"service_name": "USER-SERVICE", "type": "roundrobin", "discovery_type": "eureka"}},"createdIndex":61925,"key":"\/apisix\/routes\/1","modifiedIndex":61925}} ``` +You can also use variables in the service name. For example, to route requests based on the host header using nginx map: + +First, configure the map in your nginx configuration: + +```nginx +map $http_host $backend { + hostnames; + default service_a; + x.domain.local service_x; + y.domain.local service_y; +} +``` + +Then use the mapped variable in your route configuration: + +```shell +$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d ' +{ + "uri": "/*", + "upstream": { + "service_name": "${backend}", + "type": "roundrobin", + "discovery_type": "eureka" + } +}' +``` + +In this example, requests to `x.domain.local` will be routed to the service named "service_x", while requests to `y.domain.local` will be routed to "service_y". + Because the upstream interface URL may have conflict, usually in the gateway by prefix to distinguish: ```shell diff --git a/docs/zh/latest/discovery.md b/docs/zh/latest/discovery.md index 3aaaaaf039ff..66cf1f5ff953 100644 --- a/docs/zh/latest/discovery.md +++ b/docs/zh/latest/discovery.md @@ -221,6 +221,35 @@ Server: APISIX web server {"node":{"value":{"uri":"\/user\/*","upstream": {"service_name": "USER-SERVICE", "type": "roundrobin", "discovery_type": "eureka"}},"createdIndex":61925,"key":"\/apisix\/routes\/1","modifiedIndex":61925}} ``` +您也可以在服务名称中使用变量。例如,使用 nginx map 根据 host 头进行请求路由: + +首先,在您的 nginx 配置中配置 map: + +```nginx +map $http_host $backend { + hostnames; + default service_a; + x.domain.local service_x; + y.domain.local service_y; +} +``` + +然后在路由配置中使用映射的变量: + +```shell +$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d ' +{ + "uri": "/*", + "upstream": { + "service_name": "${backend}", + "type": "roundrobin", + "discovery_type": "eureka" + } +}' +``` + +在这个例子中,对 `x.domain.local` 的请求将被路由到名为 "service_x" 的服务,而对 `y.domain.local` 的请求将被路由到 "service_y"。 + 因为上游的接口 URL 可能会有冲突,通常会在网关通过前缀来进行区分: ```shell diff --git a/t/discovery/consul.t b/t/discovery/consul.t index 9ec87202118e..e1a63dbc91c9 100644 --- a/t/discovery/consul.t +++ b/t/discovery/consul.t @@ -781,3 +781,108 @@ location /sleep { qr// ] --- ignore_error_log + + + +=== TEST 16: test service_name as variable in route configuration +--- yaml_config eval: $::yaml_config +--- apisix_yaml +routes: + - + uri: /hello + upstream: + service_name: "${backend}" + discovery_type: consul + type: roundrobin +#END +--- http_config + map $http_host $backend { + default service_a; + } + + server { + listen 30511; + + location /hello { + content_by_lua_block { + ngx.say("server 1") + } + } + } + server { + listen 30512; + + location /hello { + content_by_lua_block { + ngx.say("server 2") + } + } + } +--- config +location /v1/agent { + proxy_pass http://127.0.0.1:8500; +} +location /sleep { + content_by_lua_block { + local args = ngx.req.get_uri_args() + local sec = args.sec or "2" + ngx.sleep(tonumber(sec)) + ngx.say("ok") + } +} +--- timeout: 6 +--- request eval +[ + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service_a1\",\"Name\":\"service_a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30511,\"Meta\":{\"service_a_version\":\"4.0\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service_a2\",\"Name\":\"service_a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30512,\"Meta\":{\"service_a_version\":\"4.0\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "GET /sleep?sec=2", + "GET /hello", +] +--- response_body_like eval +[ + qr//, + qr//, + qr/ok\n/, + qr/server [1-2]\n/, +] +--- no_error_log +[error] + + + +=== TEST 17: test empty variable in service_name +--- yaml_config eval: $::yaml_config +--- apisix_yaml +routes: + - + uri: /hello + upstream: + service_name: "${backend}" + discovery_type: consul + type: roundrobin +#END +--- http_config + map $http_host $backend { + default ""; + } +--- config + location /t { + content_by_lua_block { + local http = require "resty.http" + local httpc = http.new() + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" + local res, err = httpc:request_uri(uri, { method = "GET"}) + if err then + ngx.log(ngx.ERR, err) + ngx.status = res.status + return + end + ngx.say(res.status) + } + } +--- request +GET /t +--- response_body +503 +--- error_log +resolve_var resolves to empty string