Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support conditional response rewrite #3577

Merged
Merged
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
43 changes: 42 additions & 1 deletion apisix/plugins/response-rewrite.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
-- limitations under the License.
--
local core = require("apisix.core")
local expr = require("resty.expr.v1")
local plugin_name = "response-rewrite"
local ngx = ngx
local pairs = pairs
Expand Down Expand Up @@ -43,7 +44,16 @@ local schema = {
type = "integer",
minimum = 200,
maximum = 598,
}
},
vars = {
type = "array",
items = {
description = "Nginx builtin variable name and value",
type = "array",
maxItems = 4,
minItems = 2,
},
},
},
minProperties = 1,
additionalProperties = false,
Expand All @@ -57,6 +67,21 @@ local _M = {
schema = schema,
}

local function vars_matched(conf, ctx)
if not conf.vars then
return true
end

if not conf.response_expr then
local response_expr, _ = expr.new(conf.vars)
conf.response_expr = response_expr
end

local match_result = conf.response_expr:eval(ctx.var)

return match_result
end


function _M.check_schema(conf)
local ok, err = core.schema.check(schema, conf)
Expand Down Expand Up @@ -87,13 +112,24 @@ function _M.check_schema(conf)
end
end

if conf.vars then
local ok, err = expr.new(conf.vars)
if not ok then
return false, "failed to validate the 'vars' expression: " .. err
end
end

return true
end


do

function _M.body_filter(conf, ctx)
if not ctx.reponse_rewrite_matched then
return
end

if conf.body then

if conf.body_base64 then
Expand All @@ -107,6 +143,11 @@ function _M.body_filter(conf, ctx)
end

function _M.header_filter(conf, ctx)
ctx.reponse_rewrite_matched = vars_matched(conf, ctx)
if not ctx.reponse_rewrite_matched then
return
end

if conf.status_code then
ngx.status = conf.status_code
end
Expand Down
8 changes: 6 additions & 2 deletions docs/en/latest/plugins/response-rewrite.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ response rewrite plugin, rewrite the content returned by the upstream as well as
| 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. |
| 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 |

## How To Enable

Expand All @@ -63,7 +64,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1
"headers": {
"X-Server-id": 3,
"X-Server-status": "on"
}
},
"vars":[
[ "status","==","200" ]
]
}
},
"upstream": {
Expand All @@ -83,7 +87,7 @@ Testing based on the above examples :
curl -X GET -i http://127.0.0.1:9080/test/index.html
```

It will output like below,no matter what kind of content from upstream.
It will output like below,no matter what kind of content from upstream, the `vars` will make sure that only rewrite response that http status is `200`.

```

Expand Down
8 changes: 6 additions & 2 deletions docs/zh/latest/plugins/response-rewrite.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ title: response-rewrite
| body | string | 可选 | | | 修改上游返回的 `body` 内容,如果设置了新内容,header 里面的 content-length 字段也会被去掉 |
| body_base64 | boolean | 可选 | false | | 描述 `body` 字段是否需要 base64 解码之后再返回给客户端,用在某些图片和 Protobuffer 场景 |
| headers | object | 可选 | | | 返回给客户端的 `headers`,这里可以设置多个。头信息如果存在将重写,不存在则添加。想要删除某个 header 的话,把对应的值设置为空字符串即可 |
| vars | array[] | 可选 | | | `vars` 是一个表达式列表,只有满足条件的请求和响应才会修改 body 和 header 信息,来自 [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list)。如果 `vars` 字段为空,那么所有的重写动作都会被无条件的执行。 |

## 示例

Expand All @@ -64,7 +65,10 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1
"headers": {
"X-Server-id": 3,
"X-Server-status": "on"
}
},
"vars":[
[ "status","==","200" ]
]
}
},
"upstream": {
Expand All @@ -84,7 +88,7 @@ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f1
curl -X GET -i http://127.0.0.1:9080/test/index.html
```

如果看到返回的头部信息和内容都被修改了,即表示 `response rewrite` 插件生效了。
如果看到返回的头部信息和内容都被修改了,即表示 `response rewrite` 插件生效了,`vars` 将确保仅覆盖状态为 200 的响应

```shell
HTTP/1.1 200 OK
Expand Down
120 changes: 120 additions & 0 deletions t/plugin/response-rewrite.t
Original file line number Diff line number Diff line change
Expand Up @@ -509,3 +509,123 @@ GET /t
additional properties forbidden, found invalid_att
--- no_error_log
[error]



=== TEST 17: add validate vars
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.response-rewrite")
local ok, err = plugin.check_schema({
vars = {
{"status","==",200}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need a case fails to validate, as the successful case is already covered later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

}
})

if not ok then
ngx.say(err)
else
ngx.say("done")
end
}
}
--- request
GET /t
--- response_body
done
--- no_error_log
[error]



=== TEST 18: add plugin with invalidate vars
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.response-rewrite")
local ok, err = plugin.check_schema({
vars = {
{}
}
})

if not ok then
ngx.say(err)
else
ngx.say("done")
end
}
}
--- request
GET /t
--- response_body
property "vars" validation failed: failed to validate item 1: expect array to have at least 2 items
--- no_error_log
[error]



=== TEST 19: set route with http status code as expr
--- 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,
[[{
"plugins": {
"response-rewrite": {
"body": "new body3\n",
"status_code": 403,
"vars": [
["status","==",500]
]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uris": ["/server_error","/hello"]
}]]
)

if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]



=== TEST 20: check http code that matchs http_status
--- request
GET /server_error
--- response_body
new body3
--- error_code eval
403
--- error_log
500 Internal Server Error



=== TEST 21: check http code that not matchs http_status
--- request
GET /hello
--- response_body
hello world
--- error_code eval
200
--- no_error_log
[error]