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",
+			},
+		}),
 	)
 })