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.
 
 
 

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