mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
HTTP error handlers per Party (docs and details in progress)
Former-commit-id: 7092ebed556b56d9f1769b9b23f2340c2a3a18f7
This commit is contained in:
parent
3657aaf240
commit
c039730521
|
@ -8,6 +8,8 @@ Descubra lo que [otros dicen sobre Iris](https://iris-go.com/testimonials/) y **
|
|||
|
||||
[![](https://media.giphy.com/media/j5WLmtvwn98VPrm7li/giphy.gif)](https://iris-go.com/testimonials/)
|
||||
|
||||
[![Benchmarks: Apr 2, 2020 at 12:13pm (UTC)](https://iris-go.com/images/benchmarks.svg)](https://github.com/kataras/server-benchmarks)
|
||||
|
||||
## Aprende Iris
|
||||
|
||||
<details>
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
[![](https://media.giphy.com/media/j5WLmtvwn98VPrm7li/giphy.gif)](https://iris-go.com/testimonials/)
|
||||
|
||||
[![Benchmarks: Apr 2, 2020 at 12:13pm (UTC)](https://iris-go.com/images/benchmarks.svg)](https://github.com/kataras/server-benchmarks)
|
||||
|
||||
## آموزش آیریس
|
||||
|
||||
<details>
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
[![](https://media.giphy.com/media/j5WLmtvwn98VPrm7li/giphy.gif)](https://iris-go.com/testimonials/)
|
||||
|
||||
[![Benchmarks: Apr 2, 2020 at 12:13pm (UTC)](https://iris-go.com/images/benchmarks.svg)](https://github.com/kataras/server-benchmarks)
|
||||
|
||||
## Μαθαίνοντας το Iris
|
||||
|
||||
<details>
|
||||
|
|
|
@ -8,6 +8,8 @@ Iris는 단순하고 빠르며 좋은 성능과 모든 기능을 갖춘 Go언어
|
|||
|
||||
[![](https://media.giphy.com/media/j5WLmtvwn98VPrm7li/giphy.gif)](https://iris-go.com/testimonials/)
|
||||
|
||||
[![Benchmarks: Apr 2, 2020 at 12:13pm (UTC)](https://iris-go.com/images/benchmarks.svg)](https://github.com/kataras/server-benchmarks)
|
||||
|
||||
## Iris 배우기
|
||||
|
||||
<details>
|
||||
|
|
|
@ -7,6 +7,8 @@ Iris — это быстрый, простой, но полнофункцион
|
|||
|
||||
[![](https://media.giphy.com/media/j5WLmtvwn98VPrm7li/giphy.gif)](https://iris-go.com/testimonials/)
|
||||
|
||||
[![Benchmarks: Apr 2, 2020 at 12:13pm (UTC)](https://iris-go.com/images/benchmarks.svg)](https://github.com/kataras/server-benchmarks)
|
||||
|
||||
## Изучение Iris
|
||||
|
||||
<details>
|
||||
|
|
|
@ -8,6 +8,8 @@ Iris 是基于 Go 编写的一个快速,简单但功能齐全且非常高效
|
|||
|
||||
[![](https://media.giphy.com/media/j5WLmtvwn98VPrm7li/giphy.gif)](https://iris-go.com/testimonials/)
|
||||
|
||||
[![Benchmarks: Apr 2, 2020 at 12:13pm (UTC)](https://iris-go.com/images/benchmarks.svg)](https://github.com/kataras/server-benchmarks)
|
||||
|
||||
## 学习 Iris
|
||||
|
||||
<details>
|
||||
|
|
|
@ -13,7 +13,8 @@ import (
|
|||
- Build should builds the handler, it's being called on router's BuildRouter.
|
||||
Build(provider router.RoutesProvider) error
|
||||
- RouteExists reports whether a particular route exists.
|
||||
RouteExists(ctx iris.Context, method, path string) bool
|
||||
RouteExists(ctx iris.Context, method, path string) bool
|
||||
- FireErrorCode(ctx context.Context) should handle the given ctx.GetStatusCode().
|
||||
|
||||
For a more detailed, complete and useful example
|
||||
you can take a look at the iris' router itself which is located at:
|
||||
|
@ -44,7 +45,7 @@ func (r *customRouter) HandleRequest(ctx iris.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
ctx.SetCurrentRouteName(route.Name)
|
||||
ctx.SetCurrentRoute(route.ReadOnly)
|
||||
ctx.Do(route.Handlers)
|
||||
return
|
||||
}
|
||||
|
@ -71,6 +72,11 @@ func (r *customRouter) RouteExists(ctx iris.Context, method, path string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (r *customRouter) FireErrorCode(ctx iris.Context) {
|
||||
// responseStatusCode := ctx.GetStatusCode() // set by prior ctx.StatusCode calls
|
||||
// [...]
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := iris.New()
|
||||
|
||||
|
|
|
@ -61,9 +61,6 @@ type Application interface {
|
|||
|
||||
// FireErrorCode executes an error http status code handler
|
||||
// based on the context's status code.
|
||||
//
|
||||
// If a handler is not already registered,
|
||||
// then it creates & registers a new trivial handler on the-fly.
|
||||
FireErrorCode(ctx Context)
|
||||
|
||||
// RouteExists reports whether a particular route exists
|
||||
|
|
|
@ -138,18 +138,13 @@ type Context interface {
|
|||
// ctx.ResetRequest(r.WithContext(stdCtx)).
|
||||
ResetRequest(r *http.Request)
|
||||
|
||||
// SetCurrentRouteName sets the route's name internally,
|
||||
// in order to be able to find the correct current "read-only" Route when
|
||||
// end-developer calls the `GetCurrentRoute()` function.
|
||||
// It's being initialized by the Router, if you change that name
|
||||
// manually nothing really happens except that you'll get other
|
||||
// route via `GetCurrentRoute()`.
|
||||
// Instead, to execute a different path
|
||||
// from this context you should use the `Exec` function
|
||||
// or change the handlers via `SetHandlers/AddHandler` functions.
|
||||
SetCurrentRouteName(currentRouteName string)
|
||||
// GetCurrentRoute returns the current registered "read-only" route that
|
||||
// was being registered to this request's path.
|
||||
// SetCurrentRoutes sets the route internally,
|
||||
// See `GetCurrentRoute()` method too.
|
||||
// It's being initialized by the Router.
|
||||
// See `Exec` or `SetHandlers/AddHandler` methods to simulate a request.
|
||||
SetCurrentRoute(route RouteReadOnly)
|
||||
// GetCurrentRoute returns the current "read-only" route that
|
||||
// was registered to this request's path.
|
||||
GetCurrentRoute() RouteReadOnly
|
||||
|
||||
// Do calls the SetHandlers(handlers)
|
||||
|
@ -175,7 +170,7 @@ type Context interface {
|
|||
|
||||
// HandlerIndex sets the current index of the
|
||||
// current context's handlers chain.
|
||||
// If -1 passed then it just returns the
|
||||
// If n < 0 or the current handlers length is 0 then it just returns the
|
||||
// current handler index without change the current index.
|
||||
//
|
||||
// Look Handlers(), Next() and StopExecution() too.
|
||||
|
@ -1194,9 +1189,9 @@ type context struct {
|
|||
writer ResponseWriter
|
||||
// the original http.Request
|
||||
request *http.Request
|
||||
// the current route's name registered to this request path.
|
||||
currentRouteName string
|
||||
deferFunc Handler
|
||||
// the current route registered to this request path.
|
||||
currentRoute RouteReadOnly
|
||||
deferFunc Handler
|
||||
|
||||
// the local key-value storage
|
||||
params RequestParams // url named parameters.
|
||||
|
@ -1230,6 +1225,7 @@ func NewContext(app Application) Context {
|
|||
// 4. response writer to the http.ResponseWriter.
|
||||
// 5. request to the *http.Request.
|
||||
func (ctx *context) BeginRequest(w http.ResponseWriter, r *http.Request) {
|
||||
ctx.currentRoute = nil
|
||||
ctx.handlers = nil // will be filled by router.Serve/HTTP
|
||||
ctx.values = ctx.values[0:0] // >> >> by context.Values().Set
|
||||
ctx.params.Store = ctx.params.Store[0:0]
|
||||
|
@ -1266,8 +1262,8 @@ func (ctx *context) EndRequest() {
|
|||
ctx.deferFunc(ctx)
|
||||
}
|
||||
|
||||
if StatusCodeNotSuccessful(ctx.GetStatusCode()) &&
|
||||
!ctx.Application().ConfigurationReadOnly().GetDisableAutoFireStatusCode() {
|
||||
if !ctx.Application().ConfigurationReadOnly().GetDisableAutoFireStatusCode() &&
|
||||
StatusCodeNotSuccessful(ctx.GetStatusCode()) {
|
||||
// author's note:
|
||||
// if recording, the error handler can handle
|
||||
// the rollback and remove any response written before,
|
||||
|
@ -1332,23 +1328,18 @@ func (ctx *context) ResetRequest(r *http.Request) {
|
|||
ctx.request = r
|
||||
}
|
||||
|
||||
// SetCurrentRouteName sets the route's name internally,
|
||||
// in order to be able to find the correct current "read-only" Route when
|
||||
// end-developer calls the `GetCurrentRoute()` function.
|
||||
// It's being initialized by the Router, if you change that name
|
||||
// manually nothing really happens except that you'll get other
|
||||
// route via `GetCurrentRoute()`.
|
||||
// Instead, to execute a different path
|
||||
// from this context you should use the `Exec` function
|
||||
// or change the handlers via `SetHandlers/AddHandler` functions.
|
||||
func (ctx *context) SetCurrentRouteName(currentRouteName string) {
|
||||
ctx.currentRouteName = currentRouteName
|
||||
// SetCurrentRoutes sets the route internally,
|
||||
// See `GetCurrentRoute()` method too.
|
||||
// It's being initialized by the Router.
|
||||
// See `Exec` or `SetHandlers/AddHandler` methods to simulate a request.
|
||||
func (ctx *context) SetCurrentRoute(route RouteReadOnly) {
|
||||
ctx.currentRoute = route
|
||||
}
|
||||
|
||||
// GetCurrentRoute returns the current registered "read-only" route that
|
||||
// was being registered to this request's path.
|
||||
// GetCurrentRoute returns the current "read-only" route that
|
||||
// was registered to this request's path.
|
||||
func (ctx *context) GetCurrentRoute() RouteReadOnly {
|
||||
return ctx.app.GetRouteReadOnly(ctx.currentRouteName)
|
||||
return ctx.currentRoute
|
||||
}
|
||||
|
||||
// Do calls the SetHandlers(handlers)
|
||||
|
@ -1385,8 +1376,8 @@ func (ctx *context) Handlers() Handlers {
|
|||
|
||||
// HandlerIndex sets the current index of the
|
||||
// current context's handlers chain.
|
||||
// If -1 passed then it just returns the
|
||||
// current handler index without change the current index.rns that index, useless return value.
|
||||
// If n < 0 or the current handlers length is 0 then it just returns the
|
||||
// current handler index without change the current index.
|
||||
//
|
||||
// Look Handlers(), Next() and StopExecution() too.
|
||||
func (ctx *context) HandlerIndex(n int) (currentIndex int) {
|
||||
|
@ -1455,9 +1446,13 @@ func (ctx *context) HandlerFileLine() (file string, line int) {
|
|||
}
|
||||
|
||||
// RouteName returns the route name that this handler is running on.
|
||||
// Note that it will return empty on not found handlers.
|
||||
// Note that it may return empty on not found handlers.
|
||||
func (ctx *context) RouteName() string {
|
||||
return ctx.currentRouteName
|
||||
if ctx.currentRoute == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ctx.currentRoute.Name()
|
||||
}
|
||||
|
||||
// Next is the function that executed when `ctx.Next()` is called.
|
||||
|
|
|
@ -17,6 +17,10 @@ type RouteReadOnly interface {
|
|||
// Name returns the route's name.
|
||||
Name() string
|
||||
|
||||
// StatusErrorCode returns 0 for common resource routes
|
||||
// or the error code that an http error handler registered on.
|
||||
StatusErrorCode() int
|
||||
|
||||
// Method returns the route's method.
|
||||
Method() string
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ func (repo *repository) getRelative(r *Route) *Route {
|
|||
}
|
||||
|
||||
for _, route := range repo.routes {
|
||||
if r.Subdomain == route.Subdomain && r.Method == route.Method && r.FormattedPath == route.FormattedPath && !route.tmpl.IsTrailing() {
|
||||
if r.Subdomain == route.Subdomain && r.StatusCode == route.StatusCode && r.Method == route.Method && r.FormattedPath == route.FormattedPath && !route.tmpl.IsTrailing() {
|
||||
return route
|
||||
}
|
||||
}
|
||||
|
@ -100,12 +100,16 @@ func (repo *repository) register(route *Route, rule RouteRegisterRule) (*Route,
|
|||
}
|
||||
}
|
||||
|
||||
// fmt.Printf("repo.routes append:\t%#+v\n\n", route)
|
||||
repo.routes = append(repo.routes, route)
|
||||
if repo.pos == nil {
|
||||
repo.pos = make(map[string]int)
|
||||
|
||||
if route.StatusCode == 0 { // a common resource route, not a status code error handler.
|
||||
if repo.pos == nil {
|
||||
repo.pos = make(map[string]int)
|
||||
}
|
||||
repo.pos[route.tmpl.Src] = len(repo.routes) - 1
|
||||
}
|
||||
|
||||
repo.pos[route.tmpl.Src] = len(repo.routes) - 1
|
||||
return route, nil
|
||||
}
|
||||
|
||||
|
@ -117,8 +121,6 @@ type APIBuilder struct {
|
|||
|
||||
// the api builder global macros registry
|
||||
macros *macro.Macros
|
||||
// the api builder global handlers per status code registry (used for custom http errors)
|
||||
errorCodeHandlers *ErrorCodeHandlers
|
||||
// the api builder global routes repository
|
||||
routes *repository
|
||||
|
||||
|
@ -164,12 +166,11 @@ var _ RoutesProvider = (*APIBuilder)(nil) // passed to the default request handl
|
|||
// which is responsible to build the API and the router handler.
|
||||
func NewAPIBuilder() *APIBuilder {
|
||||
return &APIBuilder{
|
||||
macros: macro.Defaults,
|
||||
errorCodeHandlers: defaultErrorCodeHandlers(),
|
||||
errors: errgroup.New("API Builder"),
|
||||
relativePath: "/",
|
||||
routes: new(repository),
|
||||
apiBuilderDI: &APIContainer{Container: hero.New()},
|
||||
macros: macro.Defaults,
|
||||
errors: errgroup.New("API Builder"),
|
||||
relativePath: "/",
|
||||
routes: new(repository),
|
||||
apiBuilderDI: &APIContainer{Container: hero.New()},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,7 +275,11 @@ func (api *APIBuilder) SetRegisterRule(rule RouteRegisterRule) Party {
|
|||
//
|
||||
// Returns a *Route, app will throw any errors later on.
|
||||
func (api *APIBuilder) Handle(method string, relativePath string, handlers ...context.Handler) *Route {
|
||||
routes := api.CreateRoutes([]string{method}, relativePath, handlers...)
|
||||
return api.handle(0, method, relativePath, handlers...)
|
||||
}
|
||||
|
||||
func (api *APIBuilder) handle(errorCode int, method string, relativePath string, handlers ...context.Handler) *Route {
|
||||
routes := api.createRoutes(errorCode, []string{method}, relativePath, handlers...)
|
||||
|
||||
var route *Route // the last one is returned.
|
||||
var err error
|
||||
|
@ -282,6 +287,7 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
|
|||
if route == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// global
|
||||
|
||||
route.topLink = api.routes.getRelative(route)
|
||||
|
@ -417,8 +423,18 @@ func (api *APIBuilder) HandleDir(requestPath, directory string, opts ...DirOptio
|
|||
// This method can be used for third-parties Iris helpers packages and tools
|
||||
// that want a more detailed view of Party-based Routes before take the decision to register them.
|
||||
func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handlers ...context.Handler) []*Route {
|
||||
if len(methods) == 0 || methods[0] == "ALL" || methods[0] == "ANY" { // then use like it was .Any
|
||||
return api.Any(relativePath, handlers...)
|
||||
return api.createRoutes(0, methods, relativePath, handlers...)
|
||||
}
|
||||
|
||||
func (api *APIBuilder) createRoutes(errorCode int, methods []string, relativePath string, handlers ...context.Handler) []*Route {
|
||||
if statusCodeSuccessful(errorCode) {
|
||||
errorCode = 0
|
||||
}
|
||||
|
||||
if errorCode == 0 {
|
||||
if len(methods) == 0 || methods[0] == "ALL" || methods[0] == "ANY" { // then use like it was .Any
|
||||
return api.Any(relativePath, handlers...)
|
||||
}
|
||||
}
|
||||
|
||||
// no clean path yet because of subdomain indicator/separator which contains a dot.
|
||||
|
@ -444,11 +460,17 @@ func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handl
|
|||
// So if we just put `api.middleware` or `api.doneHandlers`
|
||||
// then the next `Party` will have those updated handlers
|
||||
// but dev may change the rules for that child Party, so we have to make clones of them here.
|
||||
|
||||
var (
|
||||
beginHandlers = joinHandlers(api.middleware, context.Handlers{})
|
||||
doneHandlers = joinHandlers(api.doneHandlers, context.Handlers{})
|
||||
beginHandlers context.Handlers
|
||||
doneHandlers context.Handlers
|
||||
)
|
||||
|
||||
if errorCode == 0 {
|
||||
beginHandlers = joinHandlers(api.middleware, beginHandlers)
|
||||
doneHandlers = joinHandlers(api.doneHandlers, doneHandlers)
|
||||
}
|
||||
|
||||
mainHandlers := context.Handlers(handlers)
|
||||
// before join the middleware + handlers + done handlers and apply the execution rules.
|
||||
|
||||
|
@ -476,8 +498,8 @@ func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handl
|
|||
|
||||
routes := make([]*Route, len(methods))
|
||||
|
||||
for i, m := range methods {
|
||||
route, err := NewRoute(m, subdomain, path, routeHandlers, *api.macros)
|
||||
for i, m := range methods { // single, empty method for error handlers.
|
||||
route, err := NewRoute(errorCode, m, subdomain, path, routeHandlers, *api.macros)
|
||||
if err != nil { // template path parser errors:
|
||||
api.errors.Addf("[%s:%d] %v -> %s:%s:%s", filename, line, err, m, subdomain, path)
|
||||
continue
|
||||
|
@ -523,6 +545,13 @@ func removeDuplicates(elements []string) (result []string) {
|
|||
//
|
||||
// You can even declare a subdomain with relativePath as "mysub." or see `Subdomain`.
|
||||
func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) Party {
|
||||
// if app.Party("/"), root party, then just add the middlewares
|
||||
// and return itself.
|
||||
if api.relativePath == "/" && (relativePath == "" || relativePath == "/") {
|
||||
api.Use(handlers...)
|
||||
return api
|
||||
}
|
||||
|
||||
parentPath := api.relativePath
|
||||
dot := string(SubdomainPrefix[0])
|
||||
if len(parentPath) > 0 && parentPath[0] == '/' && strings.HasSuffix(relativePath, dot) {
|
||||
|
@ -557,7 +586,6 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P
|
|||
// global/api builder
|
||||
macros: api.macros,
|
||||
routes: api.routes,
|
||||
errorCodeHandlers: api.errorCodeHandlers,
|
||||
beginGlobalHandlers: api.beginGlobalHandlers,
|
||||
doneGlobalHandlers: api.doneGlobalHandlers,
|
||||
errors: api.errors,
|
||||
|
@ -674,7 +702,7 @@ func (api *APIBuilder) GetRoutesReadOnly() []context.RouteReadOnly {
|
|||
routes := api.GetRoutes()
|
||||
readOnlyRoutes := make([]context.RouteReadOnly, len(routes))
|
||||
for i, r := range routes {
|
||||
readOnlyRoutes[i] = routeReadOnlyWrapper{r}
|
||||
readOnlyRoutes[i] = r.ReadOnly
|
||||
}
|
||||
|
||||
return readOnlyRoutes
|
||||
|
@ -692,7 +720,7 @@ func (api *APIBuilder) GetRouteReadOnly(routeName string) context.RouteReadOnly
|
|||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
return routeReadOnlyWrapper{r}
|
||||
return r.ReadOnly
|
||||
}
|
||||
|
||||
// GetRouteReadOnlyByPath returns the registered read-only route based on the template path (`Route.Tmpl().Src`).
|
||||
|
@ -702,7 +730,7 @@ func (api *APIBuilder) GetRouteReadOnlyByPath(tmplPath string) context.RouteRead
|
|||
return nil
|
||||
}
|
||||
|
||||
return routeReadOnlyWrapper{r}
|
||||
return r.ReadOnly
|
||||
}
|
||||
|
||||
// Use appends Handler(s) to the current Party's routes and child routes.
|
||||
|
@ -951,11 +979,25 @@ func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route {
|
|||
// and/or disable the gzip if gzip response recorder
|
||||
// was active.
|
||||
func (api *APIBuilder) OnErrorCode(statusCode int, handlers ...context.Handler) {
|
||||
if len(api.beginGlobalHandlers) > 0 {
|
||||
handlers = joinHandlers(api.beginGlobalHandlers, handlers)
|
||||
// TODO: think a stable way for that and document it so end-developers
|
||||
// not be suprised. Many feature requests in the past asked for that per-party error handlers.
|
||||
if api.relativePath != "/" {
|
||||
api.handle(statusCode, "", "/{tail:path}", handlers...)
|
||||
}
|
||||
|
||||
api.errorCodeHandlers.Register(statusCode, handlers...)
|
||||
api.handle(statusCode, "", "/", handlers...)
|
||||
|
||||
// if api.relativePath != "/" /* root is OK, no need to wildcard, see handler.go */ &&
|
||||
// !strings.HasSuffix(api.relativePath, "}") /* and not /users/{id:int} */ {
|
||||
// // We need to register the /users and the /users/{tail:path},
|
||||
// api.handle(statusCode, "", "/{tail:path}", handlers...)
|
||||
// }
|
||||
|
||||
// if strings.HasSuffix(api.relativePath, "/") {
|
||||
// api.handle(statusCode, "", "/", handlers...)
|
||||
// }
|
||||
|
||||
// api.handle(statusCode, "", "/{tail:path}", handlers...)
|
||||
}
|
||||
|
||||
// OnAnyErrorCode registers a handler which called when error status code written.
|
||||
|
@ -972,15 +1014,6 @@ func (api *APIBuilder) OnAnyErrorCode(handlers ...context.Handler) {
|
|||
}
|
||||
}
|
||||
|
||||
// FireErrorCode executes an error http status code handler
|
||||
// based on the context's status code.
|
||||
//
|
||||
// If a handler is not already registered,
|
||||
// it creates and registers a new trivial handler on the-fly.
|
||||
func (api *APIBuilder) FireErrorCode(ctx context.Context) {
|
||||
api.errorCodeHandlers.Fire(ctx)
|
||||
}
|
||||
|
||||
// Layout overrides the parent template layout with a more specific layout for this Party.
|
||||
// It returns the current Party.
|
||||
//
|
||||
|
|
|
@ -15,26 +15,42 @@ import (
|
|||
"github.com/kataras/pio"
|
||||
)
|
||||
|
||||
// RequestHandler the middle man between acquiring a context and releasing it.
|
||||
// By-default is the router algorithm.
|
||||
type RequestHandler interface {
|
||||
// HandleRequest should handle the request based on the Context.
|
||||
HandleRequest(ctx context.Context)
|
||||
// Build should builds the handler, it's being called on router's BuildRouter.
|
||||
Build(provider RoutesProvider) error
|
||||
// RouteExists reports whether a particular route exists.
|
||||
RouteExists(ctx context.Context, method, path string) bool
|
||||
}
|
||||
type (
|
||||
// RequestHandler the middle man between acquiring a context and releasing it.
|
||||
// By-default is the router algorithm.
|
||||
RequestHandler interface {
|
||||
// Note: A different interface in order to hide the rest of the implementation.
|
||||
// We only need the `FireErrorCode` to be accessible through the Iris application (see `iris.go#Build`)
|
||||
HTTPErrorHandler
|
||||
|
||||
// HandleRequest should handle the request based on the Context.
|
||||
HandleRequest(ctx context.Context)
|
||||
// Build should builds the handler, it's being called on router's BuildRouter.
|
||||
Build(provider RoutesProvider) error
|
||||
// RouteExists reports whether a particular route exists.
|
||||
RouteExists(ctx context.Context, method, path string) bool
|
||||
}
|
||||
|
||||
// HTTPErrorHandler should contain a method `FireErrorCode` which
|
||||
// handles http unsuccessful status codes.
|
||||
HTTPErrorHandler interface {
|
||||
FireErrorCode(ctx context.Context)
|
||||
}
|
||||
)
|
||||
|
||||
type routerHandler struct {
|
||||
config context.ConfigurationReadOnly
|
||||
logger *golog.Logger
|
||||
|
||||
trees []*trie
|
||||
hosts bool // true if at least one route contains a Subdomain.
|
||||
trees []*trie
|
||||
errorTrees []*trie
|
||||
|
||||
hosts bool // true if at least one route contains a Subdomain.
|
||||
errorHosts bool // true if error handlers are registered to at least one Subdomain.
|
||||
}
|
||||
|
||||
var _ RequestHandler = &routerHandler{}
|
||||
var _ RequestHandler = (*routerHandler)(nil)
|
||||
var _ HTTPErrorHandler = (*routerHandler)(nil)
|
||||
|
||||
// NewDefaultHandler returns the handler which is responsible
|
||||
// to map the request with a route (aka mux implementation).
|
||||
|
@ -45,7 +61,17 @@ func NewDefaultHandler(config context.ConfigurationReadOnly, logger *golog.Logge
|
|||
}
|
||||
}
|
||||
|
||||
func (h *routerHandler) getTree(method, subdomain string) *trie {
|
||||
func (h *routerHandler) getTree(statusCode int, method, subdomain string) *trie {
|
||||
if statusCode > 0 {
|
||||
for i := range h.errorTrees {
|
||||
t := h.errorTrees[i]
|
||||
if t.statusCode == statusCode && t.subdomain == subdomain {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := range h.trees {
|
||||
t := h.trees[i]
|
||||
if t.method == method && t.subdomain == subdomain {
|
||||
|
@ -59,23 +85,28 @@ func (h *routerHandler) getTree(method, subdomain string) *trie {
|
|||
// AddRoute registers a route. See `Router.AddRouteUnsafe`.
|
||||
func (h *routerHandler) AddRoute(r *Route) error {
|
||||
var (
|
||||
routeName = r.Name
|
||||
method = r.Method
|
||||
subdomain = r.Subdomain
|
||||
path = r.Path
|
||||
handlers = r.Handlers
|
||||
method = r.Method
|
||||
statusCode = r.StatusCode
|
||||
subdomain = r.Subdomain
|
||||
path = r.Path
|
||||
handlers = r.Handlers
|
||||
)
|
||||
|
||||
t := h.getTree(method, subdomain)
|
||||
t := h.getTree(statusCode, method, subdomain)
|
||||
|
||||
if t == nil {
|
||||
n := newTrieNode()
|
||||
// first time we register a route to this method with this subdomain
|
||||
t = &trie{method: method, subdomain: subdomain, root: n}
|
||||
h.trees = append(h.trees, t)
|
||||
t = &trie{statusCode: statusCode, method: method, subdomain: subdomain, root: n}
|
||||
if statusCode > 0 {
|
||||
h.errorTrees = append(h.errorTrees, t)
|
||||
} else {
|
||||
h.trees = append(h.trees, t)
|
||||
}
|
||||
}
|
||||
|
||||
t.insert(path, routeName, handlers)
|
||||
t.insert(path, r.ReadOnly, handlers)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -141,7 +172,11 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
|
|||
}
|
||||
|
||||
if r.Subdomain != "" {
|
||||
h.hosts = true
|
||||
if r.StatusCode > 0 {
|
||||
h.errorHosts = true
|
||||
} else {
|
||||
h.hosts = true
|
||||
}
|
||||
}
|
||||
|
||||
if r.topLink == nil {
|
||||
|
@ -242,28 +277,73 @@ func bindMultiParamTypesHandler(top *Route, r *Route) {
|
|||
return // should never happen, previous checks made to set the top link.
|
||||
}
|
||||
|
||||
currentStatusCode := r.StatusCode
|
||||
if currentStatusCode == 0 {
|
||||
currentStatusCode = http.StatusOK
|
||||
}
|
||||
|
||||
decisionHandler := func(ctx context.Context) {
|
||||
// println("core/router/handler.go: decision handler; " + ctx.Path() + " route.Name: " + r.Name + " vs context's " + ctx.GetCurrentRoute().Name())
|
||||
currentRouteName := ctx.RouteName()
|
||||
currentRoute := ctx.GetCurrentRoute()
|
||||
|
||||
// Different path parameters types in the same path, fallback should registered first e.g. {path} {string},
|
||||
// because the handler on this case is executing from last to top.
|
||||
if f(ctx) {
|
||||
// println("core/router/handler.go: filter for : " + r.Name + " passed")
|
||||
ctx.SetCurrentRouteName(r.Name)
|
||||
ctx.SetCurrentRoute(r.ReadOnly)
|
||||
// Note: error handlers will be the same, routes came from the same party,
|
||||
// no need to update them.
|
||||
ctx.HandlerIndex(0)
|
||||
ctx.Do(h)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetCurrentRouteName(currentRouteName)
|
||||
ctx.StatusCode(http.StatusOK)
|
||||
ctx.SetCurrentRoute(currentRoute)
|
||||
ctx.StatusCode(currentStatusCode)
|
||||
ctx.Next()
|
||||
}
|
||||
|
||||
r.topLink.beginHandlers = append(context.Handlers{decisionHandler}, r.topLink.beginHandlers...)
|
||||
}
|
||||
|
||||
func (h *routerHandler) canHandleSubdomain(ctx context.Context, subdomain string) bool {
|
||||
if subdomain == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
requestHost := ctx.Host()
|
||||
if netutil.IsLoopbackSubdomain(requestHost) {
|
||||
// this fixes a bug when listening on
|
||||
// 127.0.0.1:8080 for example
|
||||
// and have a wildcard subdomain and a route registered to root domain.
|
||||
return false // it's not a subdomain, it's something like 127.0.0.1 probably
|
||||
}
|
||||
// it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty
|
||||
if subdomain == SubdomainWildcardIndicator {
|
||||
// mydomain.com -> invalid
|
||||
// localhost -> invalid
|
||||
// sub.mydomain.com -> valid
|
||||
// sub.localhost -> valid
|
||||
serverHost := ctx.Application().ConfigurationReadOnly().GetVHost()
|
||||
if serverHost == requestHost {
|
||||
return false // it's not a subdomain, it's a full domain (with .com...)
|
||||
}
|
||||
|
||||
dotIdx := strings.IndexByte(requestHost, '.')
|
||||
slashIdx := strings.IndexByte(requestHost, '/')
|
||||
if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) {
|
||||
// if "." was found anywhere but not at the first path segment (host).
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
// continue to that, any subdomain is valid.
|
||||
} else if !strings.HasPrefix(requestHost, subdomain) { // subdomain contains the dot.
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
method := ctx.Method()
|
||||
path := ctx.Path()
|
||||
|
@ -304,40 +384,13 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
|||
continue
|
||||
}
|
||||
|
||||
if h.hosts && t.subdomain != "" {
|
||||
requestHost := ctx.Host()
|
||||
if netutil.IsLoopbackSubdomain(requestHost) {
|
||||
// this fixes a bug when listening on
|
||||
// 127.0.0.1:8080 for example
|
||||
// and have a wildcard subdomain and a route registered to root domain.
|
||||
continue // it's not a subdomain, it's something like 127.0.0.1 probably
|
||||
}
|
||||
// it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty
|
||||
if t.subdomain == SubdomainWildcardIndicator {
|
||||
// mydomain.com -> invalid
|
||||
// localhost -> invalid
|
||||
// sub.mydomain.com -> valid
|
||||
// sub.localhost -> valid
|
||||
serverHost := config.GetVHost()
|
||||
if serverHost == requestHost {
|
||||
continue // it's not a subdomain, it's a full domain (with .com...)
|
||||
}
|
||||
|
||||
dotIdx := strings.IndexByte(requestHost, '.')
|
||||
slashIdx := strings.IndexByte(requestHost, '/')
|
||||
if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) {
|
||||
// if "." was found anywhere but not at the first path segment (host).
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
// continue to that, any subdomain is valid.
|
||||
} else if !strings.HasPrefix(requestHost, t.subdomain) { // t.subdomain contains the dot.
|
||||
continue
|
||||
}
|
||||
if h.hosts && !h.canHandleSubdomain(ctx, t.subdomain) {
|
||||
continue
|
||||
}
|
||||
|
||||
n := t.search(path, ctx.Params())
|
||||
if n != nil {
|
||||
ctx.SetCurrentRouteName(n.RouteName)
|
||||
ctx.SetCurrentRoute(n.Route)
|
||||
ctx.Do(n.Handlers)
|
||||
// found
|
||||
return
|
||||
|
@ -364,6 +417,89 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
|||
ctx.StatusCode(http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (h *routerHandler) FireErrorCode(ctx context.Context) {
|
||||
statusCode := ctx.GetStatusCode() // the response's cached one.
|
||||
|
||||
// if we can reset the body
|
||||
if w, ok := ctx.IsRecording(); ok {
|
||||
if statusCodeSuccessful(w.StatusCode()) { // if not an error status code
|
||||
w.WriteHeader(statusCode) // then set it manually here, otherwise it should be set via ctx.StatusCode(...)
|
||||
}
|
||||
// reset if previous content and it's recorder, keep the status code.
|
||||
w.ClearHeaders()
|
||||
w.ResetBody()
|
||||
} else if w, ok := ctx.ResponseWriter().(*context.GzipResponseWriter); ok {
|
||||
// reset and disable the gzip in order to be an expected form of http error result
|
||||
w.ResetBody()
|
||||
w.Disable()
|
||||
} else {
|
||||
// if we can't reset the body and the body has been filled
|
||||
// which means that the status code already sent,
|
||||
// then do not fire this custom error code.
|
||||
if ctx.ResponseWriter().Written() > 0 { // != -1, rel: context/context.go#EndRequest
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for i := range h.errorTrees {
|
||||
t := h.errorTrees[i]
|
||||
|
||||
if statusCode != t.statusCode {
|
||||
continue
|
||||
}
|
||||
|
||||
if h.errorHosts && !h.canHandleSubdomain(ctx, t.subdomain) {
|
||||
continue
|
||||
}
|
||||
|
||||
n := t.search(ctx.Path(), ctx.Params())
|
||||
if n == nil {
|
||||
// try to take the root's one.
|
||||
n = t.root.getChild(pathSep)
|
||||
}
|
||||
|
||||
if n != nil {
|
||||
// fire this http status code's error handlers chain.
|
||||
|
||||
// ctx.StopExecution() // not uncomment this, is here to remember why to.
|
||||
// note for me: I don't stopping the execution of the other handlers
|
||||
// because may the user want to add a fallback error code
|
||||
// i.e
|
||||
// users := app.Party("/users")
|
||||
// users.Done(func(ctx context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }})
|
||||
|
||||
// use .HandlerIndex
|
||||
// that sets the current handler index to zero
|
||||
// in order to:
|
||||
// ignore previous runs that may changed the handler index,
|
||||
// via ctx.Next or ctx.StopExecution, if any.
|
||||
//
|
||||
// use .Do
|
||||
// that overrides the existing handlers and sets and runs these error handlers.
|
||||
// in order to:
|
||||
// ignore the route's after-handlers, if any.
|
||||
ctx.SetCurrentRoute(n.Route)
|
||||
// Should work with:
|
||||
// Manual call of ctx.Application().FireErrorCode(ctx) (handlers length > 0)
|
||||
// And on `ctx.SetStatusCode`: Context -> EndRequest -> FireErrorCode (handlers length > 0)
|
||||
// And on router: HandleRequest -> SetStatusCode -> Context ->
|
||||
// EndRequest -> FireErrorCode (handlers' length is always 0)
|
||||
ctx.HandlerIndex(0)
|
||||
ctx.Do(n.Handlers)
|
||||
return
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// not error handler found, write a default message.
|
||||
ctx.WriteString(http.StatusText(statusCode))
|
||||
}
|
||||
|
||||
func statusCodeSuccessful(statusCode int) bool {
|
||||
return !context.StatusCodeNotSuccessful(statusCode)
|
||||
}
|
||||
|
||||
func (h *routerHandler) subdomainAndPathAndMethodExists(ctx context.Context, t *trie, method, path string) bool {
|
||||
if method != "" && method != t.method {
|
||||
return false
|
||||
|
|
|
@ -33,6 +33,22 @@ type Party interface {
|
|||
// Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path
|
||||
Macros() *macro.Macros
|
||||
|
||||
// OnErrorCode registers an error http status code
|
||||
// based on the "statusCode" < 200 || >= 400 (came from `context.StatusCodeNotSuccessful`).
|
||||
// The handler is being wrapped by a generic
|
||||
// handler which will try to reset
|
||||
// the body if recorder was enabled
|
||||
// and/or disable the gzip if gzip response recorder
|
||||
// was active.
|
||||
OnErrorCode(statusCode int, handlers ...context.Handler)
|
||||
// OnAnyErrorCode registers a handler which called when error status code written.
|
||||
// Same as `OnErrorCode` but registers all http error codes based on the `context.StatusCodeNotSuccessful`
|
||||
// which defaults to < 200 || >= 400 for an error code, any previous error code will be overridden,
|
||||
// so call it first if you want to use any custom handler for a specific error status code.
|
||||
//
|
||||
// Read more at: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
OnAnyErrorCode(handlers ...context.Handler)
|
||||
|
||||
// Party groups routes which may have the same prefix and share same handlers,
|
||||
// returns that new rich subrouter.
|
||||
//
|
||||
|
|
|
@ -22,6 +22,7 @@ type Route struct {
|
|||
Name string `json:"name"` // "userRoute"
|
||||
Description string `json:"description"` // "lists a user"
|
||||
Method string `json:"method"` // "GET"
|
||||
StatusCode int `json:"statusCode"` // 404 (only for HTTP error handlers).
|
||||
methodBckp string // if Method changed to something else (which is possible at runtime as well, via RefreshRouter) then this field will be filled with the old one.
|
||||
Subdomain string `json:"subdomain"` // "admin."
|
||||
tmpl macro.Template // Tmpl().Src: "/api/user/{id:uint64}"
|
||||
|
@ -62,6 +63,9 @@ type Route struct {
|
|||
LastMod time.Time `json:"lastMod,omitempty"`
|
||||
ChangeFreq string `json:"changeFreq,omitempty"`
|
||||
Priority float32 `json:"priority,omitempty"`
|
||||
|
||||
// ReadOnly is the read-only structure of the Route.
|
||||
ReadOnly context.RouteReadOnly
|
||||
}
|
||||
|
||||
// NewRoute returns a new route based on its method,
|
||||
|
@ -69,7 +73,7 @@ type Route struct {
|
|||
// handlers and the macro container which all routes should share.
|
||||
// It parses the path based on the "macros",
|
||||
// handlers are being changed to validate the macros at serve time, if needed.
|
||||
func NewRoute(method, subdomain, unparsedPath string,
|
||||
func NewRoute(statusErrorCode int, method, subdomain, unparsedPath string,
|
||||
handlers context.Handlers, macros macro.Macros) (*Route, error) {
|
||||
tmpl, err := macro.Parse(unparsedPath, macros)
|
||||
if err != nil {
|
||||
|
@ -86,9 +90,14 @@ func NewRoute(method, subdomain, unparsedPath string,
|
|||
|
||||
path = cleanPath(path) // maybe unnecessary here.
|
||||
defaultName := method + subdomain + tmpl.Src
|
||||
if statusErrorCode > 0 {
|
||||
defaultName = fmt.Sprintf("%d_%s", statusErrorCode, defaultName)
|
||||
}
|
||||
|
||||
formattedPath := formatPath(path)
|
||||
|
||||
route := &Route{
|
||||
StatusCode: statusErrorCode,
|
||||
Name: defaultName,
|
||||
Method: method,
|
||||
methodBckp: method,
|
||||
|
@ -99,6 +108,7 @@ func NewRoute(method, subdomain, unparsedPath string,
|
|||
FormattedPath: formattedPath,
|
||||
}
|
||||
|
||||
route.ReadOnly = routeReadOnlyWrapper{route}
|
||||
return route, nil
|
||||
}
|
||||
|
||||
|
@ -189,15 +199,20 @@ func (r *Route) BuildHandlers() {
|
|||
|
||||
// String returns the form of METHOD, SUBDOMAIN, TMPL PATH.
|
||||
func (r *Route) String() string {
|
||||
start := r.Method
|
||||
if r.StatusCode > 0 {
|
||||
start = http.StatusText(r.StatusCode)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %s%s",
|
||||
r.Method, r.Subdomain, r.Tmpl().Src)
|
||||
start, r.Subdomain, r.Tmpl().Src)
|
||||
}
|
||||
|
||||
// Equal compares the method, subdomain and the
|
||||
// underline representation of the route's path,
|
||||
// instead of the `String` function which returns the front representation.
|
||||
func (r *Route) Equal(other *Route) bool {
|
||||
return r.Method == other.Method && r.Subdomain == other.Subdomain && r.Path == other.Path
|
||||
return r.StatusCode == other.StatusCode && r.Method == other.Method && r.Subdomain == other.Subdomain && r.Path == other.Path
|
||||
}
|
||||
|
||||
// DeepEqual compares the method, subdomain, the
|
||||
|
@ -467,6 +482,10 @@ type routeReadOnlyWrapper struct {
|
|||
*Route
|
||||
}
|
||||
|
||||
func (rd routeReadOnlyWrapper) StatusErrorCode() int {
|
||||
return rd.Route.StatusCode
|
||||
}
|
||||
|
||||
func (rd routeReadOnlyWrapper) Method() string {
|
||||
return rd.Route.Method
|
||||
}
|
||||
|
|
|
@ -1,161 +0,0 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"net/http" // just for status codes
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/iris/v12/context"
|
||||
)
|
||||
|
||||
func statusCodeSuccessful(statusCode int) bool {
|
||||
return !context.StatusCodeNotSuccessful(statusCode)
|
||||
}
|
||||
|
||||
// ErrorCodeHandler is the entry
|
||||
// of the list of all http error code handlers.
|
||||
type ErrorCodeHandler struct {
|
||||
StatusCode int
|
||||
Handlers context.Handlers
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// Fire executes the specific an error http error status.
|
||||
// it's being wrapped to make sure that the handler
|
||||
// will render correctly.
|
||||
func (ch *ErrorCodeHandler) Fire(ctx context.Context) {
|
||||
// if we can reset the body
|
||||
if w, ok := ctx.IsRecording(); ok {
|
||||
if statusCodeSuccessful(w.StatusCode()) { // if not an error status code
|
||||
w.WriteHeader(ch.StatusCode) // then set it manually here, otherwise it should be set via ctx.StatusCode(...)
|
||||
}
|
||||
// reset if previous content and it's recorder, keep the status code.
|
||||
w.ClearHeaders()
|
||||
w.ResetBody()
|
||||
} else if w, ok := ctx.ResponseWriter().(*context.GzipResponseWriter); ok {
|
||||
// reset and disable the gzip in order to be an expected form of http error result
|
||||
w.ResetBody()
|
||||
w.Disable()
|
||||
} else {
|
||||
// if we can't reset the body and the body has been filled
|
||||
// which means that the status code already sent,
|
||||
// then do not fire this custom error code.
|
||||
if ctx.ResponseWriter().Written() > 0 { // != -1, rel: context/context.go#EndRequest
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ctx.StopExecution() // not uncomment this, is here to remember why to.
|
||||
// note for me: I don't stopping the execution of the other handlers
|
||||
// because may the user want to add a fallback error code
|
||||
// i.e
|
||||
// users := app.Party("/users")
|
||||
// users.Done(func(ctx context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }})
|
||||
|
||||
// use .HandlerIndex
|
||||
// that sets the current handler index to zero
|
||||
// in order to:
|
||||
// ignore previous runs that may changed the handler index,
|
||||
// via ctx.Next or ctx.StopExecution, if any.
|
||||
//
|
||||
// use .Do
|
||||
// that overrides the existing handlers and sets and runs these error handlers.
|
||||
// in order to:
|
||||
// ignore the route's after-handlers, if any.
|
||||
ctx.HandlerIndex(0)
|
||||
ctx.Do(ch.Handlers)
|
||||
}
|
||||
|
||||
func (ch *ErrorCodeHandler) updateHandlers(handlers context.Handlers) {
|
||||
ch.mu.Lock()
|
||||
ch.Handlers = handlers
|
||||
ch.mu.Unlock()
|
||||
}
|
||||
|
||||
// ErrorCodeHandlers contains the http error code handlers.
|
||||
// User of this struct can register, get
|
||||
// a status code handler based on a status code or
|
||||
// fire based on a receiver context.
|
||||
type ErrorCodeHandlers struct {
|
||||
handlers []*ErrorCodeHandler
|
||||
}
|
||||
|
||||
func defaultErrorCodeHandlers() *ErrorCodeHandlers {
|
||||
chs := new(ErrorCodeHandlers)
|
||||
// register some common error handlers.
|
||||
// Note that they can be registered on-fly but
|
||||
// we don't want to reduce the performance even
|
||||
// on the first failed request.
|
||||
for _, statusCode := range []int{
|
||||
http.StatusNotFound,
|
||||
http.StatusMethodNotAllowed,
|
||||
http.StatusInternalServerError,
|
||||
} {
|
||||
chs.Register(statusCode, statusText(statusCode))
|
||||
}
|
||||
|
||||
return chs
|
||||
}
|
||||
|
||||
func statusText(statusCode int) context.Handler {
|
||||
return func(ctx context.Context) {
|
||||
ctx.WriteString(http.StatusText(statusCode))
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns an http error handler based on the "statusCode".
|
||||
// If not found it returns nil.
|
||||
func (s *ErrorCodeHandlers) Get(statusCode int) *ErrorCodeHandler {
|
||||
for i, n := 0, len(s.handlers); i < n; i++ {
|
||||
if h := s.handlers[i]; h.StatusCode == statusCode {
|
||||
return h
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Register registers an error http status code
|
||||
// based on the "statusCode" < 200 || >= 400 (`context.StatusCodeNotSuccessful`).
|
||||
// The handler is being wrapepd by a generic
|
||||
// handler which will try to reset
|
||||
// the body if recorder was enabled
|
||||
// and/or disable the gzip if gzip response recorder
|
||||
// was active.
|
||||
func (s *ErrorCodeHandlers) Register(statusCode int, handlers ...context.Handler) *ErrorCodeHandler {
|
||||
if statusCodeSuccessful(statusCode) {
|
||||
return nil
|
||||
}
|
||||
|
||||
h := s.Get(statusCode)
|
||||
if h == nil {
|
||||
// create new and add it
|
||||
ch := &ErrorCodeHandler{
|
||||
StatusCode: statusCode,
|
||||
Handlers: handlers,
|
||||
}
|
||||
|
||||
s.handlers = append(s.handlers, ch)
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// otherwise update the handlers
|
||||
h.updateHandlers(handlers)
|
||||
return h
|
||||
}
|
||||
|
||||
// Fire executes an error http status code handler
|
||||
// based on the context's status code.
|
||||
//
|
||||
// If a handler is not already registered,
|
||||
// then it creates & registers a new trivial handler on the-fly.
|
||||
func (s *ErrorCodeHandlers) Fire(ctx context.Context) {
|
||||
statusCode := ctx.GetStatusCode()
|
||||
if statusCodeSuccessful(statusCode) {
|
||||
return
|
||||
}
|
||||
ch := s.Get(statusCode)
|
||||
if ch == nil {
|
||||
ch = s.Register(statusCode, statusText(statusCode))
|
||||
}
|
||||
ch.Fire(ctx)
|
||||
}
|
|
@ -64,9 +64,73 @@ func TestOnAnyErrorCode(t *testing.T) {
|
|||
}
|
||||
|
||||
func checkAndClearBuf(t *testing.T, buff *bytes.Buffer, expected string) {
|
||||
t.Helper()
|
||||
|
||||
if got, expected := buff.String(), expected; got != expected {
|
||||
t.Fatalf("expected middleware to run before the error handler, expected %s but got %s", expected, got)
|
||||
t.Fatalf("expected middleware to run before the error handler, expected: '%s' but got: '%s'", expected, got)
|
||||
}
|
||||
|
||||
buff.Reset()
|
||||
}
|
||||
|
||||
func TestPartyOnErrorCode(t *testing.T) {
|
||||
app := iris.New()
|
||||
app.Configure(iris.WithFireMethodNotAllowed)
|
||||
|
||||
globalNotFoundResponse := "custom not found"
|
||||
app.OnErrorCode(iris.StatusNotFound, func(ctx iris.Context) {
|
||||
ctx.WriteString(globalNotFoundResponse)
|
||||
})
|
||||
|
||||
globalMethodNotAllowedResponse := "global: method not allowed"
|
||||
app.OnErrorCode(iris.StatusMethodNotAllowed, func(ctx iris.Context) {
|
||||
ctx.WriteString(globalMethodNotAllowedResponse)
|
||||
})
|
||||
|
||||
app.Get("/path", h)
|
||||
|
||||
h := func(ctx iris.Context) { ctx.WriteString(ctx.Path()) }
|
||||
usersResponse := "users: method allowed"
|
||||
|
||||
users := app.Party("/users")
|
||||
users.OnErrorCode(iris.StatusMethodNotAllowed, func(ctx iris.Context) {
|
||||
ctx.WriteString(usersResponse)
|
||||
})
|
||||
users.Get("/", h)
|
||||
// test setting the error code from a handler.
|
||||
users.Get("/badrequest", func(ctx iris.Context) { ctx.StatusCode(iris.StatusBadRequest) })
|
||||
|
||||
usersuserResponse := "users:user: method allowed"
|
||||
user := users.Party("/{id:int}")
|
||||
user.OnErrorCode(iris.StatusMethodNotAllowed, func(ctx iris.Context) {
|
||||
ctx.WriteString(usersuserResponse)
|
||||
})
|
||||
// usersuserNotFoundResponse := "users:user: not found"
|
||||
// user.OnErrorCode(iris.StatusNotFound, func(ctx iris.Context) {
|
||||
// ctx.WriteString(usersuserNotFoundResponse)
|
||||
// })
|
||||
user.Get("/", h)
|
||||
|
||||
e := httptest.New(t, app)
|
||||
|
||||
e.GET("/notfound").Expect().Status(iris.StatusNotFound).Body().Equal(globalNotFoundResponse)
|
||||
e.POST("/path").Expect().Status(iris.StatusMethodNotAllowed).Body().Equal(globalMethodNotAllowedResponse)
|
||||
e.GET("/path").Expect().Status(iris.StatusOK).Body().Equal("/path")
|
||||
|
||||
e.POST("/users").Expect().Status(iris.StatusMethodNotAllowed).
|
||||
Body().Equal(usersResponse)
|
||||
|
||||
e.POST("/users/42").Expect().Status(iris.StatusMethodNotAllowed).
|
||||
Body().Equal(usersuserResponse)
|
||||
|
||||
e.GET("/users/42").Expect().Status(iris.StatusOK).
|
||||
Body().Equal("/users/42")
|
||||
// e.GET("/users/ab").Expect().Status(iris.StatusNotFound).Body().Equal(usersuserNotFoundResponse)
|
||||
|
||||
// if not registered to the party, then the root is taking action.
|
||||
e.GET("/users/ab/cd").Expect().Status(iris.StatusNotFound).Body().Equal(globalNotFoundResponse)
|
||||
|
||||
// if not registered to the party, and not in root, then just write the status text (fallback behavior)
|
||||
e.GET("/users/badrequest").Expect().Status(iris.StatusBadRequest).
|
||||
Body().Equal(http.StatusText(iris.StatusBadRequest))
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ type trieNode struct {
|
|||
staticKey string
|
||||
|
||||
// insert data.
|
||||
Handlers context.Handlers
|
||||
RouteName string
|
||||
Route context.RouteReadOnly
|
||||
Handlers context.Handlers
|
||||
}
|
||||
|
||||
func newTrieNode() *trieNode {
|
||||
|
@ -89,7 +89,9 @@ type trie struct {
|
|||
hasRootWildcard bool
|
||||
hasRootSlash bool
|
||||
|
||||
method string
|
||||
statusCode int // for error codes only, method is ignored.
|
||||
method string
|
||||
|
||||
// subdomain is empty for default-hostname routes,
|
||||
// ex: mysubdomain.
|
||||
subdomain string
|
||||
|
@ -108,7 +110,7 @@ func slowPathSplit(path string) []string {
|
|||
return strings.Split(path, pathSep)[1:]
|
||||
}
|
||||
|
||||
func (tr *trie) insert(path, routeName string, handlers context.Handlers) {
|
||||
func (tr *trie) insert(path string, route context.RouteReadOnly, handlers context.Handlers) {
|
||||
input := slowPathSplit(path)
|
||||
|
||||
n := tr.root
|
||||
|
@ -148,8 +150,9 @@ func (tr *trie) insert(path, routeName string, handlers context.Handlers) {
|
|||
n = n.getChild(s)
|
||||
}
|
||||
|
||||
n.RouteName = routeName
|
||||
n.Route = route
|
||||
n.Handlers = handlers
|
||||
|
||||
n.paramKeys = paramKeys
|
||||
n.key = path
|
||||
n.end = true
|
||||
|
@ -163,6 +166,8 @@ func (tr *trie) insert(path, routeName string, handlers context.Handlers) {
|
|||
}
|
||||
|
||||
n.staticKey = path[:i]
|
||||
|
||||
// fmt.Printf("trie.insert: (whole path=%v) Path: %s, Route name: %s, Handlers len: %d\n", n.end, n.key, route.Name(), len(handlers))
|
||||
}
|
||||
|
||||
func (tr *trie) search(q string, params *context.RequestParams) *trieNode {
|
||||
|
|
4
iris.go
4
iris.go
|
@ -130,7 +130,8 @@ type Application struct {
|
|||
// routing embedded | exposing APIBuilder's and Router's public API.
|
||||
*router.APIBuilder
|
||||
*router.Router
|
||||
ContextPool *context.Pool
|
||||
router.HTTPErrorHandler // if Router is Downgraded this is nil.
|
||||
ContextPool *context.Pool
|
||||
|
||||
// config contains the configuration fields
|
||||
// all fields defaults to something that is working, developers don't have to set it.
|
||||
|
@ -834,6 +835,7 @@ func (app *Application) Build() error {
|
|||
if err != nil {
|
||||
rp.Err(err)
|
||||
}
|
||||
app.HTTPErrorHandler = routerHandler
|
||||
// re-build of the router from outside can be done with
|
||||
// app.RefreshRouter()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user