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 77package httprouter 78 79import ( 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). 86type Handle func(http.ResponseWriter, *http.Request, Params) 87 88// Param is a single URL parameter, consisting of a key and a value. 89type 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. 97type 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. 101func (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 112type 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 // If enabled, the router automatically replies to OPTIONS requests. 142 // Custom OPTIONS handlers take priority over automatic replies. 143 HandleOPTIONS bool 144 145 // Configurable http.Handler which is called when no matching route is 146 // found. If it is not set, http.NotFound is used. 147 NotFound http.Handler 148 149 // Configurable http.Handler which is called when a request 150 // cannot be routed and HandleMethodNotAllowed is true. 151 // If it is not set, http.Error with http.StatusMethodNotAllowed is used. 152 // The "Allow" header with allowed request methods is set before the handler 153 // is called. 154 MethodNotAllowed http.Handler 155 156 // Function to handle panics recovered from http handlers. 157 // It should be used to generate a error page and return the http error code 158 // 500 (Internal Server Error). 159 // The handler can be used to keep your server from crashing because of 160 // unrecovered panics. 161 PanicHandler func(http.ResponseWriter, *http.Request, interface{}) 162} 163 164// Make sure the Router conforms with the http.Handler interface 165var _ http.Handler = New() 166 167// New returns a new initialized Router. 168// Path auto-correction, including trailing slashes, is enabled by default. 169func New() *Router { 170 return &Router{ 171 RedirectTrailingSlash: true, 172 RedirectFixedPath: true, 173 HandleMethodNotAllowed: true, 174 HandleOPTIONS: true, 175 } 176} 177 178// GET is a shortcut for router.Handle("GET", path, handle) 179func (r *Router) GET(path string, handle Handle) { 180 r.Handle("GET", path, handle) 181} 182 183// HEAD is a shortcut for router.Handle("HEAD", path, handle) 184func (r *Router) HEAD(path string, handle Handle) { 185 r.Handle("HEAD", path, handle) 186} 187 188// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle) 189func (r *Router) OPTIONS(path string, handle Handle) { 190 r.Handle("OPTIONS", path, handle) 191} 192 193// POST is a shortcut for router.Handle("POST", path, handle) 194func (r *Router) POST(path string, handle Handle) { 195 r.Handle("POST", path, handle) 196} 197 198// PUT is a shortcut for router.Handle("PUT", path, handle) 199func (r *Router) PUT(path string, handle Handle) { 200 r.Handle("PUT", path, handle) 201} 202 203// PATCH is a shortcut for router.Handle("PATCH", path, handle) 204func (r *Router) PATCH(path string, handle Handle) { 205 r.Handle("PATCH", path, handle) 206} 207 208// DELETE is a shortcut for router.Handle("DELETE", path, handle) 209func (r *Router) DELETE(path string, handle Handle) { 210 r.Handle("DELETE", path, handle) 211} 212 213// Handle registers a new request handle with the given path and method. 214// 215// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut 216// functions can be used. 217// 218// This function is intended for bulk loading and to allow the usage of less 219// frequently used, non-standardized or custom methods (e.g. for internal 220// communication with a proxy). 221func (r *Router) Handle(method, path string, handle Handle) { 222 if path[0] != '/' { 223 panic("path must begin with '/' in path '" + path + "'") 224 } 225 226 if r.trees == nil { 227 r.trees = make(map[string]*node) 228 } 229 230 root := r.trees[method] 231 if root == nil { 232 root = new(node) 233 r.trees[method] = root 234 } 235 236 root.addRoute(path, handle) 237} 238 239// HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a 240// request handle. 241func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) { 242 r.Handler(method, path, handler) 243} 244 245// ServeFiles serves files from the given file system root. 246// The path must end with "/*filepath", files are then served from the local 247// path /defined/root/dir/*filepath. 248// For example if root is "/etc" and *filepath is "passwd", the local file 249// "/etc/passwd" would be served. 250// Internally a http.FileServer is used, therefore http.NotFound is used instead 251// of the Router's NotFound handler. 252// To use the operating system's file system implementation, 253// use http.Dir: 254// router.ServeFiles("/src/*filepath", http.Dir("/var/www")) 255func (r *Router) ServeFiles(path string, root http.FileSystem) { 256 if len(path) < 10 || path[len(path)-10:] != "/*filepath" { 257 panic("path must end with /*filepath in path '" + path + "'") 258 } 259 260 fileServer := http.FileServer(root) 261 262 r.GET(path, func(w http.ResponseWriter, req *http.Request, ps Params) { 263 req.URL.Path = ps.ByName("filepath") 264 fileServer.ServeHTTP(w, req) 265 }) 266} 267 268func (r *Router) recv(w http.ResponseWriter, req *http.Request) { 269 if rcv := recover(); rcv != nil { 270 r.PanicHandler(w, req, rcv) 271 } 272} 273 274// Lookup allows the manual lookup of a method + path combo. 275// This is e.g. useful to build a framework around this router. 276// If the path was found, it returns the handle function and the path parameter 277// values. Otherwise the third return value indicates whether a redirection to 278// the same path with an extra / without the trailing slash should be performed. 279func (r *Router) Lookup(method, path string) (Handle, Params, bool) { 280 if root := r.trees[method]; root != nil { 281 return root.getValue(path) 282 } 283 return nil, nil, false 284} 285 286func (r *Router) allowed(path, reqMethod string) (allow string) { 287 if path == "*" { // server-wide 288 for method := range r.trees { 289 if method == "OPTIONS" { 290 continue 291 } 292 293 // add request method to list of allowed methods 294 if len(allow) == 0 { 295 allow = method 296 } else { 297 allow += ", " + method 298 } 299 } 300 } else { // specific path 301 for method := range r.trees { 302 // Skip the requested method - we already tried this one 303 if method == reqMethod || method == "OPTIONS" { 304 continue 305 } 306 307 handle, _, _ := r.trees[method].getValue(path) 308 if handle != nil { 309 // add request method to list of allowed methods 310 if len(allow) == 0 { 311 allow = method 312 } else { 313 allow += ", " + method 314 } 315 } 316 } 317 } 318 if len(allow) > 0 { 319 allow += ", OPTIONS" 320 } 321 return 322} 323 324// ServeHTTP makes the router implement the http.Handler interface. 325func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { 326 if r.PanicHandler != nil { 327 defer r.recv(w, req) 328 } 329 330 path := req.URL.Path 331 332 if root := r.trees[req.Method]; root != nil { 333 if handle, ps, tsr := root.getValue(path); handle != nil { 334 handle(w, req, ps) 335 return 336 } else if req.Method != "CONNECT" && path != "/" { 337 code := 301 // Permanent redirect, request with GET method 338 if req.Method != "GET" { 339 // Temporary redirect, request with same method 340 // As of Go 1.3, Go does not support status code 308. 341 code = 307 342 } 343 344 if tsr && r.RedirectTrailingSlash { 345 if len(path) > 1 && path[len(path)-1] == '/' { 346 req.URL.Path = path[:len(path)-1] 347 } else { 348 req.URL.Path = path + "/" 349 } 350 http.Redirect(w, req, req.URL.String(), code) 351 return 352 } 353 354 // Try to fix the request path 355 if r.RedirectFixedPath { 356 fixedPath, found := root.findCaseInsensitivePath( 357 CleanPath(path), 358 r.RedirectTrailingSlash, 359 ) 360 if found { 361 req.URL.Path = string(fixedPath) 362 http.Redirect(w, req, req.URL.String(), code) 363 return 364 } 365 } 366 } 367 } 368 369 if req.Method == "OPTIONS" && r.HandleOPTIONS { 370 // Handle OPTIONS requests 371 if allow := r.allowed(path, req.Method); len(allow) > 0 { 372 w.Header().Set("Allow", allow) 373 return 374 } 375 } else { 376 // Handle 405 377 if r.HandleMethodNotAllowed { 378 if allow := r.allowed(path, req.Method); len(allow) > 0 { 379 w.Header().Set("Allow", allow) 380 if r.MethodNotAllowed != nil { 381 r.MethodNotAllowed.ServeHTTP(w, req) 382 } else { 383 http.Error(w, 384 http.StatusText(http.StatusMethodNotAllowed), 385 http.StatusMethodNotAllowed, 386 ) 387 } 388 return 389 } 390 } 391 } 392 393 // Handle 404 394 if r.NotFound != nil { 395 r.NotFound.ServeHTTP(w, req) 396 } else { 397 http.NotFound(w, req) 398 } 399} 400