mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 08:26:26 +01:00
|You will love this| New Feature: Offline routing, dynamic changes against a route's state | https://github.com/kataras/iris/issues/585
Read HISTORY.md for details
This commit is contained in:
parent
020e857b22
commit
c91a1e6628
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,4 +1,4 @@
|
|||
- Version : **6.1.0**
|
||||
- Version : **6.1.1**
|
||||
|
||||
- Operating System:
|
||||
|
||||
|
|
76
HISTORY.md
76
HISTORY.md
|
@ -2,6 +2,82 @@
|
|||
|
||||
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
|
||||
|
||||
## 6.1.0 -> 6.1.1
|
||||
|
||||
- **NEW FEATURE**: `Offline routes`.
|
||||
|
||||
- Discussion: https://github.com/kataras/iris/issues/585
|
||||
- Test: https://github.com/kataras/iris/blob/master/http_test.go#L735
|
||||
- Example: https://github.com/iris-contrib/examples/tree/master/route_state
|
||||
|
||||
**What?**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
iris.None("/api/user/:userid", func(ctx *iris.Context) {
|
||||
userid := ctx.Param("userid")
|
||||
ctx.Writef("user with id: %s", userid)
|
||||
})("user.api")
|
||||
|
||||
// change the "user.api" state from offline to online and online to offline
|
||||
iris.Get("/change", func(ctx *iris.Context) {
|
||||
routeName := "user.api"
|
||||
if iris.Lookup(routeName).IsOnline() {
|
||||
// set to offline
|
||||
iris.SetRouteOffline(routeName)
|
||||
} else {
|
||||
// set to online if it was not online(so it was offline)
|
||||
iris.SetRouteOnline(routeName, iris.MethodGet)
|
||||
}
|
||||
})
|
||||
|
||||
// iris.Get("/execute/:routename", func(ctx *iris.Context) {
|
||||
// routeName := ctx.Param("routename")
|
||||
// userAPICtx := ctx.ExecuteRoute(routeName)
|
||||
// if userAPICtx == nil {
|
||||
// ctx.Writef("Route with name: %s didnt' found or couldn't be validate with this request path!", routeName)
|
||||
// }
|
||||
// })
|
||||
|
||||
iris.Get("/execute", func(ctx *iris.Context) {
|
||||
routeName := "user.api"
|
||||
// change the path in order to be catcable from the ExecuteRoute
|
||||
// ctx.Request.URL.Path = "/api/user/42"
|
||||
// ctx.ExecRoute(routeName)
|
||||
// or:
|
||||
ctx.ExecRouteAgainst(routeName, "/api/user/42")
|
||||
})
|
||||
|
||||
iris.Get("/", func(ctx *iris.Context) {
|
||||
ctx.Writef("Hello from index /")
|
||||
})
|
||||
|
||||
//
|
||||
// START THE SERVER
|
||||
//
|
||||
// STEPS:
|
||||
// 1. navigate to http://localhost:8080/user/api/42
|
||||
// you should get 404 error
|
||||
// 2. now, navigate to http://localhost:8080/change
|
||||
// you should see a blank page
|
||||
// 3. now, navigate to http://localhost:8080/user/api/42
|
||||
// you should see the page working, NO 404 error
|
||||
// go back to the http://localhost:8080/change
|
||||
// you should get 404 error again
|
||||
// You just dynamically changed the state of a route with 3 lines of code!
|
||||
// you can do the same with group of routes and subdomains :)
|
||||
iris.Listen(":8080")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 6.0.9 -> 6.1.0
|
||||
|
||||
- Fix a not found error when serving static files through custom subdomain, this should work again: `iris.Party("mysubdomain.").StaticWeb("/", "./static")`
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<br/>
|
||||
|
||||
|
||||
<a href="https://github.com/kataras/iris/blob/master/HISTORY.md"><img src="https://img.shields.io/badge/%20version%20-%206.1.0%20-blue.svg?style=flat-square" alt="CHANGELOG/HISTORY"></a>
|
||||
<a href="https://github.com/kataras/iris/blob/master/HISTORY.md"><img src="https://img.shields.io/badge/%20version%20-%206.1.1%20-blue.svg?style=flat-square" alt="CHANGELOG/HISTORY"></a>
|
||||
|
||||
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/%20examples-repository-3362c2.svg?style=flat-square" alt="Examples"></a>
|
||||
|
||||
|
@ -947,7 +947,7 @@ I recommend testing your API using this new library, [httpexpect](https://github
|
|||
Versioning
|
||||
------------
|
||||
|
||||
Current: **v6.1.0**
|
||||
Current: **v6.1.1**
|
||||
|
||||
Older: **[v5/fasthttp](https://github.com/kataras/iris/tree/5.0.0)**
|
||||
|
||||
|
|
47
context.go
47
context.go
|
@ -177,6 +177,53 @@ func (ctx *Context) GetHandlerName() string {
|
|||
return runtime.FuncForPC(reflect.ValueOf(ctx.Middleware[len(ctx.Middleware)-1]).Pointer()).Name()
|
||||
}
|
||||
|
||||
// ExecRoute calls any route by its name (mostly "offline" route) like it was requested by the user, but it is not.
|
||||
// Offline means that the route is registered to the iris and have all features that a normal route has
|
||||
// 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.
|
||||
//
|
||||
// It doesn't changes the global state, if a route was "offline" it remains offline.
|
||||
//
|
||||
// see ExecRouteAgainst(routeName, againstRequestPath string),
|
||||
// iris.None(...) and iris.SetRouteOnline/SetRouteOffline
|
||||
// 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(routeName string) *Context {
|
||||
return ctx.ExecRouteAgainst(routeName, ctx.Path())
|
||||
}
|
||||
|
||||
// ExecRouteAgainst calls any route by its name (mostly "offline" route) against a 'virtually' request path
|
||||
// like it was requested by the user, but it is not.
|
||||
// Offline means that the route is registered to the iris and have all features that a normal route has
|
||||
// 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.
|
||||
//
|
||||
// It doesn't changes the global state, if a route was "offline" it remains offline.
|
||||
//
|
||||
// see ExecRoute(routeName),
|
||||
// iris.None(...) and iris.SetRouteOnline/SetRouteOffline
|
||||
// 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(routeName string, againstRequestPath string) *Context {
|
||||
|
||||
r := ctx.framework.Lookup(routeName)
|
||||
if r != nil {
|
||||
context := &(*ctx)
|
||||
context.Middleware = context.Middleware[0:0]
|
||||
context.values.Reset()
|
||||
tree := ctx.framework.muxAPI.mux.getTree(r.Method(), r.Subdomain())
|
||||
tree.entry.get(againstRequestPath, context)
|
||||
if len(context.Middleware) > 0 {
|
||||
context.Do()
|
||||
return context
|
||||
}
|
||||
}
|
||||
// if failed return nil in order to this fail to be catchable
|
||||
return nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------------------------
|
||||
// -----------------------------Request URL, Method, IP & Headers getters---------------
|
||||
|
|
29
http.go
29
http.go
|
@ -37,10 +37,15 @@ const (
|
|||
MethodOptions = "OPTIONS"
|
||||
// MethodTrace "TRACE"
|
||||
MethodTrace = "TRACE"
|
||||
// MethodNone is a Virtual method
|
||||
// to store the "offline" routes
|
||||
// in the mux's tree
|
||||
MethodNone = "NONE"
|
||||
)
|
||||
|
||||
var (
|
||||
// AllMethods "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"
|
||||
// AllMethods contains all the http valid methods:
|
||||
// "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", "PATCH", "OPTIONS", "TRACE"
|
||||
AllMethods = [...]string{MethodGet, MethodPost, MethodPut, MethodDelete, MethodConnect, MethodHead, MethodPatch, MethodOptions, MethodTrace}
|
||||
)
|
||||
|
||||
|
@ -701,6 +706,8 @@ type (
|
|||
Middleware() Middleware
|
||||
// SetMiddleware changes/sets the middleware(handler(s)) for this route
|
||||
SetMiddleware(Middleware)
|
||||
// IsOnline returns true if the route is marked as "online" (state)
|
||||
IsOnline() bool
|
||||
}
|
||||
|
||||
route struct {
|
||||
|
@ -798,6 +805,10 @@ func (r *route) SetMiddleware(m Middleware) {
|
|||
r.middleware = m
|
||||
}
|
||||
|
||||
func (r route) IsOnline() bool {
|
||||
return r.method != MethodNone
|
||||
}
|
||||
|
||||
// RouteConflicts checks for route's middleware conflicts
|
||||
func RouteConflicts(r *route, with string) bool {
|
||||
for _, h := range r.middleware {
|
||||
|
@ -944,10 +955,17 @@ func (mux *serveMux) register(method string, subdomain string, path string, midd
|
|||
}
|
||||
|
||||
// build collects all routes info and adds them to the registry in order to be served from the request handler
|
||||
// this happens once when server is setting the mux's handler.
|
||||
// this happens once(except when a route changes its state) when server is setting the mux's handler.
|
||||
func (mux *serveMux) build() (methodEqual func(string, string) bool) {
|
||||
|
||||
sort.Sort(bySubdomain(mux.lookups))
|
||||
// clear them for any case
|
||||
// build may called internally to re-build the routes.
|
||||
// re-build happens from BuildHandler() when a route changes its state
|
||||
// from offline to online or from online to offline
|
||||
mux.garden = mux.garden[0:0]
|
||||
// this is not used anywhere for now, but keep it here.
|
||||
mux.maxParameters = 0
|
||||
|
||||
for i := range mux.lookups {
|
||||
r := mux.lookups[i]
|
||||
|
@ -1014,7 +1032,12 @@ func HTMLEscape(s string) string {
|
|||
return htmlReplacer.Replace(s)
|
||||
}
|
||||
|
||||
// BuildHandler the default Iris router when iris.Handler is nil
|
||||
// BuildHandler the default Iris router when iris.Router is nil
|
||||
//
|
||||
// NOTE: Is called and re-set to the iris.Router when
|
||||
// a route changes its state from "online" to "offline" or "offline" to "online"
|
||||
// look iris.None(...) for more
|
||||
// and: https://github.com/kataras/iris/issues/585
|
||||
func (mux *serveMux) BuildHandler() HandlerFunc {
|
||||
|
||||
// initialize the router once
|
||||
|
|
61
http_test.go
61
http_test.go
|
@ -731,3 +731,64 @@ func TestRedirectHTTPS(t *testing.T) {
|
|||
e := httptest.New(api, t)
|
||||
e.GET("/redirect").Expect().Status(iris.StatusOK).Body().Equal(expectedBody)
|
||||
}
|
||||
|
||||
func TestRouteStateSimple(t *testing.T) {
|
||||
iris.ResetDefault()
|
||||
// here
|
||||
offlineRouteName := "user.api"
|
||||
offlineRoutePath := "/api/user/:userid"
|
||||
offlineRouteRequestedTestPath := "/api/user/42"
|
||||
offlineBody := "user with id: 42"
|
||||
|
||||
iris.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)
|
||||
})(offlineRouteName)
|
||||
|
||||
// change the "user.api" state from offline to online and online to offline
|
||||
iris.Get("/change", func(ctx *iris.Context) {
|
||||
// here
|
||||
if iris.Lookup(offlineRouteName).IsOnline() {
|
||||
// set to offline
|
||||
iris.SetRouteOffline(offlineRouteName)
|
||||
} else {
|
||||
// set to online if it was not online(so it was offline)
|
||||
iris.SetRouteOnline(offlineRouteName, iris.MethodGet)
|
||||
}
|
||||
})
|
||||
|
||||
iris.Get("/execute", func(ctx *iris.Context) {
|
||||
// here
|
||||
ctx.ExecRouteAgainst(offlineRouteName, "/api/user/42")
|
||||
})
|
||||
|
||||
hello := "Hello from index"
|
||||
iris.Get("/", func(ctx *iris.Context) {
|
||||
ctx.Writef(hello)
|
||||
})
|
||||
|
||||
e := httptest.New(iris.Default, 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)
|
||||
}
|
||||
|
|
115
iris.go
115
iris.go
|
@ -81,7 +81,7 @@ const (
|
|||
// IsLongTermSupport flag is true when the below version number is a long-term-support version
|
||||
IsLongTermSupport = false
|
||||
// Version is the current version number of the Iris web framework
|
||||
Version = "6.1.0"
|
||||
Version = "6.1.1"
|
||||
|
||||
banner = ` _____ _
|
||||
|_ _| (_)
|
||||
|
@ -171,6 +171,10 @@ type (
|
|||
|
||||
Lookup(routeName string) Route
|
||||
Lookups() []Route
|
||||
SetRouteOnline(routeName string, HTTPMethod string) bool
|
||||
SetRouteOffline(routeName string) bool
|
||||
ChangeRouteState(routeName string, HTTPMethod string) bool
|
||||
|
||||
Path(routeName string, optionalPathParameters ...interface{}) (routePath string)
|
||||
URL(routeName string, optionalPathParameters ...interface{}) (routeURL string)
|
||||
|
||||
|
@ -195,6 +199,8 @@ type (
|
|||
HandleFunc(method string, reqPath string, middleware ...HandlerFunc) RouteNameFunc
|
||||
API(reqRelativeRootPath string, api HandlerAPI, middleware ...HandlerFunc)
|
||||
|
||||
// virtual method for "offline" routes new feature
|
||||
None(reqRelativePath string, Middleware ...HandlerFunc) RouteNameFunc
|
||||
// http methods
|
||||
Get(reqRelativePath string, middleware ...HandlerFunc) RouteNameFunc
|
||||
Post(reqRelativePath string, middleware ...HandlerFunc) RouteNameFunc
|
||||
|
@ -417,11 +423,7 @@ func (s *Framework) Build() {
|
|||
// build and get the default mux' handler(*Context)
|
||||
serve := s.mux.BuildHandler()
|
||||
// build the net/http.Handler to bind it to the servers
|
||||
defaultHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := s.AcquireCtx(w, r)
|
||||
serve(ctx)
|
||||
s.ReleaseCtx(ctx)
|
||||
})
|
||||
defaultHandler := ToNativeHandler(s, serve)
|
||||
|
||||
s.Router = defaultHandler
|
||||
}
|
||||
|
@ -1048,6 +1050,88 @@ func (s *Framework) Lookups() (routes []Route) {
|
|||
return
|
||||
}
|
||||
|
||||
// SetRouteOnline sets the state of the route to "online" with a specific http method
|
||||
// it re-builds the router
|
||||
//
|
||||
// returns true if state was actually changed
|
||||
//
|
||||
// see context.ExecRoute(routeName),
|
||||
// iris.None(...) and iris.SetRouteOnline/SetRouteOffline
|
||||
// For more details look: https://github.com/kataras/iris/issues/585
|
||||
//
|
||||
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
|
||||
func SetRouteOnline(routeName string, HTTPMethod string) bool {
|
||||
return Default.SetRouteOnline(routeName, HTTPMethod)
|
||||
}
|
||||
|
||||
// SetRouteOffline sets the state of the route to "offline" and re-builds the router
|
||||
//
|
||||
// returns true if state was actually changed
|
||||
//
|
||||
// see context.ExecRoute(routeName),
|
||||
// iris.None(...) and iris.SetRouteOnline/SetRouteOffline
|
||||
// For more details look: https://github.com/kataras/iris/issues/585
|
||||
//
|
||||
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
|
||||
func SetRouteOffline(routeName string) bool {
|
||||
return Default.SetRouteOffline(routeName)
|
||||
}
|
||||
|
||||
// ChangeRouteState changes the state of the route.
|
||||
// iris.MethodNone for offline
|
||||
// and iris.MethodGet/MethodPost/MethodPut/MethodDelete /MethodConnect/MethodOptions/MethodHead/MethodTrace/MethodPatch for online
|
||||
// it re-builds the router
|
||||
//
|
||||
// returns true if state was actually changed
|
||||
//
|
||||
// see context.ExecRoute(routeName),
|
||||
// iris.None(...) and iris.SetRouteOnline/SetRouteOffline
|
||||
// For more details look: https://github.com/kataras/iris/issues/585
|
||||
//
|
||||
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
|
||||
func ChangeRouteState(routeName string, HTTPMethod string) bool {
|
||||
return Default.ChangeRouteState(routeName, HTTPMethod)
|
||||
}
|
||||
|
||||
// SetRouteOnline sets the state of the route to "online" with a specific http method
|
||||
// it re-builds the router
|
||||
//
|
||||
// returns true if state was actually changed
|
||||
func (s *Framework) SetRouteOnline(routeName string, HTTPMethod string) bool {
|
||||
return s.ChangeRouteState(routeName, HTTPMethod)
|
||||
}
|
||||
|
||||
// SetRouteOffline sets the state of the route to "offline" and re-builds the router
|
||||
//
|
||||
// returns true if state was actually changed
|
||||
func (s *Framework) SetRouteOffline(routeName string) bool {
|
||||
return s.ChangeRouteState(routeName, MethodNone)
|
||||
}
|
||||
|
||||
// ChangeRouteState changes the state of the route.
|
||||
// iris.MethodNone for offline
|
||||
// and iris.MethodGet/MethodPost/MethodPut/MethodDelete /MethodConnect/MethodOptions/MethodHead/MethodTrace/MethodPatch for online
|
||||
// it re-builds the router
|
||||
//
|
||||
// returns true if state was actually changed
|
||||
func (s *Framework) ChangeRouteState(routeName string, HTTPMethod string) bool {
|
||||
r := s.mux.lookup(routeName)
|
||||
nonSpecificMethod := len(HTTPMethod) == 0
|
||||
if r != nil {
|
||||
if r.method != HTTPMethod {
|
||||
if nonSpecificMethod {
|
||||
r.method = MethodGet // if no method given, then do it for "GET" only
|
||||
} else {
|
||||
r.method = HTTPMethod
|
||||
}
|
||||
// re-build the router/main handler
|
||||
s.Router = ToNativeHandler(s, s.mux.BuildHandler())
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Path used to check arguments with the route's named parameters and return the correct url
|
||||
// if parse failed returns empty string
|
||||
func Path(routeName string, args ...interface{}) string {
|
||||
|
@ -1607,6 +1691,16 @@ func (api *muxAPI) API(path string, restAPI HandlerAPI, middleware ...HandlerFun
|
|||
|
||||
}
|
||||
|
||||
// None registers an "offline" route
|
||||
// see context.ExecRoute(routeName),
|
||||
// iris.None(...) and iris.SetRouteOnline/SetRouteOffline
|
||||
// For more details look: https://github.com/kataras/iris/issues/585
|
||||
//
|
||||
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
|
||||
func None(path string, handlersFn ...HandlerFunc) RouteNameFunc {
|
||||
return Default.None(path, handlersFn...)
|
||||
}
|
||||
|
||||
// Get registers a route for the Get http method
|
||||
func Get(path string, handlersFn ...HandlerFunc) RouteNameFunc {
|
||||
return Default.Get(path, handlersFn...)
|
||||
|
@ -1655,7 +1749,16 @@ func Trace(path string, handlersFn ...HandlerFunc) RouteNameFunc {
|
|||
// Any registers a route for ALL of the http methods (Get,Post,Put,Head,Patch,Options,Connect,Delete)
|
||||
func Any(registeredPath string, handlersFn ...HandlerFunc) {
|
||||
Default.Any(registeredPath, handlersFn...)
|
||||
}
|
||||
|
||||
// None registers an "offline" route
|
||||
// see context.ExecRoute(routeName),
|
||||
// iris.None(...) and iris.SetRouteOnline/SetRouteOffline
|
||||
// For more details look: https://github.com/kataras/iris/issues/585
|
||||
//
|
||||
// Example: https://github.com/iris-contrib/examples/tree/master/route_state
|
||||
func (api *muxAPI) None(path string, handlersFn ...HandlerFunc) RouteNameFunc {
|
||||
return api.HandleFunc(MethodNone, path, handlersFn...)
|
||||
}
|
||||
|
||||
// Get registers a route for the Get http method
|
||||
|
|
Loading…
Reference in New Issue
Block a user