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