update the subdomain redirect example using the rewrite middleware

This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-08-26 00:07:07 +03:00
parent 5e82fa5b89
commit 1780d97d44
No known key found for this signature in database
GPG Key ID: 5DBE766BD26A54E7
4 changed files with 151 additions and 52 deletions

View File

@ -1,73 +1,59 @@
// Package main shows how to register a simple 'www' subdomain,
// using the `app.WWW` method, which will register a router wrapper which will
// redirect all 'mydomain.com' requests to 'www.mydomain.com'.
// Check the 'hosts' file to see how to test the 'mydomain.com' on your local machine.
package main package main
import "github.com/kataras/iris/v12" import (
"github.com/kataras/iris/v12"
const addr = "mydomain.com:80" "github.com/kataras/iris/v12/middleware/rewrite"
)
func main() { func main() {
app := newApp() app := newApp()
// http(s)://mydomain.com, will be redirect to http(s)://www.mydomain.com.
// The `www` variable is the `app.Subdomain("www")`.
//
// app.WWW() wraps the router so it can redirect all incoming requests
// that comes from 'http(s)://mydomain.com/%path%' (www is missing)
// to `http(s)://www.mydomain.com/%path%`.
//
// Try:
// http://mydomain.com -> http://www.mydomain.com // http://mydomain.com -> http://www.mydomain.com
// http://mydomain.com/users -> http://www.mydomain.com/users // http://mydomain.com/user -> http://www.mydomain.com/user
// http://mydomain.com/users/login -> http://www.mydomain.com/users/login // http://mydomain.com/user/login -> http://www.mydomain.com/user/login
app.Listen(addr) app.Listen(":80")
} }
func newApp() *iris.Application { func newApp() *iris.Application {
app := iris.New() app := iris.New()
app.Get("/", func(ctx iris.Context) { app.Logger().SetLevel("debug")
ctx.Writef("This will never be executed.")
})
www := app.Subdomain("www") // <- same as app.Party("www.") static := app.Subdomain("static")
www.Get("/", index) static.Get("/", staticIndex)
// www is an `iris.Party`, use it like you already know, like grouping routes. app.Get("/", index)
www.PartyFunc("/users", func(p iris.Party) { // <- same as www.Party("/users").Get(...) userRouter := app.Party("/user")
p.Get("/", usersIndex) userRouter.Get("/", userGet)
p.Get("/login", getLogin) userRouter.Get("/login", userGetLogin)
})
// redirects mydomain.com/%anypath% to www.mydomain.com/%anypath%. // redirects := rewrite.Load("redirects.yml")
// First argument is the 'from' and second is the 'to/target'. // ^ see _examples/routing/rewrite example for that.
app.SubdomainRedirect(app, www)
// SubdomainRedirect works for multi-level subdomains as well:
// subsub := www.Subdomain("subsub") // subsub.www.mydomain.com
// subsub.Get("/", func(ctx iris.Context) { ctx.Writef("subdomain is: " + ctx.Subdomain()) })
// app.SubdomainRedirect(subsub, www)
// //
// If you need to redirect any subdomain to 'www' then: // Now let's do that by code.
// app.SubdomainRedirect(app.WildcardSubdomain(), www) rewriteEngine, _ := rewrite.New(rewrite.Options{
// If you need to redirect from a subdomain to the root domain then: PrimarySubdomain: "www",
// app.SubdomainRedirect(app.Subdomain("mysubdomain"), app) })
// // Enable this line for debugging:
// Note that app.Party("mysubdomain.") and app.Subdomain("mysubdomain") // rewriteEngine.SetLogger(app.Logger())
// is the same exactly thing, the difference is that the second can omit the last dot('.'). app.WrapRouter(rewriteEngine.Rewrite)
return app return app
} }
func staticIndex(ctx iris.Context) {
ctx.Writef("This is the static.mydomain.com index.")
}
func index(ctx iris.Context) { func index(ctx iris.Context) {
ctx.Writef("This is the www.mydomain.com endpoint.") ctx.Writef("This is the www.mydomain.com index.")
} }
func usersIndex(ctx iris.Context) { func userGet(ctx iris.Context) {
ctx.Writef("This is the www.mydomain.com/users endpoint.") // Also, ctx.Subdomain(), ctx.SubdomainFull(), ctx.Host() and ctx.Path()
// can be helpful when working with subdomains.
ctx.Writef("This is the www.mydomain.com/user endpoint.")
} }
func getLogin(ctx iris.Context) { func userGetLogin(ctx iris.Context) {
ctx.Writef("This is the www.mydomain.com/users/login endpoint.") ctx.Writef("This is the www.mydomain.com/user/login endpoint.")
} }

View File

@ -980,7 +980,7 @@ func (api *APIBuilder) UseOnce(handlers ...context.Handler) {
// UseGlobal registers handlers that should run at the very beginning. // UseGlobal registers handlers that should run at the very beginning.
// It prepends those handler(s) to all routes, // It prepends those handler(s) to all routes,
// including all parties, subdomains. // including all parties, subdomains and errors.
// It doesn't care about call order, it will prepend the handlers to all // It doesn't care about call order, it will prepend the handlers to all
// existing routes and the future routes that may being registered. // existing routes and the future routes that may being registered.
// //

View File

@ -8,6 +8,7 @@ package router_test
import ( import (
"fmt" "fmt"
"net/http"
"testing" "testing"
"github.com/kataras/iris/v12" "github.com/kataras/iris/v12"
@ -285,3 +286,72 @@ func TestUseRouterSubdomains(t *testing.T) {
e.GET("/notfound").WithURL("http://old.example.com").Expect().Status(iris.StatusNotFound).Body(). e.GET("/notfound").WithURL("http://old.example.com").Expect().Status(iris.StatusNotFound).Body().
Equal("Not Found") Equal("Not Found")
} }
func TestUseWrapOrder(t *testing.T) {
var (
expectedBody = "#1 .WrapRouter\n#2 .UseRouter\n#3 .UseGlobal\n#4 .Use\n#5 Main Handler\n"
expectedNotFoundBody = "#3 .UseGlobal\n#1 .UseError\n#2 Main Error Handler\n"
makeMiddleware = func(body string) iris.Handler {
return func(ctx iris.Context) {
ctx.WriteString(body)
ctx.Next()
}
}
handler = func(ctx iris.Context) {
ctx.WriteString("#5 Main Handler\n")
}
errorHandler = func(ctx iris.Context) {
ctx.WriteString("#2 Main Error Handler\n")
}
useHandler = makeMiddleware("#4 .Use\n")
useGlobal = makeMiddleware("#3 .UseGlobal\n")
useError = func(ctx iris.Context) {
// UseError has captured the status code, because it runs
// after the router itself but only one error handlers.
ctx.WriteString("#1 .UseError\n")
ctx.Next()
}
useRouter = func(ctx iris.Context) {
if ctx.Path() == "/" {
ctx.WriteString("#2 .UseRouter\n")
}
ctx.Next()
}
wrapRouter = func(w http.ResponseWriter, r *http.Request, router http.HandlerFunc) {
if r.URL.Path == "/" {
w.Write([]byte("#1 .WrapRouter\n"))
// Note for beginners, reading this test:
// if we write something here on a not found page,
// in the raw net/http wrapper like this one,
// then the response writer sends 200 status OK
// (on first write) and so any error handler will not be fired as expected,
// these are basic things. If you w.WriteHeader you cannot change the status code later on too.
// In Iris handlers, if you write before status code set, then it sends 200
// and it cannot change too (if you want to change that behavior you use ctx.Record()).
// However if you
// just call ctx.StatusCode without content written then you are able to change the status code
// later on.
}
router(w, r)
}
)
app := iris.New()
app.Use(useHandler)
app.UseGlobal(useGlobal)
app.UseError(useError)
app.UseRouter(useRouter)
app.WrapRouter(wrapRouter)
app.OnErrorCode(iris.StatusNotFound, errorHandler)
app.Get("/", handler)
e := httptest.New(t, app)
e.GET("/NotFound").Expect().Status(iris.StatusNotFound).Body().Equal(expectedNotFoundBody)
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedBody)
}

View File

@ -13,6 +13,7 @@ import (
"github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/core/router" "github.com/kataras/iris/v12/core/router"
"github.com/kataras/golog"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -69,6 +70,7 @@ type Engine struct {
redirects []*redirectMatch redirects []*redirectMatch
options Options options Options
logger *golog.Logger
domainValidator func(string) bool domainValidator func(string) bool
} }
@ -117,6 +119,35 @@ func New(opts Options) (*Engine, error) {
return e, nil return e, nil
} }
// SetLogger attachs a logger to the Rewrite Engine,
// used only for debugging.
// Defaults to nil.
func (e *Engine) SetLogger(logger *golog.Logger) *Engine {
e.logger = logger.Child(e).SetChildPrefix("rewrite")
return e
}
// init the request logging with [DBUG].
func (e *Engine) initDebugf(format string, args ...interface{}) {
if e.logger == nil {
return
}
e.logger.Debugf(format, args...)
}
var skipDBUGSpace = strings.Repeat(" ", 7)
// continue debugging the same request with new lines and spacing,
// easier to read.
func (e *Engine) debugf(format string, args ...interface{}) {
if e.logger == nil || e.logger.Level < golog.DebugLevel {
return
}
fmt.Fprintf(e.logger.Printer, skipDBUGSpace+format, args...)
}
// Handler is an Iris Handler that can be used as a router or party or route middleware. // 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 // For a global alternative, if you want to wrap the entire Iris Application
// use the `Wrapper` instead. // use the `Wrapper` instead.
@ -141,6 +172,8 @@ func (e *Engine) Rewrite(w http.ResponseWriter, r *http.Request, routeHandler ht
if primarySubdomain := e.options.PrimarySubdomain; primarySubdomain != "" { if primarySubdomain := e.options.PrimarySubdomain; primarySubdomain != "" {
hostport := context.GetHost(r) hostport := context.GetHost(r)
root := context.GetDomain(hostport) root := context.GetDomain(hostport)
e.initDebugf("Begin request: full host: %s and root domain: %s", hostport, root)
// Note: // Note:
// localhost and 127.0.0.1 are not supported for subdomain rewrite, by purpose, // localhost and 127.0.0.1 are not supported for subdomain rewrite, by purpose,
// use a virtual host instead. // use a virtual host instead.
@ -150,10 +183,14 @@ func (e *Engine) Rewrite(w http.ResponseWriter, r *http.Request, routeHandler ht
root += getPort(hostport) root += getPort(hostport)
subdomain := strings.TrimSuffix(hostport, root) subdomain := strings.TrimSuffix(hostport, root)
e.debugf("Domain is not a loopback, requested subdomain: %s\n", subdomain)
if subdomain == "" { if subdomain == "" {
// we are in root domain, full redirect to its primary subdomain. // we are in root domain, full redirect to its primary subdomain.
r.Host = primarySubdomain + root newHost := primarySubdomain + root
r.URL.Host = primarySubdomain + root e.debugf("Redirecting from root domain to: %s\n", newHost)
r.Host = newHost
r.URL.Host = newHost
http.Redirect(w, r, r.URL.String(), http.StatusMovedPermanently) http.Redirect(w, r, r.URL.String(), http.StatusMovedPermanently)
return return
} }
@ -164,13 +201,15 @@ func (e *Engine) Rewrite(w http.ResponseWriter, r *http.Request, routeHandler ht
// to bypass the subdomain router (`routeHandler`) // to bypass the subdomain router (`routeHandler`)
// do not return, redirects should be respected. // do not return, redirects should be respected.
rootHost := strings.TrimPrefix(hostport, subdomain) rootHost := strings.TrimPrefix(hostport, subdomain)
e.debugf("Request host field was modified to: %s. Proceed without redirection\n", rootHost)
// modify those for the next redirects or the route handler. // modify those for the next redirects or the route handler.
r.Host = rootHost r.Host = rootHost
r.URL.Host = rootHost r.URL.Host = rootHost
} }
// maybe other subdomain or not at all, let's continue. // maybe other subdomain or not at all, let's continue.
} else {
e.debugf("Primary subdomain is: %s but redirect response was not sent. Domain is a loopback?\n", primarySubdomain)
} }
} }
@ -183,6 +222,7 @@ func (e *Engine) Rewrite(w http.ResponseWriter, r *http.Request, routeHandler ht
if target, ok := rd.matchAndReplace(src); ok { if target, ok := rd.matchAndReplace(src); ok {
if target == src { if target == src {
e.debugf("WARNING: source and target URLs match: %s\n", src)
routeHandler(w, r) routeHandler(w, r)
return return
} }
@ -194,6 +234,7 @@ func (e *Engine) Rewrite(w http.ResponseWriter, r *http.Request, routeHandler ht
return return
} }
e.debugf("No redirect: handle request: %s as: %s\n", r.RequestURI, u)
r.URL = u r.URL = u
routeHandler(w, r) routeHandler(w, r)
return return
@ -202,8 +243,10 @@ func (e *Engine) Rewrite(w http.ResponseWriter, r *http.Request, routeHandler ht
if !rd.isRelativePattern { if !rd.isRelativePattern {
// this performs better, no need to check query or host, // this performs better, no need to check query or host,
// the uri already built. // the uri already built.
e.debugf("Full redirect: from: %s to: %s\n", src, target)
redirectAbs(w, r, target, rd.code) redirectAbs(w, r, target, rd.code)
} else { } else {
e.debugf("Path redirect: from: %s to: %s\n", src, target)
http.Redirect(w, r, target, rd.code) http.Redirect(w, r, target, rd.code)
} }