mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
This commit is contained in:
parent
889b7942d3
commit
227170fd33
|
@ -43,7 +43,7 @@ func createRoot(redirectTo string) *iris.Application {
|
|||
fullScheme = "https://"
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fullScheme+redirectTo, iris.StatusMovedPermanently)
|
||||
http.Redirect(w, r, fullScheme+redirectTo+r.URL.RequestURI(), iris.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
return app
|
||||
|
|
2
cli.go
2
cli.go
|
@ -115,6 +115,6 @@ func injectLiveReload(contextPool *context.Pool, router *router.Router) (bool, e
|
|||
contextPool.Release(ctx)
|
||||
}
|
||||
|
||||
router.WrapRouter(wrapper)
|
||||
router.AddRouterWrapper(wrapper)
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -406,7 +406,7 @@ func (i *I18n) GetMessage(ctx *context.Context, format string, args ...interface
|
|||
}
|
||||
|
||||
// Wrapper returns a new router wrapper.
|
||||
// The result function can be passed on `Application.WrapRouter`.
|
||||
// The result function can be passed on `Application.WrapRouter/AddRouterWrapper`.
|
||||
// It compares the path prefix for translated language and
|
||||
// local redirects the requested path with the selected (from the path) language to the router.
|
||||
//
|
||||
|
@ -417,7 +417,10 @@ func (i *I18n) Wrapper() router.WrapperFunc {
|
|||
}
|
||||
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
found := false
|
||||
path := r.URL.Path[1:]
|
||||
path := r.URL.Path
|
||||
if len(path) > 0 && path[0] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
if idx := strings.IndexByte(path, '/'); idx > 0 {
|
||||
path = path[:idx]
|
||||
|
@ -451,7 +454,6 @@ func (i *I18n) Wrapper() router.WrapperFunc {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
next(w, r)
|
||||
|
|
|
@ -172,6 +172,21 @@ func (l MemoryLocalizer) GetLocale(index int) context.Locale {
|
|||
// panic(fmt.Sprintf("locale of index [%d] not found", index))
|
||||
// }
|
||||
// return loc
|
||||
/* Note(@kataras): the following is allowed as a language index can be higher
|
||||
than the length of the locale files.
|
||||
if index >= len(l) || index < 0 {
|
||||
// 1. language exists in the caller but was not found in files.
|
||||
// 2. language exists in both files and caller but the actual
|
||||
// languages are two, while the registered are 4 (when missing files),
|
||||
// that happens when Strict option is false.
|
||||
// force to the default language but what is the default language if the language index is greater than this length?
|
||||
// That's why it's allowed.
|
||||
index = 0
|
||||
}*/
|
||||
|
||||
if index < 0 {
|
||||
index = 0
|
||||
}
|
||||
|
||||
return l[index]
|
||||
}
|
||||
|
|
12
iris.go
12
iris.go
|
@ -162,9 +162,7 @@ func (app *Application) WWW() router.Party {
|
|||
// Example: https://github.com/kataras/iris/tree/master/_examples/routing/subdomains/redirect
|
||||
func (app *Application) SubdomainRedirect(from, to router.Party) router.Party {
|
||||
sd := router.NewSubdomainRedirectWrapper(app.ConfigurationReadOnly().GetVHost, from.GetRelPath(), to.GetRelPath())
|
||||
// TODO: add a debug message here or wait for a response from the issuer
|
||||
// so we can force these to run on build state (last registered, first executed).
|
||||
app.Router.WrapRouter(sd)
|
||||
app.Router.AddRouterWrapper(sd)
|
||||
return to
|
||||
}
|
||||
|
||||
|
@ -489,6 +487,7 @@ func (app *Application) Build() error {
|
|||
if app.builded {
|
||||
return nil
|
||||
}
|
||||
|
||||
// start := time.Now()
|
||||
app.builded = true // even if fails.
|
||||
|
||||
|
@ -532,7 +531,7 @@ func (app *Application) Build() error {
|
|||
if app.I18n.Loaded() {
|
||||
// {{ tr "lang" "key" arg1 arg2 }}
|
||||
app.view.AddFunc("tr", app.I18n.Tr)
|
||||
app.Router.WrapRouter(app.I18n.Wrapper())
|
||||
app.Router.AddRouterWrapper(app.I18n.Wrapper())
|
||||
}
|
||||
|
||||
if n := app.view.Len(); n > 0 {
|
||||
|
@ -560,7 +559,10 @@ func (app *Application) Build() error {
|
|||
}
|
||||
|
||||
if app.config.ForceLowercaseRouting {
|
||||
app.Router.WrapRouter(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
// This should always be executed first.
|
||||
app.Router.PrependRouterWrapper(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
r.Host = strings.ToLower(r.Host)
|
||||
r.URL.Host = strings.ToLower(r.URL.Host)
|
||||
r.URL.Path = strings.ToLower(r.URL.Path)
|
||||
next(w, r)
|
||||
})
|
||||
|
|
|
@ -18,7 +18,7 @@ type (
|
|||
// Config is the configuration for sessions. Please read it before using sessions.
|
||||
Config struct {
|
||||
// Logger instance for sessions usage, e.g. { Logger: app.Logger() }.
|
||||
// Defauls to a child of "sessions" of the latest Iris Application's main Logger.
|
||||
// Defaults to a child of "sessions" of the latest Iris Application's main Logger.
|
||||
Logger *golog.Logger
|
||||
// Cookie string, the session's client cookie name, for example: "mysessionid"
|
||||
//
|
||||
|
|
Loading…
Reference in New Issue
Block a user