mirror of
https://github.com/kataras/iris.git
synced 2026-01-06 03:27:27 +00:00
This commit is contained in:
@@ -24,6 +24,14 @@ type Router struct {
|
||||
requestHandler RequestHandler // build-accessible, can be changed to define a custom router or proxy, used on RefreshRouter too.
|
||||
mainHandler http.HandlerFunc // init-accessible
|
||||
wrapperFunc WrapperFunc
|
||||
// wrappers to be built on BuildRouter state,
|
||||
// first is executed first at this case.
|
||||
// Case:
|
||||
// - SubdomainRedirect on user call, registers a wrapper, on design state
|
||||
// - i18n,if loaded and Subdomain or PathRedirect is true, registers a wrapper too, on build state
|
||||
// the SubdomainRedirect should be the first(subdomainWrap(i18nWrap)) wrapper
|
||||
// to be executed instead of last(i18nWrap(subdomainWrap)).
|
||||
wrapperFuncs []WrapperFunc
|
||||
|
||||
cPool *context.Pool // used on RefreshRouter
|
||||
routesProvider RoutesProvider
|
||||
@@ -216,6 +224,14 @@ func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHan
|
||||
}
|
||||
}
|
||||
|
||||
for i := len(router.wrapperFuncs) - 1; i >= 0; i-- {
|
||||
w := router.wrapperFuncs[i]
|
||||
if w == nil {
|
||||
continue
|
||||
}
|
||||
router.WrapRouter(w)
|
||||
}
|
||||
|
||||
if router.wrapperFunc != nil { // if wrapper used then attach that as the router service
|
||||
router.mainHandler = newWrapper(router.wrapperFunc, router.mainHandler).ServeHTTP
|
||||
}
|
||||
@@ -268,9 +284,35 @@ func (router *Router) Downgraded() bool {
|
||||
//
|
||||
// Before build.
|
||||
func (router *Router) WrapRouter(wrapperFunc WrapperFunc) {
|
||||
// logger := context.DefaultLogger("router wrapper")
|
||||
// file, line := context.HandlerFileLineRel(wrapperFunc)
|
||||
// if router.wrapperFunc != nil {
|
||||
// wrappedFile, wrappedLine := context.HandlerFileLineRel(router.wrapperFunc)
|
||||
// logger.Infof("%s:%d wraps %s:%d", file, line, wrappedFile, wrappedLine)
|
||||
// } else {
|
||||
// logger.Infof("%s:%d wraps the main router", file, line)
|
||||
// }
|
||||
router.wrapperFunc = makeWrapperFunc(router.wrapperFunc, wrapperFunc)
|
||||
}
|
||||
|
||||
// AddRouterWrapper adds a router wrapper.
|
||||
// Unlike `WrapRouter` the first registered will be executed first
|
||||
// so a wrapper wraps its next not the previous one.
|
||||
// it defers the wrapping until the `BuildRouter`.
|
||||
// Redirection wrappers should be added using this method
|
||||
// e.g. SubdomainRedirect.
|
||||
func (router *Router) AddRouterWrapper(wrapperFunc WrapperFunc) {
|
||||
router.wrapperFuncs = append(router.wrapperFuncs, wrapperFunc)
|
||||
}
|
||||
|
||||
// PrependRouterWrapper like `AddRouterWrapper` but this wrapperFunc
|
||||
// will always be executed before the previous `AddRouterWrapper`.
|
||||
// Path form (no modification) wrappers should be added using this method
|
||||
// e.g. ForceLowercaseRouting.
|
||||
func (router *Router) PrependRouterWrapper(wrapperFunc WrapperFunc) {
|
||||
router.wrapperFuncs = append([]WrapperFunc{wrapperFunc}, router.wrapperFuncs...)
|
||||
}
|
||||
|
||||
// ServeHTTPC serves the raw context, useful if we have already a context, it by-pass the wrapper.
|
||||
func (router *Router) ServeHTTPC(ctx *context.Context) {
|
||||
router.requestHandler.HandleRequest(ctx)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/kataras/iris/v12/context"
|
||||
"github.com/kataras/iris/v12/core/netutil"
|
||||
@@ -42,7 +44,7 @@ func pathIsWildcard(partyRelPath string) bool {
|
||||
//
|
||||
// Usage(package-level):
|
||||
// sd := NewSubdomainRedirectWrapper(func() string { return "mydomain.com" }, ".", "www.")
|
||||
// router.WrapRouter(sd)
|
||||
// router.AddRouterWrapper(sd)
|
||||
//
|
||||
// Usage(high-level using `iris#Application.SubdomainRedirect`)
|
||||
// www := app.Subdomain("www")
|
||||
@@ -56,12 +58,12 @@ func pathIsWildcard(partyRelPath string) bool {
|
||||
// One or more subdomain redirect wrappers can be used to the same router instance.
|
||||
//
|
||||
// NewSubdomainRedirectWrapper may return nil if not allowed input arguments values were received
|
||||
// but in that case, the `WrapRouter` will, simply, ignore that wrapper.
|
||||
// but in that case, the `AddRouterWrapper` will, simply, ignore that wrapper.
|
||||
//
|
||||
// Example: https://github.com/kataras/iris/tree/master/_examples/routing/subdomains/redirect
|
||||
func NewSubdomainRedirectWrapper(rootDomainGetter func() string, from, to string) WrapperFunc {
|
||||
// we can return nil,
|
||||
// because if wrapper is nil then it's not be used on the `router#WrapRouter`.
|
||||
// because if wrapper is nil then it's not be used on the `router#AddRouterWrapper`.
|
||||
if from == to {
|
||||
// cannot redirect to the same location, cycle.
|
||||
return nil
|
||||
@@ -109,7 +111,6 @@ func (s *subdomainRedirectWrapper) Wrapper(w http.ResponseWriter, r *http.Reques
|
||||
// because older browsers may not be able to recognise that status code (the RFC 7538, is not so old)
|
||||
// although note that move is not the same thing as redirect: move reminds a specific address or location moved while
|
||||
// redirect is a new location.
|
||||
|
||||
host := context.GetHost(r)
|
||||
root := s.root()
|
||||
if loopback := netutil.GetLoopbackSubdomain(root); loopback != "" {
|
||||
@@ -117,7 +118,6 @@ func (s *subdomainRedirectWrapper) Wrapper(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
hasSubdomain := host != root
|
||||
|
||||
if !hasSubdomain && !s.isFromRoot {
|
||||
// if the current endpoint is not a subdomain
|
||||
// and the redirect is not configured to be used from root domain to a subdomain.
|
||||
@@ -142,14 +142,25 @@ func (s *subdomainRedirectWrapper) Wrapper(w http.ResponseWriter, r *http.Reques
|
||||
resturi := r.URL.RequestURI()
|
||||
if s.isToRoot {
|
||||
// from a specific subdomain or any subdomain to the root domain.
|
||||
http.Redirect(w, r, getFullScheme(r)+root+resturi, http.StatusMovedPermanently)
|
||||
redirectAbsolute(w, r, getFullScheme(r)+root+resturi, http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
// from a specific subdomain or any subdomain to a specific subdomain.
|
||||
http.Redirect(w, r, getFullScheme(r)+s.to+root+resturi, http.StatusMovedPermanently)
|
||||
redirectAbsolute(w, r, getFullScheme(r)+s.to+root+resturi, http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
|
||||
if s.isFromRoot && !s.isFromAny {
|
||||
// Then we must not continue,
|
||||
// the subdomain didn't match the "to" but the from
|
||||
// was the application root itself, which is not a wildcard
|
||||
// so it shouldn't accept any subdomain, we must fire 404 here.
|
||||
// Something like:
|
||||
// http://registered_host_but_not_in_app.your.mydomain.com
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
|
||||
}
|
||||
// the from subdomain is not matched and it's not from root.
|
||||
router(w, r)
|
||||
return
|
||||
@@ -159,9 +170,30 @@ func (s *subdomainRedirectWrapper) Wrapper(w http.ResponseWriter, r *http.Reques
|
||||
resturi := r.URL.RequestURI()
|
||||
// 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.
|
||||
http.Redirect(w, r, getFullScheme(r)+s.to+root+resturi, http.StatusMovedPermanently)
|
||||
redirectAbsolute(w, r, getFullScheme(r)+s.to+root+resturi, http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
|
||||
router(w, r)
|
||||
}
|
||||
|
||||
func redirectAbsolute(w http.ResponseWriter, r *http.Request, url string, code int) {
|
||||
h := w.Header()
|
||||
|
||||
// RFC 7231 notes that a short HTML body is usually included in
|
||||
// the response because older user agents may not understand 301/307.
|
||||
// Do it only if the request didn't already have a Content-Type header.
|
||||
_, hadCT := h[context.ContentTypeHeaderKey]
|
||||
|
||||
h.Set("Location", url)
|
||||
if !hadCT && (r.Method == http.MethodGet || r.Method == http.MethodHead) {
|
||||
h.Set(context.ContentTypeHeaderKey, "text/html; charset=utf-8")
|
||||
}
|
||||
w.WriteHeader(code)
|
||||
|
||||
// Shouldn't send the body for POST or HEAD; that leaves GET.
|
||||
if !hadCT && r.Method == "GET" {
|
||||
body := "<a href=\"" + template.HTMLEscapeString(url) + "\">" + http.StatusText(code) + "</a>.\n"
|
||||
fmt.Fprintln(w, body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/context"
|
||||
"github.com/kataras/iris/v12/core/router"
|
||||
"github.com/kataras/iris/v12/httptest"
|
||||
)
|
||||
|
||||
@@ -72,3 +74,88 @@ func TestLowercaseRouting(t *testing.T) {
|
||||
e.GET(strings.ToUpper(tt)).Expect().Status(httptest.StatusOK).Body().Equal(s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterWrapperOrder(t *testing.T) {
|
||||
// last is wrapping the previous.
|
||||
|
||||
// first is executed last.
|
||||
userWrappers := []router.WrapperFunc{
|
||||
func(w http.ResponseWriter, r *http.Request, main http.HandlerFunc) {
|
||||
io.WriteString(w, "6")
|
||||
main(w, r)
|
||||
},
|
||||
func(w http.ResponseWriter, r *http.Request, main http.HandlerFunc) {
|
||||
io.WriteString(w, "5")
|
||||
main(w, r)
|
||||
},
|
||||
}
|
||||
// should be executed before userWrappers.
|
||||
redirectionWrappers := []router.WrapperFunc{
|
||||
func(w http.ResponseWriter, r *http.Request, main http.HandlerFunc) {
|
||||
io.WriteString(w, "3")
|
||||
main(w, r)
|
||||
},
|
||||
func(w http.ResponseWriter, r *http.Request, main http.HandlerFunc) {
|
||||
io.WriteString(w, "4")
|
||||
main(w, r)
|
||||
},
|
||||
}
|
||||
// should be executed before redirectionWrappers.
|
||||
afterRedirectionWrappers := []router.WrapperFunc{
|
||||
func(w http.ResponseWriter, r *http.Request, main http.HandlerFunc) {
|
||||
io.WriteString(w, "2")
|
||||
main(w, r)
|
||||
},
|
||||
func(w http.ResponseWriter, r *http.Request, main http.HandlerFunc) {
|
||||
io.WriteString(w, "1")
|
||||
main(w, r)
|
||||
},
|
||||
}
|
||||
|
||||
testOrder1 := iris.New()
|
||||
for _, w := range userWrappers {
|
||||
testOrder1.WrapRouter(w)
|
||||
// this always wraps the previous one, but it's not accessible after Build state,
|
||||
// the below are simulating the SubdomainRedirect and ForceLowercaseRouting.
|
||||
}
|
||||
for _, w := range redirectionWrappers {
|
||||
testOrder1.AddRouterWrapper(w)
|
||||
}
|
||||
for _, w := range afterRedirectionWrappers {
|
||||
testOrder1.PrependRouterWrapper(w)
|
||||
}
|
||||
|
||||
testOrder2 := iris.New()
|
||||
for _, w := range redirectionWrappers {
|
||||
testOrder2.AddRouterWrapper(w)
|
||||
}
|
||||
for _, w := range userWrappers {
|
||||
testOrder2.WrapRouter(w)
|
||||
}
|
||||
for _, w := range afterRedirectionWrappers {
|
||||
testOrder2.PrependRouterWrapper(w)
|
||||
}
|
||||
|
||||
testOrder3 := iris.New()
|
||||
for _, w := range redirectionWrappers {
|
||||
testOrder3.AddRouterWrapper(w)
|
||||
}
|
||||
for _, w := range afterRedirectionWrappers {
|
||||
testOrder3.PrependRouterWrapper(w)
|
||||
}
|
||||
for _, w := range userWrappers {
|
||||
testOrder3.WrapRouter(w)
|
||||
}
|
||||
|
||||
appTests := []*iris.Application{
|
||||
testOrder1, testOrder2, testOrder3,
|
||||
}
|
||||
|
||||
expectedOrderStr := "123456"
|
||||
for _, app := range appTests {
|
||||
app.Get("/", func(ctx iris.Context) {}) // to not append the not found one.
|
||||
|
||||
e := httptest.New(t, app)
|
||||
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedOrderStr)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user