diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 84bbef9a..1db030be 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,4 +1,4 @@
-- Version : **6.1.0**
+- Version : **6.1.1**
- Operating System:
diff --git a/HISTORY.md b/HISTORY.md
index a3cc0407..bd35a97a 100644
--- a/HISTORY.md
+++ b/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")`
diff --git a/README.md b/README.md
index 4d99c02a..21991094 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
-
+
@@ -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)**
diff --git a/context.go b/context.go
index cb3a5aa7..4618f132 100644
--- a/context.go
+++ b/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---------------
diff --git a/http.go b/http.go
index 3b94799f..1ba16be4 100644
--- a/http.go
+++ b/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
diff --git a/http_test.go b/http_test.go
index 5f172633..9ff66482 100644
--- a/http_test.go
+++ b/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)
+}
diff --git a/iris.go b/iris.go
index 3abab865..33e803bd 100644
--- a/iris.go
+++ b/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