From 95c3c2a9519f9d568f0cde6a16967841fd9cf5bb Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 21 Aug 2020 18:09:21 +0300 Subject: [PATCH] rewrite middleware: add a feature which supports users.json to users?format=json local route redirection --- HISTORY.md | 26 ++++++++++++++++++----- _examples/routing/rewrite/main.go | 25 ++++++++++++++++++++-- _examples/routing/rewrite/redirects.yml | 28 ++++++++++++++++++------- context/context.go | 16 +++++++------- middleware/rewrite/rewrite.go | 25 +++++++++++++++------- 5 files changed, 89 insertions(+), 31 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index f4c3c6b1..25a36692 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -365,14 +365,30 @@ Other Improvements: - New [Rewrite Engine Middleware](https://github.com/kataras/iris/tree/master/middleware/rewrite). Set up redirection rules for path patterns using the syntax we all know. [Example Code](https://github.com/kataras/iris/tree/master/_examples/routing/rewrite). ```yml -# REDIRECT_CODE PATH_PATTERN TARGET_PATH_REPL -RedirectMatch: - # redirects /seo/* to /* +RedirectMatch: # REDIRECT_CODE_DIGITS | PATTERN_REGEX | TARGET_REPL + # Redirects /seo/* to /* - 301 /seo/(.*) /$1 - # redirects /docs/v12* to /docs + + # Redirects /docs/v12* to /docs - 301 /docs/v12(.*) /docs - # redirects /old(.*) to / + + # Redirects /old(.*) to / - 301 /old(.*) / + + # Redirects http or https://test.* to http or https://newtest.* + - 301 ^(http|https)://test.(.*) $1://newtest.$2 + + # Handles /*.json or .xml as *?format=json or xml, + # without redirect. See /users route. + # When Code is 0 then it does not redirect the request, + # instead it changes the request URL + # and leaves a route handle the request. + - 0 /(.*).(json|xml) /$1?format=$2 + +# Redirects root domain to www. +# Creation of a www subdomain inside the Application is unnecessary, +# all requests are handled by the root Application itself. +PrimarySubdomain: www ``` - New `TraceRoute bool` on [middleware/logger](https://github.com/kataras/iris/tree/master/middleware/logger) middleware. Displays information about the executed route. Also marks the handlers executed. Screenshot: diff --git a/_examples/routing/rewrite/main.go b/_examples/routing/rewrite/main.go index 9c8bd2c2..62bec59b 100644 --- a/_examples/routing/rewrite/main.go +++ b/_examples/routing/rewrite/main.go @@ -7,9 +7,11 @@ import ( func main() { app := iris.New() + app.Get("/", index) app.Get("/about", about) app.Get("/docs", docs) + app.Get("/users", listUsers) app.Subdomain("test").Get("/", testIndex) @@ -29,6 +31,7 @@ func main() { // http://localhost:8080/docs/v12some -> http://localhost:8080/docs // http://localhost:8080/oldsome -> http://localhost:8080 // http://localhost:8080/oldindex/random -> http://localhost:8080 + // http://localhost:8080/users.json -> http://localhost:8080/users?format=json app.Listen(":8080") } @@ -44,8 +47,24 @@ func docs(ctx iris.Context) { ctx.WriteString("Docs") } +func listUsers(ctx iris.Context) { + format := ctx.URLParamDefault("format", "text") + /* + switch format{ + case "json": + ctx.JSON(response) + case "xml": + ctx.XML(response) + // [...] + } + */ + ctx.Writef("Format: %s", format) +} + func testIndex(ctx iris.Context) { - ctx.WriteString("Test Subdomain Index (This should never be executed, redirects to newtest subdomain)") + ctx.WriteString(`Test Subdomain Index + (This should never be executed, + redirects to newtest subdomain)`) } func newTestIndex(ctx iris.Context) { @@ -63,6 +82,7 @@ rewriteOptions := rewrite.Options{ "301 /docs/v12(.*) /docs", "301 /old(.*) /", "301 ^(http|https)://test.(.*) $1://newtest.$2", + "0 /(.*).(json|xml) /$1?format=$2", }, PrimarySubdomain: "www", } @@ -77,5 +97,6 @@ app.Use(rewriteEngine.Handler) // // To make the entire application respect the redirect rules // you have to wrap the Iris Router and pass the `Rewrite` method instead -// as we did at this example. +// as we did at this example: +// app.WrapRouter(rewriteEngine.Rewrite) */ diff --git a/_examples/routing/rewrite/redirects.yml b/_examples/routing/rewrite/redirects.yml index 76764b4b..99a74171 100644 --- a/_examples/routing/rewrite/redirects.yml +++ b/_examples/routing/rewrite/redirects.yml @@ -1,12 +1,24 @@ -# REDIRECT_CODE PATH_PATTERN TARGET_PATH_REPL -RedirectMatch: - # redirects /seo/* to /* +RedirectMatch: # REDIRECT_CODE_DIGITS | PATTERN_REGEX | TARGET_REPL + # Redirects /seo/* to /* - 301 /seo/(.*) /$1 - # redirects /docs/v12* to /docs + + # Redirects /docs/v12* to /docs - 301 /docs/v12(.*) /docs - # redirects /old(.*) to / + + # Redirects /old(.*) to / - 301 /old(.*) / - # redirects http or https://test.* to http or https://newtest.* + + # Redirects http or https://test.* to http or https://newtest.* - 301 ^(http|https)://test.(.*) $1://newtest.$2 -# redirects root domain to www. -PrimarySubdomain: www + + # Handle /*.json or .xml as *?format=json or xml, + # without redirect. See /users route. + # When Code is 0 then it does not redirect the request, + # instead it changes the request URL + # and leaves a route handle the request. + - 0 /(.*).(json|xml) /$1?format=$2 + +# Redirects root domain to www. +# Creation of a www subdomain inside the Application is unnecessary, +# all requests are handled by the root Application itself. +PrimarySubdomain: www \ No newline at end of file diff --git a/context/context.go b/context/context.go index 2cd88fc2..10205f83 100644 --- a/context/context.go +++ b/context/context.go @@ -1906,14 +1906,14 @@ func (ctx *Context) AbsoluteURI(s string) string { } // Redirect sends a redirect response to the client -// to a specific url or relative path. -// accepts 2 parameters string and an optional int -// first parameter is the url to redirect -// second parameter is the http status should send, -// default is 302 (StatusFound), -// you can set it to 301 (Permant redirect) -// or 303 (StatusSeeOther) if POST method, -// or StatusTemporaryRedirect(307) if that's nessecery. +// of an absolute or relative target URL. +// It accepts 2 input arguments, a string and an optional integer. +// The first parameter is the target url to redirect. +// The second one is the HTTP status code should be sent +// among redirection response, +// If the second parameter is missing, then it defaults to 302 (StatusFound). +// It can be set to 301 (Permant redirect), StatusTemporaryRedirect(307) +// or 303 (StatusSeeOther) if POST method. func (ctx *Context) Redirect(urlToRedirect string, statusHeader ...int) { ctx.StopExecution() // get the previous status code given by the end-developer. diff --git a/middleware/rewrite/rewrite.go b/middleware/rewrite/rewrite.go index 9c690888..6ce71ce6 100644 --- a/middleware/rewrite/rewrite.go +++ b/middleware/rewrite/rewrite.go @@ -187,6 +187,18 @@ func (e *Engine) Rewrite(w http.ResponseWriter, r *http.Request, routeHandler ht return } + if rd.noRedirect { + u, err := r.URL.Parse(target) + if err != nil { + http.Error(w, err.Error(), http.StatusMisdirectedRequest) + return + } + + r.URL = u + routeHandler(w, r) + return + } + if !rd.isRelativePattern { // this performs better, no need to check query or host, // the uri already built. @@ -208,6 +220,7 @@ type redirectMatch struct { target string isRelativePattern bool + noRedirect bool } func (r *redirectMatch) matchAndReplace(src string) (string, bool) { @@ -241,20 +254,16 @@ func parseRedirectMatchLine(s string) (*redirectMatch, error) { return nil, fmt.Errorf("redirect match: status code digits: %s: %v", codeStr, err) } - if code <= 0 { - code = http.StatusMovedPermanently - } - regex := regexp.MustCompile(pattern) if regex.MatchString(target) { return nil, fmt.Errorf("redirect match: loop detected: pattern: %s vs target: %s", pattern, target) } v := &redirectMatch{ - code: code, - pattern: regex, - target: target, - + code: code, + pattern: regex, + target: target, + noRedirect: code <= 0, isRelativePattern: pattern[0] == '/', // search by path. }