2015-03-27 23:21:30 +02:00
package echo
2015-03-01 19:45:13 +02:00
2019-10-03 19:30:46 +02:00
import (
2021-12-04 20:02:11 +02:00
"bytes"
2021-07-15 22:34:01 +02:00
"errors"
2019-10-03 19:30:46 +02:00
"net/http"
2021-07-15 22:34:01 +02:00
"net/url"
2019-10-03 19:30:46 +02:00
)
2018-10-14 17:16:58 +02:00
2021-07-15 22:34:01 +02:00
// Router is interface for routing request contexts to registered routes.
2022-05-04 13:32:09 +02:00
//
// Contract between Echo/Context instance and the router:
2022-12-04 22:17:48 +02:00
// - all routes must be added through methods on echo.Echo instance.
// Reason: Echo instance uses RouteInfo.Params() length to allocate slice for paths parameters (see `Echo.contextPathParamAllocSize`).
// - Router must populate Context during Router.Route call with:
// - RoutableContext.SetPath
// - RoutableContext.SetRawPathParams (IMPORTANT! with same slice pointer that c.RawPathParams() returns)
// - RoutableContext.SetRouteInfo
// And optionally can set additional information to Context with RoutableContext.Set
2021-07-15 22:34:01 +02:00
type Router interface {
// Add registers Routable with the Router and returns registered RouteInfo
Add ( routable Routable ) ( RouteInfo , error )
// Remove removes route from the Router
Remove ( method string , path string ) error
// Routes returns information about all registered routes
Routes ( ) Routes
// Route searches Router for matching route and applies it to the given context. In case when no matching method
// was not found (405) or no matching route exists for path (404), router will return its implementation of 405/404
// handler function.
Route ( c RoutableContext ) HandlerFunc
}
// RoutableContext is additional interface that structures implementing Context must implement. Methods inside this
// interface are meant for request routing purposes and should not be used in middlewares.
type RoutableContext interface {
// Request returns `*http.Request`.
Request ( ) * http . Request
// RawPathParams returns raw path pathParams value. Allocation of PathParams is handled by Context.
RawPathParams ( ) * PathParams
// SetRawPathParams replaces any existing param values with new values for this context lifetime (request).
// Do not set any other value than what you got from RawPathParams as allocation of PathParams is handled by Context.
SetRawPathParams ( params * PathParams )
// SetPath sets the registered path for the handler.
SetPath ( p string )
// SetRouteInfo sets the route info of this request to the context.
SetRouteInfo ( ri RouteInfo )
// Set saves data in the context. Allows router to store arbitrary (that only router has access to) data in context
// for later use in middlewares/handler.
Set ( key string , val interface { } )
}
// Routable is interface for registering Route with Router. During route registration process the Router will
// convert Routable to RouteInfo with ToRouteInfo method. By creating custom implementation of Routable additional
// information about registered route can be stored in Routes (i.e. privileges used with route etc.)
type Routable interface {
// ToRouteInfo converts Routable to RouteInfo
//
// This method is meant to be used by Router after it parses url for path parameters, to store information about
// route just added.
ToRouteInfo ( params [ ] string ) RouteInfo
// ToRoute converts Routable to Route which Router uses to register the method handler for path.
//
// This method is meant to be used by Router to get fields (including handler and middleware functions) needed to
// add Route to Router.
ToRoute ( ) Route
// ForGroup recreates routable with added group prefix and group middlewares it is grouped to.
//
// Is necessary for Echo.Group to be able to add/register Routable with Router and having group prefix and group
// middlewares included in actually registered Route.
ForGroup ( pathPrefix string , middlewares [ ] MiddlewareFunc ) Routable
}
const (
// NotFoundRouteName is name of RouteInfo returned when router did not find matching route (404: not found).
NotFoundRouteName = "echo_route_not_found_name"
// MethodNotAllowedRouteName is name of RouteInfo returned when router did not find matching method for route (405: method not allowed).
MethodNotAllowedRouteName = "echo_route_method_not_allowed_name"
2015-03-01 19:45:13 +02:00
)
2021-07-15 22:34:01 +02:00
// Routes is collection of RouteInfo instances with various helper methods.
type Routes [ ] RouteInfo
// RouteInfo describes registered route base fields.
// Method+Path pair uniquely identifies the Route. Name can have duplicates.
type RouteInfo interface {
Method ( ) string
Path ( ) string
Name ( ) string
Params ( ) [ ] string
Reverse ( params ... interface { } ) string
// NOTE: handler and middlewares are not exposed because handler could be already wrapping middlewares and therefore
// it is not always 100% known if handler function already wraps middlewares or not. In Echo handler could be one
// function or several functions wrapping each other.
}
// PathParams is collections of PathParam instances with various helper methods
type PathParams [ ] PathParam
// PathParam is tuple pf path parameter name and its value in request path
type PathParam struct {
Name string
Value string
}
// DefaultRouter is the registry of all registered routes for an `Echo` instance for
// request matching and URL path parameter parsing.
// Note: DefaultRouter is not coroutine-safe. Do not Add/Remove routes after HTTP server has been started with Echo.
type DefaultRouter struct {
tree * node
routes Routes
notFoundHandler HandlerFunc
methodNotAllowedHandler HandlerFunc
optionsMethodHandler HandlerFunc
allowOverwritingRoute bool
unescapePathParamValues bool
useEscapedPathForRouting bool
}
// RouterConfig is configuration options for (default) router
type RouterConfig struct {
// AllowOverwritingRoute instructs Router NOT to return error when new route is registered with the same method+path
// and replaces matching route with the new one.
AllowOverwritingRoute bool
// UnescapePathParamValues instructs Router to unescape path parameter value when request if matched to the routes
UnescapePathParamValues bool
// UseEscapedPathForMatching instructs Router to use escaped request URL path (req.URL.Path) for matching the request.
UseEscapedPathForMatching bool
// NotFoundHandler sets handler for case when router did not match any routes to the request path. HTTP error 404 (not found)
NotFoundHandler HandlerFunc
// MethodNotAllowedHandler sets handler for case when router did match router with path but not with current request
// method. HTTP error 405 (method not allowed)
MethodNotAllowedHandler HandlerFunc
// OptionsMethodHandler sets handler for OPTIONS method. This has lower priority than handler set by `e.OPTIONS(path, ...)`
// When `CORS` middleware is used this handler will not be called as `CORS` will terminate in case of OPTIONS method
// middleware chain and actual handler will not be called.
OptionsMethodHandler HandlerFunc
}
// NewRouter returns a new Router instance.
func NewRouter ( config RouterConfig ) * DefaultRouter {
r := & DefaultRouter {
tree : & node {
methods : new ( routeMethods ) ,
isLeaf : true ,
isHandler : false ,
} ,
routes : make ( Routes , 0 ) ,
allowOverwritingRoute : config . AllowOverwritingRoute ,
unescapePathParamValues : config . UnescapePathParamValues ,
useEscapedPathForRouting : config . UseEscapedPathForMatching ,
notFoundHandler : notFoundHandler ,
methodNotAllowedHandler : methodNotAllowedHandler ,
optionsMethodHandler : optionsMethodHandler ,
}
if config . NotFoundHandler != nil {
r . notFoundHandler = config . NotFoundHandler
}
if config . MethodNotAllowedHandler != nil {
r . methodNotAllowedHandler = config . MethodNotAllowedHandler
}
if config . OptionsMethodHandler != nil {
r . optionsMethodHandler = config . OptionsMethodHandler
}
return r
}
type children [ ] * node
type node struct {
kind kind
label byte
prefix string
parent * node
staticChildren children
originalPath string
methods * routeMethods
paramChild * node
anyChild * node
paramsCount int
// isLeaf indicates that node does not have child routes
isLeaf bool
// isHandler indicates that node has at least one handler registered to it
isHandler bool
}
type kind uint8
2015-04-24 16:44:30 +02:00
const (
2021-03-06 01:43:59 +02:00
staticKind kind = iota
paramKind
anyKind
2020-11-21 04:48:16 +02:00
paramLabel = byte ( ':' )
anyLabel = byte ( '*' )
2015-04-24 16:44:30 +02:00
)
2021-07-15 22:34:01 +02:00
type routeMethod struct {
* routeInfo
handler HandlerFunc
orgRouteInfo RouteInfo
}
type routeMethods struct {
2022-07-17 21:19:09 +02:00
connect * routeMethod
delete * routeMethod
get * routeMethod
head * routeMethod
options * routeMethod
patch * routeMethod
post * routeMethod
propfind * routeMethod
put * routeMethod
trace * routeMethod
report * routeMethod
anyOther map [ string ] * routeMethod
// notFoundHandler is handler registered with RouteNotFound method and is executed for 404 cases
notFoundHandler * routeMethod
2021-07-15 22:34:01 +02:00
allowHeader string
}
func ( m * routeMethods ) set ( method string , r * routeMethod ) {
switch method {
case http . MethodConnect :
m . connect = r
case http . MethodDelete :
m . delete = r
case http . MethodGet :
m . get = r
case http . MethodHead :
m . head = r
case http . MethodOptions :
m . options = r
case http . MethodPatch :
m . patch = r
case http . MethodPost :
m . post = r
case PROPFIND :
m . propfind = r
case http . MethodPut :
m . put = r
case http . MethodTrace :
m . trace = r
case REPORT :
m . report = r
2022-07-17 21:19:09 +02:00
case RouteNotFound :
m . notFoundHandler = r
return // RouteNotFound/404 is not considered as a handler so no further logic needs to be executed
2021-07-15 22:34:01 +02:00
default :
if m . anyOther == nil {
m . anyOther = make ( map [ string ] * routeMethod )
}
if r . handler == nil {
delete ( m . anyOther , method )
} else {
m . anyOther [ method ] = r
}
}
m . updateAllowHeader ( )
2021-04-27 09:55:31 +02:00
}
2021-07-15 22:34:01 +02:00
func ( m * routeMethods ) find ( method string ) * routeMethod {
switch method {
case http . MethodConnect :
return m . connect
case http . MethodDelete :
return m . delete
case http . MethodGet :
return m . get
case http . MethodHead :
return m . head
case http . MethodOptions :
return m . options
case http . MethodPatch :
return m . patch
case http . MethodPost :
return m . post
case PROPFIND :
return m . propfind
case http . MethodPut :
return m . put
case http . MethodTrace :
return m . trace
case REPORT :
return m . report
default :
return m . anyOther [ method ]
}
}
func ( m * routeMethods ) updateAllowHeader ( ) {
2021-12-04 20:02:11 +02:00
buf := new ( bytes . Buffer )
buf . WriteString ( http . MethodOptions )
if m . connect != nil {
buf . WriteString ( ", " )
buf . WriteString ( http . MethodConnect )
}
if m . delete != nil {
buf . WriteString ( ", " )
buf . WriteString ( http . MethodDelete )
}
if m . get != nil {
buf . WriteString ( ", " )
buf . WriteString ( http . MethodGet )
}
if m . head != nil {
buf . WriteString ( ", " )
buf . WriteString ( http . MethodHead )
}
if m . patch != nil {
buf . WriteString ( ", " )
buf . WriteString ( http . MethodPatch )
}
if m . post != nil {
buf . WriteString ( ", " )
buf . WriteString ( http . MethodPost )
}
if m . propfind != nil {
buf . WriteString ( ", PROPFIND" )
}
if m . put != nil {
buf . WriteString ( ", " )
buf . WriteString ( http . MethodPut )
}
if m . trace != nil {
buf . WriteString ( ", " )
buf . WriteString ( http . MethodTrace )
}
if m . report != nil {
buf . WriteString ( ", REPORT" )
}
2022-12-04 22:17:48 +02:00
for method := range m . anyOther { // for simplicity, we use map and therefore order is not deterministic here
2021-07-15 22:34:01 +02:00
buf . WriteString ( ", " )
buf . WriteString ( method )
}
2021-12-04 20:02:11 +02:00
m . allowHeader = buf . String ( )
}
2021-07-15 22:34:01 +02:00
func ( m * routeMethods ) isHandler ( ) bool {
return m . get != nil ||
m . post != nil ||
m . options != nil ||
m . put != nil ||
m . delete != nil ||
m . connect != nil ||
m . head != nil ||
m . patch != nil ||
m . propfind != nil ||
m . trace != nil ||
m . report != nil ||
len ( m . anyOther ) != 0
2022-07-17 21:19:09 +02:00
// RouteNotFound/404 is not considered as a handler
2015-03-01 19:45:13 +02:00
}
2021-07-15 22:34:01 +02:00
// Routes returns all registered routes
func ( r * DefaultRouter ) Routes ( ) Routes {
return r . routes
}
// Remove unregisters registered route
func ( r * DefaultRouter ) Remove ( method string , path string ) error {
currentNode := r . tree
if currentNode == nil || ( currentNode . isLeaf && ! currentNode . isHandler ) {
return errors . New ( "router has no routes to remove" )
}
2016-04-12 07:53:31 +02:00
if path == "" {
2019-04-30 07:54:49 +02:00
path = "/"
2016-04-12 07:53:31 +02:00
}
if path [ 0 ] != '/' {
path = "/" + path
}
2015-04-24 16:44:30 +02:00
2021-07-15 22:34:01 +02:00
var nodeToRemove * node
prefixLen := 0
for {
if currentNode . originalPath == path && currentNode . isHandler {
nodeToRemove = currentNode
break
}
if currentNode . kind == staticKind {
prefixLen = prefixLen + len ( currentNode . prefix )
} else {
prefixLen = len ( currentNode . originalPath )
}
if prefixLen >= len ( path ) {
break
}
next := path [ prefixLen ]
switch next {
case paramLabel :
currentNode = currentNode . paramChild
case anyLabel :
currentNode = currentNode . anyChild
default :
currentNode = currentNode . findStaticChild ( next )
}
if currentNode == nil {
break
}
}
if nodeToRemove == nil {
return errors . New ( "could not find route to remove by given path" )
}
if ! nodeToRemove . isHandler {
return errors . New ( "could not find route to remove by given path" )
}
if mh := nodeToRemove . methods . find ( method ) ; mh == nil {
return errors . New ( "could not find route to remove by given path and method" )
}
nodeToRemove . setHandler ( method , nil )
var rIndex int
for i , rr := range r . routes {
if rr . Method ( ) == method && rr . Path ( ) == path {
rIndex = i
break
}
}
r . routes = append ( r . routes [ : rIndex ] , r . routes [ rIndex + 1 : ] ... )
if ! nodeToRemove . isHandler && nodeToRemove . isLeaf {
// TODO: if !nodeToRemove.isLeaf and has at least 2 children merge paths for remaining nodes?
current := nodeToRemove
for {
parent := current . parent
if parent == nil {
break
}
switch current . kind {
case staticKind :
var index int
for i , c := range parent . staticChildren {
if c == current {
index = i
break
}
}
parent . staticChildren = append ( parent . staticChildren [ : index ] , parent . staticChildren [ index + 1 : ] ... )
case paramKind :
parent . paramChild = nil
case anyKind :
parent . anyChild = nil
}
parent . isLeaf = parent . anyChild == nil && parent . paramChild == nil && len ( parent . staticChildren ) == 0
if ! parent . isLeaf || parent . isHandler {
break
}
current = parent
}
2021-04-27 09:55:31 +02:00
}
2021-07-15 22:34:01 +02:00
return nil
}
// AddRouteError is error returned by Router.Add containing information what actual route adding failed. Useful for
// mass adding (i.e. Any() routes)
type AddRouteError struct {
Method string
Path string
Err error
}
func ( e * AddRouteError ) Error ( ) string { return e . Method + " " + e . Path + ": " + e . Err . Error ( ) }
func ( e * AddRouteError ) Unwrap ( ) error { return e . Err }
func newAddRouteError ( route Route , err error ) * AddRouteError {
return & AddRouteError {
Method : route . Method ,
Path : route . Path ,
Err : err ,
}
}
// Add registers a new route for method and path with matching handler.
func ( r * DefaultRouter ) Add ( routable Routable ) ( RouteInfo , error ) {
route := routable . ToRoute ( )
if route . Handler == nil {
return nil , newAddRouteError ( route , errors . New ( "adding route without handler function" ) )
}
method := route . Method
path := route . Path
h := applyMiddleware ( route . Handler , route . Middlewares ... )
if ! r . allowOverwritingRoute {
for _ , rr := range r . routes {
if route . Method == rr . Method ( ) && route . Path == rr . Path ( ) {
return nil , newAddRouteError ( route , errors . New ( "adding duplicate route (same method+path) is not allowed" ) )
}
}
}
if path == "" {
path = "/"
}
if path [ 0 ] != '/' {
path = "/" + path
}
paramNames := make ( [ ] string , 0 )
originalPath := path
wasAdded := false
var ri RouteInfo
2021-03-06 01:43:59 +02:00
for i , lcpIndex := 0 , len ( path ) ; i < lcpIndex ; i ++ {
2021-07-15 22:34:01 +02:00
if path [ i ] == paramLabel {
2021-09-19 10:39:12 +02:00
if i > 0 && path [ i - 1 ] == '\\' {
2021-12-16 22:58:40 +02:00
path = path [ : i - 1 ] + path [ i : ]
i --
lcpIndex --
2021-09-19 10:39:12 +02:00
continue
}
2015-04-24 16:44:30 +02:00
j := i + 1
2021-07-15 22:34:01 +02:00
r . insert ( staticKind , path [ : i ] , method , routeMethod { routeInfo : & routeInfo { method : method } } )
2021-03-06 01:43:59 +02:00
for ; i < lcpIndex && path [ i ] != '/' ; i ++ {
2015-03-01 19:45:13 +02:00
}
2015-04-24 16:44:30 +02:00
2021-07-15 22:34:01 +02:00
paramNames = append ( paramNames , path [ j : i ] )
2015-04-24 16:44:30 +02:00
path = path [ : j ] + path [ i : ]
2021-03-06 01:43:59 +02:00
i , lcpIndex = j , len ( path )
2015-04-24 16:44:30 +02:00
2021-03-06 01:43:59 +02:00
if i == lcpIndex {
2021-04-27 09:55:31 +02:00
// path node is last fragment of route path. ie. `/users/:id`
2021-07-15 22:34:01 +02:00
ri = routable . ToRouteInfo ( paramNames )
rm := routeMethod {
routeInfo : & routeInfo { method : method , path : originalPath , params : paramNames , name : route . Name } ,
handler : h ,
orgRouteInfo : ri ,
}
r . insert ( paramKind , path [ : i ] , method , rm )
wasAdded = true
break
2019-04-29 07:22:35 +02:00
} else {
2021-07-15 22:34:01 +02:00
r . insert ( paramKind , path [ : i ] , method , routeMethod { routeInfo : & routeInfo { method : method } } )
2015-03-01 19:45:13 +02:00
}
2021-07-15 22:34:01 +02:00
} else if path [ i ] == anyLabel {
r . insert ( staticKind , path [ : i ] , method , routeMethod { routeInfo : & routeInfo { method : method } } )
paramNames = append ( paramNames , "*" )
ri = routable . ToRouteInfo ( paramNames )
rm := routeMethod {
routeInfo : & routeInfo { method : method , path : originalPath , params : paramNames , name : route . Name } ,
handler : h ,
orgRouteInfo : ri ,
}
r . insert ( anyKind , path [ : i + 1 ] , method , rm )
wasAdded = true
break
}
}
if ! wasAdded {
ri = routable . ToRouteInfo ( paramNames )
rm := routeMethod {
routeInfo : & routeInfo { method : method , path : originalPath , params : paramNames , name : route . Name } ,
handler : h ,
orgRouteInfo : ri ,
2015-03-01 19:45:13 +02:00
}
2021-07-15 22:34:01 +02:00
r . insert ( staticKind , path , method , rm )
2015-03-01 19:45:13 +02:00
}
2015-06-04 00:18:27 +02:00
2021-07-15 22:34:01 +02:00
r . storeRouteInfo ( ri )
return ri , nil
2015-03-01 19:45:13 +02:00
}
2021-07-15 22:34:01 +02:00
func ( r * DefaultRouter ) storeRouteInfo ( ri RouteInfo ) {
for i , rr := range r . routes {
if ri . Method ( ) == rr . Method ( ) && ri . Path ( ) == rr . Path ( ) {
r . routes [ i ] = ri
return
}
2015-06-04 00:18:27 +02:00
}
2021-07-15 22:34:01 +02:00
r . routes = append ( r . routes , ri )
}
2015-06-04 00:18:27 +02:00
2021-07-15 22:34:01 +02:00
func ( r * DefaultRouter ) insert ( t kind , path string , method string , ri routeMethod ) {
2021-03-06 01:43:59 +02:00
currentNode := r . tree // Current node as root
2015-03-01 19:45:13 +02:00
search := path
for {
2021-03-06 01:43:59 +02:00
searchLen := len ( search )
prefixLen := len ( currentNode . prefix )
lcpLen := 0
// LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array)
max := prefixLen
if searchLen < max {
max = searchLen
2015-06-06 00:08:32 +02:00
}
2021-03-06 01:43:59 +02:00
for ; lcpLen < max && search [ lcpLen ] == currentNode . prefix [ lcpLen ] ; lcpLen ++ {
2015-06-06 00:08:32 +02:00
}
2015-03-01 19:45:13 +02:00
2021-03-06 01:43:59 +02:00
if lcpLen == 0 {
2015-03-01 19:45:13 +02:00
// At root node
2021-03-06 01:43:59 +02:00
currentNode . label = search [ 0 ]
currentNode . prefix = search
2021-07-15 22:34:01 +02:00
if ri . handler != nil {
2021-03-06 01:43:59 +02:00
currentNode . kind = t
2021-07-15 22:34:01 +02:00
currentNode . setHandler ( method , & ri )
currentNode . paramsCount = len ( ri . params )
currentNode . originalPath = ri . path
2015-03-01 19:45:13 +02:00
}
2021-04-27 09:55:31 +02:00
currentNode . isLeaf = currentNode . staticChildren == nil && currentNode . paramChild == nil && currentNode . anyChild == nil
2021-03-06 01:43:59 +02:00
} else if lcpLen < prefixLen {
2022-07-17 21:19:09 +02:00
// Split node into two before we insert new node.
// This happens when we are inserting path that is submatch of any existing inserted paths.
// For example, we have node `/test` and now are about to insert `/te/*`. In that case
// 1. overlapping part is `/te` that is used as parent node
// 2. `st` is part from existing node that is not matching - it gets its own node (child to `/te`)
// 3. `/*` is the new part we are about to insert (child to `/te`)
2021-03-06 01:43:59 +02:00
n := newNode (
currentNode . kind ,
currentNode . prefix [ lcpLen : ] ,
currentNode ,
currentNode . staticChildren ,
2021-07-15 22:34:01 +02:00
currentNode . methods ,
currentNode . paramsCount ,
currentNode . originalPath ,
2021-03-06 01:43:59 +02:00
currentNode . paramChild ,
currentNode . anyChild ,
)
2019-10-16 21:52:10 +02:00
// Update parent path for all children to new node
2021-03-06 01:43:59 +02:00
for _ , child := range currentNode . staticChildren {
2019-10-16 21:52:10 +02:00
child . parent = n
}
2021-03-06 01:43:59 +02:00
if currentNode . paramChild != nil {
currentNode . paramChild . parent = n
2020-11-21 04:48:16 +02:00
}
2021-03-06 01:43:59 +02:00
if currentNode . anyChild != nil {
currentNode . anyChild . parent = n
2020-11-21 04:48:16 +02:00
}
2019-10-16 21:52:10 +02:00
2015-03-01 19:45:13 +02:00
// Reset parent node
2021-03-06 01:43:59 +02:00
currentNode . kind = staticKind
currentNode . label = currentNode . prefix [ 0 ]
currentNode . prefix = currentNode . prefix [ : lcpLen ]
currentNode . staticChildren = nil
2021-07-15 22:34:01 +02:00
currentNode . methods = new ( routeMethods )
currentNode . originalPath = ""
currentNode . paramsCount = 0
2021-03-06 01:43:59 +02:00
currentNode . paramChild = nil
currentNode . anyChild = nil
2021-04-27 09:55:31 +02:00
currentNode . isLeaf = false
currentNode . isHandler = false
2015-03-01 19:45:13 +02:00
2020-11-21 04:48:16 +02:00
// Only Static children could reach here
2021-03-06 01:43:59 +02:00
currentNode . addStaticChild ( n )
2015-06-06 00:08:32 +02:00
2021-03-06 01:43:59 +02:00
if lcpLen == searchLen {
2015-03-01 19:45:13 +02:00
// At parent node
2021-03-06 01:43:59 +02:00
currentNode . kind = t
2021-07-15 22:34:01 +02:00
if ri . handler != nil {
currentNode . setHandler ( method , & ri )
currentNode . paramsCount = len ( ri . params )
currentNode . originalPath = ri . path
}
2015-03-01 19:45:13 +02:00
} else {
2015-04-08 23:40:49 +02:00
// Create child node
2021-07-15 22:34:01 +02:00
n = newNode ( t , search [ lcpLen : ] , currentNode , nil , new ( routeMethods ) , 0 , ri . path , nil , nil )
if ri . handler != nil {
n . setHandler ( method , & ri )
n . paramsCount = len ( ri . params )
}
2020-11-21 04:48:16 +02:00
// Only Static children could reach here
2021-03-06 01:43:59 +02:00
currentNode . addStaticChild ( n )
2015-03-01 19:45:13 +02:00
}
2021-04-27 09:55:31 +02:00
currentNode . isLeaf = currentNode . staticChildren == nil && currentNode . paramChild == nil && currentNode . anyChild == nil
2021-03-06 01:43:59 +02:00
} else if lcpLen < searchLen {
search = search [ lcpLen : ]
c := currentNode . findChildWithLabel ( search [ 0 ] )
2015-04-14 06:57:36 +02:00
if c != nil {
2015-04-08 23:40:49 +02:00
// Go deeper
2021-03-06 01:43:59 +02:00
currentNode = c
2015-04-08 23:40:49 +02:00
continue
2015-03-01 19:45:13 +02:00
}
2015-04-08 23:40:49 +02:00
// Create child node
2021-07-15 22:34:01 +02:00
n := newNode ( t , search , currentNode , nil , new ( routeMethods ) , 0 , ri . path , nil , nil )
if ri . handler != nil {
n . setHandler ( method , & ri )
n . paramsCount = len ( ri . params )
}
2020-11-21 04:48:16 +02:00
switch t {
2021-03-06 01:43:59 +02:00
case staticKind :
currentNode . addStaticChild ( n )
case paramKind :
currentNode . paramChild = n
case anyKind :
currentNode . anyChild = n
2020-11-21 04:48:16 +02:00
}
2021-04-27 09:55:31 +02:00
currentNode . isLeaf = currentNode . staticChildren == nil && currentNode . paramChild == nil && currentNode . anyChild == nil
2015-03-01 19:45:13 +02:00
} else {
// Node already exists
2021-07-15 22:34:01 +02:00
if ri . handler != nil {
currentNode . setHandler ( method , & ri )
currentNode . paramsCount = len ( ri . params )
currentNode . originalPath = ri . path
2015-03-01 19:45:13 +02:00
}
}
2015-04-08 23:40:49 +02:00
return
2015-03-01 19:45:13 +02:00
}
}
2021-07-15 22:34:01 +02:00
func newNode ( t kind , pre string , p * node , sc children , mh * routeMethods , paramsCount int , ppath string , paramChildren , anyChildren * node ) * node {
2015-04-26 21:44:38 +02:00
return & node {
2021-03-06 01:43:59 +02:00
kind : t ,
label : pre [ 0 ] ,
prefix : pre ,
parent : p ,
staticChildren : sc ,
2021-07-15 22:34:01 +02:00
originalPath : ppath ,
paramsCount : paramsCount ,
methods : mh ,
2021-03-06 01:43:59 +02:00
paramChild : paramChildren ,
anyChild : anyChildren ,
2021-04-27 09:55:31 +02:00
isLeaf : sc == nil && paramChildren == nil && anyChildren == nil ,
isHandler : mh . isHandler ( ) ,
2015-03-01 19:45:13 +02:00
}
2015-04-26 21:44:38 +02:00
}
2020-11-21 04:48:16 +02:00
func ( n * node ) addStaticChild ( c * node ) {
2021-03-06 01:43:59 +02:00
n . staticChildren = append ( n . staticChildren , c )
2015-04-01 17:05:54 +02:00
}
2020-11-21 04:48:16 +02:00
func ( n * node ) findStaticChild ( l byte ) * node {
2021-03-06 01:43:59 +02:00
for _ , c := range n . staticChildren {
2020-11-21 04:48:16 +02:00
if c . label == l {
2015-04-24 16:44:30 +02:00
return c
}
}
return nil
}
2015-06-06 00:08:32 +02:00
func ( n * node ) findChildWithLabel ( l byte ) * node {
2022-07-17 21:19:09 +02:00
if c := n . findStaticChild ( l ) ; c != nil {
return c
2015-04-24 16:44:30 +02:00
}
2020-11-21 04:48:16 +02:00
if l == paramLabel {
2021-03-06 01:43:59 +02:00
return n . paramChild
2020-11-21 04:48:16 +02:00
}
if l == anyLabel {
2021-03-06 01:43:59 +02:00
return n . anyChild
2015-04-24 16:44:30 +02:00
}
return nil
}
2021-07-15 22:34:01 +02:00
func ( n * node ) setHandler ( method string , r * routeMethod ) {
n . methods . set ( method , r )
2022-07-17 21:19:09 +02:00
n . isHandler = n . methods . isHandler ( )
2015-10-06 15:48:33 +02:00
}
2021-07-15 22:34:01 +02:00
// Note: notFoundRouteInfo exists to avoid allocations when setting 404 RouteInfo to Context
var notFoundRouteInfo = & routeInfo {
method : "" ,
path : "" ,
params : nil ,
name : NotFoundRouteName ,
}
// Note: methodNotAllowedRouteInfo exists to avoid allocations when setting 405 RouteInfo to Context
var methodNotAllowedRouteInfo = & routeInfo {
method : "" ,
path : "" ,
params : nil ,
name : MethodNotAllowedRouteName ,
}
// notFoundHandler is handler for 404 cases
// Handle returned ErrNotFound errors in Echo.HTTPErrorHandler
var notFoundHandler = func ( c Context ) error {
return ErrNotFound
}
// methodNotAllowedHandler is handler for case when route for path+method match was not found (http code 405)
// Handle returned ErrMethodNotAllowed errors in Echo.HTTPErrorHandler
var methodNotAllowedHandler = func ( c Context ) error {
// See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed)
// response and MAY do so in any other response. For disabled resources an empty Allow header may be returned
routerAllowMethods , ok := c . Get ( ContextKeyHeaderAllow ) . ( string )
if ok && routerAllowMethods != "" {
c . Response ( ) . Header ( ) . Set ( HeaderAllow , routerAllowMethods )
2015-07-24 21:03:36 +02:00
}
2021-07-15 22:34:01 +02:00
return ErrMethodNotAllowed
2015-07-24 21:03:36 +02:00
}
2021-07-15 22:34:01 +02:00
// optionsMethodHandler is default handler for OPTIONS method.
// Use `middleware.CORS` if you need support for preflighted requests in CORS
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS
var optionsMethodHandler = func ( c Context ) error {
// See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed)
// response and MAY do so in any other response. For disabled resources an empty Allow header may be returned
routerAllowMethods , ok := c . Get ( ContextKeyHeaderAllow ) . ( string )
if ok && routerAllowMethods != "" {
c . Response ( ) . Header ( ) . Set ( HeaderAllow , routerAllowMethods )
2015-11-22 19:26:11 +02:00
}
2021-07-15 22:34:01 +02:00
return c . NoContent ( http . StatusNoContent )
2015-11-22 19:26:11 +02:00
}
2021-07-15 22:34:01 +02:00
// Route looks up a handler registered for method and path. It also parses URL for path parameters and loads them
// into context.
2016-03-20 00:47:20 +02:00
//
// For performance:
//
2016-05-04 02:23:31 +02:00
// - Get context from `Echo#AcquireContext()`
2016-03-20 00:47:20 +02:00
// - Reset it `Context#Reset()`
2016-05-04 02:23:31 +02:00
// - Return it `Echo#ReleaseContext()`.
2021-07-15 22:34:01 +02:00
func ( r * DefaultRouter ) Route ( c RoutableContext ) HandlerFunc {
pathParams := c . RawPathParams ( )
* pathParams = ( * pathParams ) [ 0 : cap ( * pathParams ) ]
2015-09-01 17:03:01 +02:00
2021-07-15 22:34:01 +02:00
req := c . Request ( )
path := req . URL . Path
if ! r . useEscapedPathForRouting && req . URL . RawPath != "" {
// Difference between URL.RawPath and URL.Path is:
// * URL.Path is where request path is stored. Value is stored in decoded form: /%47%6f%2f becomes /Go/.
// * URL.RawPath is an optional field which only gets set if the default encoding is different from Path.
path = req . URL . RawPath
}
2015-05-10 07:06:13 +02:00
var (
2021-07-15 22:34:01 +02:00
currentNode = r . tree // root as current node
2021-04-27 09:55:31 +02:00
previousBestMatchNode * node
2021-07-15 22:34:01 +02:00
matchedRouteMethod * routeMethod
2021-03-06 01:43:59 +02:00
// search stores the remaining path to check for match. By each iteration we move from start of path to end of the path
// and search value gets shorter and shorter.
2021-03-02 20:56:40 +02:00
search = path
searchIndex = 0
2021-07-15 22:34:01 +02:00
paramIndex int // Param counter
2015-05-10 07:06:13 +02:00
)
2015-03-01 19:45:13 +02:00
2021-03-02 20:56:40 +02:00
// Backtracking is needed when a dead end (leaf node) is reached in the router tree.
// To backtrack the current node will be changed to the parent node and the next kind for the
// router logic will be returned based on fromKind or kind of the dead end node (static > param > any).
// For example if there is no static node match we should check parent next sibling by kind (param).
// Backtracking itself does not check if there is a next sibling, this is done by the router logic.
backtrackToNextNodeKind := func ( fromKind kind ) ( nextNodeKind kind , valid bool ) {
2021-03-06 01:43:59 +02:00
previous := currentNode
currentNode = previous . parent
valid = currentNode != nil
2021-03-02 20:56:40 +02:00
// Next node type by priority
2021-04-27 09:55:31 +02:00
if previous . kind == anyKind {
nextNodeKind = staticKind
} else {
nextNodeKind = previous . kind + 1
}
2021-03-02 20:56:40 +02:00
2021-03-06 01:43:59 +02:00
if fromKind == staticKind {
2021-03-02 20:56:40 +02:00
// when backtracking is done from static kind block we did not change search so nothing to restore
return
2015-03-01 19:45:13 +02:00
}
2021-03-02 20:56:40 +02:00
// restore search to value it was before we move to current node we are backtracking from.
2021-03-06 01:43:59 +02:00
if previous . kind == staticKind {
2021-03-02 20:56:40 +02:00
searchIndex -= len ( previous . prefix )
} else {
2021-03-06 01:43:59 +02:00
paramIndex --
2021-03-02 20:56:40 +02:00
// for param/any node.prefix value is always `:` so we can not deduce searchIndex from that and must use pValue
// for that index as it would also contain part of path we cut off before moving into node we are backtracking from
2021-07-15 22:34:01 +02:00
searchIndex -= len ( ( * pathParams ) [ paramIndex ] . Value )
( * pathParams ) [ paramIndex ] . Value = ""
2021-03-02 20:56:40 +02:00
}
search = path [ searchIndex : ]
return
}
2021-03-06 01:43:59 +02:00
// Router tree is implemented by longest common prefix array (LCP array) https://en.wikipedia.org/wiki/LCP_array
// Tree search is implemented as for loop where one loop iteration is divided into 3 separate blocks
// Each of these blocks checks specific kind of node (static/param/any). Order of blocks reflex their priority in routing.
// Search order/priority is: static > param > any.
//
// Note: backtracking in tree is implemented by replacing/switching currentNode to previous node
// and hoping to (goto statement) next block by priority to check if it is the match.
2021-03-02 20:56:40 +02:00
for {
2021-03-06 01:43:59 +02:00
prefixLen := 0 // Prefix length
lcpLen := 0 // LCP (longest common prefix) length
2015-05-10 07:06:13 +02:00
2021-03-06 01:43:59 +02:00
if currentNode . kind == staticKind {
searchLen := len ( search )
prefixLen = len ( currentNode . prefix )
2015-06-06 00:08:32 +02:00
2021-03-06 01:43:59 +02:00
// LCP - Longest Common Prefix (https://en.wikipedia.org/wiki/LCP_array)
max := prefixLen
if searchLen < max {
max = searchLen
2015-06-06 00:08:32 +02:00
}
2021-03-06 01:43:59 +02:00
for ; lcpLen < max && search [ lcpLen ] == currentNode . prefix [ lcpLen ] ; lcpLen ++ {
2015-06-06 00:08:32 +02:00
}
2015-05-10 07:06:13 +02:00
}
2015-04-13 22:12:30 +02:00
2021-03-06 01:43:59 +02:00
if lcpLen != prefixLen {
2021-03-02 20:56:40 +02:00
// No matching prefix, let's backtrack to the first possible alternative node of the decision path
2021-03-06 01:43:59 +02:00
nk , ok := backtrackToNextNodeKind ( staticKind )
2021-03-02 20:56:40 +02:00
if ! ok {
2022-07-17 21:19:09 +02:00
break // No other possibilities on the decision path, handler will be whatever context is reset to.
2021-03-06 01:43:59 +02:00
} else if nk == paramKind {
2021-03-02 20:56:40 +02:00
goto Param
// NOTE: this case (backtracking from static node to previous any node) can not happen by current any matching logic. Any node is end of search currently
2021-03-06 01:43:59 +02:00
//} else if nk == anyKind {
2021-03-02 20:56:40 +02:00
// goto Any
} else {
// Not found (this should never be possible for static node we are looking currently)
2021-04-27 09:55:31 +02:00
break
2021-01-03 20:35:00 +02:00
}
2020-02-24 18:26:49 +02:00
}
2021-03-02 20:56:40 +02:00
// The full prefix has matched, remove the prefix from the remaining search
2021-03-06 01:43:59 +02:00
search = search [ lcpLen : ]
searchIndex = searchIndex + lcpLen
2021-03-02 20:56:40 +02:00
2022-07-17 21:19:09 +02:00
// Finish routing if is no request path remaining to search
if search == "" {
// in case of node that is handler we have exact method type match or something for 405 to use
if currentNode . isHandler {
// check if current node has handler registered for http method we are looking for. we store currentNode as
// best matching in case we do no find no more routes matching this path+method
if previousBestMatchNode == nil {
previousBestMatchNode = currentNode
}
if h := currentNode . methods . find ( req . Method ) ; h != nil {
matchedRouteMethod = h
break
}
} else if currentNode . methods . notFoundHandler != nil {
matchedRouteMethod = currentNode . methods . notFoundHandler
2021-04-27 09:55:31 +02:00
break
}
2015-04-11 20:10:19 +02:00
}
2015-03-01 19:45:13 +02:00
2015-04-11 19:09:41 +02:00
// Static node
2021-03-02 20:56:40 +02:00
if search != "" {
2021-03-06 01:43:59 +02:00
if child := currentNode . findStaticChild ( search [ 0 ] ) ; child != nil {
currentNode = child
2021-03-02 20:56:40 +02:00
continue
2015-05-10 07:06:13 +02:00
}
2015-04-11 19:09:41 +02:00
}
2015-04-04 19:44:48 +02:00
2015-09-15 22:14:30 +02:00
Param :
2020-02-19 17:10:57 +02:00
// Param node
2021-03-06 01:43:59 +02:00
if child := currentNode . paramChild ; search != "" && child != nil {
currentNode = child
2021-04-27 09:55:31 +02:00
i := 0
l := len ( search )
if currentNode . isLeaf {
2022-07-17 21:19:09 +02:00
// when param node does not have any children (path param is last piece of route path) then param node should
// act similarly to any node - consider all remaining search as match
2021-04-27 09:55:31 +02:00
i = l
} else {
for ; i < l && search [ i ] != '/' ; i ++ {
}
2015-03-01 19:45:13 +02:00
}
2021-04-27 09:55:31 +02:00
2021-07-15 22:34:01 +02:00
( * pathParams ) [ paramIndex ] . Value = search [ : i ]
2021-03-06 01:43:59 +02:00
paramIndex ++
2015-04-11 19:09:41 +02:00
search = search [ i : ]
2021-03-02 20:56:40 +02:00
searchIndex = searchIndex + i
2015-04-11 19:09:41 +02:00
continue
}
2015-04-04 19:44:48 +02:00
2016-03-04 08:06:47 +02:00
Any :
2020-02-19 17:10:57 +02:00
// Any node
2021-03-06 01:43:59 +02:00
if child := currentNode . anyChild ; child != nil {
// If any node is found, use remaining path for paramValues
currentNode = child
2021-07-15 22:34:01 +02:00
( * pathParams ) [ currentNode . paramsCount - 1 ] . Value = search
2021-04-27 09:55:31 +02:00
// update indexes/search in case we need to backtrack when no handler match is found
paramIndex ++
searchIndex += + len ( search )
search = ""
2022-07-17 21:19:09 +02:00
if rMethod := currentNode . methods . find ( req . Method ) ; rMethod != nil {
matchedRouteMethod = rMethod
break
}
// we store currentNode as best matching in case we do not find more routes matching this path+method. Needed for 405
2021-04-27 09:55:31 +02:00
if previousBestMatchNode == nil {
previousBestMatchNode = currentNode
}
2022-07-17 21:19:09 +02:00
if currentNode . methods . notFoundHandler != nil {
matchedRouteMethod = currentNode . methods . notFoundHandler
2021-04-27 09:55:31 +02:00
break
}
2020-02-19 17:10:57 +02:00
}
2021-03-02 20:56:40 +02:00
// Let's backtrack to the first possible alternative node of the decision path
2021-03-06 01:43:59 +02:00
nk , ok := backtrackToNextNodeKind ( anyKind )
2021-03-02 20:56:40 +02:00
if ! ok {
2021-04-27 09:55:31 +02:00
break // No other possibilities on the decision path
2021-03-06 01:43:59 +02:00
} else if nk == paramKind {
2021-03-02 20:56:40 +02:00
goto Param
2021-03-06 01:43:59 +02:00
} else if nk == anyKind {
2021-03-02 20:56:40 +02:00
goto Any
} else {
// Not found
2021-04-27 09:55:31 +02:00
break
2015-04-12 22:04:41 +02:00
}
2015-03-01 19:45:13 +02:00
}
2015-09-24 23:14:01 +02:00
2021-04-27 09:55:31 +02:00
if currentNode == nil && previousBestMatchNode == nil {
2021-07-15 22:34:01 +02:00
* pathParams = ( * pathParams ) [ 0 : 0 ]
c . SetRawPathParams ( pathParams )
c . SetPath ( "" )
c . SetRouteInfo ( notFoundRouteInfo )
return r . notFoundHandler // nothing matched at all with given path
2021-04-27 09:55:31 +02:00
}
2015-11-22 19:26:11 +02:00
2021-07-15 22:34:01 +02:00
var rHandler HandlerFunc
var rPath string
var rInfo RouteInfo
if matchedRouteMethod != nil {
rHandler = matchedRouteMethod . handler
rPath = matchedRouteMethod . routeInfo . path
rInfo = matchedRouteMethod . routeInfo
2021-04-27 09:55:31 +02:00
} else {
// use previous match as basis. although we have no matching handler we have path match.
// so we can send http.StatusMethodNotAllowed (405) instead of http.StatusNotFound (404)
currentNode = previousBestMatchNode
2021-12-04 20:02:11 +02:00
2021-07-15 22:34:01 +02:00
rPath = currentNode . originalPath
rInfo = notFoundRouteInfo
2022-07-17 21:19:09 +02:00
if currentNode . methods . notFoundHandler != nil {
matchedRouteMethod = currentNode . methods . notFoundHandler
rInfo = matchedRouteMethod . routeInfo
rPath = matchedRouteMethod . path
rHandler = matchedRouteMethod . handler
} else if currentNode . isHandler {
2021-07-15 22:34:01 +02:00
rInfo = methodNotAllowedRouteInfo
c . Set ( ContextKeyHeaderAllow , currentNode . methods . allowHeader )
rHandler = r . methodNotAllowedHandler
if req . Method == http . MethodOptions {
rHandler = r . optionsMethodHandler
}
}
}
c . SetPath ( rPath )
c . SetRouteInfo ( rInfo )
* pathParams = ( * pathParams ) [ 0 : currentNode . paramsCount ]
if matchedRouteMethod != nil {
for i , name := range matchedRouteMethod . params {
( * pathParams ) [ i ] . Name = name
}
}
if r . unescapePathParamValues && currentNode . kind != staticKind {
// See issue #1531, #1258 - there are cases when path parameter need to be unescaped
for i , p := range * pathParams {
tmpVal , err := url . PathUnescape ( p . Value )
if err == nil { // handle problems by ignoring them.
( * pathParams ) [ i ] . Value = tmpVal
2021-12-04 20:02:11 +02:00
}
}
2015-10-06 15:48:33 +02:00
}
2021-07-15 22:34:01 +02:00
c . SetRawPathParams ( pathParams )
return rHandler
}
// Get returns path parameter value for given name or default value.
func ( p PathParams ) Get ( name string , defaultValue string ) string {
for _ , param := range p {
if param . Name == name {
return param . Value
}
}
return defaultValue
2015-03-01 19:45:13 +02:00
}