Skip to content

Commit

Permalink
feat(response-rewrite): support filters
Browse files Browse the repository at this point in the history
* match pattern can be regex or fixed string
* match mode can be once or global, default once
* ngx.re.sub(gsub) default options jo

Signed-off-by: kwanhur <huang_hua2012@163.com>
  • Loading branch information
kwanhur committed Mar 30, 2022
1 parent c46fe49 commit 5bcf2f5
Show file tree
Hide file tree
Showing 4 changed files with 621 additions and 16 deletions.
76 changes: 74 additions & 2 deletions apisix/plugins/response-rewrite.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
--
local core = require("apisix.core")
local expr = require("resty.expr.v1")
local re_compile = require("resty.core.regex").re_match_compile
local plugin_name = "response-rewrite"
local ngx = ngx
local re_sub = ngx.re.sub
local re_gsub = ngx.re.gsub
local pairs = pairs
local ipairs = ipairs
local type = type


Expand Down Expand Up @@ -48,6 +52,39 @@ local schema = {
vars = {
type = "array",
},
filters = {
description = "a group of filters that modify response body\
by replacing one specified string by another",
type = "array",
items = {
description = "filter that modifies response body",
type = "object",
properties = {
regex = {
description = "match pattern on response body",
type = "string",
default = "",
},
scope = {
description = "regex substitution range",
type = "string",
enum = {"once", "global"},
default = "once",
},
replace = {
description = "regex substitution content",
type = "string",
default = "",
},
options = {
description = "regex options",
type = "string",
default = "jo",
}
},
},
},
oneOf = {"body", "filters"},
},
minProperties = 1,
}
Expand Down Expand Up @@ -115,6 +152,23 @@ function _M.check_schema(conf)
end
end

if conf.filters then
for _, filter in ipairs(conf.filters) do
for field, value in pairs(filter) do
if type(field) ~= 'string' then
return false, 'invalid type as filter field'
end
if field ~= "replace" and value == "" then
return false, 'invalid value as filter field ' .. field
end
end
local ok, err = re_compile(filter.regex, filter.options)
if not ok then
return false, err
end
end
end

return true
end

Expand All @@ -126,7 +180,24 @@ function _M.body_filter(conf, ctx)
return
end

if conf.body then
if conf.filters then

local body = core.response.hold_body_chunk(ctx)
if not body then
return
end

for _, filter in ipairs(conf.filters) do
if filter.scope == "once" then
body = re_sub(body, filter.regex, filter.replace, filter.options)
else
body = re_gsub(body, filter.regex, filter.replace, filter.options)
end
end

ngx.arg[1] = body

elseif conf.body then

if conf.body_base64 then
ngx.arg[1] = ngx.decode_base64(conf.body)
Expand All @@ -148,7 +219,8 @@ function _M.header_filter(conf, ctx)
ngx.status = conf.status_code
end

if conf.body then
-- fixme: if filters have no any match, response body won't be modified.
if conf.filters or conf.body then
core.response.clear_header_as_body_modified()
end

Expand Down
26 changes: 19 additions & 7 deletions docs/en/latest/plugins/response-rewrite.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,25 @@ response rewrite plugin, rewrite the content returned by the upstream as well as

## Attributes

| Name | Type | Requirement | Default | Valid | Description |
| ----------- | ------- | ----------- | ------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| status_code | integer | optional | | [200, 598] | New `status code` to client, keep the original response code by default. |
| body | string | optional | | | New `body` to client, and the content-length will be reset too. |
| body_base64 | boolean | optional | false | | Identify if `body` in configuration need base64 decoded before rewrite to client. |
| headers | object | optional | | | Set the new `headers` for client, can set up multiple. If it exists already from upstream, will rewrite the header, otherwise will add the header. You can set the corresponding value to an empty string to remove a header. The value can contain Nginx variables in `$var` format, like `$remote_addr $balancer_ip` |
| vars | array[] | optional | | | A DSL to evaluate with the given ngx.var. See `vars` [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). if the `vars` is empty, then all rewrite operations will be executed unconditionally |
| Name | Type | Requirement | Default | Valid | Description |
|-------------|---------|-------------|---------|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| status_code | integer | optional | | [200, 598] | New `status code` to client, keep the original response code by default. |
| body | string | optional | | | New `body` to client, and the content-length will be reset too. |
| body_base64 | boolean | optional | false | | Identify if `body` in configuration need base64 decoded before rewrite to client. |
| headers | object | optional | | | Set the new `headers` for client, can set up multiple. If it exists already from upstream, will rewrite the header, otherwise will add the header. You can set the corresponding value to an empty string to remove a header. The value can contain Nginx variables in `$var` format, like `$remote_addr $balancer_ip`. |
| vars | array[] | optional | | | A DSL to evaluate with the given ngx.var. See `vars` [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). if the `vars` is empty, then all rewrite operations will be executed unconditionally. |
| filters | array[] | optional | | | A group of filters that modify response body by replacing one specified string by another. |

Only one of `body`, `filters` can be specified.

### Attributes of filters

| Name | Type | Requirement | Default | Valid | Description |
|---------|--------|-------------|---------|----------------|------------------------------------------------------------------------------------------------|
| regex | string | required | | | match pattern on response body. |
| scope | string | required | "one" | "one","global" | substitution range. |
| replace | string | required | | | substitution content. |
| options | string | required | "jo" | | regex options, See `[ngx.re.match](https://github.com/openresty/lua-nginx-module#ngxrematch)`. |

## How To Enable

Expand Down
26 changes: 19 additions & 7 deletions docs/zh/latest/plugins/response-rewrite.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,25 @@ title: response-rewrite

## 属性

| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
| ----------- | ------- | ------ | ------ | ---------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| status_code | integer | 可选 | | [200, 598] | 修改上游返回状态码,默认保留原始响应代码。 |
| body | string | 可选 | | | 修改上游返回的 `body` 内容,如果设置了新内容,header 里面的 content-length 字段也会被去掉 |
| body_base64 | boolean | 可选 | false | | 描述 `body` 字段是否需要 base64 解码之后再返回给客户端,用在某些图片和 Protobuffer 场景 |
| headers | object | 可选 | | | 返回给客户端的 `headers`,这里可以设置多个。头信息如果存在将重写,不存在则添加。想要删除某个 header 的话,把对应的值设置为空字符串即可。这个值能够以 `$var` 的格式包含 Nginx 变量,比如 `$remote_addr $balancer_ip` |
| vars | array[] | 可选 | | | `vars` 是一个表达式列表,只有满足条件的请求和响应才会修改 body 和 header 信息,来自 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list)。如果 `vars` 字段为空,那么所有的重写动作都会被无条件的执行。 |
| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
|-------------|---------|-----|-------|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| status_code | integer | 可选 | | [200, 598] | 修改上游返回状态码,默认保留原始响应代码。 |
| body | string | 可选 | | | 修改上游返回的 `body` 内容,如果设置了新内容,header 里面的 content-length 字段也会被去掉。 |
| body_base64 | boolean | 可选 | false | | 描述 `body` 字段是否需要 base64 解码之后再返回给客户端,用在某些图片和 Protobuffer 场景。 |
| headers | object | 可选 | | | 返回给客户端的 `headers`,这里可以设置多个。头信息如果存在将重写,不存在则添加。想要删除某个 header 的话,把对应的值设置为空字符串即可。这个值能够以 `$var` 的格式包含 Nginx 变量,比如 `$remote_addr $balancer_ip`|
| vars | array[] | 可选 | | | `vars` 是一个表达式列表,只有满足条件的请求和响应才会修改 body 和 header 信息,来自 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list)。如果 `vars` 字段为空,那么所有的重写动作都会被无条件的执行。 |
| filters | array[] | 可选 | | | 一组过滤器,采用指定字符串表达式修改响应体。 |

`body``filters`,两个只能配置其中一个。

### Attributes of filters

| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
|---------|--------|-----|-------|----------------|--------------------------------------------------------------------------------------------|
| regex | string | 必填 | | | 用于匹配响应体正则表达式。 |
| scope | string | 必填 | "one" | "one","global" | 替换范围。 |
| replace | string | 必填 | | | 替换后的内容。 |
| options | string | 必填 | "jo" | | 正则匹配有效参数,可选项见 `[ngx.re.match](https://github.com/openresty/lua-nginx-module#ngxrematch)`. |

## 示例

Expand Down
Loading

0 comments on commit 5bcf2f5

Please sign in to comment.