You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

711 lines
20 KiB

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