mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
implement a dynamic router handler for #2167
This commit is contained in:
parent
72f9d4ba5c
commit
9d538eabb0
|
@ -41,5 +41,5 @@ func main() {
|
|||
ctx.Exec("GET", "/invisible/iris")
|
||||
})
|
||||
|
||||
app.Listen(":8080")
|
||||
app.Listen(":8080", iris.WithDynamicHandler)
|
||||
}
|
||||
|
|
|
@ -308,6 +308,14 @@ var WithLowercaseRouting = func(app *Application) {
|
|||
app.config.ForceLowercaseRouting = true
|
||||
}
|
||||
|
||||
// WithDynamicHandler enables for dynamic routing by
|
||||
// setting the `EnableDynamicHandler` to true.
|
||||
//
|
||||
// See `Configuration`.
|
||||
var WithDynamicHandler = func(app *Application) {
|
||||
app.config.EnableDynamicHandler = true
|
||||
}
|
||||
|
||||
// WithOptimizations can force the application to optimize for the best performance where is possible.
|
||||
//
|
||||
// See `Configuration`.
|
||||
|
@ -737,6 +745,14 @@ type Configuration struct {
|
|||
//
|
||||
// Defaults to false.
|
||||
ForceLowercaseRouting bool `ini:"force_lowercase_routing" json:"forceLowercaseRouting,omitempty" yaml:"ForceLowercaseRouting" toml:"ForceLowercaseRouting"`
|
||||
// EnableOptimizations enables dynamic request handler.
|
||||
// It gives the router the feature to add routes while in serve-time,
|
||||
// when `RefreshRouter` is called.
|
||||
// If this setting is set to true, the request handler will use a mutex for data(trie routing) protection,
|
||||
// hence the performance cost.
|
||||
//
|
||||
// Defaults to false.
|
||||
EnableDynamicHandler bool `ini:"enable_dynamic_handler" json:"enableDynamicHandler,omitempty" yaml:"EnableDynamicHandler" toml:"EnableDynamicHandler"`
|
||||
// FireMethodNotAllowed if it's true router checks for StatusMethodNotAllowed(405) and
|
||||
// fires the 405 error instead of 404
|
||||
// Defaults to false.
|
||||
|
@ -1008,6 +1024,11 @@ func (c *Configuration) GetForceLowercaseRouting() bool {
|
|||
return c.ForceLowercaseRouting
|
||||
}
|
||||
|
||||
// GetEnableDynamicHandler returns the EnableDynamicHandler field.
|
||||
func (c *Configuration) GetEnableDynamicHandler() bool {
|
||||
return c.EnableDynamicHandler
|
||||
}
|
||||
|
||||
// GetFireMethodNotAllowed returns the FireMethodNotAllowed field.
|
||||
func (c *Configuration) GetFireMethodNotAllowed() bool {
|
||||
return c.FireMethodNotAllowed
|
||||
|
|
|
@ -34,6 +34,8 @@ type ConfigurationReadOnly interface {
|
|||
GetEnablePathEscape() bool
|
||||
// GetForceLowercaseRouting returns the ForceLowercaseRouting field.
|
||||
GetForceLowercaseRouting() bool
|
||||
// GetEnableOptimizations returns the EnableDynamicHandler field.
|
||||
GetEnableDynamicHandler() bool
|
||||
// GetFireMethodNotAllowed returns the FireMethodNotAllowed field.
|
||||
GetFireMethodNotAllowed() bool
|
||||
// GetDisableAutoFireStatusCode returns the DisableAutoFireStatusCode field.
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/v12/context"
|
||||
|
@ -39,8 +42,18 @@ type (
|
|||
// on the given context's response status code.
|
||||
FireErrorCode(ctx *context.Context)
|
||||
}
|
||||
|
||||
// RouteAdder is an optional interface that can be implemented by a `RequestHandler`.
|
||||
RouteAdder interface {
|
||||
// AddRoute should add a route to the request handler directly.
|
||||
AddRoute(*Route) error
|
||||
}
|
||||
)
|
||||
|
||||
// ErrNotRouteAdder throws on `AddRouteUnsafe` when a registered `RequestHandler`
|
||||
// does not implements the optional `AddRoute(*Route) error` method.
|
||||
var ErrNotRouteAdder = errors.New("request handler does not implement AddRoute method")
|
||||
|
||||
type routerHandler struct {
|
||||
// Config.
|
||||
disablePathCorrection bool
|
||||
|
@ -59,8 +72,103 @@ type routerHandler struct {
|
|||
errorDefaultHandlers context.Handlers // the main handler(s) for default error code handlers, when not registered directly by the end-developer.
|
||||
}
|
||||
|
||||
var _ RequestHandler = (*routerHandler)(nil)
|
||||
var _ HTTPErrorHandler = (*routerHandler)(nil)
|
||||
var (
|
||||
_ RequestHandler = (*routerHandler)(nil)
|
||||
_ HTTPErrorHandler = (*routerHandler)(nil)
|
||||
)
|
||||
|
||||
type routerHandlerDynamic struct {
|
||||
RequestHandler
|
||||
rw sync.RWMutex
|
||||
|
||||
locked uint32
|
||||
}
|
||||
|
||||
// RouteExists reports whether a particular route exists.
|
||||
func (h *routerHandlerDynamic) RouteExists(ctx *context.Context, method, path string) (exists bool) {
|
||||
h.lock(false, func() error {
|
||||
exists = h.RequestHandler.RouteExists(ctx, method, path)
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (h *routerHandlerDynamic) AddRoute(r *Route) error {
|
||||
if v, ok := h.RequestHandler.(RouteAdder); ok {
|
||||
return h.lock(true, func() error {
|
||||
return v.AddRoute(r)
|
||||
})
|
||||
}
|
||||
|
||||
return ErrNotRouteAdder
|
||||
}
|
||||
|
||||
func (h *routerHandlerDynamic) lock(writeAccess bool, fn func() error) error {
|
||||
if atomic.CompareAndSwapUint32(&h.locked, 0, 1) {
|
||||
if writeAccess {
|
||||
h.rw.Lock()
|
||||
} else {
|
||||
h.rw.RLock()
|
||||
}
|
||||
|
||||
err := fn()
|
||||
|
||||
// check agan because fn may called the unlock method.
|
||||
if atomic.CompareAndSwapUint32(&h.locked, 1, 0) {
|
||||
if writeAccess {
|
||||
h.rw.Unlock()
|
||||
} else {
|
||||
h.rw.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return fn()
|
||||
}
|
||||
|
||||
func (h *routerHandlerDynamic) Build(provider RoutesProvider) error {
|
||||
// Build can be called inside HandleRequest if the route handler
|
||||
// calls the RefreshRouter method, and it will stuck on the rw.Lock() call,
|
||||
// so use a custom version of it.
|
||||
// h.rw.Lock()
|
||||
// defer h.rw.Unlock()
|
||||
|
||||
return h.lock(true, func() error {
|
||||
return h.RequestHandler.Build(provider)
|
||||
})
|
||||
}
|
||||
|
||||
func (h *routerHandlerDynamic) HandleRequest(ctx *context.Context) {
|
||||
h.lock(false, func() error {
|
||||
h.RequestHandler.HandleRequest(ctx)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (h *routerHandlerDynamic) FireErrorCode(ctx *context.Context) {
|
||||
h.lock(false, func() error {
|
||||
h.RequestHandler.FireErrorCode(ctx)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// NewDynamicHandler returns a new router handler which is responsible handle each request
|
||||
// with routes that can be added in serve-time.
|
||||
// It's a wrapper of the `NewDefaultHandler`.
|
||||
// It's being used when the `ConfigurationReadOnly.GetEnableDynamicHandler` is true.
|
||||
func NewDynamicHandler(config context.ConfigurationReadOnly, logger *golog.Logger) RequestHandler /* #2167 */ {
|
||||
handler := NewDefaultHandler(config, logger)
|
||||
return wrapDynamicHandler(handler)
|
||||
}
|
||||
|
||||
func wrapDynamicHandler(handler RequestHandler) RequestHandler {
|
||||
return &routerHandlerDynamic{
|
||||
RequestHandler: handler,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultHandler returns the handler which is responsible
|
||||
// to map the request with a route (aka mux implementation).
|
||||
|
@ -71,6 +179,7 @@ func NewDefaultHandler(config context.ConfigurationReadOnly, logger *golog.Logge
|
|||
fireMethodNotAllowed bool
|
||||
enablePathIntelligence bool
|
||||
forceLowercaseRouting bool
|
||||
dynamicHandlerEnabled bool
|
||||
)
|
||||
|
||||
if config != nil { // #2147
|
||||
|
@ -79,9 +188,10 @@ func NewDefaultHandler(config context.ConfigurationReadOnly, logger *golog.Logge
|
|||
fireMethodNotAllowed = config.GetFireMethodNotAllowed()
|
||||
enablePathIntelligence = config.GetEnablePathIntelligence()
|
||||
forceLowercaseRouting = config.GetForceLowercaseRouting()
|
||||
dynamicHandlerEnabled = config.GetEnableDynamicHandler()
|
||||
}
|
||||
|
||||
return &routerHandler{
|
||||
handler := &routerHandler{
|
||||
disablePathCorrection: disablePathCorrection,
|
||||
disablePathCorrectionRedirection: disablePathCorrectionRedirection,
|
||||
fireMethodNotAllowed: fireMethodNotAllowed,
|
||||
|
@ -89,6 +199,12 @@ func NewDefaultHandler(config context.ConfigurationReadOnly, logger *golog.Logge
|
|||
forceLowercaseRouting: forceLowercaseRouting,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
if dynamicHandlerEnabled {
|
||||
return wrapDynamicHandler(handler)
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func (h *routerHandler) getTree(statusCode int, method, subdomain string) *trie {
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
|
||||
func TestRegisterRule(t *testing.T) {
|
||||
app := iris.New()
|
||||
app.Configure(iris.WithDynamicHandler)
|
||||
|
||||
// collect the error on RouteError rule.
|
||||
buf := new(bytes.Buffer)
|
||||
app.Logger().SetTimeFormat("").DisableNewLine().SetOutput(buf)
|
||||
|
|
|
@ -49,23 +49,21 @@ func NewRouter() *Router {
|
|||
|
||||
// RefreshRouter re-builds the router. Should be called when a route's state
|
||||
// changed (i.e Method changed at serve-time).
|
||||
//
|
||||
// Note that in order to use RefreshRouter while in serve-time,
|
||||
// you have to set the `EnableDynamicHandler` Iris Application setting to true,
|
||||
// e.g. `app.Listen(":8080", iris.WithEnableDynamicHandler)`
|
||||
func (router *Router) RefreshRouter() error {
|
||||
return router.BuildRouter(router.cPool, router.requestHandler, router.routesProvider, true)
|
||||
}
|
||||
|
||||
// ErrNotRouteAdder throws on `AddRouteUnsafe` when a registered `RequestHandler`
|
||||
// does not implements the optional `AddRoute(*Route) error` method.
|
||||
var ErrNotRouteAdder = errors.New("request handler does not implement AddRoute method")
|
||||
|
||||
// AddRouteUnsafe adds a route directly to the router's request handler.
|
||||
// Works before or after Build state.
|
||||
// Mainly used for internal cases like `iris.WithSitemap`.
|
||||
// Do NOT use it on serve-time.
|
||||
func (router *Router) AddRouteUnsafe(routes ...*Route) error {
|
||||
if h := router.requestHandler; h != nil {
|
||||
if v, ok := h.(interface {
|
||||
AddRoute(*Route) error
|
||||
}); ok {
|
||||
if v, ok := h.(RouteAdder); ok {
|
||||
for _, r := range routes {
|
||||
return v.AddRoute(r)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user