From d767e5908949fd4ad5ada98690d44c86d5b1a893 Mon Sep 17 00:00:00 2001 From: Travis Parker Date: Thu, 22 Feb 2018 06:26:55 -0800 Subject: [PATCH 1/8] Params via Go 1.7 Contexts (#147) * Supports Params in net/http Handlers with go1.7+ Go 1.7 introduces request-scoped context, providing a place to hang Params even in net/http standard Handlers attached with Router.Handler or Router.HandlerFunc. This provides an alternative implementation of Router.Handler which attaches the params to the request context. It also includes a bit of new public API: ParamsKey is the context key used, and ParamsFromContext uses ParamsKey to get Params from a context.Context. All of this is guarded by a "go1.7" build tag. With go 1.6 and older none of the new API is available and Params continue to be thrown away in Handler/HandlerFunc-attached endpoints. * WithValue is the 'context' function * use an un-exported struct{} alias to collision-proof the key * no longer a const * update README.md to reflect additional allocations with the stdlib-compatible api --- README.md | 2 +- nonstdparams.go | 16 ++++++++++++++++ router.go | 10 ---------- stdparams.go | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 nonstdparams.go create mode 100644 stdparams.go diff --git a/README.md b/README.md index a2657d48..eb717f19 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The router is optimized for high performance and a small memory footprint. It sc **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. -**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. If the request path contains no parameters, not a single heap allocation is necessary. +**Zero Garbage:** The matching and dispatching process generates zero bytes of garbage. The only heap allocations that are made are building the slice of the key-value pairs for path parameters, and building new context and request objects (the latter only in the standard `Handler`/`HandlerFunc` api). In the 3-argument API, if the request path contains no parameters not a single heap allocation is necessary. **Best Performance:** [Benchmarks speak for themselves](https://github.com/julienschmidt/go-http-routing-benchmark). See below for technical details of the implementation. diff --git a/nonstdparams.go b/nonstdparams.go new file mode 100644 index 00000000..0278a441 --- /dev/null +++ b/nonstdparams.go @@ -0,0 +1,16 @@ +// +build !go1.7 + +package httprouter + +import "net/http" + +// 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, _ Params) { + handler.ServeHTTP(w, req) + }, + ) +} diff --git a/router.go b/router.go index bb173300..bfd4db04 100644 --- a/router.go +++ b/router.go @@ -236,16 +236,6 @@ func (r *Router) Handle(method, path string, handle Handle) { root.addRoute(path, handle) } -// Handler is an adapter which allows the usage of an http.Handler as a -// request handle. -func (r *Router) Handler(method, path string, handler http.Handler) { - r.Handle(method, path, - func(w http.ResponseWriter, req *http.Request, _ Params) { - 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) { diff --git a/stdparams.go b/stdparams.go new file mode 100644 index 00000000..abb4209d --- /dev/null +++ b/stdparams.go @@ -0,0 +1,38 @@ +// +build go1.7 + +package httprouter + +import ( + "context" + "net/http" +) + +type paramsKey struct{} + +// ParamsKey is the request context key under which URL params are stored. +// +// This is only present from go 1.7. +var ParamsKey = paramsKey{} + +// 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, p Params) { + ctx := req.Context() + ctx = context.WithValue(ctx, ParamsKey, p) + req = req.WithContext(ctx) + handler.ServeHTTP(w, req) + }, + ) +} + +// ParamsFromContext pulls the URL parameters from a request context, +// or returns nil if none are present. +// +// This is only present from go 1.7. +func ParamsFromContext(ctx context.Context) Params { + p, _ := ctx.Value(ParamsKey).(Params) + return p +} From 6115e6d293c580eb958850a81c95a66079696c0d Mon Sep 17 00:00:00 2001 From: Marcus Franke Date: Thu, 22 Feb 2018 15:27:37 +0100 Subject: [PATCH 2/8] travis: go versions (#227) There is a gap in the tested go versions. tip points already to the coming 1.10. I added the kind of missing 1.9 release --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 32713341..7b8ca276 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ go: - 1.6 - 1.7 - 1.8 + - 1.9 - tip before_install: - go get golang.org/x/tools/cmd/cover From 1a335ec78c93251aa9196c093c6f3d95d8ecef97 Mon Sep 17 00:00:00 2001 From: Rodolfo Rodriguez Date: Thu, 22 Feb 2018 08:29:06 -0600 Subject: [PATCH 3/8] Update README.md (#225) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eb717f19..eabf4aad 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ Priority Path Handle Every `*` represents the memory address of a handler function (a pointer). If you follow a path trough the tree from the root to the leaf, you get the complete route path, e.g `\blog\:post\`, where `:post` is just a placeholder ([*parameter*](#named-parameters)) for an actual post name. Unlike hash-maps, a tree structure also allows us to use dynamic parts like the `:post` parameter, since we actually match against the routing patterns instead of just comparing hashes. [As benchmarks show](https://github.com/julienschmidt/go-http-routing-benchmark), this works very well and efficient. -Since URL paths have a hierarchical structure and make use only of a limited set of characters (byte values), it is very likely that there are a lot of common prefixes. This allows us to easily reduce the routing into ever smaller problems. Moreover the router manages a separate tree for every request method. For one thing it is more space efficient than holding a method->handle map in every single node, for another thing is also allows us to greatly reduce the routing problem before even starting the look-up in the prefix-tree. +Since URL paths have a hierarchical structure and make use only of a limited set of characters (byte values), it is very likely that there are a lot of common prefixes. This allows us to easily reduce the routing into ever smaller problems. Moreover the router manages a separate tree for every request method. For one thing it is more space efficient than holding a method->handle map in every single node, it also allows us to greatly reduce the routing problem before even starting the look-up in the prefix-tree. 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: From a859014af401ec5c1e642c95b5708dea5a86579d Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Thu, 22 Feb 2018 15:38:39 +0100 Subject: [PATCH 4/8] rename params files --- stdparams.go => params_go17.go | 0 nonstdparams.go => params_legacy.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename stdparams.go => params_go17.go (100%) rename nonstdparams.go => params_legacy.go (100%) diff --git a/stdparams.go b/params_go17.go similarity index 100% rename from stdparams.go rename to params_go17.go diff --git a/nonstdparams.go b/params_legacy.go similarity index 100% rename from nonstdparams.go rename to params_legacy.go From 8545a7bcb55ffd416fa6d52914b60b8f38b0ada1 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Thu, 22 Feb 2018 16:58:21 +0100 Subject: [PATCH 5/8] travis: test with Go 1.10 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7b8ca276..ee59b69d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,10 @@ sudo: false language: go go: - - 1.6 - 1.7 - 1.8 - 1.9 + - 1.10 - tip before_install: - go get golang.org/x/tools/cmd/cover From d1898390779332322e6b5ca5011da4bf249bb056 Mon Sep 17 00:00:00 2001 From: Julien Schmidt Date: Thu, 22 Feb 2018 17:05:26 +0100 Subject: [PATCH 6/8] travis: fix Go 1.10 env --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ee59b69d..477807dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ go: - 1.7 - 1.8 - 1.9 - - 1.10 + - "1.10" - tip before_install: - go get golang.org/x/tools/cmd/cover From 5aa29e8dfcfa8e46993f5058297931ec75eb93dd Mon Sep 17 00:00:00 2001 From: Huan Wang Date: Wed, 11 Apr 2018 09:39:45 -0600 Subject: [PATCH 7/8] path: fix missing root in paths with length 2 (#239) --- path.go | 2 +- path_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/path.go b/path.go index 486134db..d64edf02 100644 --- a/path.go +++ b/path.go @@ -41,7 +41,7 @@ func CleanPath(p string) string { buf[0] = '/' } - trailing := n > 2 && p[n-1] == '/' + trailing := n > 1 && p[n-1] == '/' // A bit more clunky without a 'lazybuf' like the path package, but the loop // gets completely inlined (bufApp). So in contrast to the path package this diff --git a/path_test.go b/path_test.go index c4ceda5d..75fde322 100644 --- a/path_test.go +++ b/path_test.go @@ -22,6 +22,7 @@ var cleanTests = []struct { // missing root {"", "/"}, + {"a/", "/a/"}, {"abc", "/abc"}, {"abc/def", "/abc/def"}, {"a/b/c", "/a/b/c"}, From adbc77eec0d91467376ca515bc3a14b8434d0f18 Mon Sep 17 00:00:00 2001 From: Todd Niswonger Date: Wed, 11 Apr 2018 10:45:01 -0500 Subject: [PATCH 8/8] Throw 405 if !handleOPTIONS and no OPTIONS handler set (#201) --- router.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/router.go b/router.go index bfd4db04..558e1392 100644 --- a/router.go +++ b/router.go @@ -366,13 +366,11 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } - if req.Method == "OPTIONS" { + if req.Method == "OPTIONS" && r.HandleOPTIONS { // Handle OPTIONS requests - if r.HandleOPTIONS { - if allow := r.allowed(path, req.Method); len(allow) > 0 { - w.Header().Set("Allow", allow) - return - } + if allow := r.allowed(path, req.Method); len(allow) > 0 { + w.Header().Set("Allow", allow) + return } } else { // Handle 405