httprouter

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 7, 2014 License: BSD-3-Clause Imports: 3 Imported by: 22,745

README

HttpRouter Build Status GoDoc

HttpRouter is a high performance HTTP request router (also called multiplexer or just mux for short).

In contrast to the default mux of Go's net/http package, this router supports variables in the routing pattern and matches against the request method. It also scales better.

The router is optimized for best performance and a small memory footprint. It scales well even with very long pathes and a large number of routes. A compressing dynamic trie (radix tree) structure is used for efficient matching.

Features

Zero Garbage: The matching and dispatching process generates zero bytes of garbage. In fact, the only heap allocations that are made, is by building the map containing the key-value pairs for variables. If the request path contains no variables, not a single heap allocation is necessary.

Best Performance: Benchmarks speak for themselves. See below for technical details of the implementation.

Variables in your routing pattern: Stop parsing the requested URL path, just give the path segment a name and the router delivers the value to you. Because of the design of the router, pattern variables are very cheap.

Only explicit matches: With other routers / muxes, like http.ServeMux, a requested URL path could match multiple patterns. Therefore they have some awkward pattern priority rules, like longest match or first registered, first matched. By design of this router, a request can only match exactly one or no route. As a result, there are also no unintended matches, which makes it great for SEO.

Stop caring about trailing slashes: Choose the URL style you like, the router automatically redirects the client if a trailing slash is missing or if there is one extra. Of course it only does so, if the new path has a handler. If you don't like it, you can turn off this behavior.

No more server crashes: You can set a PanicHandler to deal with panics occouring during handling a HTTP request. The router then recovers and lets the PanicHandler log what happened and deliver a nice error page.

Of course you can also set a custom NotFound handler and serve files.

Usage

This is just a quick introduction, view the GoDoc for details.

Let's start with a trivial example:

package main

import (
    "fmt"
    "github.com/julienschmidt/httprouter"
    "net/http"
    "log"
)

func Index(w http.ResponseWriter, r *http.Request, _ map[string]string) {
    fmt.Fprint(w, "Welcome!\n")
}

func Hello(w http.ResponseWriter, r *http.Request, vars map[string]string) {
    fmt.Fprintf(w, "hello, %s!\n", vars["name"])
}

func main() {
    router := httprouter.New()
    router.GET("/", Index)
    router.GET("/hello/:name", Hello)

    log.Fatal(http.ListenAndServe(":12345", router))
}
Named parameters

As you can see, :name is a named parameter. The values are passed in a map, therefore the value of :name is available in vars["name"].

Named parameters only match a single path segment:

Pattern: /user/:user

 /user/gordon              match
 /user/you                 match
 /user/gordon/profile      no match
 /user/                    no match

Note: Since this router has only explicit matches, you can not register static routes and paramters for the same path segment. For example you can not register the patterns /user/new and /user/:user at the same time.

Catch-All routes

The second type are catch-all routes and have the form *name. Like the name suggest, they match everything. Therefore they must always be at the end of the pattern:

Pattern: /src/*filepath

 /src/                     match
 /src/somefile.go          match
 /src/subdir/somefile.go   match

How does it work?

The router relies on a tree structure which makes heavy use of common prefixes, it is basically a compact prefix tree (or just Radix tree). Nodes with a common prefix also share a common parent. Here is a short example what the routing tree could look like:

Priority   Path             Handles
9          \                map[GET:<1>]
3          ├s               map[]
2          |├earch\         map[GET:<2>, POST:<3>,]
1          |└upport\        map[GET:<4>]
2          ├blog\           map[GET:<5>]
1          |    └:post      map[]
1          |         └\     map[GET:<6>]
2          ├about-us\       map[GET:<7>]
1          |        └team\  map[GET:<8>]
1          └contact\        map[GET:<9>]

Every <num> represents the memory address of a handler function. If you follow a path trough the tree from the root to the leaf, you get the complete route path, e.g \search\, for which a GET- and a POST-handler function are registered.

Since URL pathes have a hierarchical structure and make use only of a limited set of characters, it is very likely that there are a lot of common prefixes. This allows us to easiely reduce the routing into ever smaller problems.

Unlike hash-maps, a tree structure also allows us to use dynamic parts, e.g. the :post above, which is just a placeholder for a post name, since we actually match against the routing patterns instead of just comparing hashes. As benchmarks show, hash-map based routers also don't scale very well.

For even better scalability, the child nodes on each tree level are ordered by priority, where the priority is just the number of handles registered in sub nodes (children, grandchildren, and so on..). This helps in two ways:

  1. Nodes which are part of the most routing pathes are evaluated first. This helps to make as much routes as possible to be reachable as fast as possible.
  2. It is some sort of cost compensation. The longest reachable path (highest cost) can always be evaluated first. The follwing scheme visualizes the tree structure. Nodes are evaluated from top to bottom and from left to right.
├------------
├---------
├-----
├----
├--
├--
└-

Where can I find Middleware X?

This package just provides a very efficient request router with a few extra features. The router is just a http.Handler, you can chain any http.Handler compatible middleware before the router, for example the Gorilla handlers. Or you could just write your own, it's very easy!

Here is a quick example: Does your server serve multiple domains / hosts? You want to use subdomains? Define a router per host!

// We need an object that implements the http.Handler interface.
// Therefore we need a type for which we implement the ServeHTTP method.
// We just use a map here, in which we map host names (with port) to http.Handlers
type HostSwitch map[string]http.Handler

// Implement the ServerHTTP method on our new type
func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Check if a http.Handler is registered for the given host.
	// If yes, use it to handle the request.
	if handler := hs[r.Host]; handler != nil {
		handler.ServeHTTP(w, r)
	} else {
		// Handle host names for wich no handler is registered
		http.Error(w, "Forbidden", 403) // Or Redirect?
	}
}

func main() {
	// Initialze a router as usual 
	router := httprouter.New()
	router.GET("/", Index)
	router.GET("/hello/:name", Hello)

	// Make a new HostSwitch and insert the router (our http handler)
	// for example.com and port 12345
	hs := make(HostSwitch)
	hs["example.com:12345"] = router

	// Use the HostSwitch to listen and serve on port 12345
	log.Fatal(http.ListenAndServe(":12345", hs))
}

Documentation

Overview

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, _ map[string]string) {
    fmt.Fprint(w, "Welcome!\n")
}

func Hello(w http.ResponseWriter, r *http.Request, vars map[string]string) {
    fmt.Fprintf(w, "hello, %s!\n", vars["name"])
}

func main() {
    router := httprouter.New()
    router.GET("/", Index)
    router.GET("/hello/:name", Hello)

    log.Fatal(http.ListenAndServe(":12345", 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 wildcards:

Syntax    Type
:name     Parameter
*name     CatchAll

The value of wildcards is saved in a map as vars["name"] = value. The map is passed to the Handle func as a parameter.

Parameters are variable 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

CatchAll wildcards match anything until the path end, including the directory index (the '/” before the CatchAll). Since they match anything until the end, CatchAll wildcards 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

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CleanPath

func CleanPath(p string) string

CleanPath is the URL version of path.Clean, it returns a canonical URL path for p, eliminating . and .. elements.

The following rules are applied iteratively until no further processing can be done:

  1. Replace multiple slashes with a single slash.
  2. Eliminate each . path name element (the current directory).
  3. Eliminate each inner .. path name element (the parent directory) along with the non-.. element that precedes it.
  4. Eliminate .. elements that begin a rooted path: that is, replace "/.." by "/" at the beginning of a path.

If the result of this process is an empty string, "/" is returned

func NotFound

func NotFound(w http.ResponseWriter, req *http.Request)

NotFound is the default HTTP handler func for routes that can't be matched with an existing route. NotFound tries to redirect to a canonical URL generated with CleanPath. Otherwise the request is delegated to http.NotFound.

Types

type Handle

type Handle func(http.ResponseWriter, *http.Request, map[string]string)

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 Router

type Router struct {

	// 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.
	RedirectTrailingSlash bool

	// Enables automatic redirection if the current route can't be matched but a
	// case-insensitive lookup of the path finds a handler.
	// The router then permanent redirects (http status code 301) to the
	// corrected path.
	// For example /FOO and /Foo could be redirected to /foo.
	RedirectCaseInsensitive bool

	// Configurable handler func which is used when no matching route is found.
	// Default is the NotFound func of this package.
	NotFound http.HandlerFunc

	// Handler func 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{})
	// contains filtered or unexported fields
}

Router is a http.Handler which can be used to dispatch requests to different handler functions via configurable routes

func New

func New() *Router

New returnes a new initialized Router. The router can be configured to also match the requested HTTP method or the requested Host.

func (*Router) DELETE

func (r *Router) DELETE(path string, handle Handle)

DELETE is a shortcut for router.Handle("DELETE", path, handle)

func (*Router) GET

func (r *Router) GET(path string, handle Handle)

GET is a shortcut for router.Handle("GET", path, handle)

func (*Router) Handle

func (r *Router) Handle(method, path string, handle 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 (*Router) HandlerFunc

func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc)

HandlerFunc is an adapter which allows the usage of a http.HandlerFunc as a request handle.

func (*Router) PATCH

func (r *Router) PATCH(path string, handle Handle)

PATCH is a shortcut for router.Handle("PATCH", path, handle)

func (*Router) POST

func (r *Router) POST(path string, handle Handle)

POST is a shortcut for router.Handle("POST", path, handle)

func (*Router) PUT

func (r *Router) PUT(path string, handle Handle)

PUT is a shortcut for router.Handle("PUT", path, handle)

func (*Router) ServeFiles

func (r *Router) ServeFiles(path string, root http.FileSystem)

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 (*Router) ServeHTTP

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

Make the router implement the http.Handler interface.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL