From 3ab1205aaa9f96e6b0b7a57702cd640be33201e8 Mon Sep 17 00:00:00 2001 From: Baptiste Mayelle Date: Tue, 20 Oct 2020 19:05:25 +0200 Subject: [PATCH] feat: replace with dynamic vars --- .traefik.yml | 6 ++++ README.md | 17 +++++++++- rewritebody.go | 41 ++++++++++++++++++++----- rewritebody_test.go | 75 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 8 deletions(-) diff --git a/.traefik.yml b/.traefik.yml index 01e155c..c9af63f 100644 --- a/.traefik.yml +++ b/.traefik.yml @@ -8,3 +8,9 @@ testData: rewrites: - regex: "bar" replacement: "foo" + - regex: "foo" + replacement: "{.Host}" + - regex: "bar" + replacement: "/.Host/" + delimiterLeft: "/" + delimiterRight: "/" diff --git a/README.md b/README.md index b9a7d11..c69382a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,10 @@ by replacing a search regex by a replacement string. To configure the `Rewrite Body` plugin you should create a [middleware](https://docs.traefik.io/middlewares/overview/) in your dynamic configuration as explained [here](https://docs.traefik.io/middlewares/overview/). The following example creates -and uses the `rewritebody` middleware plugin to replace all foo occurences by bar in the HTTP response body. +and uses the `rewritebody` middleware plugin to replace all foo occurences by bar in the HTTP response body.` + +You can replace with variables from the `http.Request` variable. For example to get the `req.Host` variable use `{.Host}` in your +replacement string. You can change the delimiter to use something different that the brackets. See examples for more information. If you want to apply some limits on the response body, you can chain this middleware plugin with the [Buffering middleware](https://docs.traefik.io/middlewares/buffering/) from Traefik. @@ -42,6 +45,18 @@ If you want to apply some limits on the response body, you can chain this middle regex = "foo" replacement = "bar" + # Rewrites all "bar" occurences by the host requested + [[http.middlewares.rewrite-foo.plugin.rewritebody.rewrites]] + regex = "bar" + replacement = "{.Host}" + + # Rewrites all "example.com" occurences by the Method requested and by changing the delimiters + [[http.middlewares.rewrite-foo.plugin.rewritebody.rewrites]] + regex = "example.com" + replacement = "/.Method/" + delimiterLeft = "/" + delimiterRight = "/" + [http.services] [http.services.my-service] [http.services.my-service.loadBalancer] diff --git a/rewritebody.go b/rewritebody.go index 06e5358..b4d17ca 100644 --- a/rewritebody.go +++ b/rewritebody.go @@ -6,6 +6,7 @@ import ( "bytes" "context" "fmt" + "html/template" "log" "net" "net/http" @@ -14,8 +15,10 @@ import ( // Rewrite holds one rewrite body configuration. type Rewrite struct { - Regex string `json:"regex,omitempty"` - Replacement string `json:"replacement,omitempty"` + Regex string `json:"regex,omitempty"` + Replacement string `json:"replacement,omitempty"` + DelimiterLeft string `json:"delimiterLeft,omitempty"` + DelimiterRight string `json:"delimiterRight,omitempty"` } // Config holds the plugin configuration. @@ -30,8 +33,10 @@ func CreateConfig() *Config { } type rewrite struct { - regex *regexp.Regexp - replacement []byte + regex *regexp.Regexp + replacement string + delimiterLeft string + delimiterRight string } type rewriteBody struct { @@ -51,9 +56,19 @@ func New(_ context.Context, next http.Handler, config *Config, name string) (htt return nil, fmt.Errorf("error compiling regex %q: %w", rewriteConfig.Regex, err) } + if rewriteConfig.DelimiterLeft == "" { + rewriteConfig.DelimiterLeft = "{" + } + + if rewriteConfig.DelimiterRight == "" { + rewriteConfig.DelimiterRight = "}" + } + rewrites[i] = rewrite{ - regex: regex, - replacement: []byte(rewriteConfig.Replacement), + regex: regex, + replacement: rewriteConfig.Replacement, + delimiterLeft: rewriteConfig.DelimiterLeft, + delimiterRight: rewriteConfig.DelimiterRight, } } @@ -86,7 +101,19 @@ func (r *rewriteBody) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } for _, rewrite := range r.rewrites { - bodyBytes = rewrite.regex.ReplaceAll(bodyBytes, rewrite.replacement) + tpl, err := template.New("replace").Delims(rewrite.delimiterLeft, rewrite.delimiterRight).Parse(rewrite.replacement) + if err != nil { + return + } + + var buf bytes.Buffer + + err = tpl.Execute(&buf, req) + if err != nil { + return + } + + bodyBytes = rewrite.regex.ReplaceAll(bodyBytes, buf.Bytes()) } if _, err := rw.Write(bodyBytes); err != nil { diff --git a/rewritebody_test.go b/rewritebody_test.go index 9b7bad4..aa7ec0a 100644 --- a/rewritebody_test.go +++ b/rewritebody_test.go @@ -84,6 +84,65 @@ func TestServeHTTP(t *testing.T) { expResBody: "bar is the new bar", expLastModified: true, }, + { + desc: "should replace foo by the req.Host var", + rewrites: []Rewrite{ + { + Regex: "foo", + Replacement: "{.Host}", + }, + }, + resBody: "foo is the new bar", + expResBody: "example.com is the new bar", + }, + { + desc: "should replace foo by the req.Proto/req.Method vars", + rewrites: []Rewrite{ + { + Regex: "foo", + Replacement: "{.ContentLength} {.Method}", + }, + }, + resBody: "foo is the new bar", + expResBody: "0 GET is the new bar", + }, + { + desc: "should replace foo by {}", + rewrites: []Rewrite{ + { + Regex: "foo", + Replacement: "{}", + DelimiterLeft: "/", + DelimiterRight: "/", + }, + }, + resBody: "foo is the new bar", + expResBody: "{} is the new bar", + }, + { + desc: "should replace foo by Host with delimiters /", + rewrites: []Rewrite{ + { + Regex: "foo", + Replacement: "/.Host/", + DelimiterLeft: "/", + DelimiterRight: "/", + }, + }, + resBody: "foo is {the} new bar", + expResBody: "example.com is {the} new bar", + }, + { + desc: "Replacement with unavailable var should return nothing", + rewrites: []Rewrite{ + { + Regex: "foo", + Replacement: "{.Toto}", + }, + }, + resBody: "foo is the new bar", + expResBody: "", + }, } for _, test := range tests { @@ -147,6 +206,22 @@ func TestNew(t *testing.T) { }, expErr: false, }, + { + desc: "should return no error when adding delimiters", + rewrites: []Rewrite{ + { + Regex: "foo", + Replacement: "bar", + DelimiterLeft: "/", + DelimiterRight: "/", + }, + { + Regex: "bar", + Replacement: "foo", + }, + }, + expErr: false, + }, { desc: "should return an error", rewrites: []Rewrite{