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