...

Source file src/goji.io/pat/pat.go

Documentation: goji.io/pat

     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  

View as plain text