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/)
|
[![](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
|
## Aprende Iris
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
[![](https://media.giphy.com/media/j5WLmtvwn98VPrm7li/giphy.gif)](https://iris-go.com/testimonials/)
|
[![](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>
|
<details>
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
[![](https://media.giphy.com/media/j5WLmtvwn98VPrm7li/giphy.gif)](https://iris-go.com/testimonials/)
|
[![](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
|
## Μαθαίνοντας το Iris
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
|
@ -8,6 +8,8 @@ Iris는 단순하고 빠르며 좋은 성능과 모든 기능을 갖춘 Go언어
|
||||||
|
|
||||||
[![](https://media.giphy.com/media/j5WLmtvwn98VPrm7li/giphy.gif)](https://iris-go.com/testimonials/)
|
[![](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 배우기
|
## Iris 배우기
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
|
@ -7,6 +7,8 @@ Iris — это быстрый, простой, но полнофункцион
|
||||||
|
|
||||||
[![](https://media.giphy.com/media/j5WLmtvwn98VPrm7li/giphy.gif)](https://iris-go.com/testimonials/)
|
[![](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
|
## Изучение Iris
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
|
@ -8,6 +8,8 @@ Iris 是基于 Go 编写的一个快速,简单但功能齐全且非常高效
|
||||||
|
|
||||||
[![](https://media.giphy.com/media/j5WLmtvwn98VPrm7li/giphy.gif)](https://iris-go.com/testimonials/)
|
[![](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
|
## 学习 Iris
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
Build(provider router.RoutesProvider) error
|
Build(provider router.RoutesProvider) error
|
||||||
- RouteExists reports whether a particular route exists.
|
- 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
|
For a more detailed, complete and useful example
|
||||||
you can take a look at the iris' router itself which is located at:
|
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)
|
ctx.Do(route.Handlers)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -71,6 +72,11 @@ func (r *customRouter) RouteExists(ctx iris.Context, method, path string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *customRouter) FireErrorCode(ctx iris.Context) {
|
||||||
|
// responseStatusCode := ctx.GetStatusCode() // set by prior ctx.StatusCode calls
|
||||||
|
// [...]
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
|
||||||
|
|
|
@ -61,9 +61,6 @@ type Application interface {
|
||||||
|
|
||||||
// FireErrorCode executes an error http status code handler
|
// FireErrorCode executes an error http status code handler
|
||||||
// based on the context's status code.
|
// 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)
|
FireErrorCode(ctx Context)
|
||||||
|
|
||||||
// RouteExists reports whether a particular route exists
|
// RouteExists reports whether a particular route exists
|
||||||
|
|
|
@ -138,18 +138,13 @@ type Context interface {
|
||||||
// ctx.ResetRequest(r.WithContext(stdCtx)).
|
// ctx.ResetRequest(r.WithContext(stdCtx)).
|
||||||
ResetRequest(r *http.Request)
|
ResetRequest(r *http.Request)
|
||||||
|
|
||||||
// SetCurrentRouteName sets the route's name internally,
|
// SetCurrentRoutes sets the route internally,
|
||||||
// in order to be able to find the correct current "read-only" Route when
|
// See `GetCurrentRoute()` method too.
|
||||||
// end-developer calls the `GetCurrentRoute()` function.
|
// It's being initialized by the Router.
|
||||||
// It's being initialized by the Router, if you change that name
|
// See `Exec` or `SetHandlers/AddHandler` methods to simulate a request.
|
||||||
// manually nothing really happens except that you'll get other
|
SetCurrentRoute(route RouteReadOnly)
|
||||||
// route via `GetCurrentRoute()`.
|
// GetCurrentRoute returns the current "read-only" route that
|
||||||
// Instead, to execute a different path
|
// was registered to this request's 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.
|
|
||||||
GetCurrentRoute() RouteReadOnly
|
GetCurrentRoute() RouteReadOnly
|
||||||
|
|
||||||
// Do calls the SetHandlers(handlers)
|
// Do calls the SetHandlers(handlers)
|
||||||
|
@ -175,7 +170,7 @@ type Context interface {
|
||||||
|
|
||||||
// HandlerIndex sets the current index of the
|
// HandlerIndex sets the current index of the
|
||||||
// current context's handlers chain.
|
// 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.
|
// current handler index without change the current index.
|
||||||
//
|
//
|
||||||
// Look Handlers(), Next() and StopExecution() too.
|
// Look Handlers(), Next() and StopExecution() too.
|
||||||
|
@ -1194,8 +1189,8 @@ type context struct {
|
||||||
writer ResponseWriter
|
writer ResponseWriter
|
||||||
// the original http.Request
|
// the original http.Request
|
||||||
request *http.Request
|
request *http.Request
|
||||||
// the current route's name registered to this request path.
|
// the current route registered to this request path.
|
||||||
currentRouteName string
|
currentRoute RouteReadOnly
|
||||||
deferFunc Handler
|
deferFunc Handler
|
||||||
|
|
||||||
// the local key-value storage
|
// the local key-value storage
|
||||||
|
@ -1230,6 +1225,7 @@ func NewContext(app Application) Context {
|
||||||
// 4. response writer to the http.ResponseWriter.
|
// 4. response writer to the http.ResponseWriter.
|
||||||
// 5. request to the *http.Request.
|
// 5. request to the *http.Request.
|
||||||
func (ctx *context) BeginRequest(w http.ResponseWriter, r *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.handlers = nil // will be filled by router.Serve/HTTP
|
||||||
ctx.values = ctx.values[0:0] // >> >> by context.Values().Set
|
ctx.values = ctx.values[0:0] // >> >> by context.Values().Set
|
||||||
ctx.params.Store = ctx.params.Store[0:0]
|
ctx.params.Store = ctx.params.Store[0:0]
|
||||||
|
@ -1266,8 +1262,8 @@ func (ctx *context) EndRequest() {
|
||||||
ctx.deferFunc(ctx)
|
ctx.deferFunc(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if StatusCodeNotSuccessful(ctx.GetStatusCode()) &&
|
if !ctx.Application().ConfigurationReadOnly().GetDisableAutoFireStatusCode() &&
|
||||||
!ctx.Application().ConfigurationReadOnly().GetDisableAutoFireStatusCode() {
|
StatusCodeNotSuccessful(ctx.GetStatusCode()) {
|
||||||
// author's note:
|
// author's note:
|
||||||
// if recording, the error handler can handle
|
// if recording, the error handler can handle
|
||||||
// the rollback and remove any response written before,
|
// the rollback and remove any response written before,
|
||||||
|
@ -1332,23 +1328,18 @@ func (ctx *context) ResetRequest(r *http.Request) {
|
||||||
ctx.request = r
|
ctx.request = r
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCurrentRouteName sets the route's name internally,
|
// SetCurrentRoutes sets the route internally,
|
||||||
// in order to be able to find the correct current "read-only" Route when
|
// See `GetCurrentRoute()` method too.
|
||||||
// end-developer calls the `GetCurrentRoute()` function.
|
// It's being initialized by the Router.
|
||||||
// It's being initialized by the Router, if you change that name
|
// See `Exec` or `SetHandlers/AddHandler` methods to simulate a request.
|
||||||
// manually nothing really happens except that you'll get other
|
func (ctx *context) SetCurrentRoute(route RouteReadOnly) {
|
||||||
// route via `GetCurrentRoute()`.
|
ctx.currentRoute = route
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentRoute returns the current registered "read-only" route that
|
// GetCurrentRoute returns the current "read-only" route that
|
||||||
// was being registered to this request's path.
|
// was registered to this request's path.
|
||||||
func (ctx *context) GetCurrentRoute() RouteReadOnly {
|
func (ctx *context) GetCurrentRoute() RouteReadOnly {
|
||||||
return ctx.app.GetRouteReadOnly(ctx.currentRouteName)
|
return ctx.currentRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do calls the SetHandlers(handlers)
|
// Do calls the SetHandlers(handlers)
|
||||||
|
@ -1385,8 +1376,8 @@ func (ctx *context) Handlers() Handlers {
|
||||||
|
|
||||||
// HandlerIndex sets the current index of the
|
// HandlerIndex sets the current index of the
|
||||||
// current context's handlers chain.
|
// 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.rns that index, useless return value.
|
// current handler index without change the current index.
|
||||||
//
|
//
|
||||||
// Look Handlers(), Next() and StopExecution() too.
|
// Look Handlers(), Next() and StopExecution() too.
|
||||||
func (ctx *context) HandlerIndex(n int) (currentIndex int) {
|
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.
|
// 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 {
|
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.
|
// 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 returns the route's name.
|
||||||
Name() string
|
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 returns the route's method.
|
||||||
Method() string
|
Method() string
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ func (repo *repository) getRelative(r *Route) *Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, route := range repo.routes {
|
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
|
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)
|
repo.routes = append(repo.routes, route)
|
||||||
|
|
||||||
|
if route.StatusCode == 0 { // a common resource route, not a status code error handler.
|
||||||
if repo.pos == nil {
|
if repo.pos == nil {
|
||||||
repo.pos = make(map[string]int)
|
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
|
return route, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,8 +121,6 @@ type APIBuilder struct {
|
||||||
|
|
||||||
// the api builder global macros registry
|
// the api builder global macros registry
|
||||||
macros *macro.Macros
|
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
|
// the api builder global routes repository
|
||||||
routes *repository
|
routes *repository
|
||||||
|
|
||||||
|
@ -165,7 +167,6 @@ var _ RoutesProvider = (*APIBuilder)(nil) // passed to the default request handl
|
||||||
func NewAPIBuilder() *APIBuilder {
|
func NewAPIBuilder() *APIBuilder {
|
||||||
return &APIBuilder{
|
return &APIBuilder{
|
||||||
macros: macro.Defaults,
|
macros: macro.Defaults,
|
||||||
errorCodeHandlers: defaultErrorCodeHandlers(),
|
|
||||||
errors: errgroup.New("API Builder"),
|
errors: errgroup.New("API Builder"),
|
||||||
relativePath: "/",
|
relativePath: "/",
|
||||||
routes: new(repository),
|
routes: new(repository),
|
||||||
|
@ -274,7 +275,11 @@ func (api *APIBuilder) SetRegisterRule(rule RouteRegisterRule) Party {
|
||||||
//
|
//
|
||||||
// Returns a *Route, app will throw any errors later on.
|
// Returns a *Route, app will throw any errors later on.
|
||||||
func (api *APIBuilder) Handle(method string, relativePath string, handlers ...context.Handler) *Route {
|
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 route *Route // the last one is returned.
|
||||||
var err error
|
var err error
|
||||||
|
@ -282,6 +287,7 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
|
||||||
if route == nil {
|
if route == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// global
|
// global
|
||||||
|
|
||||||
route.topLink = api.routes.getRelative(route)
|
route.topLink = api.routes.getRelative(route)
|
||||||
|
@ -417,9 +423,19 @@ func (api *APIBuilder) HandleDir(requestPath, directory string, opts ...DirOptio
|
||||||
// This method can be used for third-parties Iris helpers packages and tools
|
// 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.
|
// 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 {
|
func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handlers ...context.Handler) []*Route {
|
||||||
|
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
|
if len(methods) == 0 || methods[0] == "ALL" || methods[0] == "ANY" { // then use like it was .Any
|
||||||
return api.Any(relativePath, handlers...)
|
return api.Any(relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// no clean path yet because of subdomain indicator/separator which contains a dot.
|
// no clean path yet because of subdomain indicator/separator which contains a dot.
|
||||||
// but remove the first slash if the relative has already ending with a slash
|
// but remove the first slash if the relative has already ending with a slash
|
||||||
|
@ -444,11 +460,17 @@ func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handl
|
||||||
// So if we just put `api.middleware` or `api.doneHandlers`
|
// So if we just put `api.middleware` or `api.doneHandlers`
|
||||||
// then the next `Party` will have those updated handlers
|
// 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.
|
// but dev may change the rules for that child Party, so we have to make clones of them here.
|
||||||
|
|
||||||
var (
|
var (
|
||||||
beginHandlers = joinHandlers(api.middleware, context.Handlers{})
|
beginHandlers context.Handlers
|
||||||
doneHandlers = joinHandlers(api.doneHandlers, context.Handlers{})
|
doneHandlers context.Handlers
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if errorCode == 0 {
|
||||||
|
beginHandlers = joinHandlers(api.middleware, beginHandlers)
|
||||||
|
doneHandlers = joinHandlers(api.doneHandlers, doneHandlers)
|
||||||
|
}
|
||||||
|
|
||||||
mainHandlers := context.Handlers(handlers)
|
mainHandlers := context.Handlers(handlers)
|
||||||
// before join the middleware + handlers + done handlers and apply the execution rules.
|
// 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))
|
routes := make([]*Route, len(methods))
|
||||||
|
|
||||||
for i, m := range methods {
|
for i, m := range methods { // single, empty method for error handlers.
|
||||||
route, err := NewRoute(m, subdomain, path, routeHandlers, *api.macros)
|
route, err := NewRoute(errorCode, m, subdomain, path, routeHandlers, *api.macros)
|
||||||
if err != nil { // template path parser errors:
|
if err != nil { // template path parser errors:
|
||||||
api.errors.Addf("[%s:%d] %v -> %s:%s:%s", filename, line, err, m, subdomain, path)
|
api.errors.Addf("[%s:%d] %v -> %s:%s:%s", filename, line, err, m, subdomain, path)
|
||||||
continue
|
continue
|
||||||
|
@ -523,6 +545,13 @@ func removeDuplicates(elements []string) (result []string) {
|
||||||
//
|
//
|
||||||
// You can even declare a subdomain with relativePath as "mysub." or see `Subdomain`.
|
// You can even declare a subdomain with relativePath as "mysub." or see `Subdomain`.
|
||||||
func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) Party {
|
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
|
parentPath := api.relativePath
|
||||||
dot := string(SubdomainPrefix[0])
|
dot := string(SubdomainPrefix[0])
|
||||||
if len(parentPath) > 0 && parentPath[0] == '/' && strings.HasSuffix(relativePath, dot) {
|
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
|
// global/api builder
|
||||||
macros: api.macros,
|
macros: api.macros,
|
||||||
routes: api.routes,
|
routes: api.routes,
|
||||||
errorCodeHandlers: api.errorCodeHandlers,
|
|
||||||
beginGlobalHandlers: api.beginGlobalHandlers,
|
beginGlobalHandlers: api.beginGlobalHandlers,
|
||||||
doneGlobalHandlers: api.doneGlobalHandlers,
|
doneGlobalHandlers: api.doneGlobalHandlers,
|
||||||
errors: api.errors,
|
errors: api.errors,
|
||||||
|
@ -674,7 +702,7 @@ func (api *APIBuilder) GetRoutesReadOnly() []context.RouteReadOnly {
|
||||||
routes := api.GetRoutes()
|
routes := api.GetRoutes()
|
||||||
readOnlyRoutes := make([]context.RouteReadOnly, len(routes))
|
readOnlyRoutes := make([]context.RouteReadOnly, len(routes))
|
||||||
for i, r := range routes {
|
for i, r := range routes {
|
||||||
readOnlyRoutes[i] = routeReadOnlyWrapper{r}
|
readOnlyRoutes[i] = r.ReadOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
return readOnlyRoutes
|
return readOnlyRoutes
|
||||||
|
@ -692,7 +720,7 @@ func (api *APIBuilder) GetRouteReadOnly(routeName string) context.RouteReadOnly
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return routeReadOnlyWrapper{r}
|
return r.ReadOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRouteReadOnlyByPath returns the registered read-only route based on the template path (`Route.Tmpl().Src`).
|
// 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 nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return routeReadOnlyWrapper{r}
|
return r.ReadOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use appends Handler(s) to the current Party's routes and child routes.
|
// 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
|
// and/or disable the gzip if gzip response recorder
|
||||||
// was active.
|
// was active.
|
||||||
func (api *APIBuilder) OnErrorCode(statusCode int, handlers ...context.Handler) {
|
func (api *APIBuilder) OnErrorCode(statusCode int, handlers ...context.Handler) {
|
||||||
if len(api.beginGlobalHandlers) > 0 {
|
// TODO: think a stable way for that and document it so end-developers
|
||||||
handlers = joinHandlers(api.beginGlobalHandlers, handlers)
|
// 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.
|
// 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.
|
// Layout overrides the parent template layout with a more specific layout for this Party.
|
||||||
// It returns the current Party.
|
// It returns the current Party.
|
||||||
//
|
//
|
||||||
|
|
|
@ -15,9 +15,14 @@ import (
|
||||||
"github.com/kataras/pio"
|
"github.com/kataras/pio"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
// RequestHandler the middle man between acquiring a context and releasing it.
|
// RequestHandler the middle man between acquiring a context and releasing it.
|
||||||
// By-default is the router algorithm.
|
// By-default is the router algorithm.
|
||||||
type RequestHandler interface {
|
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 should handle the request based on the Context.
|
||||||
HandleRequest(ctx context.Context)
|
HandleRequest(ctx context.Context)
|
||||||
// Build should builds the handler, it's being called on router's BuildRouter.
|
// Build should builds the handler, it's being called on router's BuildRouter.
|
||||||
|
@ -26,15 +31,26 @@ type RequestHandler interface {
|
||||||
RouteExists(ctx context.Context, method, path string) bool
|
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 {
|
type routerHandler struct {
|
||||||
config context.ConfigurationReadOnly
|
config context.ConfigurationReadOnly
|
||||||
logger *golog.Logger
|
logger *golog.Logger
|
||||||
|
|
||||||
trees []*trie
|
trees []*trie
|
||||||
|
errorTrees []*trie
|
||||||
|
|
||||||
hosts bool // true if at least one route contains a Subdomain.
|
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
|
// NewDefaultHandler returns the handler which is responsible
|
||||||
// to map the request with a route (aka mux implementation).
|
// 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 {
|
for i := range h.trees {
|
||||||
t := h.trees[i]
|
t := h.trees[i]
|
||||||
if t.method == method && t.subdomain == subdomain {
|
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`.
|
// AddRoute registers a route. See `Router.AddRouteUnsafe`.
|
||||||
func (h *routerHandler) AddRoute(r *Route) error {
|
func (h *routerHandler) AddRoute(r *Route) error {
|
||||||
var (
|
var (
|
||||||
routeName = r.Name
|
|
||||||
method = r.Method
|
method = r.Method
|
||||||
|
statusCode = r.StatusCode
|
||||||
subdomain = r.Subdomain
|
subdomain = r.Subdomain
|
||||||
path = r.Path
|
path = r.Path
|
||||||
handlers = r.Handlers
|
handlers = r.Handlers
|
||||||
)
|
)
|
||||||
|
|
||||||
t := h.getTree(method, subdomain)
|
t := h.getTree(statusCode, method, subdomain)
|
||||||
|
|
||||||
if t == nil {
|
if t == nil {
|
||||||
n := newTrieNode()
|
n := newTrieNode()
|
||||||
// first time we register a route to this method with this subdomain
|
// first time we register a route to this method with this subdomain
|
||||||
t = &trie{method: method, subdomain: subdomain, root: n}
|
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)
|
h.trees = append(h.trees, t)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.insert(path, r.ReadOnly, handlers)
|
||||||
|
|
||||||
t.insert(path, routeName, handlers)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,8 +172,12 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Subdomain != "" {
|
if r.Subdomain != "" {
|
||||||
|
if r.StatusCode > 0 {
|
||||||
|
h.errorHosts = true
|
||||||
|
} else {
|
||||||
h.hosts = true
|
h.hosts = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if r.topLink == nil {
|
if r.topLink == nil {
|
||||||
// build the r.Handlers based on begin and done handlers, if any.
|
// build the r.Handlers based on begin and done handlers, if any.
|
||||||
|
@ -242,28 +277,73 @@ func bindMultiParamTypesHandler(top *Route, r *Route) {
|
||||||
return // should never happen, previous checks made to set the top link.
|
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) {
|
decisionHandler := func(ctx context.Context) {
|
||||||
// println("core/router/handler.go: decision handler; " + ctx.Path() + " route.Name: " + r.Name + " vs context's " + ctx.GetCurrentRoute().Name())
|
// 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},
|
// 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.
|
// because the handler on this case is executing from last to top.
|
||||||
if f(ctx) {
|
if f(ctx) {
|
||||||
// println("core/router/handler.go: filter for : " + r.Name + " passed")
|
// 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.HandlerIndex(0)
|
||||||
ctx.Do(h)
|
ctx.Do(h)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.SetCurrentRouteName(currentRouteName)
|
ctx.SetCurrentRoute(currentRoute)
|
||||||
ctx.StatusCode(http.StatusOK)
|
ctx.StatusCode(currentStatusCode)
|
||||||
ctx.Next()
|
ctx.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
r.topLink.beginHandlers = append(context.Handlers{decisionHandler}, r.topLink.beginHandlers...)
|
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) {
|
func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||||
method := ctx.Method()
|
method := ctx.Method()
|
||||||
path := ctx.Path()
|
path := ctx.Path()
|
||||||
|
@ -304,40 +384,13 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.hosts && t.subdomain != "" {
|
if h.hosts && !h.canHandleSubdomain(ctx, t.subdomain) {
|
||||||
requestHost := ctx.Host()
|
continue
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n := t.search(path, ctx.Params())
|
n := t.search(path, ctx.Params())
|
||||||
if n != nil {
|
if n != nil {
|
||||||
ctx.SetCurrentRouteName(n.RouteName)
|
ctx.SetCurrentRoute(n.Route)
|
||||||
ctx.Do(n.Handlers)
|
ctx.Do(n.Handlers)
|
||||||
// found
|
// found
|
||||||
return
|
return
|
||||||
|
@ -364,6 +417,89 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||||
ctx.StatusCode(http.StatusNotFound)
|
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 {
|
func (h *routerHandler) subdomainAndPathAndMethodExists(ctx context.Context, t *trie, method, path string) bool {
|
||||||
if method != "" && method != t.method {
|
if method != "" && method != t.method {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -33,6 +33,22 @@ type Party interface {
|
||||||
// Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path
|
// Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path
|
||||||
Macros() *macro.Macros
|
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,
|
// Party groups routes which may have the same prefix and share same handlers,
|
||||||
// returns that new rich subrouter.
|
// returns that new rich subrouter.
|
||||||
//
|
//
|
||||||
|
|
|
@ -22,6 +22,7 @@ type Route struct {
|
||||||
Name string `json:"name"` // "userRoute"
|
Name string `json:"name"` // "userRoute"
|
||||||
Description string `json:"description"` // "lists a user"
|
Description string `json:"description"` // "lists a user"
|
||||||
Method string `json:"method"` // "GET"
|
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.
|
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."
|
Subdomain string `json:"subdomain"` // "admin."
|
||||||
tmpl macro.Template // Tmpl().Src: "/api/user/{id:uint64}"
|
tmpl macro.Template // Tmpl().Src: "/api/user/{id:uint64}"
|
||||||
|
@ -62,6 +63,9 @@ type Route struct {
|
||||||
LastMod time.Time `json:"lastMod,omitempty"`
|
LastMod time.Time `json:"lastMod,omitempty"`
|
||||||
ChangeFreq string `json:"changeFreq,omitempty"`
|
ChangeFreq string `json:"changeFreq,omitempty"`
|
||||||
Priority float32 `json:"priority,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,
|
// 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.
|
// handlers and the macro container which all routes should share.
|
||||||
// It parses the path based on the "macros",
|
// It parses the path based on the "macros",
|
||||||
// handlers are being changed to validate the macros at serve time, if needed.
|
// 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) {
|
handlers context.Handlers, macros macro.Macros) (*Route, error) {
|
||||||
tmpl, err := macro.Parse(unparsedPath, macros)
|
tmpl, err := macro.Parse(unparsedPath, macros)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -86,9 +90,14 @@ func NewRoute(method, subdomain, unparsedPath string,
|
||||||
|
|
||||||
path = cleanPath(path) // maybe unnecessary here.
|
path = cleanPath(path) // maybe unnecessary here.
|
||||||
defaultName := method + subdomain + tmpl.Src
|
defaultName := method + subdomain + tmpl.Src
|
||||||
|
if statusErrorCode > 0 {
|
||||||
|
defaultName = fmt.Sprintf("%d_%s", statusErrorCode, defaultName)
|
||||||
|
}
|
||||||
|
|
||||||
formattedPath := formatPath(path)
|
formattedPath := formatPath(path)
|
||||||
|
|
||||||
route := &Route{
|
route := &Route{
|
||||||
|
StatusCode: statusErrorCode,
|
||||||
Name: defaultName,
|
Name: defaultName,
|
||||||
Method: method,
|
Method: method,
|
||||||
methodBckp: method,
|
methodBckp: method,
|
||||||
|
@ -99,6 +108,7 @@ func NewRoute(method, subdomain, unparsedPath string,
|
||||||
FormattedPath: formattedPath,
|
FormattedPath: formattedPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
route.ReadOnly = routeReadOnlyWrapper{route}
|
||||||
return route, nil
|
return route, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,15 +199,20 @@ func (r *Route) BuildHandlers() {
|
||||||
|
|
||||||
// String returns the form of METHOD, SUBDOMAIN, TMPL PATH.
|
// String returns the form of METHOD, SUBDOMAIN, TMPL PATH.
|
||||||
func (r *Route) String() string {
|
func (r *Route) String() string {
|
||||||
|
start := r.Method
|
||||||
|
if r.StatusCode > 0 {
|
||||||
|
start = http.StatusText(r.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("%s %s%s",
|
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
|
// Equal compares the method, subdomain and the
|
||||||
// underline representation of the route's path,
|
// underline representation of the route's path,
|
||||||
// instead of the `String` function which returns the front representation.
|
// instead of the `String` function which returns the front representation.
|
||||||
func (r *Route) Equal(other *Route) bool {
|
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
|
// DeepEqual compares the method, subdomain, the
|
||||||
|
@ -467,6 +482,10 @@ type routeReadOnlyWrapper struct {
|
||||||
*Route
|
*Route
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rd routeReadOnlyWrapper) StatusErrorCode() int {
|
||||||
|
return rd.Route.StatusCode
|
||||||
|
}
|
||||||
|
|
||||||
func (rd routeReadOnlyWrapper) Method() string {
|
func (rd routeReadOnlyWrapper) Method() string {
|
||||||
return rd.Route.Method
|
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) {
|
func checkAndClearBuf(t *testing.T, buff *bytes.Buffer, expected string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if got, expected := buff.String(), expected; got != expected {
|
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()
|
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
|
staticKey string
|
||||||
|
|
||||||
// insert data.
|
// insert data.
|
||||||
|
Route context.RouteReadOnly
|
||||||
Handlers context.Handlers
|
Handlers context.Handlers
|
||||||
RouteName string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTrieNode() *trieNode {
|
func newTrieNode() *trieNode {
|
||||||
|
@ -89,7 +89,9 @@ type trie struct {
|
||||||
hasRootWildcard bool
|
hasRootWildcard bool
|
||||||
hasRootSlash bool
|
hasRootSlash bool
|
||||||
|
|
||||||
|
statusCode int // for error codes only, method is ignored.
|
||||||
method string
|
method string
|
||||||
|
|
||||||
// subdomain is empty for default-hostname routes,
|
// subdomain is empty for default-hostname routes,
|
||||||
// ex: mysubdomain.
|
// ex: mysubdomain.
|
||||||
subdomain string
|
subdomain string
|
||||||
|
@ -108,7 +110,7 @@ func slowPathSplit(path string) []string {
|
||||||
return strings.Split(path, pathSep)[1:]
|
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)
|
input := slowPathSplit(path)
|
||||||
|
|
||||||
n := tr.root
|
n := tr.root
|
||||||
|
@ -148,8 +150,9 @@ func (tr *trie) insert(path, routeName string, handlers context.Handlers) {
|
||||||
n = n.getChild(s)
|
n = n.getChild(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
n.RouteName = routeName
|
n.Route = route
|
||||||
n.Handlers = handlers
|
n.Handlers = handlers
|
||||||
|
|
||||||
n.paramKeys = paramKeys
|
n.paramKeys = paramKeys
|
||||||
n.key = path
|
n.key = path
|
||||||
n.end = true
|
n.end = true
|
||||||
|
@ -163,6 +166,8 @@ func (tr *trie) insert(path, routeName string, handlers context.Handlers) {
|
||||||
}
|
}
|
||||||
|
|
||||||
n.staticKey = path[:i]
|
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 {
|
func (tr *trie) search(q string, params *context.RequestParams) *trieNode {
|
||||||
|
|
2
iris.go
2
iris.go
|
@ -130,6 +130,7 @@ type Application struct {
|
||||||
// routing embedded | exposing APIBuilder's and Router's public API.
|
// routing embedded | exposing APIBuilder's and Router's public API.
|
||||||
*router.APIBuilder
|
*router.APIBuilder
|
||||||
*router.Router
|
*router.Router
|
||||||
|
router.HTTPErrorHandler // if Router is Downgraded this is nil.
|
||||||
ContextPool *context.Pool
|
ContextPool *context.Pool
|
||||||
|
|
||||||
// config contains the configuration fields
|
// config contains the configuration fields
|
||||||
|
@ -834,6 +835,7 @@ func (app *Application) Build() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rp.Err(err)
|
rp.Err(err)
|
||||||
}
|
}
|
||||||
|
app.HTTPErrorHandler = routerHandler
|
||||||
// re-build of the router from outside can be done with
|
// re-build of the router from outside can be done with
|
||||||
// app.RefreshRouter()
|
// app.RefreshRouter()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user