mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
implement a rewrite middleware
This commit is contained in:
parent
dddfeb9ca9
commit
12737c5b7f
15
HISTORY.md
15
HISTORY.md
|
@ -362,7 +362,20 @@ Response:
|
||||||
|
|
||||||
Other Improvements:
|
Other Improvements:
|
||||||
|
|
||||||
- New `TraceRoute bool` on [request logger](https://github.com/kataras/iris/tree/master/middleware/logger) middleware. Displays information about the executed route. Also marks the handlers executed. Screenshot:
|
- 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 /*
|
||||||
|
- 301 /seo/(.*) /$1
|
||||||
|
# redirects /docs/v12* to /docs
|
||||||
|
- 301 /docs/v12(.*) /docs
|
||||||
|
# redirects /old(.*) to /
|
||||||
|
- 301 /old(.*) /
|
||||||
|
```
|
||||||
|
|
||||||
|
- 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:
|
||||||
|
|
||||||
![logger middleware: TraceRoute screenshot](https://iris-go.com/images/github/logger-trace-route.png)
|
![logger middleware: TraceRoute screenshot](https://iris-go.com/images/github/logger-trace-route.png)
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
* [From func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)](convert-handlers/negroni-like/main.go)
|
* [From func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)](convert-handlers/negroni-like/main.go)
|
||||||
* [From http.Handler or http.HandlerFunc](convert-handlers/nethttp/main.go)
|
* [From http.Handler or http.HandlerFunc](convert-handlers/nethttp/main.go)
|
||||||
* [From func(http.HandlerFunc) http.HandlerFunc](convert-handlers/real-usecase-raven/writing-middleware/main.go)
|
* [From func(http.HandlerFunc) http.HandlerFunc](convert-handlers/real-usecase-raven/writing-middleware/main.go)
|
||||||
|
* [Rewrite Middleware](routing/rewrite/main.go)
|
||||||
* [Route State](routing/route-state/main.go)
|
* [Route State](routing/route-state/main.go)
|
||||||
* [Reverse Routing](routing/reverse/main.go)
|
* [Reverse Routing](routing/reverse/main.go)
|
||||||
* [Router Wrapper](routing/custom-wrapper/main.go)
|
* [Router Wrapper](routing/custom-wrapper/main.go)
|
||||||
|
|
62
_examples/routing/rewrite/main.go
Normal file
62
_examples/routing/rewrite/main.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kataras/iris/v12"
|
||||||
|
"github.com/kataras/iris/v12/middleware/rewrite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := iris.New()
|
||||||
|
|
||||||
|
/*
|
||||||
|
rewriteOptions := rewrite.Options{
|
||||||
|
RedirectMatch: []string{
|
||||||
|
"301 /seo/(.*) /$1",
|
||||||
|
"301 /docs/v12(.*) /docs",
|
||||||
|
"301 /old(.*) /",
|
||||||
|
}}
|
||||||
|
OR Load from file:
|
||||||
|
*/
|
||||||
|
rewriteOptions := rewrite.LoadOptions("redirects.yml")
|
||||||
|
rewriteEngine, err := rewrite.New(rewriteOptions)
|
||||||
|
if err != nil { // reports any line parse errors.
|
||||||
|
app.Logger().Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Get("/", index)
|
||||||
|
app.Get("/about", about)
|
||||||
|
app.Get("/docs", docs)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// To use it per-party, even if not route match:
|
||||||
|
app.UseRouter(rewriteEngine.Handler)
|
||||||
|
// To use it per-party when route match:
|
||||||
|
app.Use(rewriteEngine.Handler)
|
||||||
|
//
|
||||||
|
// To use it on a single route just pass it to the Get/Post method.
|
||||||
|
// To make the entire application respect the rewrite rules
|
||||||
|
// you have to wrap the Iris Router and pass the Wrapper method instead,
|
||||||
|
// (recommended way to use this middleware, right before Listen/Run):
|
||||||
|
*/
|
||||||
|
app.WrapRouter(rewriteEngine.Wrapper)
|
||||||
|
|
||||||
|
// http://localhost:8080/seo
|
||||||
|
// http://localhost:8080/about
|
||||||
|
// http://localhost:8080/docs/v12/hello
|
||||||
|
// http://localhost:8080/docs/v12some
|
||||||
|
// http://localhost:8080/oldsome
|
||||||
|
// http://localhost:8080/oldindex/random
|
||||||
|
app.Listen(":8080")
|
||||||
|
}
|
||||||
|
|
||||||
|
func index(ctx iris.Context) {
|
||||||
|
ctx.WriteString("Index")
|
||||||
|
}
|
||||||
|
|
||||||
|
func about(ctx iris.Context) {
|
||||||
|
ctx.WriteString("About")
|
||||||
|
}
|
||||||
|
|
||||||
|
func docs(ctx iris.Context) {
|
||||||
|
ctx.WriteString("Docs")
|
||||||
|
}
|
8
_examples/routing/rewrite/redirects.yml
Normal file
8
_examples/routing/rewrite/redirects.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# REDIRECT_CODE PATH_PATTERN TARGET_PATH_REPL
|
||||||
|
RedirectMatch:
|
||||||
|
# redirects /seo/* to /*
|
||||||
|
- 301 /seo/(.*) /$1
|
||||||
|
# redirects /docs/v12* to /docs
|
||||||
|
- 301 /docs/v12(.*) /docs
|
||||||
|
# redirects /old(.*) to /
|
||||||
|
- 301 /old(.*) /
|
|
@ -145,7 +145,7 @@ func (su *Supervisor) newListener() (net.Listener, error) {
|
||||||
// restarts we may want for the server.
|
// restarts we may want for the server.
|
||||||
//
|
//
|
||||||
// User still be able to call .Serve instead.
|
// User still be able to call .Serve instead.
|
||||||
// l, err := netutil.TCPKeepAlive(su.Server.Addr, su.SocketReuse)
|
// l, err := netutil.TCPKeepAlive(su.Server.Addr, su.SocketSharding)
|
||||||
l, err := netutil.TCP(su.Server.Addr, su.SocketSharding)
|
l, err := netutil.TCP(su.Server.Addr, su.SocketSharding)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -132,11 +132,11 @@ func (s *subdomainRedirectWrapper) Wrapper(w http.ResponseWriter, r *http.Reques
|
||||||
resturi := r.URL.RequestURI()
|
resturi := r.URL.RequestURI()
|
||||||
if s.isToRoot {
|
if s.isToRoot {
|
||||||
// from a specific subdomain or any subdomain to the root domain.
|
// from a specific subdomain or any subdomain to the root domain.
|
||||||
redirectAbsolute(w, r, context.GetScheme(r)+root+resturi, http.StatusMovedPermanently)
|
RedirectAbsolute(w, r, context.GetScheme(r)+root+resturi, http.StatusMovedPermanently)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// from a specific subdomain or any subdomain to a specific subdomain.
|
// from a specific subdomain or any subdomain to a specific subdomain.
|
||||||
redirectAbsolute(w, r, context.GetScheme(r)+s.to+root+resturi, http.StatusMovedPermanently)
|
RedirectAbsolute(w, r, context.GetScheme(r)+s.to+root+resturi, http.StatusMovedPermanently)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +162,7 @@ func (s *subdomainRedirectWrapper) Wrapper(w http.ResponseWriter, r *http.Reques
|
||||||
resturi := r.URL.RequestURI()
|
resturi := r.URL.RequestURI()
|
||||||
// we are not inside a subdomain, so we are in the root domain
|
// we are not inside a subdomain, so we are in the root domain
|
||||||
// and the redirect is configured to be used from root domain to a subdomain.
|
// and the redirect is configured to be used from root domain to a subdomain.
|
||||||
redirectAbsolute(w, r, context.GetScheme(r)+s.to+root+resturi, http.StatusMovedPermanently)
|
RedirectAbsolute(w, r, context.GetScheme(r)+s.to+root+resturi, http.StatusMovedPermanently)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,11 +196,20 @@ func NewSubdomainRedirectHandler(toSubdomain string) context.Handler {
|
||||||
r.Host = targetHost
|
r.Host = targetHost
|
||||||
r.URL.Host = targetHost
|
r.URL.Host = targetHost
|
||||||
urlToRedirect := r.URL.String()
|
urlToRedirect := r.URL.String()
|
||||||
redirectAbsolute(ctx.ResponseWriter(), r, urlToRedirect, http.StatusMovedPermanently)
|
RedirectAbsolute(ctx.ResponseWriter(), r, urlToRedirect, http.StatusMovedPermanently)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func redirectAbsolute(w http.ResponseWriter, r *http.Request, url string, code int) {
|
// RedirectAbsolute replies to the request with a redirect to an absolute URL.
|
||||||
|
//
|
||||||
|
// The provided code should be in the 3xx range and is usually
|
||||||
|
// StatusMovedPermanently, StatusFound or StatusSeeOther.
|
||||||
|
//
|
||||||
|
// If the Content-Type header has not been set, Redirect sets it
|
||||||
|
// to "text/html; charset=utf-8" and writes a small HTML body.
|
||||||
|
// Setting the Content-Type header to any value, including nil,
|
||||||
|
// disables that behavior.
|
||||||
|
func RedirectAbsolute(w http.ResponseWriter, r *http.Request, url string, code int) {
|
||||||
h := w.Header()
|
h := w.Header()
|
||||||
|
|
||||||
// RFC 7231 notes that a short HTML body is usually included in
|
// RFC 7231 notes that a short HTML body is usually included in
|
||||||
|
|
|
@ -3,6 +3,7 @@ Builtin Handlers
|
||||||
|
|
||||||
| Middleware | Example |
|
| Middleware | Example |
|
||||||
| -----------|-------------|
|
| -----------|-------------|
|
||||||
|
| [rewrite](rewrite) | [iris/_examples/routing/rewrite](https://github.com/kataras/iris/tree/master/_examples/routing/rewrite) |
|
||||||
| [basic authentication](basicauth) | [iris/_examples/auth/basicauth](https://github.com/kataras/iris/tree/master/_examples/auth/basicauth) |
|
| [basic authentication](basicauth) | [iris/_examples/auth/basicauth](https://github.com/kataras/iris/tree/master/_examples/auth/basicauth) |
|
||||||
| [request logger](logger) | [iris/_examples/logging/request-logger](https://github.com/kataras/iris/tree/master/_examples/logging/request-logger) |
|
| [request logger](logger) | [iris/_examples/logging/request-logger](https://github.com/kataras/iris/tree/master/_examples/logging/request-logger) |
|
||||||
| [HTTP method override](methodoverride) | [iris/middleware/methodoverride/methodoverride_test.go](https://github.com/kataras/iris/blob/master/middleware/methodoverride/methodoverride_test.go) |
|
| [HTTP method override](methodoverride) | [iris/middleware/methodoverride/methodoverride_test.go](https://github.com/kataras/iris/blob/master/middleware/methodoverride/methodoverride_test.go) |
|
||||||
|
@ -29,7 +30,7 @@ Most of the experimental handlers are ported to work with _iris_'s handler form,
|
||||||
| [new relic](https://github.com/iris-contrib/middleware/tree/master/newrelic) | Official [New Relic Go Agent](https://github.com/newrelic/go-agent) | [iris-contrib/middleware/newrelic/_example](https://github.com/iris-contrib/middleware/tree/master/newrelic/_example) |
|
| [new relic](https://github.com/iris-contrib/middleware/tree/master/newrelic) | Official [New Relic Go Agent](https://github.com/newrelic/go-agent) | [iris-contrib/middleware/newrelic/_example](https://github.com/iris-contrib/middleware/tree/master/newrelic/_example) |
|
||||||
| [prometheus](https://github.com/iris-contrib/middleware/tree/master/prometheus)| Easily create metrics endpoint for the [prometheus](http://prometheus.io) instrumentation tool | [iris-contrib/middleware/prometheus/_example](https://github.com/iris-contrib/middleware/tree/master/prometheus/_example) |
|
| [prometheus](https://github.com/iris-contrib/middleware/tree/master/prometheus)| Easily create metrics endpoint for the [prometheus](http://prometheus.io) instrumentation tool | [iris-contrib/middleware/prometheus/_example](https://github.com/iris-contrib/middleware/tree/master/prometheus/_example) |
|
||||||
| [casbin](https://github.com/iris-contrib/middleware/tree/master/casbin)| An authorization library that supports access control models like ACL, RBAC, ABAC | [iris-contrib/middleware/casbin/_examples](https://github.com/iris-contrib/middleware/tree/master/casbin/_examples) |
|
| [casbin](https://github.com/iris-contrib/middleware/tree/master/casbin)| An authorization library that supports access control models like ACL, RBAC, ABAC | [iris-contrib/middleware/casbin/_examples](https://github.com/iris-contrib/middleware/tree/master/casbin/_examples) |
|
||||||
| [raven](https://github.com/iris-contrib/middleware/tree/master/raven)| Sentry client in Go | [iris-contrib/middleware/raven/_example](https://github.com/iris-contrib/middleware/blob/master/raven/_example/main.go) |
|
| [sentry-go (ex. raven)](https://github.com/getsentry/sentry-go/tree/master/iris)| Sentry client in Go | [sentry-go/example/iris](https://github.com/getsentry/sentry-go/blob/master/example/iris/main.go) | <!-- raven was deprecated by its company, the successor is sentry-go, they contain an Iris middleware. -->
|
||||||
| [csrf](https://github.com/iris-contrib/middleware/tree/master/csrf)| Cross-Site Request Forgery Protection | [iris-contrib/middleware/csrf/_example](https://github.com/iris-contrib/middleware/blob/master/csrf/_example/main.go) |
|
| [csrf](https://github.com/iris-contrib/middleware/tree/master/csrf)| Cross-Site Request Forgery Protection | [iris-contrib/middleware/csrf/_example](https://github.com/iris-contrib/middleware/blob/master/csrf/_example/main.go) |
|
||||||
| [go-i18n](https://github.com/iris-contrib/middleware/tree/master/go-i18n)| i18n Iris Loader for nicksnyder/go-i18n | [iris-contrib/middleware/go-i18n/_example](https://github.com/iris-contrib/middleware/blob/master/go-i18n/_example/main.go) |
|
| [go-i18n](https://github.com/iris-contrib/middleware/tree/master/go-i18n)| i18n Iris Loader for nicksnyder/go-i18n | [iris-contrib/middleware/go-i18n/_example](https://github.com/iris-contrib/middleware/blob/master/go-i18n/_example/main.go) |
|
||||||
| [throttler](https://github.com/iris-contrib/middleware/tree/master/throttler)| Rate limiting access to HTTP endpoints | [iris-contrib/middleware/throttler/_example](https://github.com/iris-contrib/middleware/blob/master/throttler/_example/main.go) |
|
| [throttler](https://github.com/iris-contrib/middleware/tree/master/throttler)| Rate limiting access to HTTP endpoints | [iris-contrib/middleware/throttler/_example](https://github.com/iris-contrib/middleware/blob/master/throttler/_example/main.go) |
|
||||||
|
|
219
middleware/rewrite/rewrite.go
Normal file
219
middleware/rewrite/rewrite.go
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
package rewrite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/v12/context"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options holds the developer input to customize
|
||||||
|
// the redirects for the Rewrite Engine.
|
||||||
|
// Look the `New` package-level function.
|
||||||
|
type Options struct {
|
||||||
|
// RedirectMatch accepts a slice of lines
|
||||||
|
// of form:
|
||||||
|
// REDIRECT_CODE PATH_PATTERN TARGET_PATH
|
||||||
|
// Example: []{"301 /seo/(.*) /$1"}.
|
||||||
|
RedirectMatch []string `json:"redirectMatch" yaml:"RedirectMatch"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Engine is the rewrite engine master structure.
|
||||||
|
// Navigate through _examples/routing/rewrite for more.
|
||||||
|
type Engine struct {
|
||||||
|
redirects []*redirectMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Rewrite Engine based on "opts".
|
||||||
|
// It reports any parser error.
|
||||||
|
// See its `Handler` or `Wrapper` methods. Depending
|
||||||
|
// on the needs, select one.
|
||||||
|
func New(opts Options) (*Engine, error) {
|
||||||
|
redirects := make([]*redirectMatch, 0, len(opts.RedirectMatch))
|
||||||
|
|
||||||
|
for _, line := range opts.RedirectMatch {
|
||||||
|
r, err := parseRedirectMatchLine(line)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
redirects = append(redirects, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
e := &Engine{
|
||||||
|
redirects: redirects,
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler returns a new rewrite Iris Handler.
|
||||||
|
// It panics on any error.
|
||||||
|
// Same as engine, _ := New(opts); engine.Handler.
|
||||||
|
// Usage:
|
||||||
|
// app.UseRouter(Handler(opts)).
|
||||||
|
func Handler(opts Options) context.Handler {
|
||||||
|
engine, err := New(opts)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return engine.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler is an Iris Handler that can be used as a router or party or route middleware.
|
||||||
|
// For a global alternative, if you want to wrap the entire Iris Application
|
||||||
|
// use the `Wrapper` instead.
|
||||||
|
// Usage:
|
||||||
|
// app.UseRouter(engine.Handler)
|
||||||
|
func (e *Engine) Handler(ctx *context.Context) {
|
||||||
|
// We could also do that:
|
||||||
|
// but we don't.
|
||||||
|
// e.WrapRouter(ctx.ResponseWriter(), ctx.Request(), func(http.ResponseWriter, *http.Request) {
|
||||||
|
// ctx.Next()
|
||||||
|
// })
|
||||||
|
for _, rd := range e.redirects {
|
||||||
|
src := ctx.Path()
|
||||||
|
if !rd.isRelativePattern {
|
||||||
|
src = ctx.Request().URL.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if target, ok := rd.matchAndReplace(src); ok {
|
||||||
|
if target == src {
|
||||||
|
// this should never happen: StatusTooManyRequests.
|
||||||
|
// keep the router flow.
|
||||||
|
ctx.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Redirect(target, rd.code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper wraps the entire Iris Router.
|
||||||
|
// Wrapper is a bit faster than Handler because it's executed
|
||||||
|
// even before any route matched and it stops on redirect pattern match.
|
||||||
|
// Use it to wrap the entire Iris Application, otherwise look `Handler` instead.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// app.WrapRouter(engine.Wrapper).
|
||||||
|
func (e *Engine) Wrapper(w http.ResponseWriter, r *http.Request, routeHandler http.HandlerFunc) {
|
||||||
|
for _, rd := range e.redirects {
|
||||||
|
src := r.URL.Path
|
||||||
|
if !rd.isRelativePattern {
|
||||||
|
src = r.URL.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if target, ok := rd.matchAndReplace(src); ok {
|
||||||
|
if target == src {
|
||||||
|
routeHandler(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, target, rd.code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
routeHandler(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
type redirectMatch struct {
|
||||||
|
code int
|
||||||
|
pattern *regexp.Regexp
|
||||||
|
target string
|
||||||
|
|
||||||
|
isRelativePattern bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *redirectMatch) matchAndReplace(src string) (string, bool) {
|
||||||
|
if r.pattern.MatchString(src) {
|
||||||
|
if match := r.pattern.ReplaceAllString(src, r.target); match != "" {
|
||||||
|
return match, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRedirectMatchLine(s string) (*redirectMatch, error) {
|
||||||
|
parts := strings.Split(strings.TrimSpace(s), " ")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return nil, fmt.Errorf("redirect match: invalid line: %s", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
codeStr, pattern, target := parts[0], parts[1], parts[2]
|
||||||
|
|
||||||
|
for i, ch := range codeStr {
|
||||||
|
if !isDigit(ch) {
|
||||||
|
return nil, fmt.Errorf("redirect match: status code digits: %s [%d:%c]", codeStr, i, ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
code, err := strconv.Atoi(codeStr)
|
||||||
|
if err != nil {
|
||||||
|
// this should not happen, we check abt digit
|
||||||
|
// and correctly position the error too but handle it.
|
||||||
|
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,
|
||||||
|
|
||||||
|
isRelativePattern: pattern[0] == '/', // search by path.
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDigit(ch rune) bool {
|
||||||
|
return '0' <= ch && ch <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadOptions loads rewrite Options from a system file.
|
||||||
|
func LoadOptions(filename string) (opts Options) {
|
||||||
|
ext := ".yml"
|
||||||
|
if index := strings.LastIndexByte(filename, '.'); index > 1 && len(filename)-1 > index {
|
||||||
|
ext = filename[index:]
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic("iris: rewrite: " + err.Error())
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
switch ext {
|
||||||
|
case ".yaml", ".yml":
|
||||||
|
err = yaml.NewDecoder(f).Decode(&opts)
|
||||||
|
case ".json":
|
||||||
|
err = json.NewDecoder(f).Decode(&opts)
|
||||||
|
default:
|
||||||
|
panic("iris: rewrite: unexpected file extension: " + filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic("iris: rewrite: decode: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
89
middleware/rewrite/rewrite_test.go
Normal file
89
middleware/rewrite/rewrite_test.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package rewrite
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestRedirectMatch(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
line string
|
||||||
|
parseErr string
|
||||||
|
inputs map[string]string // input, expected. Order should not matter.
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"301 /seo/(.*) /$1",
|
||||||
|
"",
|
||||||
|
map[string]string{
|
||||||
|
"/seo/path": "/path",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"301 /old(.*) /deprecated$1",
|
||||||
|
"",
|
||||||
|
map[string]string{
|
||||||
|
"/old": "/deprecated",
|
||||||
|
"/old/any": "/deprecated/any",
|
||||||
|
"/old/thing/here": "/deprecated/thing/here",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"301 /old(.*) /",
|
||||||
|
"",
|
||||||
|
map[string]string{
|
||||||
|
"/oldblabla": "/",
|
||||||
|
"/old/any": "/",
|
||||||
|
"/old/thing/here": "/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"301 /old/(.*) /deprecated/$1",
|
||||||
|
"",
|
||||||
|
map[string]string{
|
||||||
|
"/old/": "/deprecated/",
|
||||||
|
"/old/any": "/deprecated/any",
|
||||||
|
"/old/thing/here": "/deprecated/thing/here",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"3d /seo/(.*) /$1",
|
||||||
|
"redirect match: status code digits: 3d [1:d]",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"301 /$1",
|
||||||
|
"redirect match: invalid line: 301 /$1",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"301 /* /$1",
|
||||||
|
"redirect match: loop detected: pattern: /* vs target: /$1",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"301 /* /",
|
||||||
|
"redirect match: loop detected: pattern: /* vs target: /",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
r, err := parseRedirectMatchLine(tt.line)
|
||||||
|
if err != nil {
|
||||||
|
if tt.parseErr == "" {
|
||||||
|
t.Fatalf("[%d] unexpected parse error: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
errStr := err.Error()
|
||||||
|
if tt.parseErr != err.Error() {
|
||||||
|
t.Fatalf("[%d] a parse error was expected but it differs: expected: %s but got: %s", i, tt.parseErr, errStr)
|
||||||
|
}
|
||||||
|
} else if tt.parseErr != "" {
|
||||||
|
t.Fatalf("[%d] expected an error of: %s but got nil", i, tt.parseErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
for input, expected := range tt.inputs {
|
||||||
|
got, _ := r.matchAndReplace(input)
|
||||||
|
if expected != got {
|
||||||
|
t.Fatalf(`[%d:%s] expected: "%s" but got: "%s"`, i, input, expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user