Skip to content

Commit bb7dc83

Browse files
committed
Introduction of RouteGroup concept
RouteGroup is a simple route mapper that delegates to an underlying Router instance when mapping paths and takes no part in handling of requests resulting in zero added per-request cost. Implemented as a simple struct to keep in the spirit of httprouter's design. Potential improvement being to create an interface for the common functions of Router and RouteGroup.
1 parent 6aacfd5 commit bb7dc83

File tree

4 files changed

+190
-0
lines changed

4 files changed

+190
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ to the correct URL.
3535
just give the path segment a name and the router delivers the dynamic value to
3636
you. Because of the design of the router, path parameters are very cheap.
3737

38+
**RouteGroups:** A way to create groups of routes without incuring any per-request overhead.
39+
3840
**Zero Garbage:** The matching and dispatching process generates zero bytes of
3941
garbage. In fact, the only heap allocations that are made, is by building the
4042
slice of the key-value pairs for path parameters. If the request path contains

routegroup.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package httprouter
2+
3+
import (
4+
"net/http"
5+
)
6+
7+
type RouteGroup struct {
8+
r *Router
9+
p string
10+
}
11+
12+
func newRouteGroup(r *Router, path string) *RouteGroup {
13+
if path[0] != '/' {
14+
panic("path must begin with '/' in path '" + path + "'")
15+
}
16+
17+
//Strip traling / (if present) as all added sub paths must start with a /
18+
if path[len(path)-1] == '/' {
19+
path = path[:len(path)-1]
20+
}
21+
return &RouteGroup{r: r, p: path}
22+
}
23+
24+
func (r *RouteGroup) NewGroup(path string) *RouteGroup {
25+
return newRouteGroup(r.r, r.subPath(path))
26+
}
27+
28+
func (r *RouteGroup) Handle(method, path string, handle Handle) {
29+
r.r.Handle(method, r.subPath(path), handle)
30+
}
31+
32+
func (r *RouteGroup) Handler(method, path string, handler http.Handler) {
33+
r.r.Handler(method, r.subPath(path), handler)
34+
}
35+
36+
func (r *RouteGroup) HandlerFunc(method, path string, handler http.HandlerFunc) {
37+
r.r.HandlerFunc(method, r.subPath(path), handler)
38+
}
39+
40+
func (r *RouteGroup) GET(path string, handle Handle) {
41+
r.Handle("GET", path, handle)
42+
}
43+
func (r *RouteGroup) HEAD(path string, handle Handle) {
44+
r.Handle("HEAD", path, handle)
45+
}
46+
func (r *RouteGroup) OPTIONS(path string, handle Handle) {
47+
r.Handle("OPTIONS", path, handle)
48+
}
49+
func (r *RouteGroup) POST(path string, handle Handle) {
50+
r.Handle("POST", path, handle)
51+
}
52+
func (r *RouteGroup) PUT(path string, handle Handle) {
53+
r.Handle("PUT", path, handle)
54+
}
55+
func (r *RouteGroup) PATCH(path string, handle Handle) {
56+
r.Handle("PATCH", path, handle)
57+
}
58+
func (r *RouteGroup) DELETE(path string, handle Handle) {
59+
r.Handle("DELETE", path, handle)
60+
}
61+
62+
func (r *RouteGroup) subPath(path string) string {
63+
if path[0] != '/' {
64+
panic("path must start with a '/'")
65+
}
66+
return r.p + path
67+
}

routegroup_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package httprouter
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
)
7+
8+
func TestRouteGroupOfARouteGroup(t *testing.T) {
9+
var get bool
10+
router := New()
11+
foo := router.NewGroup("/foo") // creates /foo group
12+
bar := foo.NewGroup("/bar")
13+
14+
bar.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
15+
get = true
16+
})
17+
18+
w := new(mockResponseWriter)
19+
20+
r, _ := http.NewRequest("GET", "/foo/bar/GET", nil)
21+
router.ServeHTTP(w, r)
22+
if !get {
23+
t.Error("routing GET /foo/bar/GET failed")
24+
}
25+
26+
}
27+
28+
func TestRouteGroupAPI(t *testing.T) {
29+
var get, head, options, post, put, patch, delete, handler, handlerFunc bool
30+
31+
httpHandler := handlerStruct{&handler}
32+
33+
router := New()
34+
group := router.NewGroup("/foo") // creates /foo group
35+
36+
group.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
37+
get = true
38+
})
39+
group.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
40+
head = true
41+
})
42+
group.OPTIONS("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
43+
options = true
44+
})
45+
group.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) {
46+
post = true
47+
})
48+
group.PUT("/PUT", func(w http.ResponseWriter, r *http.Request, _ Params) {
49+
put = true
50+
})
51+
group.PATCH("/PATCH", func(w http.ResponseWriter, r *http.Request, _ Params) {
52+
patch = true
53+
})
54+
group.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ Params) {
55+
delete = true
56+
})
57+
group.Handler("GET", "/Handler", httpHandler)
58+
group.HandlerFunc("GET", "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) {
59+
handlerFunc = true
60+
})
61+
62+
w := new(mockResponseWriter)
63+
64+
r, _ := http.NewRequest("GET", "/foo/GET", nil)
65+
router.ServeHTTP(w, r)
66+
if !get {
67+
t.Error("routing /foo/GET failed")
68+
}
69+
70+
r, _ = http.NewRequest("HEAD", "/foo/GET", nil)
71+
router.ServeHTTP(w, r)
72+
if !head {
73+
t.Error("routing HEAD failed")
74+
}
75+
76+
r, _ = http.NewRequest("OPTIONS", "/foo/GET", nil)
77+
router.ServeHTTP(w, r)
78+
if !options {
79+
t.Error("routing OPTIONS failed")
80+
}
81+
82+
r, _ = http.NewRequest("POST", "/foo/POST", nil)
83+
router.ServeHTTP(w, r)
84+
if !post {
85+
t.Error("routing POST failed")
86+
}
87+
88+
r, _ = http.NewRequest("PUT", "/foo/PUT", nil)
89+
router.ServeHTTP(w, r)
90+
if !put {
91+
t.Error("routing PUT failed")
92+
}
93+
94+
r, _ = http.NewRequest("PATCH", "/foo/PATCH", nil)
95+
router.ServeHTTP(w, r)
96+
if !patch {
97+
t.Error("routing PATCH failed")
98+
}
99+
100+
r, _ = http.NewRequest("DELETE", "/foo/DELETE", nil)
101+
router.ServeHTTP(w, r)
102+
if !delete {
103+
t.Error("routing DELETE failed")
104+
}
105+
106+
r, _ = http.NewRequest("GET", "/foo/Handler", nil)
107+
router.ServeHTTP(w, r)
108+
if !handler {
109+
t.Error("routing Handler failed")
110+
}
111+
112+
r, _ = http.NewRequest("GET", "/foo/HandlerFunc", nil)
113+
router.ServeHTTP(w, r)
114+
if !handlerFunc {
115+
t.Error("routing HandlerFunc failed")
116+
}
117+
}

router.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ func New() *Router {
168168
}
169169
}
170170

171+
func (r *Router) NewGroup(path string) *RouteGroup {
172+
return newRouteGroup(r, path)
173+
}
174+
171175
// GET is a shortcut for router.Handle("GET", path, handle)
172176
func (r *Router) GET(path string, handle Handle) {
173177
r.Handle("GET", path, handle)

0 commit comments

Comments
 (0)