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.
 
 
 

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