// Copyright 2013 Julien Schmidt. All rights reserved. // Use of this source code is governed by a BSD-style license that can be found // in the LICENSE file. package myth // Package httprouter is a trie based high performance HTTP request router. // // A trivial example is: // // package main // // import ( // "fmt" // "github.com/julienschmidt/httprouter" // "net/http" // "log" // ) // // func Index(w http.ResponseWriter, r *http.Request) { // fmt.Fprint(w, "Welcome!\n") // } // // func Hello(w http.ResponseWriter, r *http.Request) { // var name string // if vars := httprouter.Vars(); vars != nil { // name, _ = vars["name"] // } // fmt.Fprintf(w, "hello, %s!\n", name) // } // // func main() { // router := httprouter.New() // router.GET("/", Index) // router.GET("/hello/:name", Hello) // // log.Fatal(http.ListenAndServe(":8080", router)) // } // // The router matches incoming requests by the request method and the path. // If a handle is registered for this path and method, the router delegates the // request to that function. // For the methods GET, POST, PUT, PATCH and DELETE shortcut functions exist to // register handles, for all other methods router.Handle can be used. // // The registered path, against which the router matches incoming requests, can // contain two types of parameters: // Syntax Type // :name named parameter // *name catch-all parameter // // Named parameters are dynamic path segments. They match anything until the // next '/' or the path end: // Path: /blog/:category/:post // // Requests: // /blog/go/request-routers match: category="go", post="request-routers" // /blog/go/request-routers/ no match, but the router would redirect // /blog/go/ no match // /blog/go/request-routers/comments no match // // Catch-all parameters match anything until the path end, including the // directory index (the '/' before the catch-all). Since they match anything // until the end, catch-all parameters must always be the final path element. // Path: /files/*filepath // // Requests: // /files/ match: filepath="/" // /files/LICENSE match: filepath="/LICENSE" // /files/templates/article.html match: filepath="/templates/article.html" // /files no match, but the router would redirect // // The value of parameters is saved as a slice of the Param struct, consisting // each of a key and a value. The slice is passed to the Handle func as a third // parameter. // There are two ways to retrieve the value of a parameter: // // by the name of the parameter // user := ps.ByName("user") // defined by :user or *user // // // by the index of the parameter. This way you can also get the name (key) // thirdKey := ps[2].Key // the name of the 3rd parameter // thirdValue := ps[2].Value // the value of the 3rd parameter import ( "net/http" ) // Handle is a function that can be registered to a route to handle HTTP // requests. Like http.HandlerFunc, but has a third parameter for the values of // wildcards (variables). type Handle func(http.ResponseWriter, *http.Request) // Router is a http.Handler which can be used to dispatch requests to different // handler functions via configurable routes type Router struct { trees map[string]*node // Enables automatic redirection if the current route can't be matched but a // handler for the path with (without) the trailing slash exists. // For example if /foo/ is requested but a route only exists for /foo, the // client is redirected to /foo with http status code 301 for GET requests // and 307 for all other request methods. RedirectTrailingSlash bool // If enabled, the router tries to fix the current request path, if no // handle is registered for it. // First superfluous path elements like ../ or // are removed. // Afterwards the router does a case-insensitive lookup of the cleaned path. // If a handle can be found for this route, the router makes a redirection // to the corrected path with status code 301 for GET requests and 307 for // all other request methods. // For example /FOO and /..//Foo could be redirected to /foo. // RedirectTrailingSlash is independent of this option. RedirectFixedPath bool // If enabled, the router checks if another method is allowed for the // current route, if the current request can not be routed. // If this is the case, the request is answered with 'Method Not Allowed' // and HTTP status code 405. // If no other Method is allowed, the request is delegated to the NotFound // handler. HandleMethodNotAllowed bool // If enabled, the router automatically replies to OPTIONS requests. // Custom OPTIONS handlers take priority over automatic replies. HandleOPTIONS bool // Configurable http.Handler which is called when no matching route is // found. If it is not set, http.NotFound is used. NotFound http.Handler // Configurable http.Handler which is called when a request // cannot be routed and HandleMethodNotAllowed is true. // If it is not set, http.Error with http.StatusMethodNotAllowed is used. // The "Allow" header with allowed request methods is set before the handler // is called. MethodNotAllowed http.Handler // Function to handle panics recovered from http handlers. // It should be used to generate a error page and return the http error code // 500 (Internal Server Error). // The handler can be used to keep your server from crashing because of // unrecovered panics. PanicHandler func(http.ResponseWriter, *http.Request, interface{}) } // Make sure the Router conforms with the http.Handler interface var _ http.Handler = NewRouter() // NewRouter returns a new initialized Router. // Path auto-correction, including trailing slashes, is enabled by default. func NewRouter() *Router { return &Router{ RedirectTrailingSlash: true, RedirectFixedPath: true, HandleMethodNotAllowed: true, HandleOPTIONS: true, } } // GET is a shortcut for router.Handle("GET", path, handle) func (r *Router) GET(path string, handle Handle) { r.Handle(http.MethodGet, path, handle) } // HEAD is a shortcut for router.Handle("HEAD", path, handle) func (r *Router) HEAD(path string, handle Handle) { r.Handle(http.MethodHead, path, handle) } // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle) func (r *Router) OPTIONS(path string, handle Handle) { r.Handle(http.MethodOptions, path, handle) } // POST is a shortcut for router.Handle("POST", path, handle) func (r *Router) POST(path string, handle Handle) { r.Handle(http.MethodPost, path, handle) } // PUT is a shortcut for router.Handle("PUT", path, handle) func (r *Router) PUT(path string, handle Handle) { r.Handle(http.MethodPut, path, handle) } // PATCH is a shortcut for router.Handle("PATCH", path, handle) func (r *Router) PATCH(path string, handle Handle) { r.Handle(http.MethodPatch, path, handle) } // DELETE is a shortcut for router.Handle("DELETE", path, handle) func (r *Router) DELETE(path string, handle Handle) { r.Handle(http.MethodDelete, path, handle) } // Handle registers a new request handle with the given path and method. // // For GET, POST, PUT, PATCH and DELETE requests the respective shortcut // functions can be used. // // This function is intended for bulk loading and to allow the usage of less // frequently used, non-standardized or custom methods (e.g. for internal // communication with a proxy). func (r *Router) Handle(method, path string, handle Handle) { if path[0] != '/' { panic("path must begin with '/' in path '" + path + "'") } if r.trees == nil { r.trees = make(map[string]*node) } root := r.trees[method] if root == nil { root = new(node) r.trees[method] = root } root.addRoute(path, handle) } // Handler is an adapter which allows the usage of an http.Handler as a // request handle. With go 1.7+, the Params will be available in the // request context under ParamsKey. func (r *Router) Handler(method, path string, handler http.Handler) { r.Handle(method, path, func(w http.ResponseWriter, req *http.Request) { handler.ServeHTTP(w, req) }, ) } // HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a // request handle. func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) { r.Handler(method, path, handler) } // ServeFiles serves files from the given file system root. // The path must end with "/*filepath", files are then served from the local // path /defined/root/dir/*filepath. // For example if root is "/etc" and *filepath is "passwd", the local file // "/etc/passwd" would be served. // Internally a http.FileServer is used, therefore http.NotFound is used instead // of the Router's NotFound handler. // To use the operating system's file system implementation, // use http.Dir: // router.ServeFiles("/src/*filepath", http.Dir("/var/www")) func (r *Router) ServeFiles(path string, root http.FileSystem) { if len(path) < 10 || path[len(path)-10:] != "/*filepath" { panic("path must end with /*filepath in path '" + path + "'") } fileServer := http.FileServer(root) r.GET(path, func(w http.ResponseWriter, req *http.Request) { if vars := Vars(req); vars != nil { path, _ := vars["filepath"] req.URL.Path = path } fileServer.ServeHTTP(w, req) }) } func (r *Router) recv(w http.ResponseWriter, req *http.Request) { if rcv := recover(); rcv != nil { r.PanicHandler(w, req, rcv) } } // Lookup allows the manual lookup of a method + path combo. // This is e.g. useful to build a framework around this router. // If the path was found, it returns the handle function and the path parameter // values. Otherwise the third return value indicates whether a redirection to // the same path with an extra / without the trailing slash should be performed. func (r *Router) Lookup(method, path string) (Handle, map[string]string, bool) { if root := r.trees[method]; root != nil { return root.getValue(path) } return nil, nil, false } func (r *Router) allowed(path, reqMethod string) (allow string) { if path == "*" { // server-wide for method := range r.trees { if method == http.MethodOptions { continue } // add request method to list of allowed methods if len(allow) == 0 { allow = method } else { allow += ", " + method } } } else { // specific path for method := range r.trees { // Skip the requested method - we already tried this one if method == reqMethod || method == http.MethodOptions { continue } handle, _, _ := r.trees[method].getValue(path) if handle != nil { // add request method to list of allowed methods if len(allow) == 0 { allow = method } else { allow += ", " + method } } } } if len(allow) > 0 { allow += ", OPTIONS" } return } // ServeHTTP makes the router implement the http.Handler interface. func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { if r.PanicHandler != nil { defer r.recv(w, req) } path := req.URL.Path if root := r.trees[req.Method]; root != nil { if handle, ps, tsr := root.getValue(path); handle != nil { req = setVars(req, ps) req = setCurrentPath(req, path) handle(w, req) return } else if req.Method != http.MethodConnect && path != "/" { code := 301 // Permanent redirect, request with GET method if req.Method != http.MethodGet { // Temporary redirect, request with same method // As of Go 1.3, Go does not support status code 308. code = 307 } if tsr && r.RedirectTrailingSlash { if len(path) > 1 && path[len(path)-1] == '/' { req.URL.Path = path[:len(path)-1] } else { req.URL.Path = path + "/" } http.Redirect(w, req, req.URL.String(), code) return } // Try to fix the request path if r.RedirectFixedPath { fixedPath, found := root.findCaseInsensitivePath( CleanPath(path), r.RedirectTrailingSlash, ) if found { req.URL.Path = string(fixedPath) http.Redirect(w, req, req.URL.String(), code) return } } } } if req.Method == http.MethodOptions && r.HandleOPTIONS { // Handle OPTIONS requests if allow := r.allowed(path, req.Method); len(allow) > 0 { w.Header().Set("Allow", allow) return } } else { // Handle 405 if r.HandleMethodNotAllowed { if allow := r.allowed(path, req.Method); len(allow) > 0 { w.Header().Set("Allow", allow) if r.MethodNotAllowed != nil { r.MethodNotAllowed.ServeHTTP(w, req) } else { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed, ) } return } } } // Handle 404 if r.NotFound != nil { r.NotFound.ServeHTTP(w, req) } else { http.NotFound(w, req) } }