Offling routing and prioritize others before static handlers https://github.com/kataras/iris/issues/585

Read HISTORY.md
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-01-12 10:24:27 +02:00
parent c91a1e6628
commit 3489ba3365
6 changed files with 227 additions and 86 deletions

View File

@ -8,10 +8,14 @@
- Discussion: https://github.com/kataras/iris/issues/585 - Discussion: https://github.com/kataras/iris/issues/585
- Test: https://github.com/kataras/iris/blob/master/http_test.go#L735 - Test: https://github.com/kataras/iris/blob/master/http_test.go#L735
- Example: https://github.com/iris-contrib/examples/tree/master/route_state - Example 1: https://github.com/iris-contrib/examples/tree/master/route_state
- Example 2, SPA: https://github.com/iris-contrib/examples/tree/master/spa_2_using_offline_routing
**What?** **What?**
- Give priority to an API path inside a Static route
```go ```go
package main package main
@ -21,38 +25,57 @@ import (
func main() { func main() {
iris.None("/api/user/:userid", func(ctx *iris.Context) { usersAPI := iris.None("/api/users/:userid", func(ctx *iris.Context) {
ctx.Writef("user with id: %s", ctx.Param("userid"))
})("api.users.id")
iris.StaticWeb("/", "./www", usersAPI)
//
// START THE SERVER
//
iris.Listen("localhost:8080")
}
```
- Play with(very advanced usage, used by big companies): enable(online) or disable(offline) routes at runtime with one line of code.
```go
package main
import (
"github.com/kataras/iris"
)
func main() {
// You can find the Route by iris.Lookup("theRouteName")
// you can set a route name as: myRoute := iris.Get("/mypath", handler)("theRouteName")
// that will set a name to the route and returns its iris.Route instance for further usage.
api := iris.None("/api/users/:userid", func(ctx *iris.Context) {
userid := ctx.Param("userid") userid := ctx.Param("userid")
ctx.Writef("user with id: %s", userid) ctx.Writef("user with id: %s", userid)
})("user.api") })("users.api")
// change the "user.api" state from offline to online and online to offline // change the "users.api" state from offline to online and online to offline
iris.Get("/change", func(ctx *iris.Context) { iris.Get("/change", func(ctx *iris.Context) {
routeName := "user.api" if api.IsOnline() {
if iris.Lookup(routeName).IsOnline() {
// set to offline // set to offline
iris.SetRouteOffline(routeName) iris.SetRouteOffline(api)
} else { } else {
// set to online if it was not online(so it was offline) // set to online if it was not online(so it was offline)
iris.SetRouteOnline(routeName, iris.MethodGet) iris.SetRouteOnline(api, 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) { iris.Get("/execute", func(ctx *iris.Context) {
routeName := "user.api"
// change the path in order to be catcable from the ExecuteRoute // change the path in order to be catcable from the ExecuteRoute
// ctx.Request.URL.Path = "/api/user/42" // ctx.Request.URL.Path = "/api/users/42"
// ctx.ExecRoute(routeName) // ctx.ExecRoute(iris.Route)
// or: // or:
ctx.ExecRouteAgainst(routeName, "/api/user/42") ctx.ExecRouteAgainst(api, "/api/users/42")
}) })
iris.Get("/", func(ctx *iris.Context) { iris.Get("/", func(ctx *iris.Context) {
@ -63,11 +86,11 @@ func main() {
// START THE SERVER // START THE SERVER
// //
// STEPS: // STEPS:
// 1. navigate to http://localhost:8080/user/api/42 // 1. navigate to http://localhost:8080/api/users/42
// you should get 404 error // you should get 404 error
// 2. now, navigate to http://localhost:8080/change // 2. now, navigate to http://localhost:8080/change
// you should see a blank page // you should see a blank page
// 3. now, navigate to http://localhost:8080/user/api/42 // 3. now, navigate to http://localhost:8080/api/users/42
// you should see the page working, NO 404 error // you should see the page working, NO 404 error
// go back to the http://localhost:8080/change // go back to the http://localhost:8080/change
// you should get 404 error again // you should get 404 error again
@ -78,6 +101,25 @@ func main() {
``` ```
- New built'n Middleware: `iris.Prioritize(route)` in order to give priority to a route inside other handler (used internally on StaticWeb's builder)
```go
usersAPI := iris.None("/api/users/:userid", func(ctx *iris.Context) {
ctx.Writef("user with id: %s", ctx.Param("userid"))
})("api.users.id") // we need to call empty ("") in order to get its iris.Route instance
// or ("the name of the route")
// which later on can be found with iris.Lookup("the name of the route")
static := iris.StaticHandler("/", "./www", false, false)
// manually give a priority to the usersAPI, if not found then continue to the static handler
iris.Get("/*file", iris.Prioritize(usersAPI), static)
iris.Get("/*file", static)
iris.Listen(":8080")
```
## 6.0.9 -> 6.1.0 ## 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")` - Fix a not found error when serving static files through custom subdomain, this should work again: `iris.Party("mysubdomain.").StaticWeb("/", "./static")`

View File

@ -177,11 +177,15 @@ func (ctx *Context) GetHandlerName() string {
return runtime.FuncForPC(reflect.ValueOf(ctx.Middleware[len(ctx.Middleware)-1]).Pointer()).Name() 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. // ExecRoute calls any route (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 // 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 // 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 can validate paths, has sessions, path parameters and all.
// //
// You can find the Route by iris.Lookup("theRouteName")
// you can set a route name as: myRoute := iris.Get("/mypath", handler)("theRouteName")
// that will set a name to the route and returns its iris.Route instance for further usage.
//
// It doesn't changes the global state, if a route was "offline" it remains offline. // It doesn't changes the global state, if a route was "offline" it remains offline.
// //
// see ExecRouteAgainst(routeName, againstRequestPath string), // see ExecRouteAgainst(routeName, againstRequestPath string),
@ -189,16 +193,20 @@ func (ctx *Context) GetHandlerName() string {
// For more details look: https://github.com/kataras/iris/issues/585 // For more details look: https://github.com/kataras/iris/issues/585
// //
// Example: https://github.com/iris-contrib/examples/tree/master/route_state // Example: https://github.com/iris-contrib/examples/tree/master/route_state
func (ctx *Context) ExecRoute(routeName string) *Context { func (ctx *Context) ExecRoute(r Route) *Context {
return ctx.ExecRouteAgainst(routeName, ctx.Path()) return ctx.ExecRouteAgainst(r, ctx.Path())
} }
// ExecRouteAgainst calls any route by its name (mostly "offline" route) against a 'virtually' request path // ExecRouteAgainst calls any iris.Route against a 'virtually' request path
// like it was requested by the user, but it is not. // 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 // 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 // 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 can validate paths, has sessions, path parameters and all.
// //
// You can find the Route by iris.Lookup("theRouteName")
// you can set a route name as: myRoute := iris.Get("/mypath", handler)("theRouteName")
// that will set a name to the route and returns its iris.Route instance for further usage.
//
// It doesn't changes the global state, if a route was "offline" it remains offline. // It doesn't changes the global state, if a route was "offline" it remains offline.
// //
// see ExecRoute(routeName), // see ExecRoute(routeName),
@ -206,9 +214,7 @@ func (ctx *Context) ExecRoute(routeName string) *Context {
// For more details look: https://github.com/kataras/iris/issues/585 // For more details look: https://github.com/kataras/iris/issues/585
// //
// Example: https://github.com/iris-contrib/examples/tree/master/route_state // Example: https://github.com/iris-contrib/examples/tree/master/route_state
func (ctx *Context) ExecRouteAgainst(routeName string, againstRequestPath string) *Context { func (ctx *Context) ExecRouteAgainst(r Route, againstRequestPath string) *Context {
r := ctx.framework.Lookup(routeName)
if r != nil { if r != nil {
context := &(*ctx) context := &(*ctx)
context.Middleware = context.Middleware[0:0] context.Middleware = context.Middleware[0:0]
@ -220,10 +226,42 @@ func (ctx *Context) ExecRouteAgainst(routeName string, againstRequestPath string
return context return context
} }
} }
// if failed return nil in order to this fail to be catchable
return nil return nil
} }
// Prioritize is a middleware which executes a route against this path
// when the request's Path has a prefix of the route's STATIC PART
// is not executing ExecRoute to determinate if it's valid, for performance reasons
// 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 set a route name as: myRoute := iris.Get("/mypath", handler)("theRouteName")
// that will set a name to the route and returns its iris.Route instance for further usage.
//
// if the route found then it executes that and don't continue to the next handler
// if not found then continue to the next handler
func Prioritize(r Route) HandlerFunc {
if r != nil {
return func(ctx *Context) {
reqPath := ctx.Path()
if strings.HasPrefix(reqPath, r.StaticPath()) {
newctx := ctx.ExecRouteAgainst(r, reqPath)
if newctx == nil { // route not found.
ctx.EmitError(StatusNotFound)
}
return
}
// execute the next handler if no prefix
// here look, the only error we catch is the 404,
// we can't go ctx.Next() and believe that the next handler will manage the error
// because it will not, we are not on the router.
ctx.Next()
}
}
return func(ctx *Context) { ctx.Next() }
}
// ------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------
// -----------------------------Request URL, Method, IP & Headers getters--------------- // -----------------------------Request URL, Method, IP & Headers getters---------------

32
http.go
View File

@ -698,8 +698,16 @@ type (
Subdomain() string Subdomain() string
// Method returns the http method // Method returns the http method
Method() string Method() string
// SetMethod sets the route's method
// requires re-build of the iris.Router
SetMethod(string)
// Path returns the path // Path returns the path
Path() string Path() string
// staticPath returns the static part of the path
StaticPath() string
// SetPath changes/sets the path for this route // SetPath changes/sets the path for this route
SetPath(string) SetPath(string)
// Middleware returns the slice of Handler([]Handler) registered to this route // Middleware returns the slice of Handler([]Handler) registered to this route
@ -716,6 +724,7 @@ type (
subdomain string subdomain string
method string method string
path string path string
staticPath string
middleware Middleware middleware Middleware
formattedPath string formattedPath string
formattedParts int formattedParts int
@ -740,6 +749,7 @@ var _ Route = &route{}
func newRoute(method string, subdomain string, path string, middleware Middleware) *route { func newRoute(method string, subdomain string, path string, middleware Middleware) *route {
r := &route{name: path + subdomain, method: method, subdomain: subdomain, path: path, middleware: middleware} r := &route{name: path + subdomain, method: method, subdomain: subdomain, path: path, middleware: middleware}
r.formatPath() r.formatPath()
r.calculateStaticPath()
return r return r
} }
@ -773,8 +783,20 @@ func (r *route) formatPath() {
r.formattedPath = tempPath r.formattedPath = tempPath
} }
func (r *route) setName(newName string) { func (r *route) calculateStaticPath() {
for i := 0; i < len(r.path); i++ {
if r.path[i] == matchEverythingByte || r.path[i] == parameterStartByte {
r.staticPath = r.path[0 : i-1] // stop at the first dynamic path symbol and set the static path to its [0:previous]
return
}
}
// not a dynamic symbol found, set its static path to its path.
r.staticPath = r.path
}
func (r *route) setName(newName string) Route {
r.name = newName r.name = newName
return r
} }
func (r route) Name() string { func (r route) Name() string {
@ -789,10 +811,18 @@ func (r route) Method() string {
return r.method return r.method
} }
func (r *route) SetMethod(method string) {
r.method = method
}
func (r route) Path() string { func (r route) Path() string {
return r.path return r.path
} }
func (r route) StaticPath() string {
return r.staticPath
}
func (r *route) SetPath(s string) { func (r *route) SetPath(s string) {
r.path = s r.path = s
} }

View File

@ -734,36 +734,34 @@ func TestRedirectHTTPS(t *testing.T) {
func TestRouteStateSimple(t *testing.T) { func TestRouteStateSimple(t *testing.T) {
iris.ResetDefault() iris.ResetDefault()
// here
offlineRouteName := "user.api"
offlineRoutePath := "/api/user/:userid" offlineRoutePath := "/api/user/:userid"
offlineRouteRequestedTestPath := "/api/user/42" offlineRouteRequestedTestPath := "/api/user/42"
offlineBody := "user with id: 42" offlineBody := "user with id: 42"
iris.None(offlineRoutePath, func(ctx *iris.Context) { offlineRoute := iris.None(offlineRoutePath, func(ctx *iris.Context) {
userid := ctx.Param("userid") userid := ctx.Param("userid")
if userid != "42" { if userid != "42" {
// we are expecting userid 42 always in this test so // we are expecting userid 42 always in this test so
t.Fatalf("what happened? expected userid to be 42 but got %s", userid) t.Fatalf("what happened? expected userid to be 42 but got %s", userid)
} }
ctx.Writef(offlineBody) ctx.Writef(offlineBody)
})(offlineRouteName) })("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 // change the "user.api" state from offline to online and online to offline
iris.Get("/change", func(ctx *iris.Context) { iris.Get("/change", func(ctx *iris.Context) {
// here // here
if iris.Lookup(offlineRouteName).IsOnline() { if offlineRoute.IsOnline() {
// set to offline // set to offline
iris.SetRouteOffline(offlineRouteName) iris.SetRouteOffline(offlineRoute)
} else { } else {
// set to online if it was not online(so it was offline) // set to online if it was not online(so it was offline)
iris.SetRouteOnline(offlineRouteName, iris.MethodGet) iris.SetRouteOnline(offlineRoute, iris.MethodGet)
} }
}) })
iris.Get("/execute", func(ctx *iris.Context) { iris.Get("/execute", func(ctx *iris.Context) {
// here // here
ctx.ExecRouteAgainst(offlineRouteName, "/api/user/42") ctx.ExecRouteAgainst(offlineRoute, "/api/user/42")
}) })
hello := "Hello from index" hello := "Hello from index"

82
iris.go
View File

@ -171,9 +171,9 @@ type (
Lookup(routeName string) Route Lookup(routeName string) Route
Lookups() []Route Lookups() []Route
SetRouteOnline(routeName string, HTTPMethod string) bool SetRouteOnline(r Route, HTTPMethod string) bool
SetRouteOffline(routeName string) bool SetRouteOffline(r Route) bool
ChangeRouteState(routeName string, HTTPMethod string) bool ChangeRouteState(r Route, HTTPMethod string) bool
Path(routeName string, optionalPathParameters ...interface{}) (routePath string) Path(routeName string, optionalPathParameters ...interface{}) (routePath string)
URL(routeName string, optionalPathParameters ...interface{}) (routeURL string) URL(routeName string, optionalPathParameters ...interface{}) (routeURL string)
@ -219,8 +219,8 @@ type (
StaticEmbedded(reqRelativePath string, contentType string, assets func(string) ([]byte, error), assetsNames func() []string) RouteNameFunc StaticEmbedded(reqRelativePath string, contentType string, assets func(string) ([]byte, error), assetsNames func() []string) RouteNameFunc
Favicon(systemFilePath string, optionalReqRelativePath ...string) RouteNameFunc Favicon(systemFilePath string, optionalReqRelativePath ...string) RouteNameFunc
// static file system // static file system
StaticHandler(reqRelativePath string, systemPath string, showList bool, enableGzip bool) HandlerFunc StaticHandler(reqRelativePath string, systemPath string, showList bool, enableGzip bool, exceptRoutes ...Route) HandlerFunc
StaticWeb(reqRelativePath string, systemPath string) RouteNameFunc StaticWeb(reqRelativePath string, systemPath string, exceptRoutes ...Route) RouteNameFunc
// party layout for template engines // party layout for template engines
Layout(layoutTemplateFileName string) MuxAPI Layout(layoutTemplateFileName string) MuxAPI
@ -231,7 +231,12 @@ type (
} }
// RouteNameFunc the func returns from the MuxAPi's methods, optionally sets the name of the Route (*route) // RouteNameFunc the func returns from the MuxAPi's methods, optionally sets the name of the Route (*route)
RouteNameFunc func(customRouteName string) //
// You can find the Route by iris.Lookup("theRouteName")
// you can set a route name as: myRoute := iris.Get("/mypath", handler)("theRouteName")
// that will set a name to the route and returns its iris.Route instance for further usage.
//
RouteNameFunc func(customRouteName string) Route
) )
// Framework is our God |\| Google.Search('Greek mythology Iris') // Framework is our God |\| Google.Search('Greek mythology Iris')
@ -1060,8 +1065,8 @@ func (s *Framework) Lookups() (routes []Route) {
// For more details look: https://github.com/kataras/iris/issues/585 // For more details look: https://github.com/kataras/iris/issues/585
// //
// Example: https://github.com/iris-contrib/examples/tree/master/route_state // Example: https://github.com/iris-contrib/examples/tree/master/route_state
func SetRouteOnline(routeName string, HTTPMethod string) bool { func SetRouteOnline(r Route, HTTPMethod string) bool {
return Default.SetRouteOnline(routeName, HTTPMethod) return Default.SetRouteOnline(r, HTTPMethod)
} }
// SetRouteOffline sets the state of the route to "offline" and re-builds the router // SetRouteOffline sets the state of the route to "offline" and re-builds the router
@ -1073,8 +1078,8 @@ func SetRouteOnline(routeName string, HTTPMethod string) bool {
// For more details look: https://github.com/kataras/iris/issues/585 // For more details look: https://github.com/kataras/iris/issues/585
// //
// Example: https://github.com/iris-contrib/examples/tree/master/route_state // Example: https://github.com/iris-contrib/examples/tree/master/route_state
func SetRouteOffline(routeName string) bool { func SetRouteOffline(r Route) bool {
return Default.SetRouteOffline(routeName) return Default.SetRouteOffline(r)
} }
// ChangeRouteState changes the state of the route. // ChangeRouteState changes the state of the route.
@ -1089,23 +1094,23 @@ func SetRouteOffline(routeName string) bool {
// For more details look: https://github.com/kataras/iris/issues/585 // For more details look: https://github.com/kataras/iris/issues/585
// //
// Example: https://github.com/iris-contrib/examples/tree/master/route_state // Example: https://github.com/iris-contrib/examples/tree/master/route_state
func ChangeRouteState(routeName string, HTTPMethod string) bool { func ChangeRouteState(r Route, HTTPMethod string) bool {
return Default.ChangeRouteState(routeName, HTTPMethod) return Default.ChangeRouteState(r, HTTPMethod)
} }
// SetRouteOnline sets the state of the route to "online" with a specific http method // SetRouteOnline sets the state of the route to "online" with a specific http method
// it re-builds the router // it re-builds the router
// //
// returns true if state was actually changed // returns true if state was actually changed
func (s *Framework) SetRouteOnline(routeName string, HTTPMethod string) bool { func (s *Framework) SetRouteOnline(r Route, HTTPMethod string) bool {
return s.ChangeRouteState(routeName, HTTPMethod) return s.ChangeRouteState(r, HTTPMethod)
} }
// SetRouteOffline sets the state of the route to "offline" and re-builds the router // SetRouteOffline sets the state of the route to "offline" and re-builds the router
// //
// returns true if state was actually changed // returns true if state was actually changed
func (s *Framework) SetRouteOffline(routeName string) bool { func (s *Framework) SetRouteOffline(r Route) bool {
return s.ChangeRouteState(routeName, MethodNone) return s.ChangeRouteState(r, MethodNone)
} }
// ChangeRouteState changes the state of the route. // ChangeRouteState changes the state of the route.
@ -1114,15 +1119,14 @@ func (s *Framework) SetRouteOffline(routeName string) bool {
// it re-builds the router // it re-builds the router
// //
// returns true if state was actually changed // returns true if state was actually changed
func (s *Framework) ChangeRouteState(routeName string, HTTPMethod string) bool { func (s *Framework) ChangeRouteState(r Route, HTTPMethod string) bool {
r := s.mux.lookup(routeName)
nonSpecificMethod := len(HTTPMethod) == 0
if r != nil { if r != nil {
if r.method != HTTPMethod { nonSpecificMethod := len(HTTPMethod) == 0
if r.Method() != HTTPMethod {
if nonSpecificMethod { if nonSpecificMethod {
r.method = MethodGet // if no method given, then do it for "GET" only r.SetMethod(MethodGet) // if no method given, then do it for "GET" only
} else { } else {
r.method = HTTPMethod r.SetMethod(HTTPMethod)
} }
// re-build the router/main handler // re-build the router/main handler
s.Router = ToNativeHandler(s, s.mux.BuildHandler()) s.Router = ToNativeHandler(s, s.mux.BuildHandler())
@ -2064,32 +2068,13 @@ func (api *muxAPI) Favicon(favPath string, requestPath ...string) RouteNameFunc
return api.registerResourceRoute(reqPath, h) return api.registerResourceRoute(reqPath, h)
} }
// StripPrefix returns a handler that serves HTTP requests
// by removing the given prefix from the request URL's Path
// and invoking the handler h. StripPrefix handles a
// request for a path that doesn't begin with prefix by
// replying with an HTTP 404 not found error.
func StripPrefix(prefix string, h HandlerFunc) HandlerFunc {
if prefix == "" {
return h
}
return func(ctx *Context) {
if p := strings.TrimPrefix(ctx.Request.URL.Path, prefix); len(p) < len(ctx.Request.URL.Path) {
ctx.Request.URL.Path = p
h(ctx)
} else {
ctx.NotFound()
}
}
}
// StaticHandler returns a new Handler which serves static files // StaticHandler returns a new Handler which serves static files
func StaticHandler(reqPath string, systemPath string, showList bool, enableGzip bool) HandlerFunc { func StaticHandler(reqPath string, systemPath string, showList bool, enableGzip bool) HandlerFunc {
return Default.StaticHandler(reqPath, systemPath, showList, enableGzip) return Default.StaticHandler(reqPath, systemPath, showList, enableGzip)
} }
// StaticHandler returns a new Handler which serves static files // StaticHandler returns a new Handler which serves static files
func (api *muxAPI) StaticHandler(reqPath string, systemPath string, showList bool, enableGzip bool) HandlerFunc { func (api *muxAPI) StaticHandler(reqPath string, systemPath string, showList bool, enableGzip bool, exceptRoutes ...Route) HandlerFunc {
// here we separate the path from the subdomain (if any), we care only for the path // here we separate the path from the subdomain (if any), we care only for the path
// fixes a bug when serving static files via a subdomain // fixes a bug when serving static files via a subdomain
fullpath := api.relativePath + reqPath fullpath := api.relativePath + reqPath
@ -2102,6 +2087,7 @@ func (api *muxAPI) StaticHandler(reqPath string, systemPath string, showList boo
Path(path). Path(path).
Listing(showList). Listing(showList).
Gzip(enableGzip). Gzip(enableGzip).
Except(exceptRoutes...).
Build() Build()
managedStaticHandler := func(ctx *Context) { managedStaticHandler := func(ctx *Context) {
@ -2124,6 +2110,8 @@ func (api *muxAPI) StaticHandler(reqPath string, systemPath string, showList boo
// //
// first parameter: the route path // first parameter: the route path
// second parameter: the system directory // second parameter: the system directory
// third OPTIONAL parameter: the exception routes
// (= give priority to these routes instead of the static handler)
// for more options look iris.StaticHandler. // for more options look iris.StaticHandler.
// //
// iris.StaticWeb("/static", "./static") // iris.StaticWeb("/static", "./static")
@ -2133,8 +2121,8 @@ func (api *muxAPI) StaticHandler(reqPath string, systemPath string, showList boo
// "index.html". // "index.html".
// //
// StaticWeb calls the StaticHandler(reqPath, systemPath, listingDirectories: false, gzip: false ). // StaticWeb calls the StaticHandler(reqPath, systemPath, listingDirectories: false, gzip: false ).
func StaticWeb(reqPath string, systemPath string) RouteNameFunc { func StaticWeb(reqPath string, systemPath string, exceptRoutes ...Route) RouteNameFunc {
return Default.StaticWeb(reqPath, systemPath) return Default.StaticWeb(reqPath, systemPath, exceptRoutes...)
} }
// StaticWeb returns a handler that serves HTTP requests // StaticWeb returns a handler that serves HTTP requests
@ -2142,6 +2130,8 @@ func StaticWeb(reqPath string, systemPath string) RouteNameFunc {
// //
// first parameter: the route path // first parameter: the route path
// second parameter: the system directory // second parameter: the system directory
// third OPTIONAL parameter: the exception routes
// (= give priority to these routes instead of the static handler)
// for more options look iris.StaticHandler. // for more options look iris.StaticHandler.
// //
// iris.StaticWeb("/static", "./static") // iris.StaticWeb("/static", "./static")
@ -2151,8 +2141,8 @@ func StaticWeb(reqPath string, systemPath string) RouteNameFunc {
// "index.html". // "index.html".
// //
// StaticWeb calls the StaticHandler(reqPath, systemPath, listingDirectories: false, gzip: false ). // StaticWeb calls the StaticHandler(reqPath, systemPath, listingDirectories: false, gzip: false ).
func (api *muxAPI) StaticWeb(reqPath string, systemPath string) RouteNameFunc { func (api *muxAPI) StaticWeb(reqPath string, systemPath string, exceptRoutes ...Route) RouteNameFunc {
h := api.StaticHandler(reqPath, systemPath, false, false) h := api.StaticHandler(reqPath, systemPath, false, false, exceptRoutes...)
routePath := validateWildcard(reqPath, "file") routePath := validateWildcard(reqPath, "file")
return api.registerResourceRoute(routePath, h) return api.registerResourceRoute(routePath, h)
} }

View File

@ -14,6 +14,7 @@ type StaticHandlerBuilder interface {
Gzip(enable bool) StaticHandlerBuilder Gzip(enable bool) StaticHandlerBuilder
Listing(listDirectoriesOnOff bool) StaticHandlerBuilder Listing(listDirectoriesOnOff bool) StaticHandlerBuilder
StripPath(yesNo bool) StaticHandlerBuilder StripPath(yesNo bool) StaticHandlerBuilder
Except(r ...Route) StaticHandlerBuilder
Build() HandlerFunc Build() HandlerFunc
} }
@ -27,6 +28,7 @@ type webfs struct {
// these are init on the Build() call // these are init on the Build() call
filesystem http.FileSystem filesystem http.FileSystem
once sync.Once once sync.Once
exceptions []Route
handler HandlerFunc handler HandlerFunc
} }
@ -88,6 +90,13 @@ func (w *webfs) StripPath(yesNo bool) StaticHandlerBuilder {
return w return w
} }
// Except add a route exception,
// gives priority to that Route over the static handler.
func (w *webfs) Except(r ...Route) StaticHandlerBuilder {
w.exceptions = append(w.exceptions, r...)
return w
}
type ( type (
noListFile struct { noListFile struct {
http.File http.File
@ -130,7 +139,7 @@ func (w *webfs) Build() HandlerFunc {
fsHandler = http.StripPrefix(prefix, fileserver) fsHandler = http.StripPrefix(prefix, fileserver)
} }
w.handler = func(ctx *Context) { h := func(ctx *Context) {
writer := ctx.ResponseWriter writer := ctx.ResponseWriter
if w.gzip && ctx.clientAllowsGzip() { if w.gzip && ctx.clientAllowsGzip() {
@ -143,7 +152,41 @@ func (w *webfs) Build() HandlerFunc {
fsHandler.ServeHTTP(writer, ctx.Request) fsHandler.ServeHTTP(writer, ctx.Request)
} }
if len(w.exceptions) > 0 {
middleware := make(Middleware, len(w.exceptions)+1)
for i := range w.exceptions {
middleware[i] = Prioritize(w.exceptions[i])
}
middleware[len(w.exceptions)] = HandlerFunc(h)
w.handler = func(ctx *Context) {
ctx.Middleware = append(middleware, ctx.Middleware...)
ctx.Do()
}
} else {
w.handler = h
}
}) })
return w.handler return w.handler
} }
// StripPrefix returns a handler that serves HTTP requests
// by removing the given prefix from the request URL's Path
// and invoking the handler h. StripPrefix handles a
// request for a path that doesn't begin with prefix by
// replying with an HTTP 404 not found error.
func StripPrefix(prefix string, h HandlerFunc) HandlerFunc {
if prefix == "" {
return h
}
return func(ctx *Context) {
if p := strings.TrimPrefix(ctx.Request.URL.Path, prefix); len(p) < len(ctx.Request.URL.Path) {
ctx.Request.URL.Path = p
h(ctx)
} else {
ctx.NotFound()
}
}
}