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