|
| 1 | +# Xmux |
| 2 | + |
| 3 | +[](https://godoc.org/github.com/rs/xhandler/xmux) [](https://raw.githubusercontent.com/rs/xhandler/master/LICENSE) [](https://travis-ci.org/rs/xhandler) [](http://gocover.io/github.com/rs/xhandler/xmux) |
| 4 | + |
| 5 | +Xmux is a lightweight high performance HTTP request muxer on top [xhandler](https://github.com/rs/xhandler). Xmux gets its speed from the fork of the amazing [httprouter](https://github.com/julienschmidt/httprouter). Route parameters are stored in `net/context` instead of being passed as an additional parameter. |
| 6 | + |
| 7 | +In contrast to the [default mux](http://golang.org/pkg/net/http/#ServeMux) of Go's `net/http` package, this muxer supports variables in the routing pattern and matches against the request method. It also scales better. |
| 8 | + |
| 9 | +The muxer is optimized for high performance and a small memory footprint. It scales well even with very long paths and a large number of routes. A compressing dynamic trie (radix tree) structure is used for efficient matching. |
| 10 | + |
| 11 | +## Features |
| 12 | + |
| 13 | +**Only explicit matches:** With other muxers, like [http.ServeMux](http://golang.org/pkg/net/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 and improves the user experience. |
| 14 | + |
| 15 | +**Stop caring about trailing slashes:** Choose the URL style you like, the muxer 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](http://godoc.org/github.com/rs/xhandler/xmux#Mux.RedirectTrailingSlash). |
| 16 | + |
| 17 | +**Path auto-correction:** Besides detecting the missing or additional trailing slash at no extra cost, the muxer can also fix wrong cases and remove superfluous path elements (like `../` or `//`). Is [CAPTAIN CAPS LOCK](http://www.urbandictionary.com/define.php?term=Captain+Caps+Lock) one of your users? Xmux can help him by making a case-insensitive look-up and redirecting him to the correct URL. |
| 18 | + |
| 19 | +**Parameters in your routing pattern:** Stop parsing the requested URL path, just give the path segment a name and the router delivers the dynamic value to you. Because of the design of the router, path parameters are very cheap. |
| 20 | + |
| 21 | +**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 slice of the key-value pairs for path parameters and the `net/context` instance to store them in the context. If the request path contains no parameters, not a single heap allocation is necessary. |
| 22 | + |
| 23 | +**No more server crashes:** You can set a [Panic handler](http://godoc.org/github.com/rs/xhandler/xmux#Mux.PanicHandler) to deal with panics occurring during handling a HTTP request. The router then recovers and lets the `PanicHandler` log what happened and deliver a nice error page. |
| 24 | + |
| 25 | +Of course you can also set **custom [NotFound](http://godoc.org/github.com/rs/xhandler/xmux#Mux.NotFound) and [MethodNotAllowed](http://godoc.org/github.com/rs/xhandler/xmux#Mux.MethodNotAllowed) handlers**. |
| 26 | + |
| 27 | +## Usage |
| 28 | + |
| 29 | +This is just a quick introduction, view the [GoDoc](http://godoc.org/github.com/rs/xhandler/xmux) for details. |
| 30 | + |
| 31 | +Let's start with a trivial example: |
| 32 | +```go |
| 33 | +package main |
| 34 | + |
| 35 | +import ( |
| 36 | + "fmt" |
| 37 | + "log" |
| 38 | + "net/http" |
| 39 | + |
| 40 | + "github.com/rs/xhandler" |
| 41 | + "github.com/rs/xhandler/xmux" |
| 42 | + "golang.org/x/net/context" |
| 43 | +) |
| 44 | + |
| 45 | +func Index(ctx context.Context, w http.ResponseWriter, r *http.Request) { |
| 46 | + fmt.Fprint(w, "Welcome!\n") |
| 47 | +} |
| 48 | + |
| 49 | +func Hello(ctx context.Context, w http.ResponseWriter, r *http.Request) { |
| 50 | + fmt.Fprintf(w, "hello, %s!\n", xmux.Params(ctx).Get("name")) |
| 51 | +} |
| 52 | + |
| 53 | +func main() { |
| 54 | + mux := xmux.New() |
| 55 | + mux.GET("/", Index) |
| 56 | + mux.GET("/hello/:name", Hello) |
| 57 | + |
| 58 | + log.Fatal(http.ListenAndServe(":8080", xhandler.New(context.Background(), mux))) |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +You may also chain middleware using `xhandler.Chain`: |
| 63 | + |
| 64 | +```go |
| 65 | +package main |
| 66 | + |
| 67 | +import ( |
| 68 | + "fmt" |
| 69 | + "log" |
| 70 | + "net/http" |
| 71 | + "time" |
| 72 | + |
| 73 | + "github.com/rs/xhandler" |
| 74 | + "github.com/rs/xhandler/xmux" |
| 75 | + "golang.org/x/net/context" |
| 76 | +) |
| 77 | + |
| 78 | +func main() { |
| 79 | + c := xhandler.Chain{} |
| 80 | + |
| 81 | + // Append a context-aware middleware handler |
| 82 | + c.UseC(xhandler.CloseHandler) |
| 83 | + |
| 84 | + // Another context-aware middleware handler |
| 85 | + c.UseC(xhandler.TimeoutHandler(2 * time.Second)) |
| 86 | + |
| 87 | + mux := xmux.New() |
| 88 | + |
| 89 | + // Use c.Handler to terminate the chain with your final handler |
| 90 | + mux.GET("/welcome/:name", xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, req *http.Request) { |
| 91 | + fmt.Fprintf(w, "Welcome %s!", xmux.Params(ctx).Get("name")) |
| 92 | + })) |
| 93 | + |
| 94 | + if err := http.ListenAndServe(":8080", c.Handler(mux)); err != nil { |
| 95 | + log.Fatal(err) |
| 96 | + } |
| 97 | +} |
| 98 | +``` |
| 99 | + |
| 100 | +### Named parameters |
| 101 | + |
| 102 | +As you can see, `:name` is a *named parameter*. The values are accessible via `xmux.Params(ctx)`, which returns `xmux.ParamHolder`. |
| 103 | +You can get the value of a parameter by its name using `Get(name)` method: |
| 104 | + |
| 105 | +Named parameters only match a single path segment: |
| 106 | + |
| 107 | +``` |
| 108 | +Pattern: /user/:user |
| 109 | +
|
| 110 | + /user/gordon match |
| 111 | + /user/you match |
| 112 | + /user/gordon/profile no match |
| 113 | + /user/ no match |
| 114 | +``` |
| 115 | + |
| 116 | +**Note:** Since this muxer has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns `/user/new` and `/user/:user` for the same request method at the same time. The routing of different request methods is independent from each other. |
| 117 | + |
| 118 | +### Catch-All parameters |
| 119 | + |
| 120 | +The second type are *catch-all* parameters and have the form `*name`. Like the name suggests, they match everything. Therefore they must always be at the **end** of the pattern: |
| 121 | + |
| 122 | +``` |
| 123 | +Pattern: /src/*filepath |
| 124 | +
|
| 125 | + /src/ match |
| 126 | + /src/somefile.go match |
| 127 | + /src/subdir/somefile.go match |
| 128 | +``` |
| 129 | + |
| 130 | +## Benchmarks |
| 131 | + |
| 132 | +Thanks to [Julien Schmidt](https://github.com/julienschmidt) excellent [HTTP routing benchmark](https://github.com/julienschmidt/go-http-routing-benchmark), we can see that xhandler's muxer is pretty close to `httprouter` as it is a fork of it. The small overhead is due to the `net/context` allocation used to store route parameters. It still outperform other routers, thanks to amazing `httprouter`'s radix tree based matcher. |
| 133 | + |
| 134 | +``` |
| 135 | +BenchmarkXhandler_APIStatic-8 50000000 39.6 ns/op 0 B/op 0 allocs/op |
| 136 | +BenchmarkChi_APIStatic-8 3000000 439 ns/op 144 B/op 5 allocs/op |
| 137 | +BenchmarkGoji_APIStatic-8 5000000 272 ns/op 0 B/op 0 allocs/op |
| 138 | +BenchmarkHTTPRouter_APIStatic-8 50000000 37.3 ns/op 0 B/op 0 allocs/op |
| 139 | +
|
| 140 | +BenchmarkXhandler_APIParam-8 5000000 328 ns/op 160 B/op 4 allocs/op |
| 141 | +BenchmarkChi_APIParam-8 2000000 675 ns/op 432 B/op 6 allocs/op |
| 142 | +BenchmarkGoji_APIParam-8 2000000 692 ns/op 336 B/op 2 allocs/op |
| 143 | +BenchmarkHTTPRouter_APIParam-8 10000000 166 ns/op 64 B/op 1 allocs/op |
| 144 | +
|
| 145 | +BenchmarkXhandler_API2Params-8 5000000 362 ns/op 160 B/op 4 allocs/op |
| 146 | +BenchmarkChi_API2Params-8 2000000 814 ns/op 432 B/op 6 allocs/op |
| 147 | +BenchmarkGoji_API2Params-8 2000000 680 ns/op 336 B/op 2 allocs/op |
| 148 | +BenchmarkHTTPRouter_API2Params-8 10000000 183 ns/op 64 B/op 1 allocs/op |
| 149 | +
|
| 150 | +BenchmarkXhandler_APIAll-8 200000 6473 ns/op 2176 B/op 64 allocs/op |
| 151 | +BenchmarkChi_APIAll-8 100000 17261 ns/op 8352 B/op 146 allocs/op |
| 152 | +BenchmarkGoji_APIAll-8 100000 15052 ns/op 5377 B/op 32 allocs/op |
| 153 | +BenchmarkHTTPRouter_APIAll-8 500000 3716 ns/op 640 B/op 16 allocs/op |
| 154 | +
|
| 155 | +BenchmarkXhandler_Param1-8 5000000 271 ns/op 128 B/op 4 allocs/op |
| 156 | +BenchmarkChi_Param1-8 2000000 620 ns/op 432 B/op 6 allocs/op |
| 157 | +BenchmarkGoji_Param1-8 3000000 522 ns/op 336 B/op 2 allocs/op |
| 158 | +BenchmarkHTTPRouter_Param1-8 20000000 112 ns/op 32 B/op 1 allocs/op |
| 159 | +
|
| 160 | +BenchmarkXhandler_Param5-8 3000000 414 ns/op 256 B/op 4 allocs/op |
| 161 | +BenchmarkChi_Param5-8 1000000 1204 ns/op 432 B/op 6 allocs/op |
| 162 | +BenchmarkGoji_Param5-8 2000000 847 ns/op 336 B/op 2 allocs/op |
| 163 | +BenchmarkHTTPRouter_Param5-8 5000000 247 ns/op 160 B/op 1 allocs/op |
| 164 | +
|
| 165 | +BenchmarkXhandler_Param20-8 2000000 747 ns/op 736 B/op 4 allocs/op |
| 166 | +BenchmarkChi_Param20-8 2000000 746 ns/op 736 B/op 4 allocs/op |
| 167 | +BenchmarkGoji_Param20-8 500000 2439 ns/op 1247 B/op 2 allocs/op |
| 168 | +BenchmarkHTTPRouter_Param20-8 3000000 585 ns/op 640 B/op 1 allocs/op |
| 169 | +
|
| 170 | +BenchmarkXhandler_ParamWrite-8 5000000 404 ns/op 144 B/op 5 allocs/op |
| 171 | +BenchmarkChi_ParamWrite-8 3000000 407 ns/op 144 B/op 5 allocs/op |
| 172 | +BenchmarkGoji_ParamWrite-8 2000000 594 ns/op 336 B/op 2 allocs/op |
| 173 | +BenchmarkHTTPRouter_ParamWrite-8 10000000 166 ns/op 32 B/op 1 allocs/op |
| 174 | +``` |
| 175 | + |
| 176 | +You can run this benchmark by using `go test -bench=.` in `xmux`'s root. |
| 177 | + |
| 178 | + |
| 179 | +## Licenses |
| 180 | + |
| 181 | +All source code is licensed under the [MIT License](https://raw.github.com/rs/xhandler/master/LICENSE). |
| 182 | + |
| 183 | +Xmux is forked from [httprouter](https://github.com/julienschmidt/httprouter) with [BSD License](https://github.com/julienschmidt/httprouter/blob/master/LICENSE). |
0 commit comments