David K. Bainbridge | 215e024 | 2017-09-05 23:18:24 -0700 | [diff] [blame] | 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | package mux |
| 6 | |
| 7 | import ( |
| 8 | "errors" |
| 9 | "fmt" |
| 10 | "net/http" |
| 11 | "path" |
| 12 | "regexp" |
| 13 | "strings" |
| 14 | ) |
| 15 | |
| 16 | var ( |
| 17 | ErrMethodMismatch = errors.New("method is not allowed") |
| 18 | ) |
| 19 | |
| 20 | // NewRouter returns a new router instance. |
| 21 | func NewRouter() *Router { |
| 22 | return &Router{namedRoutes: make(map[string]*Route), KeepContext: false} |
| 23 | } |
| 24 | |
| 25 | // Router registers routes to be matched and dispatches a handler. |
| 26 | // |
| 27 | // It implements the http.Handler interface, so it can be registered to serve |
| 28 | // requests: |
| 29 | // |
| 30 | // var router = mux.NewRouter() |
| 31 | // |
| 32 | // func main() { |
| 33 | // http.Handle("/", router) |
| 34 | // } |
| 35 | // |
| 36 | // Or, for Google App Engine, register it in a init() function: |
| 37 | // |
| 38 | // func init() { |
| 39 | // http.Handle("/", router) |
| 40 | // } |
| 41 | // |
| 42 | // This will send all incoming requests to the router. |
| 43 | type Router struct { |
| 44 | // Configurable Handler to be used when no route matches. |
| 45 | NotFoundHandler http.Handler |
| 46 | |
| 47 | // Configurable Handler to be used when the request method does not match the route. |
| 48 | MethodNotAllowedHandler http.Handler |
| 49 | |
| 50 | // Parent route, if this is a subrouter. |
| 51 | parent parentRoute |
| 52 | // Routes to be matched, in order. |
| 53 | routes []*Route |
| 54 | // Routes by name for URL building. |
| 55 | namedRoutes map[string]*Route |
| 56 | // See Router.StrictSlash(). This defines the flag for new routes. |
| 57 | strictSlash bool |
| 58 | // See Router.SkipClean(). This defines the flag for new routes. |
| 59 | skipClean bool |
| 60 | // If true, do not clear the request context after handling the request. |
| 61 | // This has no effect when go1.7+ is used, since the context is stored |
| 62 | // on the request itself. |
| 63 | KeepContext bool |
| 64 | // see Router.UseEncodedPath(). This defines a flag for all routes. |
| 65 | useEncodedPath bool |
| 66 | } |
| 67 | |
| 68 | // Match matches registered routes against the request. |
| 69 | func (r *Router) Match(req *http.Request, match *RouteMatch) bool { |
| 70 | for _, route := range r.routes { |
| 71 | if route.Match(req, match) { |
| 72 | return true |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | if match.MatchErr == ErrMethodMismatch && r.MethodNotAllowedHandler != nil { |
| 77 | match.Handler = r.MethodNotAllowedHandler |
| 78 | return true |
| 79 | } |
| 80 | |
| 81 | // Closest match for a router (includes sub-routers) |
| 82 | if r.NotFoundHandler != nil { |
| 83 | match.Handler = r.NotFoundHandler |
| 84 | return true |
| 85 | } |
| 86 | return false |
| 87 | } |
| 88 | |
| 89 | // ServeHTTP dispatches the handler registered in the matched route. |
| 90 | // |
| 91 | // When there is a match, the route variables can be retrieved calling |
| 92 | // mux.Vars(request). |
| 93 | func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
| 94 | if !r.skipClean { |
| 95 | path := req.URL.Path |
| 96 | if r.useEncodedPath { |
| 97 | path = getPath(req) |
| 98 | } |
| 99 | // Clean path to canonical form and redirect. |
| 100 | if p := cleanPath(path); p != path { |
| 101 | |
| 102 | // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query. |
| 103 | // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: |
| 104 | // http://code.google.com/p/go/issues/detail?id=5252 |
| 105 | url := *req.URL |
| 106 | url.Path = p |
| 107 | p = url.String() |
| 108 | |
| 109 | w.Header().Set("Location", p) |
| 110 | w.WriteHeader(http.StatusMovedPermanently) |
| 111 | return |
| 112 | } |
| 113 | } |
| 114 | var match RouteMatch |
| 115 | var handler http.Handler |
| 116 | if r.Match(req, &match) { |
| 117 | handler = match.Handler |
| 118 | req = setVars(req, match.Vars) |
| 119 | req = setCurrentRoute(req, match.Route) |
| 120 | } |
| 121 | |
| 122 | if handler == nil && match.MatchErr == ErrMethodMismatch { |
| 123 | handler = methodNotAllowedHandler() |
| 124 | } |
| 125 | |
| 126 | if handler == nil { |
| 127 | handler = http.NotFoundHandler() |
| 128 | } |
| 129 | |
| 130 | if !r.KeepContext { |
| 131 | defer contextClear(req) |
| 132 | } |
| 133 | handler.ServeHTTP(w, req) |
| 134 | } |
| 135 | |
| 136 | // Get returns a route registered with the given name. |
| 137 | func (r *Router) Get(name string) *Route { |
| 138 | return r.getNamedRoutes()[name] |
| 139 | } |
| 140 | |
| 141 | // GetRoute returns a route registered with the given name. This method |
| 142 | // was renamed to Get() and remains here for backwards compatibility. |
| 143 | func (r *Router) GetRoute(name string) *Route { |
| 144 | return r.getNamedRoutes()[name] |
| 145 | } |
| 146 | |
| 147 | // StrictSlash defines the trailing slash behavior for new routes. The initial |
| 148 | // value is false. |
| 149 | // |
| 150 | // When true, if the route path is "/path/", accessing "/path" will redirect |
| 151 | // to the former and vice versa. In other words, your application will always |
| 152 | // see the path as specified in the route. |
| 153 | // |
| 154 | // When false, if the route path is "/path", accessing "/path/" will not match |
| 155 | // this route and vice versa. |
| 156 | // |
| 157 | // Special case: when a route sets a path prefix using the PathPrefix() method, |
| 158 | // strict slash is ignored for that route because the redirect behavior can't |
| 159 | // be determined from a prefix alone. However, any subrouters created from that |
| 160 | // route inherit the original StrictSlash setting. |
| 161 | func (r *Router) StrictSlash(value bool) *Router { |
| 162 | r.strictSlash = value |
| 163 | return r |
| 164 | } |
| 165 | |
| 166 | // SkipClean defines the path cleaning behaviour for new routes. The initial |
| 167 | // value is false. Users should be careful about which routes are not cleaned |
| 168 | // |
| 169 | // When true, if the route path is "/path//to", it will remain with the double |
| 170 | // slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/ |
| 171 | // |
| 172 | // When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will |
| 173 | // become /fetch/http/xkcd.com/534 |
| 174 | func (r *Router) SkipClean(value bool) *Router { |
| 175 | r.skipClean = value |
| 176 | return r |
| 177 | } |
| 178 | |
| 179 | // UseEncodedPath tells the router to match the encoded original path |
| 180 | // to the routes. |
| 181 | // For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to". |
| 182 | // This behavior has the drawback of needing to match routes against |
| 183 | // r.RequestURI instead of r.URL.Path. Any modifications (such as http.StripPrefix) |
| 184 | // to r.URL.Path will not affect routing when this flag is on and thus may |
| 185 | // induce unintended behavior. |
| 186 | // |
| 187 | // If not called, the router will match the unencoded path to the routes. |
| 188 | // For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to" |
| 189 | func (r *Router) UseEncodedPath() *Router { |
| 190 | r.useEncodedPath = true |
| 191 | return r |
| 192 | } |
| 193 | |
| 194 | // ---------------------------------------------------------------------------- |
| 195 | // parentRoute |
| 196 | // ---------------------------------------------------------------------------- |
| 197 | |
| 198 | func (r *Router) getBuildScheme() string { |
| 199 | if r.parent != nil { |
| 200 | return r.parent.getBuildScheme() |
| 201 | } |
| 202 | return "" |
| 203 | } |
| 204 | |
| 205 | // getNamedRoutes returns the map where named routes are registered. |
| 206 | func (r *Router) getNamedRoutes() map[string]*Route { |
| 207 | if r.namedRoutes == nil { |
| 208 | if r.parent != nil { |
| 209 | r.namedRoutes = r.parent.getNamedRoutes() |
| 210 | } else { |
| 211 | r.namedRoutes = make(map[string]*Route) |
| 212 | } |
| 213 | } |
| 214 | return r.namedRoutes |
| 215 | } |
| 216 | |
| 217 | // getRegexpGroup returns regexp definitions from the parent route, if any. |
| 218 | func (r *Router) getRegexpGroup() *routeRegexpGroup { |
| 219 | if r.parent != nil { |
| 220 | return r.parent.getRegexpGroup() |
| 221 | } |
| 222 | return nil |
| 223 | } |
| 224 | |
| 225 | func (r *Router) buildVars(m map[string]string) map[string]string { |
| 226 | if r.parent != nil { |
| 227 | m = r.parent.buildVars(m) |
| 228 | } |
| 229 | return m |
| 230 | } |
| 231 | |
| 232 | // ---------------------------------------------------------------------------- |
| 233 | // Route factories |
| 234 | // ---------------------------------------------------------------------------- |
| 235 | |
| 236 | // NewRoute registers an empty route. |
| 237 | func (r *Router) NewRoute() *Route { |
| 238 | route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath} |
| 239 | r.routes = append(r.routes, route) |
| 240 | return route |
| 241 | } |
| 242 | |
| 243 | // Handle registers a new route with a matcher for the URL path. |
| 244 | // See Route.Path() and Route.Handler(). |
| 245 | func (r *Router) Handle(path string, handler http.Handler) *Route { |
| 246 | return r.NewRoute().Path(path).Handler(handler) |
| 247 | } |
| 248 | |
| 249 | // HandleFunc registers a new route with a matcher for the URL path. |
| 250 | // See Route.Path() and Route.HandlerFunc(). |
| 251 | func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, |
| 252 | *http.Request)) *Route { |
| 253 | return r.NewRoute().Path(path).HandlerFunc(f) |
| 254 | } |
| 255 | |
| 256 | // Headers registers a new route with a matcher for request header values. |
| 257 | // See Route.Headers(). |
| 258 | func (r *Router) Headers(pairs ...string) *Route { |
| 259 | return r.NewRoute().Headers(pairs...) |
| 260 | } |
| 261 | |
| 262 | // Host registers a new route with a matcher for the URL host. |
| 263 | // See Route.Host(). |
| 264 | func (r *Router) Host(tpl string) *Route { |
| 265 | return r.NewRoute().Host(tpl) |
| 266 | } |
| 267 | |
| 268 | // MatcherFunc registers a new route with a custom matcher function. |
| 269 | // See Route.MatcherFunc(). |
| 270 | func (r *Router) MatcherFunc(f MatcherFunc) *Route { |
| 271 | return r.NewRoute().MatcherFunc(f) |
| 272 | } |
| 273 | |
| 274 | // Methods registers a new route with a matcher for HTTP methods. |
| 275 | // See Route.Methods(). |
| 276 | func (r *Router) Methods(methods ...string) *Route { |
| 277 | return r.NewRoute().Methods(methods...) |
| 278 | } |
| 279 | |
| 280 | // Path registers a new route with a matcher for the URL path. |
| 281 | // See Route.Path(). |
| 282 | func (r *Router) Path(tpl string) *Route { |
| 283 | return r.NewRoute().Path(tpl) |
| 284 | } |
| 285 | |
| 286 | // PathPrefix registers a new route with a matcher for the URL path prefix. |
| 287 | // See Route.PathPrefix(). |
| 288 | func (r *Router) PathPrefix(tpl string) *Route { |
| 289 | return r.NewRoute().PathPrefix(tpl) |
| 290 | } |
| 291 | |
| 292 | // Queries registers a new route with a matcher for URL query values. |
| 293 | // See Route.Queries(). |
| 294 | func (r *Router) Queries(pairs ...string) *Route { |
| 295 | return r.NewRoute().Queries(pairs...) |
| 296 | } |
| 297 | |
| 298 | // Schemes registers a new route with a matcher for URL schemes. |
| 299 | // See Route.Schemes(). |
| 300 | func (r *Router) Schemes(schemes ...string) *Route { |
| 301 | return r.NewRoute().Schemes(schemes...) |
| 302 | } |
| 303 | |
| 304 | // BuildVarsFunc registers a new route with a custom function for modifying |
| 305 | // route variables before building a URL. |
| 306 | func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route { |
| 307 | return r.NewRoute().BuildVarsFunc(f) |
| 308 | } |
| 309 | |
| 310 | // Walk walks the router and all its sub-routers, calling walkFn for each route |
| 311 | // in the tree. The routes are walked in the order they were added. Sub-routers |
| 312 | // are explored depth-first. |
| 313 | func (r *Router) Walk(walkFn WalkFunc) error { |
| 314 | return r.walk(walkFn, []*Route{}) |
| 315 | } |
| 316 | |
| 317 | // SkipRouter is used as a return value from WalkFuncs to indicate that the |
| 318 | // router that walk is about to descend down to should be skipped. |
| 319 | var SkipRouter = errors.New("skip this router") |
| 320 | |
| 321 | // WalkFunc is the type of the function called for each route visited by Walk. |
| 322 | // At every invocation, it is given the current route, and the current router, |
| 323 | // and a list of ancestor routes that lead to the current route. |
| 324 | type WalkFunc func(route *Route, router *Router, ancestors []*Route) error |
| 325 | |
| 326 | func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error { |
| 327 | for _, t := range r.routes { |
| 328 | err := walkFn(t, r, ancestors) |
| 329 | if err == SkipRouter { |
| 330 | continue |
| 331 | } |
| 332 | if err != nil { |
| 333 | return err |
| 334 | } |
| 335 | for _, sr := range t.matchers { |
| 336 | if h, ok := sr.(*Router); ok { |
| 337 | ancestors = append(ancestors, t) |
| 338 | err := h.walk(walkFn, ancestors) |
| 339 | if err != nil { |
| 340 | return err |
| 341 | } |
| 342 | ancestors = ancestors[:len(ancestors)-1] |
| 343 | } |
| 344 | } |
| 345 | if h, ok := t.handler.(*Router); ok { |
| 346 | ancestors = append(ancestors, t) |
| 347 | err := h.walk(walkFn, ancestors) |
| 348 | if err != nil { |
| 349 | return err |
| 350 | } |
| 351 | ancestors = ancestors[:len(ancestors)-1] |
| 352 | } |
| 353 | } |
| 354 | return nil |
| 355 | } |
| 356 | |
| 357 | // ---------------------------------------------------------------------------- |
| 358 | // Context |
| 359 | // ---------------------------------------------------------------------------- |
| 360 | |
| 361 | // RouteMatch stores information about a matched route. |
| 362 | type RouteMatch struct { |
| 363 | Route *Route |
| 364 | Handler http.Handler |
| 365 | Vars map[string]string |
| 366 | |
| 367 | // MatchErr is set to appropriate matching error |
| 368 | // It is set to ErrMethodMismatch if there is a mismatch in |
| 369 | // the request method and route method |
| 370 | MatchErr error |
| 371 | } |
| 372 | |
| 373 | type contextKey int |
| 374 | |
| 375 | const ( |
| 376 | varsKey contextKey = iota |
| 377 | routeKey |
| 378 | ) |
| 379 | |
| 380 | // Vars returns the route variables for the current request, if any. |
| 381 | func Vars(r *http.Request) map[string]string { |
| 382 | if rv := contextGet(r, varsKey); rv != nil { |
| 383 | return rv.(map[string]string) |
| 384 | } |
| 385 | return nil |
| 386 | } |
| 387 | |
| 388 | // CurrentRoute returns the matched route for the current request, if any. |
| 389 | // This only works when called inside the handler of the matched route |
| 390 | // because the matched route is stored in the request context which is cleared |
| 391 | // after the handler returns, unless the KeepContext option is set on the |
| 392 | // Router. |
| 393 | func CurrentRoute(r *http.Request) *Route { |
| 394 | if rv := contextGet(r, routeKey); rv != nil { |
| 395 | return rv.(*Route) |
| 396 | } |
| 397 | return nil |
| 398 | } |
| 399 | |
| 400 | func setVars(r *http.Request, val interface{}) *http.Request { |
| 401 | return contextSet(r, varsKey, val) |
| 402 | } |
| 403 | |
| 404 | func setCurrentRoute(r *http.Request, val interface{}) *http.Request { |
| 405 | return contextSet(r, routeKey, val) |
| 406 | } |
| 407 | |
| 408 | // ---------------------------------------------------------------------------- |
| 409 | // Helpers |
| 410 | // ---------------------------------------------------------------------------- |
| 411 | |
| 412 | // getPath returns the escaped path if possible; doing what URL.EscapedPath() |
| 413 | // which was added in go1.5 does |
| 414 | func getPath(req *http.Request) string { |
| 415 | if req.RequestURI != "" { |
| 416 | // Extract the path from RequestURI (which is escaped unlike URL.Path) |
| 417 | // as detailed here as detailed in https://golang.org/pkg/net/url/#URL |
| 418 | // for < 1.5 server side workaround |
| 419 | // http://localhost/path/here?v=1 -> /path/here |
| 420 | path := req.RequestURI |
| 421 | path = strings.TrimPrefix(path, req.URL.Scheme+`://`) |
| 422 | path = strings.TrimPrefix(path, req.URL.Host) |
| 423 | if i := strings.LastIndex(path, "?"); i > -1 { |
| 424 | path = path[:i] |
| 425 | } |
| 426 | if i := strings.LastIndex(path, "#"); i > -1 { |
| 427 | path = path[:i] |
| 428 | } |
| 429 | return path |
| 430 | } |
| 431 | return req.URL.Path |
| 432 | } |
| 433 | |
| 434 | // cleanPath returns the canonical path for p, eliminating . and .. elements. |
| 435 | // Borrowed from the net/http package. |
| 436 | func cleanPath(p string) string { |
| 437 | if p == "" { |
| 438 | return "/" |
| 439 | } |
| 440 | if p[0] != '/' { |
| 441 | p = "/" + p |
| 442 | } |
| 443 | np := path.Clean(p) |
| 444 | // path.Clean removes trailing slash except for root; |
| 445 | // put the trailing slash back if necessary. |
| 446 | if p[len(p)-1] == '/' && np != "/" { |
| 447 | np += "/" |
| 448 | } |
| 449 | |
| 450 | return np |
| 451 | } |
| 452 | |
| 453 | // uniqueVars returns an error if two slices contain duplicated strings. |
| 454 | func uniqueVars(s1, s2 []string) error { |
| 455 | for _, v1 := range s1 { |
| 456 | for _, v2 := range s2 { |
| 457 | if v1 == v2 { |
| 458 | return fmt.Errorf("mux: duplicated route variable %q", v2) |
| 459 | } |
| 460 | } |
| 461 | } |
| 462 | return nil |
| 463 | } |
| 464 | |
| 465 | // checkPairs returns the count of strings passed in, and an error if |
| 466 | // the count is not an even number. |
| 467 | func checkPairs(pairs ...string) (int, error) { |
| 468 | length := len(pairs) |
| 469 | if length%2 != 0 { |
| 470 | return length, fmt.Errorf( |
| 471 | "mux: number of parameters must be multiple of 2, got %v", pairs) |
| 472 | } |
| 473 | return length, nil |
| 474 | } |
| 475 | |
| 476 | // mapFromPairsToString converts variadic string parameters to a |
| 477 | // string to string map. |
| 478 | func mapFromPairsToString(pairs ...string) (map[string]string, error) { |
| 479 | length, err := checkPairs(pairs...) |
| 480 | if err != nil { |
| 481 | return nil, err |
| 482 | } |
| 483 | m := make(map[string]string, length/2) |
| 484 | for i := 0; i < length; i += 2 { |
| 485 | m[pairs[i]] = pairs[i+1] |
| 486 | } |
| 487 | return m, nil |
| 488 | } |
| 489 | |
| 490 | // mapFromPairsToRegex converts variadic string parameters to a |
| 491 | // string to regex map. |
| 492 | func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) { |
| 493 | length, err := checkPairs(pairs...) |
| 494 | if err != nil { |
| 495 | return nil, err |
| 496 | } |
| 497 | m := make(map[string]*regexp.Regexp, length/2) |
| 498 | for i := 0; i < length; i += 2 { |
| 499 | regex, err := regexp.Compile(pairs[i+1]) |
| 500 | if err != nil { |
| 501 | return nil, err |
| 502 | } |
| 503 | m[pairs[i]] = regex |
| 504 | } |
| 505 | return m, nil |
| 506 | } |
| 507 | |
| 508 | // matchInArray returns true if the given string value is in the array. |
| 509 | func matchInArray(arr []string, value string) bool { |
| 510 | for _, v := range arr { |
| 511 | if v == value { |
| 512 | return true |
| 513 | } |
| 514 | } |
| 515 | return false |
| 516 | } |
| 517 | |
| 518 | // matchMapWithString returns true if the given key/value pairs exist in a given map. |
| 519 | func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool { |
| 520 | for k, v := range toCheck { |
| 521 | // Check if key exists. |
| 522 | if canonicalKey { |
| 523 | k = http.CanonicalHeaderKey(k) |
| 524 | } |
| 525 | if values := toMatch[k]; values == nil { |
| 526 | return false |
| 527 | } else if v != "" { |
| 528 | // If value was defined as an empty string we only check that the |
| 529 | // key exists. Otherwise we also check for equality. |
| 530 | valueExists := false |
| 531 | for _, value := range values { |
| 532 | if v == value { |
| 533 | valueExists = true |
| 534 | break |
| 535 | } |
| 536 | } |
| 537 | if !valueExists { |
| 538 | return false |
| 539 | } |
| 540 | } |
| 541 | } |
| 542 | return true |
| 543 | } |
| 544 | |
| 545 | // matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against |
| 546 | // the given regex |
| 547 | func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool { |
| 548 | for k, v := range toCheck { |
| 549 | // Check if key exists. |
| 550 | if canonicalKey { |
| 551 | k = http.CanonicalHeaderKey(k) |
| 552 | } |
| 553 | if values := toMatch[k]; values == nil { |
| 554 | return false |
| 555 | } else if v != nil { |
| 556 | // If value was defined as an empty string we only check that the |
| 557 | // key exists. Otherwise we also check for equality. |
| 558 | valueExists := false |
| 559 | for _, value := range values { |
| 560 | if v.MatchString(value) { |
| 561 | valueExists = true |
| 562 | break |
| 563 | } |
| 564 | } |
| 565 | if !valueExists { |
| 566 | return false |
| 567 | } |
| 568 | } |
| 569 | } |
| 570 | return true |
| 571 | } |
| 572 | |
| 573 | // methodNotAllowed replies to the request with an HTTP status code 405. |
| 574 | func methodNotAllowed(w http.ResponseWriter, r *http.Request) { |
| 575 | w.WriteHeader(http.StatusMethodNotAllowed) |
| 576 | } |
| 577 | |
| 578 | // methodNotAllowedHandler returns a simple request handler |
| 579 | // that replies to each request with a status code 405. |
| 580 | func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) } |