1 package router 2 3 import ( 4 "fmt" 5 "net/http" 6 "regexp" 7 "strings" 8 ) 9 10 // Router is a general purpose router of a method or verb (like HTTP "GET") and 11 // path (like "/foo/bar") to a tree of Route objects. 12 type Router struct { 13 // DefaultMethods is the comma-separated list of methods to use if a route 14 // has none specified e.g. HTTP "GET, POST". 15 // 16 // router.New() sets this to only "GET" by default. 17 DefaultMethods string 18 19 root *Route 20 routesByName map[string]*Route 21 parents map[*Route]*Route 22 } 23 24 // Route describes a mapping between methods (like HTTP "GET") and path 25 // component (like "foo" in "/foo/bar") to a user-supplied handler value. 26 // 27 // Each route may contain child routes which map to subsequent path components. 28 // For example a route may match "foo" in "/foo/bar", and a child route could 29 // match "bar". 30 // 31 // A path such as "/user/123/profile" resolves into the components ["", "user", 32 // "123", "profile"]. A path such as "/user/123/profile/" resolves into the 33 // components ["", "user", "123", "profile", ""]. As such, the root Route 34 // and folder-index Routes will normally be configured to match the empty 35 // string "". 36 // 37 // A route can be Final, in which case it matches all remaining path components 38 // at once. 39 type Route struct { 40 // Name (optional) is a way of uniquely identifying a specific route. The 41 // name must be unique to the whole router tree. 42 Name string 43 44 // Key (optional) is a way of uniquely identifying a path parameter (e.g. 45 // the path "/user/123/profile" might have a parameter for the user ID). 46 // 47 // This key is used to query the value of the path parameter by name in a 48 // Match result. 49 // 50 // All keys must be unique to a route and its parents, but does not have to 51 // be unique across the entire router tree. 52 Key string 53 54 // Pattern (optional) is a way of matching against a path component. This 55 // may be a literal string e.g. "user", or or a compiled regular expression 56 // (https://golang.org/pkg/regexp/) such as regexp.MustCompile(`$\d{1,5}^`) 57 // (this matches one to five ASCII digits). 58 // 59 // Note that the regexp implementation provided by regexp is guaranteed to 60 // run in time linear in the size of the input so it is generally not 61 // essential to limit the length of the path component. Path components are 62 // limited to 255 characters regardless. 63 // 64 // If the pattern is nil or an empty string, the route matches an empty 65 // path component. This is useful for the root node, or index pages for 66 // folders. 67 // 68 // As a special case, the string "*" matches everything. This is useful for 69 // a per-directory catch-all. 70 // 71 // Patterns are matched in the following order first, then in order of 72 // sequence defined: 73 // 74 // 1 Empty path (Pattern is nil or empty string; Final has no effect) 75 // 2 Final Exact matches (Pattern is string and Final is true) 76 // 3 Exact matches (Pattern is string and Final is false) 77 // 4 Final Regex matches (Pattern is Regexp and Final is true) 78 // 5 Regex matches (Pattern is Regexp and Final is false) 79 // 6 Wildcard (string "*" and Final is false) 80 // 7 Final Wildcard (string "*" and Final is true) 81 Pattern interface{} 82 83 // Methods (optional) is a comma-separated string of methods or verbs 84 // accepted by the route e.g. HTTP "GET, POST". If left empty, implies 85 // only the Router's DefaultMethods. 86 // 87 // Note that "OPTIONS" is not handled automatically - this is up to you. 88 Methods string 89 90 // Handler (optional) is the caller-supplied information attached to a 91 // route e.g. a HTTP Handler or any custom value. 92 // 93 // It is up to the caller to do something with the resulting Handler - 94 // nothing is called automatically for you. 95 Handler interface{} 96 97 // Children (optional) are any child Routes for subsequent path components. 98 // For example, the path "/foo/bar" decomposes into the path components 99 // ["", "foo", "bar"], and can be matched by a route with pattern "", 100 // child route with pattern "foo", and grandchild route "bar". 101 Children []Route 102 103 // Final (optional; default false), if true, indicates that the pattern 104 // should be matched against the entire remaining path, not just the 105 // current path component. 106 // 107 // For example, a Route might have a pattern to accept arbitrary 108 // files in subdirectories, like "static/assets/foo.png". It would be 109 // Final with a Pattern value of something like 110 // regexp.MustCompile(`(\w+/)*\w+\.\w`) 111 Final bool 112 } 113 114 // Match is the result of a routing query - may be nil if no match found 115 type Match struct { 116 // Route is the route picked - never nil 117 Route *Route 118 119 // Router is the router that was matched on 120 Router *Router 121 122 // mapping of route keys => path parameters; 123 // this may be nil: use Value() or Values() 124 params map[string]string 125 126 // mapping of route keys => submatches of paath parameters, like 127 // `(*Regexp) FindStringSubmatch` 128 // from https://golang.org/pkg/regexp/#Regexp.FindStringSubmatch 129 // this may be nil: use Submatch() or Submatches() 130 subparams map[string][]string 131 } 132 133 // Values returns a non-nil mapping of the route keys => path parameters 134 func (match Match) Values() map[string]string { 135 if match.params == nil { match.params = make(map[string]string) } 136 return match.params 137 } 138 139 // Value returns a parsed path parameter identified by a Route Key. 140 // 141 // For example, you might create a tree of routes such that the path 142 // "/user/123/profile" captures the path component "123" and associates it 143 // with a "user-id" route.Key. 144 func (match Match) Value(key string) string { 145 if match.params == nil { return "" } 146 return match.params[key] 147 } 148 149 // Submatches returns a non-nil mapping of the route keys => submatches of path 150 // parameters. 151 func (match Match) Submatches() map[string][]string { 152 if match.subparams == nil { match.subparams = make(map[string][]string) } 153 return match.subparams 154 } 155 156 // Submatch returns a submatch of a parsed path parameter identified by a 157 // Route Key and 158 // 159 // For example, you might create a tree of routes such that the path 160 // "/user/123.json" captures the path component "123.json" and associates it 161 // with a "user-id" route.Key. For a pattern like "(\d+)(\.(\w+))?", the submatches for that key would be 162 // ["123.json", "123", ".json", "json"], so index 1 gives "123". 163 func (match Match) Submatch(key string, index int) string { 164 if match.subparams == nil { return "" } 165 args := match.subparams[key] 166 if args == nil { return "" } 167 if index >= len(args) { return "" } 168 return args[index] 169 } 170 171 // Creates a new router with DefaultMethods set to GET 172 func New(root Route) (*Router, error) { 173 router := &Router{} 174 175 router.DefaultMethods = "GET" 176 177 // we take a copy of the routes rather than just hold a pointer because 178 // we modify the route Children by sorting them, and we return 179 180 router.root = &root 181 sortRoutes(router.root.Children) // note, must be done first! 182 183 routesByName, err := scanRouteNames(router.root) 184 if err != nil { return nil, err } 185 router.routesByName = routesByName 186 187 // router.methods = scanMethods(router.root) 188 router.parents = scanParents(router.root) 189 190 return router, nil 191 } 192 193 // Matches a HTTP request to a route. See Match 194 func (router *Router) MatchHttpRequest(r *http.Request) *Match { 195 return router.Match(r.Method, r.URL.Path) 196 } 197 198 // Match attempts to match a method (e.g. a HTTP method like "GET") and path (e.g. "/foo/bar") to a route in 199 // a router's tree of routes. In the event that there is no match, returns nil. 200 func (router *Router) Match(method string, path string) *Match { 201 var params map[string]string 202 var subparams map[string][]string 203 204 route := router.match(method, path, router.root, ¶ms, &subparams) 205 if route == nil { return nil } 206 207 return &Match{route, router, params, subparams} 208 } 209 210 // Parent returns the parent Route of a Route in the router's tree of Routes. May be nil (i.e. at the root). 211 func (router *Router) Parent(route *Route) *Route { 212 return router.parents[route] 213 } 214 215 // Format creates a URL for a route - the opposite of routing. Any 216 // regexp.Regexp patterns are replaced using each arg in sequence. 217 // 218 // WARNING: The return value from this function may be controlled by the 219 // User Agent. Escape it as necessary. 220 func (router *Router) Format(route *Route, args ... string) (string, error) { 221 components := make([]string, 0) 222 current := route 223 index := len(args) - 1 224 225 for current != nil { 226 227 switch v := current.Pattern.(type) { 228 case nil: 229 components = append(components, "") 230 case string: 231 components = append(components, v) 232 case *regexp.Regexp: 233 if index < 0 { 234 return "", fmt.Errorf("not enough arguments to format path") 235 } 236 components = append(components, args[index]) 237 index-- 238 default: 239 panic(fmt.Errorf("invalid Route Pattern type %T", v)) 240 } 241 242 current = router.Parent(current) 243 } 244 245 reverseStringList(components...) 246 247 return strings.Join(components, "/"), nil 248 } 249 250 // MustFormat is like Format, but panics on error. 251 func (router *Router) MustFormat(route *Route, args ... string) string { 252 result, err := router.Format(route, args...) 253 if err != nil { 254 panic(fmt.Errorf("unable to format a route: %v", err)) 255 } 256 return result 257 } 258 259 // Format creates a URL for a route - the opposite of routing. Any 260 // regexp.Regexp patterns are replaced using each the route Key to lookup 261 // a value in the the mapping argument. Pass in Match Values() to have 262 // this automatically filled based on the parsed request. 263 // 264 // WARNING: The return value from this function may be controlled by the 265 // User Agent. Escape it as necessary. 266 func (router *Router) FormatMap(route *Route, mapping map[string]string) (string, error) { 267 components := make([]string, 0) 268 current := route 269 270 for current != nil { 271 272 switch v := current.Pattern.(type) { 273 case nil: 274 components = append(components, "") 275 case string: 276 components = append(components, v) 277 case *regexp.Regexp: 278 components = append(components, mapping[current.Key]) 279 default: 280 panic(fmt.Errorf("invalid Route Pattern type %T", v)) 281 } 282 283 current = router.Parent(current) 284 } 285 286 reverseStringList(components...) 287 288 return strings.Join(components, "/"), nil 289 } 290 291 // MustFormatMap is like FormatMap, but panics on error. 292 func (router *Router) MustFormatMap(route *Route, mapping map[string]string) string { 293 result, err := router.FormatMap(route, mapping) 294 if err != nil { 295 panic(fmt.Errorf("unable to format a route: %v", err)) 296 } 297 return result 298 } 299 300 // Named returns a named Route where `name == route.Name`. Nil if not found. 301 func (router *Router) Named(name string) *Route { 302 return router.routesByName[name] 303 } 304 305 // MustNamed is like Named, but panics if not found. 306 func (router *Router) MustNamed(name string) *Route { 307 route := router.routesByName[name] 308 if route == nil { 309 panic(fmt.Errorf("named route %q not found", name)) 310 } 311 return route 312 } 313