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
import "github.com/kataras/iris/v12"
const addr = "mydomain.com:80"
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/rewrite"
)
func main() {
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/users -> http://www.mydomain.com/users
// http://mydomain.com/users/login -> http://www.mydomain.com/users/login
app.Listen(addr)
// http://mydomain.com/user -> http://www.mydomain.com/user
// http://mydomain.com/user/login -> http://www.mydomain.com/user/login
app.Listen(":80")
}
func newApp() *iris.Application {
app := iris.New()
app.Get("/", func(ctx iris.Context) {
ctx.Writef("This will never be executed.")
})
app.Logger().SetLevel("debug")
www := app.Subdomain("www") // <- same as app.Party("www.")
www.Get("/", index)
static := app.Subdomain("static")
static.Get("/", staticIndex)
// www is an `iris.Party`, use it like you already know, like grouping routes.
www.PartyFunc("/users", func(p iris.Party) { // <- same as www.Party("/users").Get(...)
p.Get("/", usersIndex)
p.Get("/login", getLogin)
})
app.Get("/", index)
userRouter := app.Party("/user")
userRouter.Get("/", userGet)
userRouter.Get("/login", userGetLogin)
// redirects mydomain.com/%anypath% to www.mydomain.com/%anypath%.
// First argument is the 'from' and second is the 'to/target'.
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)
// redirects := rewrite.Load("redirects.yml")
// ^ see _examples/routing/rewrite example for that.
//
// If you need to redirect any subdomain to 'www' then:
// app.SubdomainRedirect(app.WildcardSubdomain(), www)
// If you need to redirect from a subdomain to the root domain then:
// app.SubdomainRedirect(app.Subdomain("mysubdomain"), app)
//
// Note that app.Party("mysubdomain.") and app.Subdomain("mysubdomain")
// is the same exactly thing, the difference is that the second can omit the last dot('.').
// Now let's do that by code.
rewriteEngine, _ := rewrite.New(rewrite.Options{
PrimarySubdomain: "www",
})
// Enable this line for debugging:
// rewriteEngine.SetLogger(app.Logger())
app.WrapRouter(rewriteEngine.Rewrite)
return app
}
func staticIndex(ctx iris.Context) {
ctx.Writef("This is the static.mydomain.com index.")
}
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) {
ctx.Writef("This is the www.mydomain.com/users endpoint.")
func userGet(ctx iris.Context) {
// 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) {
ctx.Writef("This is the www.mydomain.com/users/login endpoint.")
func userGetLogin(ctx iris.Context) {
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.
// 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
// existing routes and the future routes that may being registered.
//

View File

@ -8,6 +8,7 @@ package router_test
import (
"fmt"
"net/http"
"testing"
"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().
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/core/router"
"github.com/kataras/golog"
"gopkg.in/yaml.v3"
)
@ -69,6 +70,7 @@ type Engine struct {
redirects []*redirectMatch
options Options
logger *golog.Logger
domainValidator func(string) bool
}
@ -117,6 +119,35 @@ func New(opts Options) (*Engine, error) {
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.
// For a global alternative, if you want to wrap the entire Iris Application
// 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 != "" {
hostport := context.GetHost(r)
root := context.GetDomain(hostport)
e.initDebugf("Begin request: full host: %s and root domain: %s", hostport, root)
// Note:
// localhost and 127.0.0.1 are not supported for subdomain rewrite, by purpose,
// use a virtual host instead.
@ -150,10 +183,14 @@ func (e *Engine) Rewrite(w http.ResponseWriter, r *http.Request, routeHandler ht
root += getPort(hostport)
subdomain := strings.TrimSuffix(hostport, root)
e.debugf("Domain is not a loopback, requested subdomain: %s\n", subdomain)
if subdomain == "" {
// we are in root domain, full redirect to its primary subdomain.
r.Host = primarySubdomain + root
r.URL.Host = primarySubdomain + root
newHost := 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)
return
}
@ -164,13 +201,15 @@ func (e *Engine) Rewrite(w http.ResponseWriter, r *http.Request, routeHandler ht
// to bypass the subdomain router (`routeHandler`)
// do not return, redirects should be respected.
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.
r.Host = rootHost
r.URL.Host = rootHost
}
// 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 == src {
e.debugf("WARNING: source and target URLs match: %s\n", src)
routeHandler(w, r)
return
}
@ -194,6 +234,7 @@ func (e *Engine) Rewrite(w http.ResponseWriter, r *http.Request, routeHandler ht
return
}
e.debugf("No redirect: handle request: %s as: %s\n", r.RequestURI, u)
r.URL = u
routeHandler(w, r)
return
@ -202,8 +243,10 @@ func (e *Engine) Rewrite(w http.ResponseWriter, r *http.Request, routeHandler ht
if !rd.isRelativePattern {
// this performs better, no need to check query or host,
// the uri already built.
e.debugf("Full redirect: from: %s to: %s\n", src, target)
redirectAbs(w, r, target, rd.code)
} else {
e.debugf("Path redirect: from: %s to: %s\n", src, target)
http.Redirect(w, r, target, rd.code)
}