Skip to content

Commit

Permalink
feat: support conditional response rewrite (#3577)
Browse files Browse the repository at this point in the history
* support conditinal response rewrite

* fix code style

* fix typo

* fix typo

* fix test case

* add test case and fix code style

* drop space

* add fail test cases

* enhance docs
  • Loading branch information
lilien1010 authored Mar 4, 2021
1 parent 9885103 commit 66539b3
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 5 deletions.
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}
}
})

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]

0 comments on commit 66539b3

Please sign in to comment.