From 04a1cb8909b0deeb1d56559383567aabf00b3707 Mon Sep 17 00:00:00 2001 From: Umputun Date: Sun, 18 Feb 2018 14:53:51 -0600 Subject: [PATCH] add rewrite middleware --- Gopkg.lock | 8 +- app/rest/api/middleware.go | 32 +++++ vendor/github.com/pkg4go/rewrite/.gitignore | 1 + vendor/github.com/pkg4go/rewrite/.travis.yml | 5 + vendor/github.com/pkg4go/rewrite/Readme.md | 35 +++++ vendor/github.com/pkg4go/rewrite/rewrite.go | 134 +++++++++++++++++++ 6 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/pkg4go/rewrite/.gitignore create mode 100644 vendor/github.com/pkg4go/rewrite/.travis.yml create mode 100644 vendor/github.com/pkg4go/rewrite/Readme.md create mode 100644 vendor/github.com/pkg4go/rewrite/rewrite.go diff --git a/Gopkg.lock b/Gopkg.lock index 2110ee1f..e77dfede 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -111,6 +111,12 @@ revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" +[[projects]] + branch = "master" + name = "github.com/pkg4go/rewrite" + packages = ["."] + revision = "7a613570d084fe7506c069d2cd8216d4795bd7e0" + [[projects]] name = "github.com/pmezard/go-difflib" packages = ["difflib"] @@ -195,6 +201,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "64f6f97f932d2714aa7eb0b94e94bb555feb5ab9c33673a7731103f4cee9157f" + inputs-digest = "03e0673f56f51384e8528f760cd44327828e5ef158b97bbbebc678c52cec9cc3" solver-name = "gps-cdcl" solver-version = 1 diff --git a/app/rest/api/middleware.go b/app/rest/api/middleware.go index 7af8d6ec..776e7201 100644 --- a/app/rest/api/middleware.go +++ b/app/rest/api/middleware.go @@ -2,6 +2,7 @@ package api import ( "bytes" + "context" "fmt" "io/ioutil" "log" @@ -14,6 +15,7 @@ import ( "time" "github.com/go-chi/chi/middleware" + "github.com/pkg4go/rewrite" "github.com/umputun/remark/app/rest" ) @@ -155,5 +157,35 @@ func Logger(flags ...LoggerFlag) func(http.Handler) http.Handler { } return f +} +// Rewrite middleware with from->to rule. Supports regex (like nginx) and prevents multiple rewrites +func Rewrite(from, to string) func(http.Handler) http.Handler { + rule, err := rewrite.NewRule(from, to) + if err != nil { + log.Printf("[WARN] can't parse rewrite rule %s - > %s, %s", from, to, err) + } + + f := func(h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + // prevent double rewrites + if ctx != nil { + if _, ok := ctx.Value(rest.ContextKey("rewrite")).(bool); ok { + h.ServeHTTP(w, r) + return + } + } + + if err == nil { + rule.Rewrite(r) + ctx = context.WithValue(ctx, rest.ContextKey("rewrite"), true) + r = r.WithContext(ctx) + } + h.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + } + return f } diff --git a/vendor/github.com/pkg4go/rewrite/.gitignore b/vendor/github.com/pkg4go/rewrite/.gitignore new file mode 100644 index 00000000..e43b0f98 --- /dev/null +++ b/vendor/github.com/pkg4go/rewrite/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/vendor/github.com/pkg4go/rewrite/.travis.yml b/vendor/github.com/pkg4go/rewrite/.travis.yml new file mode 100644 index 00000000..7960fc95 --- /dev/null +++ b/vendor/github.com/pkg4go/rewrite/.travis.yml @@ -0,0 +1,5 @@ +language: go +go: + - 1.5 + - 1.6 + - tip diff --git a/vendor/github.com/pkg4go/rewrite/Readme.md b/vendor/github.com/pkg4go/rewrite/Readme.md new file mode 100644 index 00000000..b85e55fc --- /dev/null +++ b/vendor/github.com/pkg4go/rewrite/Readme.md @@ -0,0 +1,35 @@ + +[![Build status][travis-img]][travis-url] +[![License][license-img]][license-url] +[![GoDoc][doc-img]][doc-url] + +### rewrite + +golang URL rewriting + +### Usage + +```go +import "github.com/pkg4go/rewrite" + +// ... + +handler := rewrite.NewHandler(map[string]string{ + "/a": "/b", + "/api/(.*)", "/api/v1/$1", + "/api/(.*)/actions/(.*)", "/api/v1/$1/actions/$2", + "/from/:one/to/:two", "/from/:two/to/:one", +}) + +// ... +``` + +### License +MIT + +[travis-img]: https://img.shields.io/travis/pkg4go/rewrite.svg?style=flat-square +[travis-url]: https://travis-ci.org/pkg4go/rewrite +[license-img]: https://img.shields.io/badge/license-MIT-green.svg?style=flat-square +[license-url]: http://opensource.org/licenses/MIT +[doc-img]: https://img.shields.io/badge/GoDoc-reference-blue.svg?style=flat-square +[doc-url]: http://godoc.org/github.com/pkg4go/rewrite diff --git a/vendor/github.com/pkg4go/rewrite/rewrite.go b/vendor/github.com/pkg4go/rewrite/rewrite.go new file mode 100644 index 00000000..25e10d37 --- /dev/null +++ b/vendor/github.com/pkg4go/rewrite/rewrite.go @@ -0,0 +1,134 @@ +package rewrite + +import "net/http" +import "net/url" +import "strings" +import "regexp" +import "path" +import "fmt" + +const headerField = "X-Rewrite-Original-URI" + +type Rule struct { + Pattern string + To string + *regexp.Regexp +} + +var regfmt = regexp.MustCompile(`:[^/#?()\.\\]+`) + +func NewRule(pattern, to string) (*Rule, error) { + pattern = regfmt.ReplaceAllStringFunc(pattern, func(m string) string { + return fmt.Sprintf(`(?P<%s>[^/#?]+)`, m[1:]) + }) + + reg, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + + return &Rule{ + pattern, + to, + reg, + }, nil +} + +func (r *Rule) Rewrite(req *http.Request) bool { + oriPath := req.URL.Path + + if !r.MatchString(oriPath) { + return false + } + + to := path.Clean(r.Replace(req.URL)) + + u, e := url.Parse(to) + if e != nil { + return false + } + + req.Header.Set(headerField, req.URL.RequestURI()) + + req.URL.Path = u.Path + req.URL.RawPath = u.RawPath + if u.RawQuery != "" { + req.URL.RawQuery = u.RawQuery + } + + return true +} + +func (r *Rule) Replace(u *url.URL) string { + if !hit("\\$|\\:", r.To) { + return r.To + } + + uri := u.RequestURI() + + regFrom := regexp.MustCompile(r.Pattern) + match := regFrom.FindStringSubmatchIndex(uri) + + result := regFrom.ExpandString([]byte(""), r.To, uri, match) + + str := string(result[:]) + + if hit("\\:", str) { + return r.replaceNamedParams(uri, str) + } + + return str +} + +var urlreg = regexp.MustCompile(`:[^/#?()\.\\]+|\(\?P<[a-zA-Z0-9]+>.*\)`) + +func (r *Rule) replaceNamedParams(from, to string) string { + fromMatches := r.FindStringSubmatch(from) + + if len(fromMatches) > 0 { + for i, name := range r.SubexpNames() { + if len(name) > 0 { + to = strings.Replace(to, ":"+name, fromMatches[i], -1) + } + } + } + + return to +} + +func NewHandler(rules map[string]string) RewriteHandler { + var h RewriteHandler + + for key, val := range rules { + r, e := NewRule(key, val) + if e != nil { + panic(e) + } + + h.rules = append(h.rules, r) + } + + return h +} + +type RewriteHandler struct { + rules []*Rule +} + +func (h *RewriteHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) { + for _, r := range h.rules { + ok := r.Rewrite(req) + if ok { + break + } + } +} + +func hit(pattern, str string) bool { + r, e := regexp.MatchString(pattern, str) + if e != nil { + return false + } + + return r +}