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 <[email protected]>
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 <[email protected]>
---
 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",
+                       },
+               }),
        )
 })

Reply via email to