github.com/hellobchain/third_party@v0.0.0-20230331131523-deb0478a2e52/gorilla/mux/route.go (about) 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 "github.com/hellobchain/newcryptosm/http" 11 "net/url" 12 "regexp" 13 "strings" 14 ) 15 16 // Route stores information to match a request and build URLs. 17 type Route struct { 18 // Request handler for the route. 19 handler http.Handler 20 // If true, this route never matches: it is only used to build URLs. 21 buildOnly bool 22 // The name used to build URLs. 23 name string 24 // Error resulted from building a route. 25 err error 26 27 // "global" reference to all named routes 28 namedRoutes map[string]*Route 29 30 // config possibly passed in from `Router` 31 routeConf 32 } 33 34 // SkipClean reports whether path cleaning is enabled for this route via 35 // Router.SkipClean. 36 func (r *Route) SkipClean() bool { 37 return r.skipClean 38 } 39 40 // Match matches the route against the request. 41 func (r *Route) Match(req *http.Request, match *RouteMatch) bool { 42 if r.buildOnly || r.err != nil { 43 return false 44 } 45 46 // Set MatchErr to nil to prevent 47 // subsequent matching subrouters from failing to run middleware. 48 // If not reset, the middleware would see a non-nil MatchErr and be skipped, 49 // even when there was a matching route. 50 match.MatchErr = nil 51 var matchErr error 52 53 // Match everything. 54 for _, m := range r.matchers { 55 if matched := m.Match(req, match); !matched { 56 if _, ok := m.(methodMatcher); ok { 57 matchErr = ErrMethodMismatch 58 continue 59 } 60 matchErr = nil 61 return false 62 } 63 } 64 65 if matchErr != nil { 66 match.MatchErr = matchErr 67 return false 68 } 69 70 if match.MatchErr == ErrMethodMismatch { 71 // We found a route which matches request method, clear MatchErr 72 match.MatchErr = nil 73 // Then override the mis-matched handler 74 match.Handler = r.handler 75 } 76 77 // Yay, we have a match. Let's collect some info about it. 78 if match.Route == nil { 79 match.Route = r 80 } 81 if match.Handler == nil { 82 match.Handler = r.handler 83 } 84 if match.Vars == nil { 85 match.Vars = make(map[string]string) 86 } 87 88 // Set variables. 89 r.regexp.setMatch(req, match, r) 90 return true 91 } 92 93 // ---------------------------------------------------------------------------- 94 // Route attributes 95 // ---------------------------------------------------------------------------- 96 97 // GetError returns an error resulted from building the route, if any. 98 func (r *Route) GetError() error { 99 return r.err 100 } 101 102 // BuildOnly sets the route to never match: it is only used to build URLs. 103 func (r *Route) BuildOnly() *Route { 104 r.buildOnly = true 105 return r 106 } 107 108 // Handler -------------------------------------------------------------------- 109 110 // Handler sets a handler for the route. 111 func (r *Route) Handler(handler http.Handler) *Route { 112 if r.err == nil { 113 r.handler = handler 114 } 115 return r 116 } 117 118 // HandlerFunc sets a handler function for the route. 119 func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route { 120 return r.Handler(http.HandlerFunc(f)) 121 } 122 123 // GetHandler returns the handler for the route, if any. 124 func (r *Route) GetHandler() http.Handler { 125 return r.handler 126 } 127 128 // Name ----------------------------------------------------------------------- 129 130 // Name sets the name for the route, used to build URLs. 131 // It is an error to call Name more than once on a route. 132 func (r *Route) Name(name string) *Route { 133 if r.name != "" { 134 r.err = fmt.Errorf("mux: route already has name %q, can't set %q", 135 r.name, name) 136 } 137 if r.err == nil { 138 r.name = name 139 r.namedRoutes[name] = r 140 } 141 return r 142 } 143 144 // GetName returns the name for the route, if any. 145 func (r *Route) GetName() string { 146 return r.name 147 } 148 149 // ---------------------------------------------------------------------------- 150 // Matchers 151 // ---------------------------------------------------------------------------- 152 153 // matcher types try to match a request. 154 type matcher interface { 155 Match(*http.Request, *RouteMatch) bool 156 } 157 158 // addMatcher adds a matcher to the route. 159 func (r *Route) addMatcher(m matcher) *Route { 160 if r.err == nil { 161 r.matchers = append(r.matchers, m) 162 } 163 return r 164 } 165 166 // addRegexpMatcher adds a host or path matcher and builder to a route. 167 func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error { 168 if r.err != nil { 169 return r.err 170 } 171 if typ == regexpTypePath || typ == regexpTypePrefix { 172 if len(tpl) > 0 && tpl[0] != '/' { 173 return fmt.Errorf("mux: path must start with a slash, got %q", tpl) 174 } 175 if r.regexp.path != nil { 176 tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl 177 } 178 } 179 rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{ 180 strictSlash: r.strictSlash, 181 useEncodedPath: r.useEncodedPath, 182 }) 183 if err != nil { 184 return err 185 } 186 for _, q := range r.regexp.queries { 187 if err = uniqueVars(rr.varsN, q.varsN); err != nil { 188 return err 189 } 190 } 191 if typ == regexpTypeHost { 192 if r.regexp.path != nil { 193 if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { 194 return err 195 } 196 } 197 r.regexp.host = rr 198 } else { 199 if r.regexp.host != nil { 200 if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil { 201 return err 202 } 203 } 204 if typ == regexpTypeQuery { 205 r.regexp.queries = append(r.regexp.queries, rr) 206 } else { 207 r.regexp.path = rr 208 } 209 } 210 r.addMatcher(rr) 211 return nil 212 } 213 214 // Headers -------------------------------------------------------------------- 215 216 // headerMatcher matches the request against header values. 217 type headerMatcher map[string]string 218 219 func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { 220 return matchMapWithString(m, r.Header, true) 221 } 222 223 // Headers adds a matcher for request header values. 224 // It accepts a sequence of key/value pairs to be matched. For example: 225 // 226 // r := mux.NewRouter() 227 // r.Headers("Content-Type", "application/json", 228 // "X-Requested-With", "XMLHttpRequest") 229 // 230 // The above route will only match if both request header values match. 231 // If the value is an empty string, it will match any value if the key is set. 232 func (r *Route) Headers(pairs ...string) *Route { 233 if r.err == nil { 234 var headers map[string]string 235 headers, r.err = mapFromPairsToString(pairs...) 236 return r.addMatcher(headerMatcher(headers)) 237 } 238 return r 239 } 240 241 // headerRegexMatcher matches the request against the route given a regex for the header 242 type headerRegexMatcher map[string]*regexp.Regexp 243 244 func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool { 245 return matchMapWithRegex(m, r.Header, true) 246 } 247 248 // HeadersRegexp accepts a sequence of key/value pairs, where the value has regex 249 // support. For example: 250 // 251 // r := mux.NewRouter() 252 // r.HeadersRegexp("Content-Type", "application/(text|json)", 253 // "X-Requested-With", "XMLHttpRequest") 254 // 255 // The above route will only match if both the request header matches both regular expressions. 256 // If the value is an empty string, it will match any value if the key is set. 257 // Use the start and end of string anchors (^ and $) to match an exact value. 258 func (r *Route) HeadersRegexp(pairs ...string) *Route { 259 if r.err == nil { 260 var headers map[string]*regexp.Regexp 261 headers, r.err = mapFromPairsToRegex(pairs...) 262 return r.addMatcher(headerRegexMatcher(headers)) 263 } 264 return r 265 } 266 267 // Host ----------------------------------------------------------------------- 268 269 // Host adds a matcher for the URL host. 270 // It accepts a template with zero or more URL variables enclosed by {}. 271 // Variables can define an optional regexp pattern to be matched: 272 // 273 // - {name} matches anything until the next dot. 274 // 275 // - {name:pattern} matches the given regexp pattern. 276 // 277 // For example: 278 // 279 // r := mux.NewRouter() 280 // r.Host("www.example.com") 281 // r.Host("{subdomain}.domain.com") 282 // r.Host("{subdomain:[a-z]+}.domain.com") 283 // 284 // Variable names must be unique in a given route. They can be retrieved 285 // calling mux.Vars(request). 286 func (r *Route) Host(tpl string) *Route { 287 r.err = r.addRegexpMatcher(tpl, regexpTypeHost) 288 return r 289 } 290 291 // MatcherFunc ---------------------------------------------------------------- 292 293 // MatcherFunc is the function signature used by custom matchers. 294 type MatcherFunc func(*http.Request, *RouteMatch) bool 295 296 // Match returns the match for a given request. 297 func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { 298 return m(r, match) 299 } 300 301 // MatcherFunc adds a custom function to be used as request matcher. 302 func (r *Route) MatcherFunc(f MatcherFunc) *Route { 303 return r.addMatcher(f) 304 } 305 306 // Methods -------------------------------------------------------------------- 307 308 // methodMatcher matches the request against HTTP methods. 309 type methodMatcher []string 310 311 func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool { 312 return matchInArray(m, r.Method) 313 } 314 315 // Methods adds a matcher for HTTP methods. 316 // It accepts a sequence of one or more methods to be matched, e.g.: 317 // "GET", "POST", "PUT". 318 func (r *Route) Methods(methods ...string) *Route { 319 for k, v := range methods { 320 methods[k] = strings.ToUpper(v) 321 } 322 return r.addMatcher(methodMatcher(methods)) 323 } 324 325 // Path ----------------------------------------------------------------------- 326 327 // Path adds a matcher for the URL path. 328 // It accepts a template with zero or more URL variables enclosed by {}. The 329 // template must start with a "/". 330 // Variables can define an optional regexp pattern to be matched: 331 // 332 // - {name} matches anything until the next slash. 333 // 334 // - {name:pattern} matches the given regexp pattern. 335 // 336 // For example: 337 // 338 // r := mux.NewRouter() 339 // r.Path("/products/").Handler(ProductsHandler) 340 // r.Path("/products/{key}").Handler(ProductsHandler) 341 // r.Path("/articles/{category}/{id:[0-9]+}"). 342 // Handler(ArticleHandler) 343 // 344 // Variable names must be unique in a given route. They can be retrieved 345 // calling mux.Vars(request). 346 func (r *Route) Path(tpl string) *Route { 347 r.err = r.addRegexpMatcher(tpl, regexpTypePath) 348 return r 349 } 350 351 // PathPrefix ----------------------------------------------------------------- 352 353 // PathPrefix adds a matcher for the URL path prefix. This matches if the given 354 // template is a prefix of the full URL path. See Route.Path() for details on 355 // the tpl argument. 356 // 357 // Note that it does not treat slashes specially ("/foobar/" will be matched by 358 // the prefix "/foo") so you may want to use a trailing slash here. 359 // 360 // Also note that the setting of Router.StrictSlash() has no effect on routes 361 // with a PathPrefix matcher. 362 func (r *Route) PathPrefix(tpl string) *Route { 363 r.err = r.addRegexpMatcher(tpl, regexpTypePrefix) 364 return r 365 } 366 367 // Query ---------------------------------------------------------------------- 368 369 // Queries adds a matcher for URL query values. 370 // It accepts a sequence of key/value pairs. Values may define variables. 371 // For example: 372 // 373 // r := mux.NewRouter() 374 // r.Queries("foo", "bar", "id", "{id:[0-9]+}") 375 // 376 // The above route will only match if the URL contains the defined queries 377 // values, e.g.: ?foo=bar&id=42. 378 // 379 // It the value is an empty string, it will match any value if the key is set. 380 // 381 // Variables can define an optional regexp pattern to be matched: 382 // 383 // - {name} matches anything until the next slash. 384 // 385 // - {name:pattern} matches the given regexp pattern. 386 func (r *Route) Queries(pairs ...string) *Route { 387 length := len(pairs) 388 if length%2 != 0 { 389 r.err = fmt.Errorf( 390 "mux: number of parameters must be multiple of 2, got %v", pairs) 391 return nil 392 } 393 for i := 0; i < length; i += 2 { 394 if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil { 395 return r 396 } 397 } 398 399 return r 400 } 401 402 // Schemes -------------------------------------------------------------------- 403 404 // schemeMatcher matches the request against URL schemes. 405 type schemeMatcher []string 406 407 func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { 408 return matchInArray(m, r.URL.Scheme) 409 } 410 411 // Schemes adds a matcher for URL schemes. 412 // It accepts a sequence of schemes to be matched, e.g.: "http", "https". 413 func (r *Route) Schemes(schemes ...string) *Route { 414 for k, v := range schemes { 415 schemes[k] = strings.ToLower(v) 416 } 417 if len(schemes) > 0 { 418 r.buildScheme = schemes[0] 419 } 420 return r.addMatcher(schemeMatcher(schemes)) 421 } 422 423 // BuildVarsFunc -------------------------------------------------------------- 424 425 // BuildVarsFunc is the function signature used by custom build variable 426 // functions (which can modify route variables before a route's URL is built). 427 type BuildVarsFunc func(map[string]string) map[string]string 428 429 // BuildVarsFunc adds a custom function to be used to modify build variables 430 // before a route's URL is built. 431 func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route { 432 if r.buildVarsFunc != nil { 433 // compose the old and new functions 434 old := r.buildVarsFunc 435 r.buildVarsFunc = func(m map[string]string) map[string]string { 436 return f(old(m)) 437 } 438 } else { 439 r.buildVarsFunc = f 440 } 441 return r 442 } 443 444 // Subrouter ------------------------------------------------------------------ 445 446 // Subrouter creates a subrouter for the route. 447 // 448 // It will test the inner routes only if the parent route matched. For example: 449 // 450 // r := mux.NewRouter() 451 // s := r.Host("www.example.com").Subrouter() 452 // s.HandleFunc("/products/", ProductsHandler) 453 // s.HandleFunc("/products/{key}", ProductHandler) 454 // s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) 455 // 456 // Here, the routes registered in the subrouter won't be tested if the host 457 // doesn't match. 458 func (r *Route) Subrouter() *Router { 459 // initialize a subrouter with a copy of the parent route's configuration 460 router := &Router{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes} 461 r.addMatcher(router) 462 return router 463 } 464 465 // ---------------------------------------------------------------------------- 466 // URL building 467 // ---------------------------------------------------------------------------- 468 469 // URL builds a URL for the route. 470 // 471 // It accepts a sequence of key/value pairs for the route variables. For 472 // example, given this route: 473 // 474 // r := mux.NewRouter() 475 // r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). 476 // Name("article") 477 // 478 // ...a URL for it can be built using: 479 // 480 // url, err := r.Get("article").URL("category", "technology", "id", "42") 481 // 482 // ...which will return an url.URL with the following path: 483 // 484 // "/articles/technology/42" 485 // 486 // This also works for host variables: 487 // 488 // r := mux.NewRouter() 489 // r.Host("{subdomain}.domain.com"). 490 // HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). 491 // Name("article") 492 // 493 // // url.String() will be "http://news.domain.com/articles/technology/42" 494 // url, err := r.Get("article").URL("subdomain", "news", 495 // "category", "technology", 496 // "id", "42") 497 // 498 // All variables defined in the route are required, and their values must 499 // conform to the corresponding patterns. 500 func (r *Route) URL(pairs ...string) (*url.URL, error) { 501 if r.err != nil { 502 return nil, r.err 503 } 504 values, err := r.prepareVars(pairs...) 505 if err != nil { 506 return nil, err 507 } 508 var scheme, host, path string 509 queries := make([]string, 0, len(r.regexp.queries)) 510 if r.regexp.host != nil { 511 if host, err = r.regexp.host.url(values); err != nil { 512 return nil, err 513 } 514 scheme = "http" 515 if r.buildScheme != "" { 516 scheme = r.buildScheme 517 } 518 } 519 if r.regexp.path != nil { 520 if path, err = r.regexp.path.url(values); err != nil { 521 return nil, err 522 } 523 } 524 for _, q := range r.regexp.queries { 525 var query string 526 if query, err = q.url(values); err != nil { 527 return nil, err 528 } 529 queries = append(queries, query) 530 } 531 return &url.URL{ 532 Scheme: scheme, 533 Host: host, 534 Path: path, 535 RawQuery: strings.Join(queries, "&"), 536 }, nil 537 } 538 539 // URLHost builds the host part of the URL for a route. See Route.URL(). 540 // 541 // The route must have a host defined. 542 func (r *Route) URLHost(pairs ...string) (*url.URL, error) { 543 if r.err != nil { 544 return nil, r.err 545 } 546 if r.regexp.host == nil { 547 return nil, errors.New("mux: route doesn't have a host") 548 } 549 values, err := r.prepareVars(pairs...) 550 if err != nil { 551 return nil, err 552 } 553 host, err := r.regexp.host.url(values) 554 if err != nil { 555 return nil, err 556 } 557 u := &url.URL{ 558 Scheme: "http", 559 Host: host, 560 } 561 if r.buildScheme != "" { 562 u.Scheme = r.buildScheme 563 } 564 return u, nil 565 } 566 567 // URLPath builds the path part of the URL for a route. See Route.URL(). 568 // 569 // The route must have a path defined. 570 func (r *Route) URLPath(pairs ...string) (*url.URL, error) { 571 if r.err != nil { 572 return nil, r.err 573 } 574 if r.regexp.path == nil { 575 return nil, errors.New("mux: route doesn't have a path") 576 } 577 values, err := r.prepareVars(pairs...) 578 if err != nil { 579 return nil, err 580 } 581 path, err := r.regexp.path.url(values) 582 if err != nil { 583 return nil, err 584 } 585 return &url.URL{ 586 Path: path, 587 }, nil 588 } 589 590 // GetPathTemplate returns the template used to build the 591 // route match. 592 // This is useful for building simple REST API documentation and for instrumentation 593 // against third-party services. 594 // An error will be returned if the route does not define a path. 595 func (r *Route) GetPathTemplate() (string, error) { 596 if r.err != nil { 597 return "", r.err 598 } 599 if r.regexp.path == nil { 600 return "", errors.New("mux: route doesn't have a path") 601 } 602 return r.regexp.path.template, nil 603 } 604 605 // GetPathRegexp returns the expanded regular expression used to match route path. 606 // This is useful for building simple REST API documentation and for instrumentation 607 // against third-party services. 608 // An error will be returned if the route does not define a path. 609 func (r *Route) GetPathRegexp() (string, error) { 610 if r.err != nil { 611 return "", r.err 612 } 613 if r.regexp.path == nil { 614 return "", errors.New("mux: route does not have a path") 615 } 616 return r.regexp.path.regexp.String(), nil 617 } 618 619 // GetQueriesRegexp returns the expanded regular expressions used to match the 620 // route queries. 621 // This is useful for building simple REST API documentation and for instrumentation 622 // against third-party services. 623 // An error will be returned if the route does not have queries. 624 func (r *Route) GetQueriesRegexp() ([]string, error) { 625 if r.err != nil { 626 return nil, r.err 627 } 628 if r.regexp.queries == nil { 629 return nil, errors.New("mux: route doesn't have queries") 630 } 631 var queries []string 632 for _, query := range r.regexp.queries { 633 queries = append(queries, query.regexp.String()) 634 } 635 return queries, nil 636 } 637 638 // GetQueriesTemplates returns the templates used to build the 639 // query matching. 640 // This is useful for building simple REST API documentation and for instrumentation 641 // against third-party services. 642 // An error will be returned if the route does not define queries. 643 func (r *Route) GetQueriesTemplates() ([]string, error) { 644 if r.err != nil { 645 return nil, r.err 646 } 647 if r.regexp.queries == nil { 648 return nil, errors.New("mux: route doesn't have queries") 649 } 650 var queries []string 651 for _, query := range r.regexp.queries { 652 queries = append(queries, query.template) 653 } 654 return queries, nil 655 } 656 657 // GetMethods returns the methods the route matches against 658 // This is useful for building simple REST API documentation and for instrumentation 659 // against third-party services. 660 // An error will be returned if route does not have methods. 661 func (r *Route) GetMethods() ([]string, error) { 662 if r.err != nil { 663 return nil, r.err 664 } 665 for _, m := range r.matchers { 666 if methods, ok := m.(methodMatcher); ok { 667 return []string(methods), nil 668 } 669 } 670 return nil, errors.New("mux: route doesn't have methods") 671 } 672 673 // GetHostTemplate returns the template used to build the 674 // route match. 675 // This is useful for building simple REST API documentation and for instrumentation 676 // against third-party services. 677 // An error will be returned if the route does not define a host. 678 func (r *Route) GetHostTemplate() (string, error) { 679 if r.err != nil { 680 return "", r.err 681 } 682 if r.regexp.host == nil { 683 return "", errors.New("mux: route doesn't have a host") 684 } 685 return r.regexp.host.template, nil 686 } 687 688 // prepareVars converts the route variable pairs into a map. If the route has a 689 // BuildVarsFunc, it is invoked. 690 func (r *Route) prepareVars(pairs ...string) (map[string]string, error) { 691 m, err := mapFromPairsToString(pairs...) 692 if err != nil { 693 return nil, err 694 } 695 return r.buildVars(m), nil 696 } 697 698 func (r *Route) buildVars(m map[string]string) map[string]string { 699 if r.buildVarsFunc != nil { 700 m = r.buildVarsFunc(m) 701 } 702 return m 703 }