1 /* 2 Package pat is a URL-matching domain-specific language for Goji. 3 4 5 Quick Reference 6 7 The following table gives an overview of the language this package accepts. See 8 the subsequent sections for a more detailed explanation of what each pattern 9 does. 10 11 Pattern Matches Does Not Match 12 13 / / /hello 14 15 /hello /hello /hi 16 /hello/ 17 18 /user/:name /user/carl /user/carl/photos 19 /user/alice /user/carl/ 20 /user/ 21 22 /:file.:ext /data.json /.json 23 /info.txt /data. 24 /data.tar.gz /data.json/download 25 26 /user/* /user/ /user 27 /user/carl 28 /user/carl/photos 29 30 31 Static Paths 32 33 Most URL paths may be specified directly: the pattern "/hello" matches URLs with 34 precisely that path ("/hello/", for instance, is treated as distinct). 35 36 Note that this package operates on raw (i.e., escaped) paths (see the 37 documentation for net/url.URL.EscapedPath). In order to match a character that 38 can appear escaped in a URL path, use its percent-encoded form. 39 40 41 Named Matches 42 43 Named matches allow URL paths to contain any value in a particular path segment. 44 Such matches are denoted by a leading ":", for example ":name" in the rule 45 "/user/:name", and permit any non-empty value in that position. For instance, in 46 the previous "/user/:name" example, the path "/user/carl" is matched, while 47 "/user/" or "/user/carl/" (note the trailing slash) are not matched. Pat rules 48 can contain any number of named matches. 49 50 Named matches set URL variables by comparing pattern names to the segments they 51 matched. In our "/user/:name" example, a request for "/user/carl" would bind the 52 "name" variable to the value "carl". Use the Param function to extract these 53 variables from the request context. Variable names in a single pattern must be 54 unique. 55 56 Matches are ordinarily delimited by slashes ("/"), but several other characters 57 are accepted as delimiters (with slightly different semantics): the period 58 ("."), semicolon (";"), and comma (",") characters. For instance, given the 59 pattern "/:file.:ext", the request "/data.json" would match, binding "file" to 60 "data" and "ext" to "json". Note that these special characters are treated 61 slightly differently than slashes: the above pattern also matches the path 62 "/data.tar.gz", with "ext" getting set to "tar.gz"; and the pattern "/:file" 63 matches names with dots in them (like "data.json"). 64 65 66 Prefix Matches 67 68 Pat can also match prefixes of routes using wildcards. Prefix wildcard routes 69 end with "/*", and match just the path segments preceding the asterisk. For 70 instance, the pattern "/user/*" will match "/user/" and "/user/carl/photos" but 71 not "/user" (note the lack of a trailing slash). 72 73 The unmatched suffix, including the leading slash ("/"), are placed into the 74 request context, which allows subsequent routing (e.g., a subrouter) to continue 75 from where this pattern left off. For instance, in the "/user/*" pattern from 76 above, a request for "/user/carl/photos" will consume the "/user" prefix, 77 leaving the path "/carl/photos" for subsequent patterns to handle. A subrouter 78 pattern for "/:name/photos" would match this remaining path segment, for 79 instance. 80 */ 81 package pat 82 83 import ( 84 "net/http" 85 "regexp" 86 "sort" 87 "strings" 88 89 "goji.io/pattern" 90 ) 91 92 type patNames []struct { 93 name pattern.Variable 94 idx int 95 } 96 97 func (p patNames) Len() int { 98 return len(p) 99 } 100 func (p patNames) Less(i, j int) bool { 101 return p[i].name < p[j].name 102 } 103 func (p patNames) Swap(i, j int) { 104 p[i], p[j] = p[j], p[i] 105 } 106 107 /* 108 Pattern implements goji.Pattern using a path-matching domain specific language. 109 See the package documentation for more information about the semantics of this 110 object. 111 */ 112 type Pattern struct { 113 raw string 114 methods map[string]struct{} 115 // These are parallel arrays of each pattern string (sans ":"), the 116 // breaks each expect afterwords (used to support e.g., "." dividers), 117 // and the string literals in between every pattern. There is always one 118 // more literal than pattern, and they are interleaved like this: 119 // <literal> <pattern> <literal> <pattern> <literal> etc... 120 pats patNames 121 breaks []byte 122 literals []string 123 wildcard bool 124 } 125 126 // "Break characters" are characters that can end patterns. They are not allowed 127 // to appear in pattern names. "/" was chosen because it is the standard path 128 // separator, and "." was chosen because it often delimits file extensions. ";" 129 // and "," were chosen because Section 3.3 of RFC 3986 suggests their use. 130 const bc = "/.;," 131 132 var patternRe = regexp.MustCompile(`[` + bc + `]:([^` + bc + `]+)`) 133 134 /* 135 New returns a new Pattern from the given Pat route. See the package 136 documentation for more information about what syntax is accepted by this 137 function. 138 */ 139 func New(pat string) *Pattern { 140 p := &Pattern{raw: pat} 141 142 if strings.HasSuffix(pat, "/*") { 143 pat = pat[:len(pat)-1] 144 p.wildcard = true 145 } 146 147 matches := patternRe.FindAllStringSubmatchIndex(pat, -1) 148 numMatches := len(matches) 149 p.pats = make(patNames, numMatches) 150 p.breaks = make([]byte, numMatches) 151 p.literals = make([]string, numMatches+1) 152 153 n := 0 154 for i, match := range matches { 155 a, b := match[2], match[3] 156 p.literals[i] = pat[n : a-1] // Need to leave off the colon 157 p.pats[i].name = pattern.Variable(pat[a:b]) 158 p.pats[i].idx = i 159 if b == len(pat) { 160 p.breaks[i] = '/' 161 } else { 162 p.breaks[i] = pat[b] 163 } 164 n = b 165 } 166 p.literals[numMatches] = pat[n:] 167 168 sort.Sort(p.pats) 169 170 return p 171 } 172 173 /* 174 Match runs the Pat pattern on the given request, returning a non-nil output 175 request if the input request matches the pattern. 176 177 This function satisfies goji.Pattern. 178 */ 179 func (p *Pattern) Match(r *http.Request) *http.Request { 180 if p.methods != nil { 181 if _, ok := p.methods[r.Method]; !ok { 182 return nil 183 } 184 } 185 186 // Check Path 187 ctx := r.Context() 188 path := pattern.Path(ctx) 189 var scratch []string 190 if p.wildcard { 191 scratch = make([]string, len(p.pats)+1) 192 } else if len(p.pats) > 0 { 193 scratch = make([]string, len(p.pats)) 194 } 195 196 for i := range p.pats { 197 sli := p.literals[i] 198 if !strings.HasPrefix(path, sli) { 199 return nil 200 } 201 path = path[len(sli):] 202 203 m := 0 204 bc := p.breaks[i] 205 for ; m < len(path); m++ { 206 if path[m] == bc || path[m] == '/' { 207 break 208 } 209 } 210 if m == 0 { 211 // Empty strings are not matches, otherwise routes like 212 // "/:foo" would match the path "/" 213 return nil 214 } 215 scratch[i] = path[:m] 216 path = path[m:] 217 } 218 219 // There's exactly one more literal than pat. 220 tail := p.literals[len(p.pats)] 221 if p.wildcard { 222 if !strings.HasPrefix(path, tail) { 223 return nil 224 } 225 scratch[len(p.pats)] = path[len(tail)-1:] 226 } else if path != tail { 227 return nil 228 } 229 230 for i := range p.pats { 231 var err error 232 scratch[i], err = unescape(scratch[i]) 233 if err != nil { 234 // If we encounter an encoding error here, there's 235 // really not much we can do about it with our current 236 // API, and I'm not really interested in supporting 237 // clients that misencode URLs anyways. 238 return nil 239 } 240 } 241 242 return r.WithContext(&match{ctx, p, scratch}) 243 } 244 245 /* 246 PathPrefix returns a string prefix that the Paths of all requests that this 247 Pattern accepts must contain. 248 249 This function satisfies goji's PathPrefix Pattern optimization. 250 */ 251 func (p *Pattern) PathPrefix() string { 252 return p.literals[0] 253 } 254 255 /* 256 HTTPMethods returns a set of HTTP methods that all requests that this 257 Pattern matches must be in, or nil if it's not possible to determine 258 which HTTP methods might be matched. 259 260 This function satisfies goji's HTTPMethods Pattern optimization. 261 */ 262 func (p *Pattern) HTTPMethods() map[string]struct{} { 263 return p.methods 264 } 265 266 /* 267 String returns the pattern string that was used to create this Pattern. 268 */ 269 func (p *Pattern) String() string { 270 return p.raw 271 } 272 273 /* 274 Param returns the bound parameter with the given name. For instance, given the 275 route: 276 277 /user/:name 278 279 and the URL Path: 280 281 /user/carl 282 283 a call to Param(r, "name") would return the string "carl". It is the caller's 284 responsibility to ensure that the variable has been bound. Attempts to access 285 variables that have not been set (or which have been invalidly set) are 286 considered programmer errors and will trigger a panic. 287 */ 288 func Param(r *http.Request, name string) string { 289 return r.Context().Value(pattern.Variable(name)).(string) 290 } 291