Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 72 additions & 12 deletions apisix/plugins/proxy-rewrite.lua
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,19 @@ local schema = {
["^[^:]+$"] = {
oneOf = {
{ type = "string" },
{ type = "number" }
{ type = "number" },
{
-- multiple values for the same
-- header name, e.g. ["v1", "v2"]
type = "array",
minItems = 1,
items = {
oneOf = {
{ type = "string" },
{ type = "number" },
}
}
}
}
}
},
Expand All @@ -106,6 +118,18 @@ local schema = {
oneOf = {
{ type = "string" },
{ type = "number" },
{
-- replace the header with multiple
-- values, e.g. ["v1", "v2"]
type = "array",
minItems = 1,
items = {
oneOf = {
{ type = "string" },
{ type = "number" },
}
}
}
Comment thread
nic-6443 marked this conversation as resolved.
}
}
},
Expand Down Expand Up @@ -275,6 +299,13 @@ do
end


local function resolve_header_value(value, ctx)
local val = core.utils.resolve_var_with_captures(value,
ctx.proxy_rewrite_regex_uri_captures)
return core.utils.resolve_var(val, ctx.var)
end


function _M.rewrite(conf, ctx)
for _, name in ipairs(upstream_names) do
if conf[name] then
Expand Down Expand Up @@ -369,22 +400,51 @@ function _M.rewrite(conf, ctx)

local field_cnt = #hdr_op.add
for i = 1, field_cnt, 2 do
local val = core.utils.resolve_var_with_captures(hdr_op.add[i + 1],
ctx.proxy_rewrite_regex_uri_captures)
val = core.utils.resolve_var(val, ctx.var)
-- A nil or empty table value will cause add_header function to throw an error.
if val then
local header = hdr_op.add[i]
core.request.add_header(ctx, header, val)
local header = hdr_op.add[i]
local value = hdr_op.add[i + 1]
-- an array value adds the header once per element (multiple
-- headers with the same name); a scalar adds it once.
if type(value) == "table" then
for j = 1, #value do
local val = resolve_header_value(value[j], ctx)
-- guard nil only: add_header throws on nil, while an empty
-- string is a valid value kept to preserve the existing
-- behavior for an unresolved variable/capture.
if val then
core.request.add_header(ctx, header, val)
end
end
else
local val = resolve_header_value(value, ctx)
if val then
core.request.add_header(ctx, header, val)
end
end
end

local field_cnt = #hdr_op.set
for i = 1, field_cnt, 2 do
local val = core.utils.resolve_var_with_captures(hdr_op.set[i + 1],
ctx.proxy_rewrite_regex_uri_captures)
val = core.utils.resolve_var(val, ctx.var)
core.request.set_header(ctx, hdr_op.set[i], val)
local header = hdr_op.set[i]
local value = hdr_op.set[i + 1]
-- an array value replaces the header with multiple values in a
-- single set; a scalar sets a single value.
if type(value) == "table" then
local vals = {}
local n = 0
for j = 1, #value do
local val = resolve_header_value(value[j], ctx)
if val then
n = n + 1
vals[n] = val
end
end
if n > 0 then
core.request.set_header(ctx, header, vals)
end
else
local val = resolve_header_value(value, ctx)
core.request.set_header(ctx, header, val)
end
end

local field_cnt = #hdr_op.remove
Expand Down
38 changes: 36 additions & 2 deletions docs/en/latest/plugins/proxy-rewrite.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ The `proxy-rewrite` Plugin offers options to rewrite requests that APISIX forwar
| regex_uri | array[string] | False | | | Regular expressions used to match the URI path from client requests and compose a new Upstream URI path. When both `uri` and `regex_uri` are configured, `uri` has a higher priority. The array should contain one or more **key-value pairs**, with the key being the regular expression to match URI against and value being the new Upstream URI path. For example, with `["^/iresty/(. *)/(. *)", "/$1-$2", ^/theothers/*", "/theothers"]`, if a request is originally sent to `/iresty/hello/world`, the Plugin will rewrite the Upstream URI path to `/iresty/hello-world`; if a request is originally sent to `/theothers/hello/world`, the Plugin will rewrite the Upstream URI path to `/theothers`. |
| host | string | False | | | Set [`Host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) request header. |
| headers | object | False | | | Header actions to be executed. Can be set to objects of action verbs `add`, `remove`, and/or `set`; or an object consisting of headers to be `set`. When multiple action verbs are configured, actions are executed in the order of `add`, `remove`, and `set`. |
| headers.add | object | False | | | Headers to append to requests. If a header already present in the request, the header value will be appended. Header value could be set to a constant, one or more [NGINX variables](https://nginx.org/en/docs/http/ngx_http_core_module.html), or the matched result of `regex_uri` using variables such as `$1-$2-$3`. |
| headers.set | object | False | | | Headers to set to requests. If a header already present in the request, the header value will be overwritten. Header value could be set to a constant, one or more [NGINX variables](https://nginx.org/en/docs/http/ngx_http_core_module.html), or the matched result of `regex_uri` using variables such as `$1-$2-$3`. Should not be used to set `Host`. |
| headers.add | object | False | | | Headers to append to requests. If a header already present in the request, the header value will be appended. Header value could be set to a constant, one or more [NGINX variables](https://nginx.org/en/docs/http/ngx_http_core_module.html), or the matched result of `regex_uri` using variables such as `$1-$2-$3`. A value could also be an array of such values (e.g. `["val1", "val2"]`) to append the header multiple times, resulting in multiple headers with the same name. |
| headers.set | object | False | | | Headers to set to requests. If a header already present in the request, the header value will be overwritten. Header value could be set to a constant, one or more [NGINX variables](https://nginx.org/en/docs/http/ngx_http_core_module.html), or the matched result of `regex_uri` using variables such as `$1-$2-$3`. A value could also be an array of such values (e.g. `["val1", "val2"]`) to replace the header with multiple values (multiple headers with the same name). Should not be used to set `Host`. |
| headers.remove | array[string] | False | | | Headers to remove from requests.
| use_real_request_uri_unsafe | boolean | False | false | | If true, bypass URI normalization and allow for the full original request URI. Enabling this option is considered unsafe. |

Expand Down Expand Up @@ -227,6 +227,40 @@ You should see a response similar to the following:

Note that both headers present and the header value of `X-Api-Version` configured in the Plugin is appended by the header value passed in the request.

### Set or Append Multiple Values for the Same Header

Both `set` and `add` accept an array value to produce multiple headers with the same name. Use `set` to replace any incoming header with the listed values, or `add` to append them to the existing ones.

The following example sets two `X-Api-Version` headers on the upstream request:

```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "proxy-rewrite-route",
"methods": ["GET"],
"uri": "/",
"plugins": {
"proxy-rewrite": {
"uri": "/headers",
"headers": {
"set": {
"X-Api-Version": ["v1", "v2"]
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```

The upstream receives `X-Api-Version: v1` and `X-Api-Version: v2`. Replacing `set` with `add` keeps any `X-Api-Version` already present in the client request and appends `v1` and `v2` to it.

### Remove Existing Header

The following example demonstrates how you can remove an existing header `User-Agent`.
Expand Down
38 changes: 36 additions & 2 deletions docs/zh/latest/plugins/proxy-rewrite.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ description: proxy-rewrite 插件支持重写 APISIX 转发到上游服务的请
| regex_uri | array[string] || | | 用于匹配客户端请求的 URI 路径并组成新的上游 URI 路径的正则表达式。当同时配置 `uri``regex_uri` 时,`uri` 具有更高的优先级。该数组应包含一个或多个 **键值对**,其中键是用于匹配 URI 的正则表达式,值是新的上游 URI 路径。例如,对于 `["^/iresty/(. *)/(. *)", "/$1-$2", ^/theothers/*", "/theothers"]`,如果请求最初发送到 `/iresty/hello/world`,插件会将上游 URI 路径重写为 `/iresty/hello-world`;如果请求最初发送到 `/theothers/hello/world`,插件会将上游 URI 路径重写为 `/theothers`|
| host | string || | | 设置 [`Host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) 请求标头。|
| headers | object || | | 要执行的标头操作。可以设置为动作动词 `add``remove` 和/或 `set` 的对象;或由要 `set` 的标头组成的对象。当配置了多个动作动词时,动作将按照“添加”、“删除”和“设置”的顺序执行。|
| headers.add | object || | | 要附加到请求的标头。如果请求中已经存在标头,则会附加标头值。标头值可以设置为常量、一个或多个 [NGINX 变量](https://nginx.org/en/docs/http/ngx_http_core_module.html),或者 `regex_uri` 的匹配结果(使用变量,例如 `$1-$2-$3`)。|
| headers.set | object || | | 要设置请求的标头。如果请求中已经存在标头,则会覆盖标头值。标头值可以设置为常量、一个或多个 [NGINX 变量](https://nginx.org/en/docs/http/ngx_http_core_module.html),或者 `regex_uri` 的匹配结果(使用变量,例如 `$1-$2-$3`)。不应将其用于设置 `Host`|
| headers.add | object || | | 要附加到请求的标头。如果请求中已经存在标头,则会附加标头值。标头值可以设置为常量、一个或多个 [NGINX 变量](https://nginx.org/en/docs/http/ngx_http_core_module.html),或者 `regex_uri` 的匹配结果(使用变量,例如 `$1-$2-$3`)。标头值也可以是上述值的数组(例如 `["val1", "val2"]`),从而多次附加该标头,生成多个同名标头。|
| headers.set | object || | | 要设置请求的标头。如果请求中已经存在标头,则会覆盖标头值。标头值可以设置为常量、一个或多个 [NGINX 变量](https://nginx.org/en/docs/http/ngx_http_core_module.html),或者 `regex_uri` 的匹配结果(使用变量,例如 `$1-$2-$3`)。标头值也可以是上述值的数组(例如 `["val1", "val2"]`),从而将该标头替换为多个值(多个同名标头)。不应将其用于设置 `Host`|
| headers.remove | array[string] | 否 | | | 从请求中删除的标头。
| use_real_request_uri_unsafe | boolean || false | | 如果为 True,则绕过 URI 规范化并允许完整的原始请求 URI。启用此选项被视为不安全。|

Expand Down Expand Up @@ -227,6 +227,40 @@ curl "http://127.0.0.1:9080/" -H '"X-Api-Version": "v2"'

请注意,两个标头均存在,并且插件中配置的 `X-Api-Version` 标头值均附加在请求中传递的标头值上。

### 为同一标头设置或附加多个值

`set``add` 都支持数组值,用于生成多个同名标头。使用 `set` 将传入标头替换为列表中的多个值,或使用 `add` 在已有标头之上追加这些值。

以下示例在上游请求上设置两个 `X-Api-Version` 标头:

```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "proxy-rewrite-route",
"methods": ["GET"],
"uri": "/",
"plugins": {
"proxy-rewrite": {
"uri": "/headers",
"headers": {
"set": {
"X-Api-Version": ["v1", "v2"]
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```

上游会收到 `X-Api-Version: v1``X-Api-Version: v2`。将 `set` 替换为 `add` 则保留客户端请求中已有的 `X-Api-Version`,并在其后追加 `v1``v2`

### 删除现有标头

以下示例演示了如何删除现有标头 `User-Agent`
Expand Down
28 changes: 28 additions & 0 deletions t/lib/server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1272,4 +1272,32 @@ function _M.mock_compressed_upstream_response()
end


-- echo received request headers, emitting one line per occurrence so that
-- same-name (multi-value) headers are distinguishable from a single
-- comma-joined value. A genuine multi-value header arrives as a table from
-- ngx.req.get_headers() and is printed as repeated "name: value" lines.
function _M.plugin_proxy_rewrite_multi_header()
local headers = ngx.req.get_headers()

local keys = {}
for k in pairs(headers) do
if not builtin_hdr_ignore_list[k] then
table.insert(keys, k)
end
end
table.sort(keys)

for _, key in ipairs(keys) do
local v = headers[key]
if type(v) == "table" then
for _, item in ipairs(v) do
ngx.say(key, ": ", item)
end
else
ngx.say(key, ": ", v)
end
end
end


return _M
Loading
Loading