~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/julienschmidt/httprouter/router.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
// Copyright 2013 Julien Schmidt. All rights reserved.
 
2
// Use of this source code is governed by a BSD-style license that can be found
 
3
// in the LICENSE file.
 
4
 
 
5
// Package httprouter is a trie based high performance HTTP request router.
 
6
//
 
7
// A trivial example is:
 
8
//
 
9
//  package main
 
10
//
 
11
//  import (
 
12
//      "fmt"
 
13
//      "github.com/julienschmidt/httprouter"
 
14
//      "net/http"
 
15
//      "log"
 
16
//  )
 
17
//
 
18
//  func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
 
19
//      fmt.Fprint(w, "Welcome!\n")
 
20
//  }
 
21
//
 
22
//  func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
 
23
//      fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
 
24
//  }
 
25
//
 
26
//  func main() {
 
27
//      router := httprouter.New()
 
28
//      router.GET("/", Index)
 
29
//      router.GET("/hello/:name", Hello)
 
30
//
 
31
//      log.Fatal(http.ListenAndServe(":8080", router))
 
32
//  }
 
33
//
 
34
// The router matches incoming requests by the request method and the path.
 
35
// If a handle is registered for this path and method, the router delegates the
 
36
// request to that function.
 
37
// For the methods GET, POST, PUT, PATCH and DELETE shortcut functions exist to
 
38
// register handles, for all other methods router.Handle can be used.
 
39
//
 
40
// The registered path, against which the router matches incoming requests, can
 
41
// contain two types of parameters:
 
42
//  Syntax    Type
 
43
//  :name     named parameter
 
44
//  *name     catch-all parameter
 
45
//
 
46
// Named parameters are dynamic path segments. They match anything until the
 
47
// next '/' or the path end:
 
48
//  Path: /blog/:category/:post
 
49
//
 
50
//  Requests:
 
51
//   /blog/go/request-routers            match: category="go", post="request-routers"
 
52
//   /blog/go/request-routers/           no match, but the router would redirect
 
53
//   /blog/go/                           no match
 
54
//   /blog/go/request-routers/comments   no match
 
55
//
 
56
// Catch-all parameters match anything until the path end, including the
 
57
// directory index (the '/' before the catch-all). Since they match anything
 
58
// until the end, catch-all parameters must always be the final path element.
 
59
//  Path: /files/*filepath
 
60
//
 
61
//  Requests:
 
62
//   /files/                             match: filepath="/"
 
63
//   /files/LICENSE                      match: filepath="/LICENSE"
 
64
//   /files/templates/article.html       match: filepath="/templates/article.html"
 
65
//   /files                              no match, but the router would redirect
 
66
//
 
67
// The value of parameters is saved as a slice of the Param struct, consisting
 
68
// each of a key and a value. The slice is passed to the Handle func as a third
 
69
// parameter.
 
70
// There are two ways to retrieve the value of a parameter:
 
71
//  // by the name of the parameter
 
72
//  user := ps.ByName("user") // defined by :user or *user
 
73
//
 
74
//  // by the index of the parameter. This way you can also get the name (key)
 
75
//  thirdKey   := ps[2].Key   // the name of the 3rd parameter
 
76
//  thirdValue := ps[2].Value // the value of the 3rd parameter
 
77
package httprouter
 
78
 
 
79
import (
 
80
        "net/http"
 
81
)
 
82
 
 
83
// Handle is a function that can be registered to a route to handle HTTP
 
84
// requests. Like http.HandlerFunc, but has a third parameter for the values of
 
85
// wildcards (variables).
 
86
type Handle func(http.ResponseWriter, *http.Request, Params)
 
87
 
 
88
// Param is a single URL parameter, consisting of a key and a value.
 
89
type Param struct {
 
90
        Key   string
 
91
        Value string
 
92
}
 
93
 
 
94
// Params is a Param-slice, as returned by the router.
 
95
// The slice is ordered, the first URL parameter is also the first slice value.
 
96
// It is therefore safe to read values by the index.
 
97
type Params []Param
 
98
 
 
99
// ByName returns the value of the first Param which key matches the given name.
 
100
// If no matching Param is found, an empty string is returned.
 
101
func (ps Params) ByName(name string) string {
 
102
        for i := range ps {
 
103
                if ps[i].Key == name {
 
104
                        return ps[i].Value
 
105
                }
 
106
        }
 
107
        return ""
 
108
}
 
109
 
 
110
// Router is a http.Handler which can be used to dispatch requests to different
 
111
// handler functions via configurable routes
 
112
type Router struct {
 
113
        trees map[string]*node
 
114
 
 
115
        // Enables automatic redirection if the current route can't be matched but a
 
116
        // handler for the path with (without) the trailing slash exists.
 
117
        // For example if /foo/ is requested but a route only exists for /foo, the
 
118
        // client is redirected to /foo with http status code 301 for GET requests
 
119
        // and 307 for all other request methods.
 
120
        RedirectTrailingSlash bool
 
121
 
 
122
        // If enabled, the router tries to fix the current request path, if no
 
123
        // handle is registered for it.
 
124
        // First superfluous path elements like ../ or // are removed.
 
125
        // Afterwards the router does a case-insensitive lookup of the cleaned path.
 
126
        // If a handle can be found for this route, the router makes a redirection
 
127
        // to the corrected path with status code 301 for GET requests and 307 for
 
128
        // all other request methods.
 
129
        // For example /FOO and /..//Foo could be redirected to /foo.
 
130
        // RedirectTrailingSlash is independent of this option.
 
131
        RedirectFixedPath bool
 
132
 
 
133
        // If enabled, the router checks if another method is allowed for the
 
134
        // current route, if the current request can not be routed.
 
135
        // If this is the case, the request is answered with 'Method Not Allowed'
 
136
        // and HTTP status code 405.
 
137
        // If no other Method is allowed, the request is delegated to the NotFound
 
138
        // handler.
 
139
        HandleMethodNotAllowed bool
 
140
 
 
141
        // Configurable http.Handler which is called when no matching route is
 
142
        // found. If it is not set, http.NotFound is used.
 
143
        NotFound http.Handler
 
144
 
 
145
        // Configurable http.Handler which is called when a request
 
146
        // cannot be routed and HandleMethodNotAllowed is true.
 
147
        // If it is not set, http.Error with http.StatusMethodNotAllowed is used.
 
148
        MethodNotAllowed http.Handler
 
149
 
 
150
        // Function to handle panics recovered from http handlers.
 
151
        // It should be used to generate a error page and return the http error code
 
152
        // 500 (Internal Server Error).
 
153
        // The handler can be used to keep your server from crashing because of
 
154
        // unrecovered panics.
 
155
        PanicHandler func(http.ResponseWriter, *http.Request, interface{})
 
156
}
 
157
 
 
158
// Make sure the Router conforms with the http.Handler interface
 
159
var _ http.Handler = New()
 
160
 
 
161
// New returns a new initialized Router.
 
162
// Path auto-correction, including trailing slashes, is enabled by default.
 
163
func New() *Router {
 
164
        return &Router{
 
165
                RedirectTrailingSlash:  true,
 
166
                RedirectFixedPath:      true,
 
167
                HandleMethodNotAllowed: true,
 
168
        }
 
169
}
 
170
 
 
171
// GET is a shortcut for router.Handle("GET", path, handle)
 
172
func (r *Router) GET(path string, handle Handle) {
 
173
        r.Handle("GET", path, handle)
 
174
}
 
175
 
 
176
// HEAD is a shortcut for router.Handle("HEAD", path, handle)
 
177
func (r *Router) HEAD(path string, handle Handle) {
 
178
        r.Handle("HEAD", path, handle)
 
179
}
 
180
 
 
181
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
 
182
func (r *Router) OPTIONS(path string, handle Handle) {
 
183
        r.Handle("OPTIONS", path, handle)
 
184
}
 
185
 
 
186
// POST is a shortcut for router.Handle("POST", path, handle)
 
187
func (r *Router) POST(path string, handle Handle) {
 
188
        r.Handle("POST", path, handle)
 
189
}
 
190
 
 
191
// PUT is a shortcut for router.Handle("PUT", path, handle)
 
192
func (r *Router) PUT(path string, handle Handle) {
 
193
        r.Handle("PUT", path, handle)
 
194
}
 
195
 
 
196
// PATCH is a shortcut for router.Handle("PATCH", path, handle)
 
197
func (r *Router) PATCH(path string, handle Handle) {
 
198
        r.Handle("PATCH", path, handle)
 
199
}
 
200
 
 
201
// DELETE is a shortcut for router.Handle("DELETE", path, handle)
 
202
func (r *Router) DELETE(path string, handle Handle) {
 
203
        r.Handle("DELETE", path, handle)
 
204
}
 
205
 
 
206
// Handle registers a new request handle with the given path and method.
 
207
//
 
208
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
 
209
// functions can be used.
 
210
//
 
211
// This function is intended for bulk loading and to allow the usage of less
 
212
// frequently used, non-standardized or custom methods (e.g. for internal
 
213
// communication with a proxy).
 
214
func (r *Router) Handle(method, path string, handle Handle) {
 
215
        if path[0] != '/' {
 
216
                panic("path must begin with '/' in path '" + path + "'")
 
217
        }
 
218
 
 
219
        if r.trees == nil {
 
220
                r.trees = make(map[string]*node)
 
221
        }
 
222
 
 
223
        root := r.trees[method]
 
224
        if root == nil {
 
225
                root = new(node)
 
226
                r.trees[method] = root
 
227
        }
 
228
 
 
229
        root.addRoute(path, handle)
 
230
}
 
231
 
 
232
// Handler is an adapter which allows the usage of an http.Handler as a
 
233
// request handle.
 
234
func (r *Router) Handler(method, path string, handler http.Handler) {
 
235
        r.Handle(method, path,
 
236
                func(w http.ResponseWriter, req *http.Request, _ Params) {
 
237
                        handler.ServeHTTP(w, req)
 
238
                },
 
239
        )
 
240
}
 
241
 
 
242
// HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a
 
243
// request handle.
 
244
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
 
245
        r.Handler(method, path, handler)
 
246
}
 
247
 
 
248
// ServeFiles serves files from the given file system root.
 
249
// The path must end with "/*filepath", files are then served from the local
 
250
// path /defined/root/dir/*filepath.
 
251
// For example if root is "/etc" and *filepath is "passwd", the local file
 
252
// "/etc/passwd" would be served.
 
253
// Internally a http.FileServer is used, therefore http.NotFound is used instead
 
254
// of the Router's NotFound handler.
 
255
// To use the operating system's file system implementation,
 
256
// use http.Dir:
 
257
//     router.ServeFiles("/src/*filepath", http.Dir("/var/www"))
 
258
func (r *Router) ServeFiles(path string, root http.FileSystem) {
 
259
        if len(path) < 10 || path[len(path)-10:] != "/*filepath" {
 
260
                panic("path must end with /*filepath in path '" + path + "'")
 
261
        }
 
262
 
 
263
        fileServer := http.FileServer(root)
 
264
 
 
265
        r.GET(path, func(w http.ResponseWriter, req *http.Request, ps Params) {
 
266
                req.URL.Path = ps.ByName("filepath")
 
267
                fileServer.ServeHTTP(w, req)
 
268
        })
 
269
}
 
270
 
 
271
func (r *Router) recv(w http.ResponseWriter, req *http.Request) {
 
272
        if rcv := recover(); rcv != nil {
 
273
                r.PanicHandler(w, req, rcv)
 
274
        }
 
275
}
 
276
 
 
277
// Lookup allows the manual lookup of a method + path combo.
 
278
// This is e.g. useful to build a framework around this router.
 
279
// If the path was found, it returns the handle function and the path parameter
 
280
// values. Otherwise the third return value indicates whether a redirection to
 
281
// the same path with an extra / without the trailing slash should be performed.
 
282
func (r *Router) Lookup(method, path string) (Handle, Params, bool) {
 
283
        if root := r.trees[method]; root != nil {
 
284
                return root.getValue(path)
 
285
        }
 
286
        return nil, nil, false
 
287
}
 
288
 
 
289
// ServeHTTP makes the router implement the http.Handler interface.
 
290
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 
291
        if r.PanicHandler != nil {
 
292
                defer r.recv(w, req)
 
293
        }
 
294
 
 
295
        if root := r.trees[req.Method]; root != nil {
 
296
                path := req.URL.Path
 
297
 
 
298
                if handle, ps, tsr := root.getValue(path); handle != nil {
 
299
                        handle(w, req, ps)
 
300
                        return
 
301
                } else if req.Method != "CONNECT" && path != "/" {
 
302
                        code := 301 // Permanent redirect, request with GET method
 
303
                        if req.Method != "GET" {
 
304
                                // Temporary redirect, request with same method
 
305
                                // As of Go 1.3, Go does not support status code 308.
 
306
                                code = 307
 
307
                        }
 
308
 
 
309
                        if tsr && r.RedirectTrailingSlash {
 
310
                                if len(path) > 1 && path[len(path)-1] == '/' {
 
311
                                        req.URL.Path = path[:len(path)-1]
 
312
                                } else {
 
313
                                        req.URL.Path = path + "/"
 
314
                                }
 
315
                                http.Redirect(w, req, req.URL.String(), code)
 
316
                                return
 
317
                        }
 
318
 
 
319
                        // Try to fix the request path
 
320
                        if r.RedirectFixedPath {
 
321
                                fixedPath, found := root.findCaseInsensitivePath(
 
322
                                        CleanPath(path),
 
323
                                        r.RedirectTrailingSlash,
 
324
                                )
 
325
                                if found {
 
326
                                        req.URL.Path = string(fixedPath)
 
327
                                        http.Redirect(w, req, req.URL.String(), code)
 
328
                                        return
 
329
                                }
 
330
                        }
 
331
                }
 
332
        }
 
333
 
 
334
        // Handle 405
 
335
        if r.HandleMethodNotAllowed {
 
336
                for method := range r.trees {
 
337
                        // Skip the requested method - we already tried this one
 
338
                        if method == req.Method {
 
339
                                continue
 
340
                        }
 
341
 
 
342
                        handle, _, _ := r.trees[method].getValue(req.URL.Path)
 
343
                        if handle != nil {
 
344
                                if r.MethodNotAllowed != nil {
 
345
                                        r.MethodNotAllowed.ServeHTTP(w, req)
 
346
                                } else {
 
347
                                        http.Error(w,
 
348
                                                http.StatusText(http.StatusMethodNotAllowed),
 
349
                                                http.StatusMethodNotAllowed,
 
350
                                        )
 
351
                                }
 
352
                                return
 
353
                        }
 
354
                }
 
355
        }
 
356
 
 
357
        // Handle 404
 
358
        if r.NotFound != nil {
 
359
                r.NotFound.ServeHTTP(w, req)
 
360
        } else {
 
361
                http.NotFound(w, req)
 
362
        }
 
363
}