You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by so...@apache.org on 2022/09/29 08:52:37 UTC
[apisix-go-plugin-runner] branch master updated: feat: response_rewrite plugin support replace body via origin res body (#109)
This is an automated email from the ASF dual-hosted git repository.
soulbird pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-go-plugin-runner.git
The following commit(s) were added to refs/heads/master by this push:
new 57208c8 feat: response_rewrite plugin support replace body via origin res body (#109)
57208c8 is described below
commit 57208c8c058c32e694d0e7fbd4fea7304a571138
Author: soulbird <zh...@outlook.com>
AuthorDate: Thu Sep 29 16:52:32 2022 +0800
feat: response_rewrite plugin support replace body via origin res body (#109)
* feat: response_rewrite plugin support replace body via origin response body
Co-authored-by: soulbird <zh...@gmail.com>
---
ci/docker-compose.yml | 8 ++-
ci/openresty/nginx.conf | 63 ++++++++++++++++++++++
cmd/go-runner/plugins/response_rewrite.go | 58 ++++++++++++++++++--
cmd/go-runner/plugins/response_rewrite_test.go | 32 +++++++++++
docs/en/latest/getting-started.md | 33 +++++++++++-
tests/e2e/plugins/plugins_response_rewrite_test.go | 56 +++++++++++++++++++
6 files changed, 241 insertions(+), 9 deletions(-)
diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml
index 40faba1..ac51d24 100644
--- a/ci/docker-compose.yml
+++ b/ci/docker-compose.yml
@@ -49,14 +49,12 @@ services:
apisix:
web:
- image: mendhak/http-https-echo
- environment:
- HTTP_PORT: 8888
- HTTPS_PORT: 9999
+ image: openresty/openresty
restart: unless-stopped
+ volumes:
+ - ./openresty/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro
ports:
- "8888:8888"
- - "9999:9999"
networks:
apisix:
diff --git a/ci/openresty/nginx.conf b/ci/openresty/nginx.conf
new file mode 100644
index 0000000..47cb4dd
--- /dev/null
+++ b/ci/openresty/nginx.conf
@@ -0,0 +1,63 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+user root;
+worker_processes auto;
+
+pcre_jit on;
+
+
+error_log logs/error.log info;
+
+pid logs/nginx.pid;
+
+
+events {
+ worker_connections 1024;
+}
+
+
+http {
+ include mime.types;
+ default_type application/octet-stream;
+
+ # fake server, only for test
+ server {
+ listen 8888;
+
+ server_tokens off;
+
+ location / {
+ content_by_lua_block {
+ ngx.say("hello world")
+ }
+
+ more_clear_headers Date;
+ }
+
+ location /echo {
+ content_by_lua_block {
+ ngx.req.read_body()
+ local hdrs = ngx.req.get_headers()
+ for k, v in pairs(hdrs) do
+ ngx.header[k] = v
+ end
+ ngx.print(ngx.req.get_body_data() or "")
+ }
+ }
+ }
+}
diff --git a/cmd/go-runner/plugins/response_rewrite.go b/cmd/go-runner/plugins/response_rewrite.go
index 0af1ba8..4f6b2e1 100644
--- a/cmd/go-runner/plugins/response_rewrite.go
+++ b/cmd/go-runner/plugins/response_rewrite.go
@@ -18,7 +18,10 @@
package plugins
import (
+ "bytes"
"encoding/json"
+ "fmt"
+ "regexp"
pkgHTTP "github.com/apache/apisix-go-plugin-runner/pkg/http"
"github.com/apache/apisix-go-plugin-runner/pkg/log"
@@ -32,6 +35,14 @@ func init() {
}
}
+type RegexFilter struct {
+ Regex string `json:"regex"`
+ Scope string `json:"scope"`
+ Replace string `json:"replace"`
+
+ regexComplied *regexp.Regexp
+}
+
// ResponseRewrite is a demo to show how to rewrite response data.
type ResponseRewrite struct {
// Embed the default plugin here,
@@ -43,6 +54,7 @@ type ResponseRewriteConf struct {
Status int `json:"status"`
Headers map[string]string `json:"headers"`
Body string `json:"body"`
+ Filters []RegexFilter `json:"filters"`
}
func (p *ResponseRewrite) Name() string {
@@ -52,7 +64,18 @@ func (p *ResponseRewrite) Name() string {
func (p *ResponseRewrite) ParseConf(in []byte) (interface{}, error) {
conf := ResponseRewriteConf{}
err := json.Unmarshal(in, &conf)
- return conf, err
+ if err != nil {
+ return nil, err
+ }
+ for i := 0; i < len(conf.Filters); i++ {
+ if reg, err := regexp.Compile(conf.Filters[i].Regex); err != nil {
+ return nil, fmt.Errorf("failed to compile regex `%s`: %v",
+ conf.Filters[i].Regex, err)
+ } else {
+ conf.Filters[i].regexComplied = reg
+ }
+ }
+ return conf, nil
}
func (p *ResponseRewrite) ResponseFilter(conf interface{}, w pkgHTTP.Response) {
@@ -68,10 +91,39 @@ func (p *ResponseRewrite) ResponseFilter(conf interface{}, w pkgHTTP.Response) {
}
}
- if len(cfg.Body) == 0 {
+ body := []byte(cfg.Body)
+ if len(cfg.Filters) > 0 {
+ originBody, err := w.ReadBody()
+ if err != nil {
+ log.Errorf("failed to read response body: ", err)
+ return
+ }
+ matched := false
+ for i := 0; i < len(cfg.Filters); i++ {
+ f := cfg.Filters[i]
+ found := f.regexComplied.Find(originBody)
+ if found != nil {
+ matched = true
+ if f.Scope == "once" {
+ originBody = bytes.Replace(originBody, found, []byte(f.Replace), 1)
+ } else if f.Scope == "global" {
+ originBody = bytes.ReplaceAll(originBody, found, []byte(f.Replace))
+ }
+ }
+ }
+ if matched {
+ body = originBody
+ goto write
+ }
+ // When configuring the Filters field, the Body field will be invalid.
+ return
+ }
+
+ if len(body) == 0 {
return
}
- _, err := w.Write([]byte(cfg.Body))
+write:
+ _, err := w.Write(body)
if err != nil {
log.Errorf("failed to write: %s", err)
}
diff --git a/cmd/go-runner/plugins/response_rewrite_test.go b/cmd/go-runner/plugins/response_rewrite_test.go
index f06f4d2..438df6c 100644
--- a/cmd/go-runner/plugins/response_rewrite_test.go
+++ b/cmd/go-runner/plugins/response_rewrite_test.go
@@ -71,3 +71,35 @@ func TestResponseRewrite_ConfEmpty(t *testing.T) {
assert.Equal(t, "Go", w.Header().Get("X-Resp-A6-Runner"))
assert.Equal(t, "", conf.(ResponseRewriteConf).Body)
}
+
+func TestResponseRewrite_ReplaceGlobal(t *testing.T) {
+ in := []byte(`{"filters":[{"regex":"world","scope":"global","replace":"golang"}]}`)
+ rr := &ResponseRewrite{}
+ conf, err := rr.ParseConf(in)
+ assert.Nil(t, err)
+ assert.Equal(t, 1, len(conf.(ResponseRewriteConf).Filters))
+
+ w := pkgHTTPTest.NewRecorder()
+ w.Code = 200
+ w.OriginBody = []byte("hello world world")
+ rr.ResponseFilter(conf, w)
+ assert.Equal(t, 200, w.StatusCode())
+ body, _ := ioutil.ReadAll(w.Body)
+ assert.Equal(t, "hello golang golang", string(body))
+}
+
+func TestResponseRewrite_ReplaceOnce(t *testing.T) {
+ in := []byte(`{"filters":[{"regex":"world","scope":"once","replace":"golang"}]}`)
+ rr := &ResponseRewrite{}
+ conf, err := rr.ParseConf(in)
+ assert.Nil(t, err)
+ assert.Equal(t, 1, len(conf.(ResponseRewriteConf).Filters))
+
+ w := pkgHTTPTest.NewRecorder()
+ w.Code = 200
+ w.OriginBody = []byte("hello world world")
+ rr.ResponseFilter(conf, w)
+ assert.Equal(t, 200, w.StatusCode())
+ body, _ := ioutil.ReadAll(w.Body)
+ assert.Equal(t, "hello golang world", string(body))
+}
diff --git a/docs/en/latest/getting-started.md b/docs/en/latest/getting-started.md
index 093de73..ec558ec 100644
--- a/docs/en/latest/getting-started.md
+++ b/docs/en/latest/getting-started.md
@@ -33,7 +33,7 @@ The following table describes the compatibility between apisix-go-plugin-runner
| apisix-go-plugin-runner | Apache APISIX |
|------------------------:|--------------------------------------:|
-| `master` | `>= 2.14.1`, `2.14.1` is recommended. |
+| `master` | `master` is recommended. |
| `0.4.0` | `>= 2.14.1`, `2.14.1` is recommended. |
| `0.3.0` | `>= 2.13.0`, `2.13.0` is recommended. |
| `0.2.0` | `>= 2.9.0`, `2.9.0` is recommended. |
@@ -148,10 +148,19 @@ at the same time by set RespHeader in `pkgHTTP.Request`.
`ResponseFilter` supports rewriting the response during the response phase, we can see an example of its use in the ResponseRewrite plugin:
```go
+type RegexFilter struct {
+ Regex string `json:"regex"`
+ Scope string `json:"scope"`
+ Replace string `json:"replace"`
+
+ regexComplied *regexp.Regexp
+}
+
type ResponseRewriteConf struct {
Status int `json:"status"`
Headers map[string]string `json:"headers"`
Body string `json:"body"`
+ Filters []RegexFilter `json:"filters"`
}
func (p *ResponseRewrite) ResponseFilter(conf interface{}, w pkgHTTP.Response) {
@@ -167,6 +176,28 @@ func (p *ResponseRewrite) ResponseFilter(conf interface{}, w pkgHTTP.Response) {
}
}
+ body := []byte(cfg.Body)
+ if len(cfg.Filters) > 0 {
+ originBody, err := w.ReadBody()
+
+ ......
+
+ for i := 0; i < len(cfg.Filters); i++ {
+ f := cfg.Filters[i]
+ found := f.regexComplied.Find(originBody)
+ if found != nil {
+ matched = true
+ if f.Scope == "once" {
+ originBody = bytes.Replace(originBody, found, []byte(f.Replace), 1)
+ } else if f.Scope == "global" {
+ originBody = bytes.ReplaceAll(originBody, found, []byte(f.Replace))
+ }
+ }
+ }
+
+ .......
+
+ }
if len(cfg.Body) == 0 {
return
}
diff --git a/tests/e2e/plugins/plugins_response_rewrite_test.go b/tests/e2e/plugins/plugins_response_rewrite_test.go
index f301d82..fbb5ecb 100644
--- a/tests/e2e/plugins/plugins_response_rewrite_test.go
+++ b/tests/e2e/plugins/plugins_response_rewrite_test.go
@@ -68,5 +68,61 @@ var _ = ginkgo.Describe("ResponseRewrite Plugin", func() {
"X-Server-Id": "9527",
},
}),
+ /*
+ {
+ ....
+ "filters": [
+ {
+ "regex": "world",
+ "scope": "global",
+ "replace": "golang"
+ },
+ {
+ "regex": "hello",
+ "scope": "once",
+ "replace": "nice"
+ }
+ ]
+ "body":"response rewrite"
+ }
+ */
+ table.Entry("Config APISIX.", tools.HttpTestCase{
+ Object: tools.GetA6CPExpect(),
+ Method: http.MethodPut,
+ Path: "/apisix/admin/routes/1",
+ Body: `{
+ "uri":"/echo",
+ "plugins":{
+ "ext-plugin-post-resp":{
+ "conf":[
+ {
+ "name":"response-rewrite",
+ "value":"{\"headers\":{\"X-Server-Id\":\"9527\"},\"filters\":[{\"regex\":\"world\",\"scope\":\"global\",\"replace\":\"golang\"},{\"regex\":\"hello\",\"scope\":\"once\",\"replace\":\"nice\"}],\"body\":\"response rewrite\"}"
+ }
+ ]
+ }
+ },
+ "upstream":{
+ "nodes":{
+ "web:8888":1
+ },
+ "type":"roundrobin"
+ }
+ }`,
+ Headers: map[string]string{"X-API-KEY": tools.GetAdminToken()},
+ ExpectStatusRange: httpexpect.Status2xx,
+ }),
+ table.Entry("Should replace response.", tools.HttpTestCase{
+ Object: tools.GetA6DPExpect(),
+ Method: http.MethodGet,
+ Path: "/echo",
+ Body: "hello hello world world",
+ ExpectBody: []string{"nice hello golang golang"},
+ ExpectStatus: http.StatusOK,
+ ExpectHeaders: map[string]string{
+ "X-Resp-A6-Runner": "Go",
+ "X-Server-Id": "9527",
+ },
+ }),
)
})