mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 02:56:26 +01:00
Make ExecRouteAgainst to work better and fix gorillamux and httprouter, remove of contextlinker policy it's useless now
Former-commit-id: 8d3501e6c490d630d3a4bec0c077dcd7d532242f
This commit is contained in:
parent
ab63ebcfa5
commit
7263649002
|
@ -766,7 +766,6 @@ We have 8 policies, so far, and some of them have 'subpolicies' (the RouterRever
|
|||
- StaticPath
|
||||
- WildcardPath
|
||||
- URLPath
|
||||
- RouteContextLinker
|
||||
- RouterBuilderPolicy
|
||||
- RouterWrapperPolicy
|
||||
- RenderPolicy
|
||||
|
|
|
@ -8,8 +8,8 @@ package gorillamux
|
|||
// package main
|
||||
//
|
||||
// import (
|
||||
// "gopkg.in/kataras/iris.v6/adaptors/gorillamux"
|
||||
// "gopkg.in/kataras/iris.v6"
|
||||
// "gopkg.in/kataras/iris.v6/adaptors/gorillamux"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
|
@ -37,7 +37,8 @@ const dynamicSymbol = '{'
|
|||
// New returns a new gorilla mux router which can be plugged inside iris.
|
||||
// This is magic.
|
||||
func New() iris.Policies {
|
||||
router := mux.NewRouter()
|
||||
var router *mux.Router
|
||||
|
||||
var logger func(iris.LogMode, string)
|
||||
return iris.Policies{
|
||||
EventPolicy: iris.EventPolicy{Boot: func(s *iris.Framework) {
|
||||
|
@ -76,17 +77,11 @@ func New() iris.Policies {
|
|||
}
|
||||
return ""
|
||||
},
|
||||
RouteContextLinker: func(r iris.RouteInfo, ctx *iris.Context) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
route := router.Get(r.Name())
|
||||
if route != nil {
|
||||
mapToContext(ctx.Request, r.Middleware(), ctx)
|
||||
}
|
||||
},
|
||||
},
|
||||
RouterBuilderPolicy: func(repo iris.RouteRepository, context iris.ContextPool) http.Handler {
|
||||
router = mux.NewRouter() // re-set the router here,
|
||||
// the RouterBuilderPolicy re-runs on every method change (route "offline/online" states mostly)
|
||||
|
||||
repo.Visit(func(route iris.RouteInfo) {
|
||||
registerRoute(route, router, context)
|
||||
})
|
||||
|
@ -102,49 +97,47 @@ func New() iris.Policies {
|
|||
}
|
||||
}
|
||||
|
||||
func mapToContext(r *http.Request, middleware iris.Middleware, ctx *iris.Context) {
|
||||
if params := mux.Vars(r); len(params) > 0 {
|
||||
// set them with ctx.Set in order to be accesible by ctx.Param in the user's handler
|
||||
for k, v := range params {
|
||||
ctx.Set(k, v)
|
||||
}
|
||||
}
|
||||
// including the iris.Default.Use/UseFunc and the route's middleware,
|
||||
// main handler and any done handlers.
|
||||
ctx.Middleware = middleware
|
||||
}
|
||||
|
||||
// so easy:
|
||||
func registerRoute(route iris.RouteInfo, gorillaRouter *mux.Router, context iris.ContextPool) {
|
||||
if route.IsOnline() {
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Acquire(w, r)
|
||||
|
||||
mapToContext(r, route.Middleware(), ctx)
|
||||
ctx.Do()
|
||||
|
||||
context.Release(ctx)
|
||||
}
|
||||
|
||||
// remember, we get a new iris.Route foreach of the HTTP Methods, so this should be work
|
||||
methods := []string{route.Method()}
|
||||
// if route has cors then we register the route with the "OPTIONS" method too
|
||||
if route.HasCors() {
|
||||
methods = append(methods, http.MethodOptions)
|
||||
}
|
||||
gorillaRoute := gorillaRouter.HandleFunc(route.Path(), handler).Methods(methods...).Name(route.Name())
|
||||
|
||||
subdomain := route.Subdomain()
|
||||
if subdomain != "" {
|
||||
if subdomain == "*." {
|
||||
// it's an iris wildcard subdomain
|
||||
// so register it as wildcard on gorilla mux too
|
||||
subdomain = "{subdomain}."
|
||||
} else {
|
||||
// it's a static subdomain (which contains the dot)
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
context.Run(w, r, func(ctx *iris.Context) {
|
||||
if params := mux.Vars(ctx.Request); len(params) > 0 {
|
||||
// set them with ctx.Set in order to be accesible by ctx.Param in the user's handler
|
||||
for k, v := range params {
|
||||
ctx.Set(k, v)
|
||||
}
|
||||
}
|
||||
// host = subdomain + listening host
|
||||
gorillaRoute.Host(subdomain + context.Framework().Config.VHost)
|
||||
}
|
||||
// including the global middleware, done handlers too
|
||||
ctx.Middleware = route.Middleware()
|
||||
ctx.Do()
|
||||
})
|
||||
}
|
||||
|
||||
// remember, we get a new iris.Route foreach of the HTTP Methods, so this should be work
|
||||
methods := []string{route.Method()}
|
||||
// if route has cors then we register the route with the "OPTIONS" method too
|
||||
if route.HasCors() {
|
||||
methods = append(methods, http.MethodOptions)
|
||||
}
|
||||
gorillaRoute := gorillaRouter.HandleFunc(route.Path(), handler).
|
||||
Methods(methods...).
|
||||
Name(route.Name())
|
||||
|
||||
subdomain := route.Subdomain()
|
||||
if subdomain != "" {
|
||||
if subdomain == "*." {
|
||||
// it's an iris wildcard subdomain
|
||||
// so register it as wildcard on gorilla mux too
|
||||
subdomain = "{subdomain}."
|
||||
} else {
|
||||
// it's a static subdomain (which contains the dot)
|
||||
}
|
||||
// host = subdomain + listening host
|
||||
gorillaRoute.Host(subdomain + context.Framework().Config.VHost)
|
||||
}
|
||||
|
||||
// Author's notes: even if the Method is iris.MethodNone
|
||||
// the gorillamux saves the route, so we don't need to use the repo.OnMethodChanged
|
||||
// and route.IsOnline() and we don't need the RouteContextLinker, we just serve like request on Offline routes*
|
||||
}
|
||||
|
|
|
@ -545,15 +545,12 @@ func New() iris.Policies {
|
|||
//
|
||||
// return fmt.Sprintf(r.formattedPath, arguments...)
|
||||
// },
|
||||
RouteContextLinker: func(r iris.RouteInfo, ctx *iris.Context) {
|
||||
tree := mux.getTree(r.Method(), r.Subdomain())
|
||||
if tree != nil {
|
||||
tree.entry.get(ctx.Request.URL.Path, ctx)
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
RouterBuilderPolicy: func(repo iris.RouteRepository, context iris.ContextPool) http.Handler {
|
||||
fatalErr := false
|
||||
mux.garden = mux.garden[0:0] // re-set the nodes
|
||||
mux.hosts = false
|
||||
repo.Visit(func(r iris.RouteInfo) {
|
||||
if fatalErr {
|
||||
return
|
||||
|
|
59
context.go
59
context.go
|
@ -277,7 +277,7 @@ func (ctx *Context) GetHandlerName() string {
|
|||
// BUT it isn't available by browsing, its handlers executed only when other handler's context call them
|
||||
// it can validate paths, has sessions, path parameters and all.
|
||||
//
|
||||
// You can find the Route by iris.Lookup("theRouteName")
|
||||
// You can find the Route by iris.Default.Routes().Lookup("theRouteName")
|
||||
// you can set a route name as: myRoute := iris.Default.Get("/mypath", handler)("theRouteName")
|
||||
// that will set a name to the route and returns its iris.Route instance for further usage.
|
||||
//
|
||||
|
@ -288,8 +288,8 @@ func (ctx *Context) GetHandlerName() string {
|
|||
// For more details look: https://github.com/kataras/iris/issues/585
|
||||
//
|
||||
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
|
||||
func (ctx *Context) ExecRoute(r RouteInfo) *Context {
|
||||
return ctx.ExecRouteAgainst(r, ctx.Path())
|
||||
func (ctx *Context) ExecRoute(r RouteInfo) {
|
||||
ctx.ExecRouteAgainst(r, ctx.Path())
|
||||
}
|
||||
|
||||
// ExecRouteAgainst calls any iris.Route against a 'virtually' request path
|
||||
|
@ -298,7 +298,7 @@ func (ctx *Context) ExecRoute(r RouteInfo) *Context {
|
|||
// BUT it isn't available by browsing, its handlers executed only when other handler's context call them
|
||||
// it can validate paths, has sessions, path parameters and all.
|
||||
//
|
||||
// You can find the Route by iris.Lookup("theRouteName")
|
||||
// You can find the Route by iris.Default.Routes().Lookup("theRouteName")
|
||||
// you can set a route name as: myRoute := iris.Default.Get("/mypath", handler)("theRouteName")
|
||||
// that will set a name to the route and returns its iris.Route instance for further usage.
|
||||
//
|
||||
|
@ -309,23 +309,34 @@ func (ctx *Context) ExecRoute(r RouteInfo) *Context {
|
|||
// For more details look: https://github.com/kataras/iris/issues/585
|
||||
//
|
||||
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
|
||||
func (ctx *Context) ExecRouteAgainst(r RouteInfo, againstRequestPath string) *Context {
|
||||
if r != nil {
|
||||
context := &(*ctx)
|
||||
context.Middleware = context.Middleware[0:0]
|
||||
context.values.Reset()
|
||||
context.Request.RequestURI = againstRequestPath
|
||||
context.Request.URL.Path = againstRequestPath
|
||||
context.Request.URL.RawPath = againstRequestPath
|
||||
ctx.framework.policies.RouterReversionPolicy.RouteContextLinker(r, context)
|
||||
// tree := ctx.framework.muxAPI.mux.getTree(r.Method(), r.Subdomain())
|
||||
// tree.entry.get(againstRequestPath, context)
|
||||
if len(context.Middleware) > 0 {
|
||||
context.Do()
|
||||
return context
|
||||
}
|
||||
//
|
||||
// User can get the response by simple using rec := ctx.Recorder(); rec.Body()/rec.StatusCode()/rec.Header()
|
||||
// The route will be executed via the Router, as it would requested by client.
|
||||
func (ctx *Context) ExecRouteAgainst(r RouteInfo, againstRequestPath string) {
|
||||
if r != nil && againstRequestPath != "" {
|
||||
// ok no need to clone the whole context, let's be dirty here for the sake of performance.
|
||||
backupMidldeware := ctx.Middleware[0:]
|
||||
backupPath := ctx.Path()
|
||||
bakcupMethod := ctx.Method()
|
||||
backupValues := ctx.values
|
||||
backupPos := ctx.Pos
|
||||
// sessions stays.
|
||||
|
||||
ctx.values.Reset()
|
||||
ctx.Middleware = ctx.Middleware[0:0]
|
||||
ctx.Request.RequestURI = againstRequestPath
|
||||
ctx.Request.URL.Path = againstRequestPath
|
||||
ctx.Request.Method = r.Method()
|
||||
|
||||
ctx.framework.Router.ServeHTTP(ctx.ResponseWriter, ctx.Request)
|
||||
|
||||
ctx.Middleware = backupMidldeware
|
||||
ctx.Request.RequestURI = backupPath
|
||||
ctx.Request.URL.Path = backupPath
|
||||
ctx.Request.Method = bakcupMethod
|
||||
ctx.values = backupValues
|
||||
ctx.Pos = backupPos
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prioritize is a middleware which executes a route against this path
|
||||
|
@ -334,7 +345,7 @@ func (ctx *Context) ExecRouteAgainst(r RouteInfo, againstRequestPath string) *Co
|
|||
// if this function is not enough for you and you want to test more than one parameterized path
|
||||
// then use the: if c := ExecRoute(r); c == nil { /* move to the next, the route is not valid */ }
|
||||
//
|
||||
// You can find the Route by iris.Lookup("theRouteName")
|
||||
// You can find the Route by iris.Default.Routes().Lookup("theRouteName")
|
||||
// you can set a route name as: myRoute := iris.Default.Get("/mypath", handler)("theRouteName")
|
||||
// that will set a name to the route and returns its iris.Route instance for further usage.
|
||||
//
|
||||
|
@ -346,10 +357,8 @@ func Prioritize(r RouteInfo) HandlerFunc {
|
|||
reqPath := ctx.Path()
|
||||
staticPath := ctx.framework.policies.RouterReversionPolicy.StaticPath(r.Path())
|
||||
if strings.HasPrefix(reqPath, staticPath) {
|
||||
newctx := ctx.ExecRouteAgainst(r, reqPath)
|
||||
if newctx == nil { // route not found.
|
||||
ctx.EmitError(StatusNotFound)
|
||||
}
|
||||
ctx.ExecRouteAgainst(r, reqPath) // returns 404 page from EmitErrors, these things depends on router adaptors
|
||||
// we are done here.
|
||||
return
|
||||
}
|
||||
// execute the next handler if no prefix
|
||||
|
|
|
@ -93,7 +93,7 @@ func New(app *iris.Framework, t *testing.T, setters ...OptionSetter) *httpexpect
|
|||
testConfiguration := httpexpect.Config{
|
||||
BaseURL: baseURL,
|
||||
Client: &http.Client{
|
||||
Transport: httpexpect.NewBinder(app),
|
||||
Transport: httpexpect.NewBinder(app.Router),
|
||||
Jar: httpexpect.NewJar(),
|
||||
},
|
||||
Reporter: httpexpect.NewAssertReporter(t),
|
||||
|
|
10
iris.go
10
iris.go
|
@ -340,6 +340,16 @@ func New(setters ...OptionSetter) *Framework {
|
|||
if routerBuilder != nil {
|
||||
// buid the router using user's selection build policy
|
||||
s.Router.build(routerBuilder)
|
||||
|
||||
s.Router.repository.OnMethodChanged(func(route RouteInfo, oldMethod string) {
|
||||
// set service not available temporarily until the router completes the building
|
||||
// this won't take more than 100ms, but we want to inform the user.
|
||||
s.Router.handler = ToNativeHandler(s, HandlerFunc(func(ctx *Context) {
|
||||
ctx.EmitError(StatusServiceUnavailable)
|
||||
}))
|
||||
// Re-build the whole router if state changed (from offline to online state mostly)
|
||||
s.Router.build(routerBuilder)
|
||||
})
|
||||
}
|
||||
}
|
||||
}})
|
||||
|
|
|
@ -272,11 +272,6 @@ type (
|
|||
// URLPath used for reverse routing on templates with {{ url }} and {{ path }} funcs.
|
||||
// Receives the route name and arguments and returns its http path
|
||||
URLPath func(r RouteInfo, args ...string) string
|
||||
|
||||
// RouteContextLinker should put the route's handlers and named parameters(if any) to the ctx
|
||||
// it's used to execute virtually an "offline" route
|
||||
// against a context like it was requested by user, but it is not.
|
||||
RouteContextLinker func(r RouteInfo, ctx *Context)
|
||||
}
|
||||
// RouterBuilderPolicy is the most useful Policy for custom routers.
|
||||
// A custom router should adapt this policy which is a func
|
||||
|
@ -317,10 +312,6 @@ func (r RouterReversionPolicy) Adapt(frame *Policies) {
|
|||
if r.URLPath != nil {
|
||||
frame.RouterReversionPolicy.URLPath = r.URLPath
|
||||
}
|
||||
|
||||
if r.RouteContextLinker != nil {
|
||||
frame.RouterReversionPolicy.RouteContextLinker = r.RouteContextLinker
|
||||
}
|
||||
}
|
||||
|
||||
// Adapt adaps a RouterBuilderPolicy object to the main *Policies.
|
||||
|
|
|
@ -52,12 +52,6 @@ func newTestNativeRouter() Policies {
|
|||
}
|
||||
return path
|
||||
},
|
||||
RouteContextLinker: func(r RouteInfo, ctx *Context) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
ctx.Middleware = r.Middleware()
|
||||
},
|
||||
},
|
||||
RouterBuilderPolicy: func(repo RouteRepository, context ContextPool) http.Handler {
|
||||
servemux := http.NewServeMux()
|
||||
|
|
1
route.go
1
route.go
|
@ -237,6 +237,7 @@ func (r *routeRepository) ChangeMethod(routeInfo RouteInfo,
|
|||
}
|
||||
|
||||
if valid {
|
||||
|
||||
route := r.getRouteByName(routeInfo.Name())
|
||||
if route != nil && route.method != newMethod {
|
||||
oldMethod := route.method
|
||||
|
|
101
route_test.go
Normal file
101
route_test.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package iris_test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/kataras/iris.v6"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/gorillamux"
|
||||
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
|
||||
"gopkg.in/kataras/iris.v6/httptest"
|
||||
)
|
||||
|
||||
func testRouteStateSimple(t *testing.T, router iris.Policy, offlineRoutePath string) {
|
||||
app := iris.New()
|
||||
app.Adapt(router)
|
||||
|
||||
offlineRouteRequestedTestPath := "/api/user/42"
|
||||
offlineBody := "user with id: 42"
|
||||
|
||||
offlineRoute := app.None(offlineRoutePath, func(ctx *iris.Context) {
|
||||
userid := ctx.Param("userid")
|
||||
if userid != "42" {
|
||||
// we are expecting userid 42 always in this test so
|
||||
t.Fatalf("what happened? expected userid to be 42 but got %s", userid)
|
||||
}
|
||||
ctx.Writef(offlineBody)
|
||||
}).ChangeName("api.users") // or an empty (), required, in order to get the Route instance.
|
||||
|
||||
// change the "user.api" state from offline to online and online to offline
|
||||
app.Get("/change", func(ctx *iris.Context) {
|
||||
// here
|
||||
if offlineRoute.IsOnline() {
|
||||
// set to offline
|
||||
app.Routes().Offline(offlineRoute)
|
||||
} else {
|
||||
// set to online if it was not online(so it was offline)
|
||||
app.Routes().Online(offlineRoute, iris.MethodGet)
|
||||
}
|
||||
})
|
||||
|
||||
app.Get("/execute", func(ctx *iris.Context) {
|
||||
// here
|
||||
ctx.ExecRouteAgainst(offlineRoute, "/api/user/42")
|
||||
})
|
||||
|
||||
// append the body and change the status code from an 'offline' route execution
|
||||
app.Get("/execute_modified", func(ctx *iris.Context) {
|
||||
ctx.Set("mykey", "myval")
|
||||
// here
|
||||
ctx.Record() // if we want to control the response
|
||||
ctx.ExecRouteAgainst(offlineRoute, "/api/user/42")
|
||||
ctx.Write([]byte("modified from status code: " + strconv.Itoa(ctx.StatusCode())))
|
||||
ctx.SetStatusCode(iris.StatusUseProxy)
|
||||
|
||||
if ctx.Path() != "/execute_modified" {
|
||||
t.Fatalf("Expected Request Path of this context NOT to change but got: '%s' ", ctx.Path())
|
||||
}
|
||||
|
||||
if got := ctx.Get("mykey"); got != "myval" {
|
||||
t.Fatalf("Expected Value 'mykey' of this context NOT to change('%s') but got: '%s' ", "myval", got)
|
||||
}
|
||||
ctx.Next()
|
||||
}, func(ctx *iris.Context) {
|
||||
ctx.Writef("-original_middleware_here")
|
||||
})
|
||||
|
||||
hello := "Hello from index"
|
||||
app.Get("/", func(ctx *iris.Context) {
|
||||
ctx.Writef(hello)
|
||||
})
|
||||
|
||||
e := httptest.New(app, t)
|
||||
|
||||
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(hello)
|
||||
// here
|
||||
// the status should be not found, the route is invisible from outside world
|
||||
e.GET(offlineRouteRequestedTestPath).Expect().Status(iris.StatusNotFound)
|
||||
|
||||
// set the route online with the /change
|
||||
e.GET("/change").Expect().Status(iris.StatusOK)
|
||||
// try again, it should be online now
|
||||
e.GET(offlineRouteRequestedTestPath).Expect().Status(iris.StatusOK).Body().Equal(offlineBody)
|
||||
// change to offline again
|
||||
e.GET("/change").Expect().Status(iris.StatusOK)
|
||||
// and test again, it should be offline now
|
||||
e.GET(offlineRouteRequestedTestPath).Expect().Status(iris.StatusNotFound)
|
||||
|
||||
// finally test the execute on the offline route
|
||||
// it should be remains offline but execute the route like it is from client request.
|
||||
e.GET("/execute").Expect().Status(iris.StatusOK).Body().Equal(offlineBody)
|
||||
e.GET(offlineRouteRequestedTestPath).Expect().Status(iris.StatusNotFound)
|
||||
e.GET("/execute_modified").Expect().Status(iris.StatusUseProxy).Body().
|
||||
Equal(offlineBody + "modified from status code: 200-original_middleware_here")
|
||||
}
|
||||
|
||||
func TestRouteStateSimple(t *testing.T) {
|
||||
// httprouter adaptor
|
||||
testRouteStateSimple(t, httprouter.New(), "/api/user/:userid")
|
||||
// gorillamux adaptor
|
||||
testRouteStateSimple(t, gorillamux.New(), "/api/user/{userid}")
|
||||
}
|
Loading…
Reference in New Issue
Block a user