Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
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.
542 lines
16 KiB
542 lines
16 KiB
// Copyright 2012 The Gorilla Authors. All rights reserved. |
|
// Use of this source code is governed by a BSD-style |
|
// license that can be found in the LICENSE file. |
|
|
|
package mux |
|
|
|
import ( |
|
"errors" |
|
"fmt" |
|
"net/http" |
|
"path" |
|
"regexp" |
|
"strings" |
|
) |
|
|
|
// NewRouter returns a new router instance. |
|
func NewRouter() *Router { |
|
return &Router{namedRoutes: make(map[string]*Route), KeepContext: false} |
|
} |
|
|
|
// Router registers routes to be matched and dispatches a handler. |
|
// |
|
// It implements the http.Handler interface, so it can be registered to serve |
|
// requests: |
|
// |
|
// var router = mux.NewRouter() |
|
// |
|
// func main() { |
|
// http.Handle("/", router) |
|
// } |
|
// |
|
// Or, for Google App Engine, register it in a init() function: |
|
// |
|
// func init() { |
|
// http.Handle("/", router) |
|
// } |
|
// |
|
// This will send all incoming requests to the router. |
|
type Router struct { |
|
// Configurable Handler to be used when no route matches. |
|
NotFoundHandler http.Handler |
|
// Parent route, if this is a subrouter. |
|
parent parentRoute |
|
// Routes to be matched, in order. |
|
routes []*Route |
|
// Routes by name for URL building. |
|
namedRoutes map[string]*Route |
|
// See Router.StrictSlash(). This defines the flag for new routes. |
|
strictSlash bool |
|
// See Router.SkipClean(). This defines the flag for new routes. |
|
skipClean bool |
|
// If true, do not clear the request context after handling the request. |
|
// This has no effect when go1.7+ is used, since the context is stored |
|
// on the request itself. |
|
KeepContext bool |
|
// see Router.UseEncodedPath(). This defines a flag for all routes. |
|
useEncodedPath bool |
|
} |
|
|
|
// Match matches registered routes against the request. |
|
func (r *Router) Match(req *http.Request, match *RouteMatch) bool { |
|
for _, route := range r.routes { |
|
if route.Match(req, match) { |
|
return true |
|
} |
|
} |
|
|
|
// Closest match for a router (includes sub-routers) |
|
if r.NotFoundHandler != nil { |
|
match.Handler = r.NotFoundHandler |
|
return true |
|
} |
|
return false |
|
} |
|
|
|
// ServeHTTP dispatches the handler registered in the matched route. |
|
// |
|
// When there is a match, the route variables can be retrieved calling |
|
// mux.Vars(request). |
|
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
|
if !r.skipClean { |
|
path := req.URL.Path |
|
if r.useEncodedPath { |
|
path = getPath(req) |
|
} |
|
// Clean path to canonical form and redirect. |
|
if p := cleanPath(path); p != path { |
|
|
|
// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query. |
|
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: |
|
// http://code.google.com/p/go/issues/detail?id=5252 |
|
url := *req.URL |
|
url.Path = p |
|
p = url.String() |
|
|
|
w.Header().Set("Location", p) |
|
w.WriteHeader(http.StatusMovedPermanently) |
|
return |
|
} |
|
} |
|
var match RouteMatch |
|
var handler http.Handler |
|
if r.Match(req, &match) { |
|
handler = match.Handler |
|
req = setVars(req, match.Vars) |
|
req = setCurrentRoute(req, match.Route) |
|
} |
|
if handler == nil { |
|
handler = http.NotFoundHandler() |
|
} |
|
if !r.KeepContext { |
|
defer contextClear(req) |
|
} |
|
handler.ServeHTTP(w, req) |
|
} |
|
|
|
// Get returns a route registered with the given name. |
|
func (r *Router) Get(name string) *Route { |
|
return r.getNamedRoutes()[name] |
|
} |
|
|
|
// GetRoute returns a route registered with the given name. This method |
|
// was renamed to Get() and remains here for backwards compatibility. |
|
func (r *Router) GetRoute(name string) *Route { |
|
return r.getNamedRoutes()[name] |
|
} |
|
|
|
// StrictSlash defines the trailing slash behavior for new routes. The initial |
|
// value is false. |
|
// |
|
// When true, if the route path is "/path/", accessing "/path" will redirect |
|
// to the former and vice versa. In other words, your application will always |
|
// see the path as specified in the route. |
|
// |
|
// When false, if the route path is "/path", accessing "/path/" will not match |
|
// this route and vice versa. |
|
// |
|
// Special case: when a route sets a path prefix using the PathPrefix() method, |
|
// strict slash is ignored for that route because the redirect behavior can't |
|
// be determined from a prefix alone. However, any subrouters created from that |
|
// route inherit the original StrictSlash setting. |
|
func (r *Router) StrictSlash(value bool) *Router { |
|
r.strictSlash = value |
|
return r |
|
} |
|
|
|
// SkipClean defines the path cleaning behaviour for new routes. The initial |
|
// value is false. Users should be careful about which routes are not cleaned |
|
// |
|
// When true, if the route path is "/path//to", it will remain with the double |
|
// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/ |
|
// |
|
// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will |
|
// become /fetch/http/xkcd.com/534 |
|
func (r *Router) SkipClean(value bool) *Router { |
|
r.skipClean = value |
|
return r |
|
} |
|
|
|
// UseEncodedPath tells the router to match the encoded original path |
|
// to the routes. |
|
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to". |
|
// This behavior has the drawback of needing to match routes against |
|
// r.RequestURI instead of r.URL.Path. Any modifications (such as http.StripPrefix) |
|
// to r.URL.Path will not affect routing when this flag is on and thus may |
|
// induce unintended behavior. |
|
// |
|
// If not called, the router will match the unencoded path to the routes. |
|
// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to" |
|
func (r *Router) UseEncodedPath() *Router { |
|
r.useEncodedPath = true |
|
return r |
|
} |
|
|
|
// ---------------------------------------------------------------------------- |
|
// parentRoute |
|
// ---------------------------------------------------------------------------- |
|
|
|
// getNamedRoutes returns the map where named routes are registered. |
|
func (r *Router) getNamedRoutes() map[string]*Route { |
|
if r.namedRoutes == nil { |
|
if r.parent != nil { |
|
r.namedRoutes = r.parent.getNamedRoutes() |
|
} else { |
|
r.namedRoutes = make(map[string]*Route) |
|
} |
|
} |
|
return r.namedRoutes |
|
} |
|
|
|
// getRegexpGroup returns regexp definitions from the parent route, if any. |
|
func (r *Router) getRegexpGroup() *routeRegexpGroup { |
|
if r.parent != nil { |
|
return r.parent.getRegexpGroup() |
|
} |
|
return nil |
|
} |
|
|
|
func (r *Router) buildVars(m map[string]string) map[string]string { |
|
if r.parent != nil { |
|
m = r.parent.buildVars(m) |
|
} |
|
return m |
|
} |
|
|
|
// ---------------------------------------------------------------------------- |
|
// Route factories |
|
// ---------------------------------------------------------------------------- |
|
|
|
// NewRoute registers an empty route. |
|
func (r *Router) NewRoute() *Route { |
|
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath} |
|
r.routes = append(r.routes, route) |
|
return route |
|
} |
|
|
|
// Handle registers a new route with a matcher for the URL path. |
|
// See Route.Path() and Route.Handler(). |
|
func (r *Router) Handle(path string, handler http.Handler) *Route { |
|
return r.NewRoute().Path(path).Handler(handler) |
|
} |
|
|
|
// HandleFunc registers a new route with a matcher for the URL path. |
|
// See Route.Path() and Route.HandlerFunc(). |
|
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, |
|
*http.Request)) *Route { |
|
return r.NewRoute().Path(path).HandlerFunc(f) |
|
} |
|
|
|
// Headers registers a new route with a matcher for request header values. |
|
// See Route.Headers(). |
|
func (r *Router) Headers(pairs ...string) *Route { |
|
return r.NewRoute().Headers(pairs...) |
|
} |
|
|
|
// Host registers a new route with a matcher for the URL host. |
|
// See Route.Host(). |
|
func (r *Router) Host(tpl string) *Route { |
|
return r.NewRoute().Host(tpl) |
|
} |
|
|
|
// MatcherFunc registers a new route with a custom matcher function. |
|
// See Route.MatcherFunc(). |
|
func (r *Router) MatcherFunc(f MatcherFunc) *Route { |
|
return r.NewRoute().MatcherFunc(f) |
|
} |
|
|
|
// Methods registers a new route with a matcher for HTTP methods. |
|
// See Route.Methods(). |
|
func (r *Router) Methods(methods ...string) *Route { |
|
return r.NewRoute().Methods(methods...) |
|
} |
|
|
|
// Path registers a new route with a matcher for the URL path. |
|
// See Route.Path(). |
|
func (r *Router) Path(tpl string) *Route { |
|
return r.NewRoute().Path(tpl) |
|
} |
|
|
|
// PathPrefix registers a new route with a matcher for the URL path prefix. |
|
// See Route.PathPrefix(). |
|
func (r *Router) PathPrefix(tpl string) *Route { |
|
return r.NewRoute().PathPrefix(tpl) |
|
} |
|
|
|
// Queries registers a new route with a matcher for URL query values. |
|
// See Route.Queries(). |
|
func (r *Router) Queries(pairs ...string) *Route { |
|
return r.NewRoute().Queries(pairs...) |
|
} |
|
|
|
// Schemes registers a new route with a matcher for URL schemes. |
|
// See Route.Schemes(). |
|
func (r *Router) Schemes(schemes ...string) *Route { |
|
return r.NewRoute().Schemes(schemes...) |
|
} |
|
|
|
// BuildVarsFunc registers a new route with a custom function for modifying |
|
// route variables before building a URL. |
|
func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route { |
|
return r.NewRoute().BuildVarsFunc(f) |
|
} |
|
|
|
// Walk walks the router and all its sub-routers, calling walkFn for each route |
|
// in the tree. The routes are walked in the order they were added. Sub-routers |
|
// are explored depth-first. |
|
func (r *Router) Walk(walkFn WalkFunc) error { |
|
return r.walk(walkFn, []*Route{}) |
|
} |
|
|
|
// SkipRouter is used as a return value from WalkFuncs to indicate that the |
|
// router that walk is about to descend down to should be skipped. |
|
var SkipRouter = errors.New("skip this router") |
|
|
|
// WalkFunc is the type of the function called for each route visited by Walk. |
|
// At every invocation, it is given the current route, and the current router, |
|
// and a list of ancestor routes that lead to the current route. |
|
type WalkFunc func(route *Route, router *Router, ancestors []*Route) error |
|
|
|
func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error { |
|
for _, t := range r.routes { |
|
if t.regexp == nil || t.regexp.path == nil || t.regexp.path.template == "" { |
|
continue |
|
} |
|
|
|
err := walkFn(t, r, ancestors) |
|
if err == SkipRouter { |
|
continue |
|
} |
|
if err != nil { |
|
return err |
|
} |
|
for _, sr := range t.matchers { |
|
if h, ok := sr.(*Router); ok { |
|
err := h.walk(walkFn, ancestors) |
|
if err != nil { |
|
return err |
|
} |
|
} |
|
} |
|
if h, ok := t.handler.(*Router); ok { |
|
ancestors = append(ancestors, t) |
|
err := h.walk(walkFn, ancestors) |
|
if err != nil { |
|
return err |
|
} |
|
ancestors = ancestors[:len(ancestors)-1] |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
// ---------------------------------------------------------------------------- |
|
// Context |
|
// ---------------------------------------------------------------------------- |
|
|
|
// RouteMatch stores information about a matched route. |
|
type RouteMatch struct { |
|
Route *Route |
|
Handler http.Handler |
|
Vars map[string]string |
|
} |
|
|
|
type contextKey int |
|
|
|
const ( |
|
varsKey contextKey = iota |
|
routeKey |
|
) |
|
|
|
// Vars returns the route variables for the current request, if any. |
|
func Vars(r *http.Request) map[string]string { |
|
if rv := contextGet(r, varsKey); rv != nil { |
|
return rv.(map[string]string) |
|
} |
|
return nil |
|
} |
|
|
|
// CurrentRoute returns the matched route for the current request, if any. |
|
// This only works when called inside the handler of the matched route |
|
// because the matched route is stored in the request context which is cleared |
|
// after the handler returns, unless the KeepContext option is set on the |
|
// Router. |
|
func CurrentRoute(r *http.Request) *Route { |
|
if rv := contextGet(r, routeKey); rv != nil { |
|
return rv.(*Route) |
|
} |
|
return nil |
|
} |
|
|
|
func setVars(r *http.Request, val interface{}) *http.Request { |
|
return contextSet(r, varsKey, val) |
|
} |
|
|
|
func setCurrentRoute(r *http.Request, val interface{}) *http.Request { |
|
return contextSet(r, routeKey, val) |
|
} |
|
|
|
// ---------------------------------------------------------------------------- |
|
// Helpers |
|
// ---------------------------------------------------------------------------- |
|
|
|
// getPath returns the escaped path if possible; doing what URL.EscapedPath() |
|
// which was added in go1.5 does |
|
func getPath(req *http.Request) string { |
|
if req.RequestURI != "" { |
|
// Extract the path from RequestURI (which is escaped unlike URL.Path) |
|
// as detailed here as detailed in https://golang.org/pkg/net/url/#URL |
|
// for < 1.5 server side workaround |
|
// http://localhost/path/here?v=1 -> /path/here |
|
path := req.RequestURI |
|
path = strings.TrimPrefix(path, req.URL.Scheme+`://`) |
|
path = strings.TrimPrefix(path, req.URL.Host) |
|
if i := strings.LastIndex(path, "?"); i > -1 { |
|
path = path[:i] |
|
} |
|
if i := strings.LastIndex(path, "#"); i > -1 { |
|
path = path[:i] |
|
} |
|
return path |
|
} |
|
return req.URL.Path |
|
} |
|
|
|
// cleanPath returns the canonical path for p, eliminating . and .. elements. |
|
// Borrowed from the net/http package. |
|
func cleanPath(p string) string { |
|
if p == "" { |
|
return "/" |
|
} |
|
if p[0] != '/' { |
|
p = "/" + p |
|
} |
|
np := path.Clean(p) |
|
// path.Clean removes trailing slash except for root; |
|
// put the trailing slash back if necessary. |
|
if p[len(p)-1] == '/' && np != "/" { |
|
np += "/" |
|
} |
|
|
|
return np |
|
} |
|
|
|
// uniqueVars returns an error if two slices contain duplicated strings. |
|
func uniqueVars(s1, s2 []string) error { |
|
for _, v1 := range s1 { |
|
for _, v2 := range s2 { |
|
if v1 == v2 { |
|
return fmt.Errorf("mux: duplicated route variable %q", v2) |
|
} |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
// checkPairs returns the count of strings passed in, and an error if |
|
// the count is not an even number. |
|
func checkPairs(pairs ...string) (int, error) { |
|
length := len(pairs) |
|
if length%2 != 0 { |
|
return length, fmt.Errorf( |
|
"mux: number of parameters must be multiple of 2, got %v", pairs) |
|
} |
|
return length, nil |
|
} |
|
|
|
// mapFromPairsToString converts variadic string parameters to a |
|
// string to string map. |
|
func mapFromPairsToString(pairs ...string) (map[string]string, error) { |
|
length, err := checkPairs(pairs...) |
|
if err != nil { |
|
return nil, err |
|
} |
|
m := make(map[string]string, length/2) |
|
for i := 0; i < length; i += 2 { |
|
m[pairs[i]] = pairs[i+1] |
|
} |
|
return m, nil |
|
} |
|
|
|
// mapFromPairsToRegex converts variadic string paramers to a |
|
// string to regex map. |
|
func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) { |
|
length, err := checkPairs(pairs...) |
|
if err != nil { |
|
return nil, err |
|
} |
|
m := make(map[string]*regexp.Regexp, length/2) |
|
for i := 0; i < length; i += 2 { |
|
regex, err := regexp.Compile(pairs[i+1]) |
|
if err != nil { |
|
return nil, err |
|
} |
|
m[pairs[i]] = regex |
|
} |
|
return m, nil |
|
} |
|
|
|
// matchInArray returns true if the given string value is in the array. |
|
func matchInArray(arr []string, value string) bool { |
|
for _, v := range arr { |
|
if v == value { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
// matchMapWithString returns true if the given key/value pairs exist in a given map. |
|
func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool { |
|
for k, v := range toCheck { |
|
// Check if key exists. |
|
if canonicalKey { |
|
k = http.CanonicalHeaderKey(k) |
|
} |
|
if values := toMatch[k]; values == nil { |
|
return false |
|
} else if v != "" { |
|
// If value was defined as an empty string we only check that the |
|
// key exists. Otherwise we also check for equality. |
|
valueExists := false |
|
for _, value := range values { |
|
if v == value { |
|
valueExists = true |
|
break |
|
} |
|
} |
|
if !valueExists { |
|
return false |
|
} |
|
} |
|
} |
|
return true |
|
} |
|
|
|
// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against |
|
// the given regex |
|
func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool { |
|
for k, v := range toCheck { |
|
// Check if key exists. |
|
if canonicalKey { |
|
k = http.CanonicalHeaderKey(k) |
|
} |
|
if values := toMatch[k]; values == nil { |
|
return false |
|
} else if v != nil { |
|
// If value was defined as an empty string we only check that the |
|
// key exists. Otherwise we also check for equality. |
|
valueExists := false |
|
for _, value := range values { |
|
if v.MatchString(value) { |
|
valueExists = true |
|
break |
|
} |
|
} |
|
if !valueExists { |
|
return false |
|
} |
|
} |
|
} |
|
return true |
|
}
|
|
|