~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/bmizerany/pat/mux.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Package pat implements a simple URL pattern muxer
 
2
package pat
 
3
 
 
4
import (
 
5
        "net/http"
 
6
        "net/url"
 
7
        "strings"
 
8
)
 
9
 
 
10
// PatternServeMux is an HTTP request multiplexer. It matches the URL of each
 
11
// incoming request against a list of registered patterns with their associated
 
12
// methods and calls the handler for the pattern that most closely matches the
 
13
// URL.
 
14
//
 
15
// Pattern matching attempts each pattern in the order in which they were
 
16
// registered.
 
17
//
 
18
// Patterns may contain literals or captures. Capture names start with a colon
 
19
// and consist of letters A-Z, a-z, _, and 0-9. The rest of the pattern
 
20
// matches literally. The portion of the URL matching each name ends with an
 
21
// occurrence of the character in the pattern immediately following the name,
 
22
// or a /, whichever comes first. It is possible for a name to match the empty
 
23
// string.
 
24
//
 
25
// Example pattern with one capture:
 
26
//   /hello/:name
 
27
// Will match:
 
28
//   /hello/blake
 
29
//   /hello/keith
 
30
// Will not match:
 
31
//   /hello/blake/
 
32
//   /hello/blake/foo
 
33
//   /foo
 
34
//   /foo/bar
 
35
//
 
36
// Example 2:
 
37
//    /hello/:name/
 
38
// Will match:
 
39
//   /hello/blake/
 
40
//   /hello/keith/foo
 
41
//   /hello/blake
 
42
//   /hello/keith
 
43
// Will not match:
 
44
//   /foo
 
45
//   /foo/bar
 
46
//
 
47
// A pattern ending with a slash will add an implicit redirect for its non-slash
 
48
// version. For example: Get("/foo/", handler) also registers
 
49
// Get("/foo", handler) as a redirect. You may override it by registering
 
50
// Get("/foo", anotherhandler) before the slash version.
 
51
//
 
52
// Retrieve the capture from the r.URL.Query().Get(":name") in a handler (note
 
53
// the colon). If a capture name appears more than once, the additional values
 
54
// are appended to the previous values (see
 
55
// http://golang.org/pkg/net/url/#Values)
 
56
//
 
57
// A trivial example server is:
 
58
//
 
59
//      package main
 
60
//
 
61
//      import (
 
62
//              "io"
 
63
//              "net/http"
 
64
//              "github.com/bmizerany/pat"
 
65
//              "log"
 
66
//      )
 
67
//
 
68
//      // hello world, the web server
 
69
//      func HelloServer(w http.ResponseWriter, req *http.Request) {
 
70
//              io.WriteString(w, "hello, "+req.URL.Query().Get(":name")+"!\n")
 
71
//      }
 
72
//
 
73
//      func main() {
 
74
//              m := pat.New()
 
75
//              m.Get("/hello/:name", http.HandlerFunc(HelloServer))
 
76
//
 
77
//              // Register this pat with the default serve mux so that other packages
 
78
//              // may also be exported. (i.e. /debug/pprof/*)
 
79
//              http.Handle("/", m)
 
80
//              err := http.ListenAndServe(":12345", nil)
 
81
//              if err != nil {
 
82
//                      log.Fatal("ListenAndServe: ", err)
 
83
//              }
 
84
//      }
 
85
//
 
86
// When "Method Not Allowed":
 
87
//
 
88
// Pat knows what methods are allowed given a pattern and a URI. For
 
89
// convenience, PatternServeMux will add the Allow header for requests that
 
90
// match a pattern for a method other than the method requested and set the
 
91
// Status to "405 Method Not Allowed".
 
92
//
 
93
// If the NotFound handler is set, then it is used whenever the pattern doesn't
 
94
// match the request path for the current method (and the Allow header is not
 
95
// altered).
 
96
type PatternServeMux struct {
 
97
        // NotFound, if set, is used whenever the request doesn't match any
 
98
        // pattern for its method. NotFound should be set before serving any
 
99
        // requests.
 
100
        NotFound http.Handler
 
101
        handlers map[string][]*patHandler
 
102
}
 
103
 
 
104
// New returns a new PatternServeMux.
 
105
func New() *PatternServeMux {
 
106
        return &PatternServeMux{handlers: make(map[string][]*patHandler)}
 
107
}
 
108
 
 
109
// ServeHTTP matches r.URL.Path against its routing table using the rules
 
110
// described above.
 
111
func (p *PatternServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
112
        for _, ph := range p.handlers[r.Method] {
 
113
                if params, ok := ph.try(r.URL.Path); ok {
 
114
                        if len(params) > 0 && !ph.redirect {
 
115
                                r.URL.RawQuery = url.Values(params).Encode() + "&" + r.URL.RawQuery
 
116
                        }
 
117
                        ph.ServeHTTP(w, r)
 
118
                        return
 
119
                }
 
120
        }
 
121
 
 
122
        if p.NotFound != nil {
 
123
                p.NotFound.ServeHTTP(w, r)
 
124
                return
 
125
        }
 
126
 
 
127
        allowed := make([]string, 0, len(p.handlers))
 
128
        for meth, handlers := range p.handlers {
 
129
                if meth == r.Method {
 
130
                        continue
 
131
                }
 
132
 
 
133
                for _, ph := range handlers {
 
134
                        if _, ok := ph.try(r.URL.Path); ok {
 
135
                                allowed = append(allowed, meth)
 
136
                        }
 
137
                }
 
138
        }
 
139
 
 
140
        if len(allowed) == 0 {
 
141
                http.NotFound(w, r)
 
142
                return
 
143
        }
 
144
 
 
145
        w.Header().Add("Allow", strings.Join(allowed, ", "))
 
146
        http.Error(w, "Method Not Allowed", 405)
 
147
}
 
148
 
 
149
// Head will register a pattern with a handler for HEAD requests.
 
150
func (p *PatternServeMux) Head(pat string, h http.Handler) {
 
151
        p.Add("HEAD", pat, h)
 
152
}
 
153
 
 
154
// Get will register a pattern with a handler for GET requests.
 
155
// It also registers pat for HEAD requests. If this needs to be overridden, use
 
156
// Head before Get with pat.
 
157
func (p *PatternServeMux) Get(pat string, h http.Handler) {
 
158
        p.Add("HEAD", pat, h)
 
159
        p.Add("GET", pat, h)
 
160
}
 
161
 
 
162
// Post will register a pattern with a handler for POST requests.
 
163
func (p *PatternServeMux) Post(pat string, h http.Handler) {
 
164
        p.Add("POST", pat, h)
 
165
}
 
166
 
 
167
// Put will register a pattern with a handler for PUT requests.
 
168
func (p *PatternServeMux) Put(pat string, h http.Handler) {
 
169
        p.Add("PUT", pat, h)
 
170
}
 
171
 
 
172
// Del will register a pattern with a handler for DELETE requests.
 
173
func (p *PatternServeMux) Del(pat string, h http.Handler) {
 
174
        p.Add("DELETE", pat, h)
 
175
}
 
176
 
 
177
// Options will register a pattern with a handler for OPTIONS requests.
 
178
func (p *PatternServeMux) Options(pat string, h http.Handler) {
 
179
        p.Add("OPTIONS", pat, h)
 
180
}
 
181
 
 
182
// Patch will register a pattern with a handler for PATCH requests.
 
183
func (p *PatternServeMux) Patch(pat string, h http.Handler) {
 
184
        p.Add("PATCH", pat, h)
 
185
}
 
186
 
 
187
// Add will register a pattern with a handler for meth requests.
 
188
func (p *PatternServeMux) Add(meth, pat string, h http.Handler) {
 
189
        p.add(meth, pat, h, false)
 
190
}
 
191
 
 
192
func (p *PatternServeMux) add(meth, pat string, h http.Handler, redirect bool) {
 
193
        handlers := p.handlers[meth]
 
194
        for _, p1 := range handlers {
 
195
                if p1.pat == pat {
 
196
                        return // found existing pattern; do nothing
 
197
                }
 
198
        }
 
199
        handler := &patHandler{
 
200
                pat:      pat,
 
201
                Handler:  h,
 
202
                redirect: redirect,
 
203
        }
 
204
        p.handlers[meth] = append(handlers, handler)
 
205
 
 
206
        n := len(pat)
 
207
        if n > 0 && pat[n-1] == '/' {
 
208
                p.add(meth, pat[:n-1], http.HandlerFunc(addSlashRedirect), true)
 
209
        }
 
210
}
 
211
 
 
212
func addSlashRedirect(w http.ResponseWriter, r *http.Request) {
 
213
        u := *r.URL
 
214
        u.Path += "/"
 
215
        http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
 
216
}
 
217
 
 
218
// Tail returns the trailing string in path after the final slash for a pat ending with a slash.
 
219
//
 
220
// Examples:
 
221
//
 
222
//      Tail("/hello/:title/", "/hello/mr/mizerany") == "mizerany"
 
223
//      Tail("/:a/", "/x/y/z")                       == "y/z"
 
224
//
 
225
func Tail(pat, path string) string {
 
226
        var i, j int
 
227
        for i < len(path) {
 
228
                switch {
 
229
                case j >= len(pat):
 
230
                        if pat[len(pat)-1] == '/' {
 
231
                                return path[i:]
 
232
                        }
 
233
                        return ""
 
234
                case pat[j] == ':':
 
235
                        var nextc byte
 
236
                        _, nextc, j = match(pat, isAlnum, j+1)
 
237
                        _, _, i = match(path, matchPart(nextc), i)
 
238
                case path[i] == pat[j]:
 
239
                        i++
 
240
                        j++
 
241
                default:
 
242
                        return ""
 
243
                }
 
244
        }
 
245
        return ""
 
246
}
 
247
 
 
248
type patHandler struct {
 
249
        pat string
 
250
        http.Handler
 
251
        redirect bool
 
252
}
 
253
 
 
254
func (ph *patHandler) try(path string) (url.Values, bool) {
 
255
        p := make(url.Values)
 
256
        var i, j int
 
257
        for i < len(path) {
 
258
                switch {
 
259
                case j >= len(ph.pat):
 
260
                        if ph.pat != "/" && len(ph.pat) > 0 && ph.pat[len(ph.pat)-1] == '/' {
 
261
                                return p, true
 
262
                        }
 
263
                        return nil, false
 
264
                case ph.pat[j] == ':':
 
265
                        var name, val string
 
266
                        var nextc byte
 
267
                        name, nextc, j = match(ph.pat, isAlnum, j+1)
 
268
                        val, _, i = match(path, matchPart(nextc), i)
 
269
                        p.Add(":"+name, val)
 
270
                case path[i] == ph.pat[j]:
 
271
                        i++
 
272
                        j++
 
273
                default:
 
274
                        return nil, false
 
275
                }
 
276
        }
 
277
        if j != len(ph.pat) {
 
278
                return nil, false
 
279
        }
 
280
        return p, true
 
281
}
 
282
 
 
283
func matchPart(b byte) func(byte) bool {
 
284
        return func(c byte) bool {
 
285
                return c != b && c != '/'
 
286
        }
 
287
}
 
288
 
 
289
func match(s string, f func(byte) bool, i int) (matched string, next byte, j int) {
 
290
        j = i
 
291
        for j < len(s) && f(s[j]) {
 
292
                j++
 
293
        }
 
294
        if j < len(s) {
 
295
                next = s[j]
 
296
        }
 
297
        return s[i:j], next, j
 
298
}
 
299
 
 
300
func isAlpha(ch byte) bool {
 
301
        return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
 
302
}
 
303
 
 
304
func isDigit(ch byte) bool {
 
305
        return '0' <= ch && ch <= '9'
 
306
}
 
307
 
 
308
func isAlnum(ch byte) bool {
 
309
        return isAlpha(ch) || isDigit(ch)
 
310
}