github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/framework/http/httprouter/router.go (about) 1 // Copyright 2013 Julien Schmidt. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be found 3 // in the LICENSE file. 4 5 // Package httprouter is a trie based high performance HTTP request router. 6 // 7 // A trivial example is: 8 // 9 // package main 10 // 11 // import ( 12 // "fmt" 13 // "github.com/julienschmidt/httprouter" 14 // "net/http" 15 // "log" 16 // ) 17 // 18 // func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 19 // fmt.Fprint(w, "Welcome!\n") 20 // } 21 // 22 // func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 23 // fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) 24 // } 25 // 26 // func main() { 27 // router := httprouter.New() 28 // router.GET("/", Index) 29 // router.GET("/hello/:name", Hello) 30 // 31 // log.Fatal(http.ListenAndServe(":8080", router)) 32 // } 33 // 34 // The router matches incoming requests by the request method and the path. 35 // If a handle is registered for this path and method, the router delegates the 36 // request to that function. 37 // For the methods GET, POST, PUT, PATCH, DELETE and OPTIONS shortcut functions exist to 38 // register handles, for all other methods router.Handle can be used. 39 // 40 // The registered path, against which the router matches incoming requests, can 41 // contain two types of parameters: 42 // Syntax Type 43 // :name named parameter 44 // *name catch-all parameter 45 // 46 // Named parameters are dynamic path segments. They match anything until the 47 // next '/' or the path end: 48 // Path: /blog/:category/:post 49 // 50 // Requests: 51 // /blog/go/request-routers match: category="go", post="request-routers" 52 // /blog/go/request-routers/ no match, but the router would redirect 53 // /blog/go/ no match 54 // /blog/go/request-routers/comments no match 55 // 56 // Catch-all parameters match anything until the path end, including the 57 // directory index (the '/' before the catch-all). Since they match anything 58 // until the end, catch-all parameters must always be the final path element. 59 // Path: /files/*filepath 60 // 61 // Requests: 62 // /files/ match: filepath="/" 63 // /files/LICENSE match: filepath="/LICENSE" 64 // /files/templates/article.html match: filepath="/templates/article.html" 65 // /files no match, but the router would redirect 66 // 67 // The value of parameters is saved as a slice of the Param struct, consisting 68 // each of a key and a value. The slice is passed to the Handle func as a third 69 // parameter. 70 // There are two ways to retrieve the value of a parameter: 71 // // by the name of the parameter 72 // user := ps.ByName("user") // defined by :user or *user 73 // 74 // // by the index of the parameter. This way you can also get the name (key) 75 // thirdKey := ps[2].Key // the name of the 3rd parameter 76 // thirdValue := ps[2].Value // the value of the 3rd parameter 77 package httprouter 78 79 import ( 80 "context" 81 "github.com/ucarion/urlpath" 82 "net/http" 83 "strings" 84 "sync" 85 ) 86 87 // Handle is a function that can be registered to a route to handle HTTP 88 // requests. Like http.HandlerFunc, but has a third parameter for the values of 89 // wildcards (path variables). 90 type Handle func(http.ResponseWriter, *http.Request, Params) 91 92 // Param is a single URL parameter, consisting of a key and a value. 93 type Param struct { 94 Key string 95 Value string 96 } 97 98 // Params is a Param-slice, as returned by the router. 99 // The slice is ordered, the first URL parameter is also the first slice value. 100 // It is therefore safe to read values by the index. 101 type Params []Param 102 103 // ByName returns the value of the first Param which key matches the given name. 104 // If no matching Param is found, an empty string is returned. 105 func (ps Params) ByName(name string) string { 106 for _, p := range ps { 107 if p.Key == name { 108 return p.Value 109 } 110 } 111 return "" 112 } 113 114 type paramsKey struct{} 115 116 // ParamsKey is the request context key under which URL params are stored. 117 var ParamsKey = paramsKey{} 118 119 // ParamsFromContext pulls the URL parameters from a request context, 120 // or returns nil if none are present. 121 func ParamsFromContext(ctx context.Context) Params { 122 p, _ := ctx.Value(ParamsKey).(Params) 123 return p 124 } 125 126 // MatchedRouteNameParam is the Param name under which the name of the matched 127 // route is stored, if Router.SaveMatchedRoutePath is set. 128 var MatchedRouteNameParam = "$matchedRouteName" 129 130 // MatchedRouteName retrieves the name of the matched route. 131 // Router.SaveMatchedRoutePath must have been enabled when the respective 132 // handler was added, otherwise this function always returns an empty string. 133 func (ps Params) MatchedRouteName() string { 134 return ps.ByName(MatchedRouteNameParam) 135 } 136 137 // Router is a http.Handler which can be used to dispatch requests to different 138 // handler functions via configurable routes 139 type Router struct { 140 paramsPool sync.Pool 141 142 // If enabled, adds the matched route path onto the http.Request context 143 // before invoking the handler. 144 // The matched route path is only added to handlers of routes that were 145 // registered when this option was enabled. 146 SaveMatchedRoutePath bool 147 148 // If enabled, the router checks if another method is allowed for the 149 // current route, if the current request can not be routed. 150 // If this is the case, the request is answered with 'Method Not Allowed' 151 // and HTTP status code 405. 152 // If no other Method is allowed, the request is delegated to the NotFound 153 // handler. 154 HandleMethodNotAllowed bool 155 156 // If enabled, the router automatically replies to OPTIONS requests. 157 // Custom OPTIONS handlers take priority over automatic replies. 158 HandleOPTIONS bool 159 160 // An optional http.Handler that is called on automatic OPTIONS requests. 161 // The handler is only called if HandleOPTIONS is true and no OPTIONS 162 // handler for the specific path was set. 163 // The "Allowed" header is set before calling the handler. 164 GlobalOPTIONS http.Handler 165 166 // Cached value of global (*) allowed methods 167 globalAllowed string 168 169 // Configurable http.Handler which is called when no matching route is 170 // found. If it is not set, http.NotFound is used. 171 NotFound http.Handler 172 173 // Configurable http.Handler which is called when a request 174 // cannot be routed and HandleMethodNotAllowed is true. 175 // If it is not set, http.Error with http.StatusMethodNotAllowed is used. 176 // The "Allow" header with allowed request methods is set before the handler 177 // is called. 178 MethodNotAllowed http.Handler 179 180 // Function to handle panics recovered from http handlers. 181 // It should be used to generate a error page and return the http error code 182 // 500 (Internal Server Error). 183 // The handler can be used to keep your server from crashing because of 184 // unrecovered panics. 185 PanicHandler func(http.ResponseWriter, *http.Request, interface{}) 186 187 registeredPaths map[string][]string 188 handlers map[string]Handle 189 dynamicHandlers []map[*urlpath.Path]Handle 190 } 191 192 // Make sure the Router conforms with the http.Handler interface 193 var _ http.Handler = New() 194 195 var httpMethods = []string{http.MethodGet, http.MethodHead, http.MethodPost, 196 http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, 197 http.MethodOptions, http.MethodTrace} 198 199 // New returns a new initialized Router. 200 // Path auto-correction, including trailing slashes, is enabled by default. 201 func New() *Router { 202 r := &Router{ 203 HandleMethodNotAllowed: true, 204 HandleOPTIONS: true, 205 registeredPaths: make(map[string][]string), 206 handlers: make(map[string]Handle), 207 dynamicHandlers: make([]map[*urlpath.Path]Handle, len(httpMethods)), 208 } 209 for i := range httpMethods { 210 r.dynamicHandlers[i] = make(map[*urlpath.Path]Handle) 211 } 212 r.paramsPool.New = func() interface{} { 213 ps := make(Params, 0, 1) 214 return &ps 215 } 216 return r 217 } 218 219 func (r *Router) NewGroup(path string) *RouteGroup { 220 return newRouteGroup(r, path) 221 } 222 223 func (r *Router) getParams() *Params { 224 ps, _ := r.paramsPool.Get().(*Params) 225 *ps = (*ps)[0:0] // reset slice 226 return ps 227 } 228 229 func (r *Router) putParams(ps *Params) { 230 if ps != nil { 231 r.paramsPool.Put(ps) 232 } 233 } 234 235 func (r *Router) saveMatchedRoutePath(name string, handle Handle) Handle { 236 return func(w http.ResponseWriter, req *http.Request, ps Params) { 237 if ps == nil { 238 psp := r.getParams() 239 ps = (*psp)[0:1] 240 ps[0] = Param{Key: MatchedRouteNameParam, Value: name} 241 handle(w, req, ps) 242 r.putParams(psp) 243 } else { 244 ps = append(ps, Param{Key: MatchedRouteNameParam, Value: name}) 245 handle(w, req, ps) 246 } 247 } 248 } 249 250 // GET is a shortcut for router.Handle(http.MethodGet, path, handle) 251 func (r *Router) GET(path string, handle Handle) { 252 r.Handle(http.MethodGet, path, handle) 253 } 254 255 // HEAD is a shortcut for router.Handle(http.MethodHead, path, handle) 256 func (r *Router) HEAD(path string, handle Handle) { 257 r.Handle(http.MethodHead, path, handle) 258 } 259 260 // OPTIONS is a shortcut for router.Handle(http.MethodOptions, path, handle) 261 func (r *Router) OPTIONS(path string, handle Handle) { 262 r.Handle(http.MethodOptions, path, handle) 263 } 264 265 // POST is a shortcut for router.Handle(http.MethodPost, path, handle) 266 func (r *Router) POST(path string, handle Handle) { 267 r.Handle(http.MethodPost, path, handle) 268 } 269 270 // PUT is a shortcut for router.Handle(http.MethodPut, path, handle) 271 func (r *Router) PUT(path string, handle Handle) { 272 r.Handle(http.MethodPut, path, handle) 273 } 274 275 // PATCH is a shortcut for router.Handle(http.MethodPatch, path, handle) 276 func (r *Router) PATCH(path string, handle Handle) { 277 r.Handle(http.MethodPatch, path, handle) 278 } 279 280 // DELETE is a shortcut for router.Handle(http.MethodDelete, path, handle) 281 func (r *Router) DELETE(path string, handle Handle) { 282 r.Handle(http.MethodDelete, path, handle) 283 } 284 285 func (r *Router) methodIndexOf(method string) int { 286 switch method { 287 case http.MethodGet: 288 return 0 289 case http.MethodHead: 290 return 1 291 case http.MethodPost: 292 return 2 293 case http.MethodPut: 294 return 3 295 case http.MethodPatch: 296 return 4 297 case http.MethodDelete: 298 return 5 299 case http.MethodConnect: 300 return 6 301 case http.MethodOptions: 302 return 7 303 case http.MethodTrace: 304 return 8 305 } 306 return -1 307 } 308 309 func path2key(method, path string) string { 310 var sb strings.Builder 311 sb.WriteString(method) 312 sb.WriteString(":") 313 sb.WriteString(path) 314 return sb.String() 315 } 316 317 func countParams(path string) uint16 { 318 var n uint 319 for i := range []byte(path) { 320 switch path[i] { 321 case ':', '*': 322 n++ 323 } 324 } 325 return uint16(n) 326 } 327 328 // Handle registers a new request handle with the given path and method. 329 // 330 // For GET, POST, PUT, PATCH and DELETE requests the respective shortcut 331 // functions can be used. 332 // 333 // This function is intended for bulk loading and to allow the usage of less 334 // frequently used, non-standardized or custom methods (e.g. for internal 335 // communication with a proxy). 336 func (r *Router) Handle(method, path string, handle Handle, name ...string) { 337 switch { 338 case len(method) == 0: 339 panic("method must not be empty") 340 case len(path) < 1 || path[0] != '/': 341 panic("path must begin with '/' in path '" + path + "'") 342 case handle == nil: 343 panic("handler must not be nil") 344 } 345 idx := r.methodIndexOf(method) 346 if idx < 0 { 347 panic("unknown http method") 348 } 349 _, f := r.registeredPaths[method] 350 r.registeredPaths[method] = append(r.registeredPaths[method], path) 351 if !f { 352 r.globalAllowed = r.allowed("*", "") 353 } 354 if r.SaveMatchedRoutePath { 355 if len(name) == 0 { 356 panic("route name must not be nil") 357 } 358 handle = r.saveMatchedRoutePath(name[0], handle) 359 } 360 if strings.Contains(path, "*") { 361 pt := urlpath.New(path) 362 r.dynamicHandlers[idx][&pt] = handle 363 } else { 364 r.handlers[path2key(method, path)] = handle 365 } 366 } 367 368 // Handler is an adapter which allows the usage of an http.Handler as a 369 // request handle. 370 // The Params are available in the request context under ParamsKey. 371 func (r *Router) Handler(method, path string, handler http.Handler, name ...string) { 372 r.Handle(method, path, 373 func(w http.ResponseWriter, req *http.Request, p Params) { 374 if len(p) > 0 { 375 req = req.WithContext(context.WithValue(req.Context(), ParamsKey, p)) 376 } 377 handler.ServeHTTP(w, req) 378 }, 379 name..., 380 ) 381 } 382 383 // HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a 384 // request handle. 385 func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc, name ...string) { 386 r.Handler(method, path, handler, name...) 387 } 388 389 func (r *Router) recv(w http.ResponseWriter, req *http.Request) { 390 if rcv := recover(); rcv != nil { 391 r.PanicHandler(w, req, rcv) 392 } 393 } 394 395 func (r *Router) search(method, path string) Handle { 396 idx := r.methodIndexOf(method) 397 if idx < 0 { 398 return nil 399 } 400 for k := range r.dynamicHandlers[idx] { 401 if _, ok := k.Match(path); !ok { 402 continue 403 } 404 return r.dynamicHandlers[idx][k] 405 } 406 return nil 407 } 408 409 func (r *Router) allowed(path, reqMethod string) (allow string) { 410 allowed := make([]string, 0, 9) 411 412 if path == "*" || path == "/*" { // server-wide{ // server-wide 413 // empty method is used for internal calls to refresh the cache 414 if reqMethod == "" { 415 for method := range r.registeredPaths { 416 if method == http.MethodOptions { 417 continue 418 } 419 // Add request method to list of allowed methods 420 allowed = append(allowed, method) 421 } 422 } else { 423 return r.globalAllowed 424 } 425 } else { // specific path 426 for method := range r.registeredPaths { 427 // Skip the requested method - we already tried this one 428 if method == reqMethod || method == http.MethodOptions { 429 continue 430 } 431 432 handle, _ := r.handlers[path2key(method, path)] 433 if handle == nil { 434 handle = r.search(method, path) 435 } 436 if handle != nil { 437 // Add request method to list of allowed methods 438 allowed = append(allowed, method) 439 } 440 } 441 } 442 443 if len(allowed) > 0 { 444 // Add request method to list of allowed methods 445 allowed = append(allowed, http.MethodOptions) 446 447 // Sort allowed methods. 448 // sort.Strings(allowed) unfortunately causes unnecessary allocations 449 // due to allowed being moved to the heap and interface conversion 450 for i, l := 1, len(allowed); i < l; i++ { 451 for j := i; j > 0 && allowed[j] < allowed[j-1]; j-- { 452 allowed[j], allowed[j-1] = allowed[j-1], allowed[j] 453 } 454 } 455 456 // return as comma separated list 457 return strings.Join(allowed, ", ") 458 } 459 return 460 } 461 462 // ServeHTTP makes the router implement the http.Handler interface. 463 func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { 464 if r.PanicHandler != nil { 465 defer r.recv(w, req) 466 } 467 468 path := req.URL.Path 469 method := req.Method 470 methodIndex := r.methodIndexOf(method) 471 472 if methodIndex > -1 { 473 handle := r.handlers[path2key(method, path)] 474 if handle == nil { 475 handle = r.search(method, path) 476 } 477 if handle != nil { 478 handle(w, req, nil) 479 return 480 } 481 } 482 483 if req.Method == http.MethodOptions && r.HandleOPTIONS { 484 // Handle OPTIONS requests 485 if allow := r.allowed(path, http.MethodOptions); allow != "" { 486 w.Header().Set("Allow", allow) 487 if r.GlobalOPTIONS != nil { 488 r.GlobalOPTIONS.ServeHTTP(w, req) 489 } 490 return 491 } 492 } else if r.HandleMethodNotAllowed { // Handle 405 493 if allow := r.allowed(path, req.Method); allow != "" { 494 w.Header().Set("Allow", allow) 495 if r.MethodNotAllowed != nil { 496 r.MethodNotAllowed.ServeHTTP(w, req) 497 } else { 498 http.Error(w, 499 http.StatusText(http.StatusMethodNotAllowed), 500 http.StatusMethodNotAllowed, 501 ) 502 } 503 return 504 } 505 } 506 507 // Handle 404 508 if r.NotFound != nil { 509 r.NotFound.ServeHTTP(w, req) 510 } else { 511 http.NotFound(w, req) 512 } 513 }