Skip to content

Commit e91a3a5

Browse files
authored
Route grouping #27 (#34)
[major] NewRouter function now accepts optional Routes instead of slice of Routes [minor] Router now has a convenience method `Add(routes...*Route)` [minor] Route grouping feature added (usage available in the sample app, `cmd/main.go`) [-] updated tests [-] fixed module version in go.mod [-] fixed version in go.mod [-] fixed v6 imports [-] fixed middleware not applied on routegroups
1 parent ba2999f commit e91a3a5

18 files changed

+236
-59
lines changed

Diff for: .travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
language: go
22
go:
33
# webgo is still compatible with 1.8, but the tests are importing versioned
4-
# modules which fails for older Go versions. And using `errors.Is` which was introduced in Go 1.13
4+
# modules which fails for older Go versions and using `errors.Is` which was introduced in Go 1.13
55
# - "1.8"
66
- "1.13"
77
- master

Diff for: README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[![](https://godoc.org/github.com/nathany/looper?status.svg)](http://godoc.org/github.com/bnkamalesh/webgo)
88
[![](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go#web-frameworks)
99

10-
# WebGo v5.3.1
10+
# WebGo v6.2.0
1111

1212
WebGo is a minimalistic framework for [Go](https://golang.org) to build web applications (server side) with no 3rd party dependencies. WebGo will always be Go standard library compliant; with the HTTP handlers having the same signature as [http.HandlerFunc](https://golang.org/pkg/net/http/#HandlerFunc).
1313

@@ -118,7 +118,7 @@ cfg := &webgo.Config{
118118
CertFile: "/path/to/certfile",
119119
KeyFile: "/path/to/keyfile",
120120
}
121-
router := webgo.NewRouter(cfg, routes())
121+
router := webgo.NewRouter(cfg, routes()...)
122122
router.StartHTTPS()
123123
```
124124

@@ -132,7 +132,7 @@ cfg := &webgo.Config{
132132
KeyFile: "/path/to/keyfile",
133133
}
134134

135-
router := webgo.NewRouter(cfg, routes())
135+
router := webgo.NewRouter(cfg, routes()...)
136136
go router.StartHTTPS()
137137
router.Start()
138138
```
@@ -154,7 +154,7 @@ func main() {
154154
WriteTimeout: 60 * time.Second,
155155
ShutdownTimeout: 15 * time.Second,
156156
}
157-
router := webgo.NewRouter(cfg, routes())
157+
router := webgo.NewRouter(cfg, routes()...)
158158

159159
go func() {
160160
<-osSig

Diff for: cmd/README.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,17 @@ You can try the following API calls with the sample app. It also uses all the fe
4141
- http://localhost:8080/matchall/hello
4242
- http://localhost:8080/matchall/hello/world
4343
- http://localhost:8080/matchall/hello/world/user
44-
3. `http://localhost:8080/api/<param>
44+
3. `http://localhost:8080/api/<param>`
4545
- Route with a named 'param' configured
4646
- It will match all requests which match `/api/<single parameter>`
4747
- e.g.
4848
- http://localhost:8080/api/hello
4949
- http://localhost:8080/api/world
5050
4. `http://localhost:8080/error-setter`
51-
- Route which sets an error and sets response status 500
51+
- Route which sets an error and sets response status 500
52+
5. `http://localhost:8080/v5.4/api/<param>`
53+
- Route with a named 'param' configured
54+
- It will match all requests which match `/v5.4/api/<single parameter>`
55+
- e.g.
56+
- http://localhost:8080/v5.4/api/hello
57+
- http://localhost:8080/v5.4/api/world

Diff for: cmd/main.go

+24-7
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import (
99
"strings"
1010
"time"
1111

12-
"github.com/bnkamalesh/webgo/v5"
13-
"github.com/bnkamalesh/webgo/v5/middleware/accesslog"
14-
"github.com/bnkamalesh/webgo/v5/middleware/cors"
12+
"github.com/bnkamalesh/webgo/v6"
13+
"github.com/bnkamalesh/webgo/v6/middleware/accesslog"
14+
"github.com/bnkamalesh/webgo/v6/middleware/cors"
1515
)
1616

1717
var (
@@ -77,6 +77,11 @@ func errLogger(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
7777
}
7878
}
7979

80+
func routegroupMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
81+
w.Header().Add("routegroup", "true")
82+
next(w, r)
83+
}
84+
8085
// StaticFiles is used to serve static files
8186
func StaticFiles(rw http.ResponseWriter, r *http.Request) {
8287
wctx := webgo.Context(r)
@@ -154,14 +159,26 @@ func main() {
154159
WriteTimeout: 60 * time.Second,
155160
}
156161

157-
router := webgo.NewRouter(cfg, getRoutes())
158-
router.UseOnSpecialHandlers(accesslog.AccessLog)
159-
router.Use(errLogger, accesslog.AccessLog, cors.CORS(nil))
160-
161162
webgo.GlobalLoggerConfig(
162163
nil, nil,
163164
webgo.LogCfgDisableDebug,
164165
)
165166

167+
routeGroup := webgo.NewRouteGroup("/v6.2", false)
168+
routeGroup.Add(webgo.Route{
169+
Name: "router-group-prefix-v6.2_api",
170+
Method: http.MethodGet,
171+
Pattern: "/api/:param",
172+
Handlers: []http.HandlerFunc{chain, helloWorld},
173+
})
174+
routeGroup.Use(routegroupMiddleware)
175+
176+
routes := getRoutes()
177+
routes = append(routes, routeGroup.Routes()...)
178+
179+
router := webgo.NewRouter(cfg, routes...)
180+
router.UseOnSpecialHandlers(accesslog.AccessLog)
181+
router.Use(errLogger, accesslog.AccessLog, cors.CORS(nil))
182+
166183
router.Start()
167184
}

Diff for: config_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
)
88

99
func TestConfig_LoadInvalid(t *testing.T) {
10+
t.Parallel()
1011
tl := &testLogger{
1112
out: bytes.Buffer{},
1213
}
@@ -28,6 +29,7 @@ func TestConfig_LoadInvalid(t *testing.T) {
2829
}
2930

3031
func TestConfig_LoadValid(t *testing.T) {
32+
t.Parallel()
3133
cfg := Config{}
3234
cfg.Load("tests/config.json")
3335

@@ -42,6 +44,7 @@ func TestConfig_LoadValid(t *testing.T) {
4244
}
4345

4446
func TestConfig_Validate(t *testing.T) {
47+
t.Parallel()
4548
type fields struct {
4649
Host string
4750
Port string

Diff for: errors_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
)
66

77
func Test_loggerWithCfg(t *testing.T) {
8+
t.Parallel()
89
cfgs := []logCfg{
910
LogCfgDisableDebug,
1011
LogCfgDisableInfo,

Diff for: go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module github.com/bnkamalesh/webgo/v5
1+
module github.com/bnkamalesh/webgo/v6
22

33
go 1.14
44

Diff for: middleware/accesslog/accesslog.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"net/http"
1111
"time"
1212

13-
"github.com/bnkamalesh/webgo/v5"
13+
"github.com/bnkamalesh/webgo/v6"
1414
)
1515

1616
// AccessLog is a middleware which prints access log to stdout

Diff for: middleware/accesslog/accesslog_test.go

+6-8
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"testing"
1515
"time"
1616

17-
"github.com/bnkamalesh/webgo/v5"
17+
"github.com/bnkamalesh/webgo/v6"
1818
)
1919

2020
func TestAccessLog(t *testing.T) {
@@ -80,13 +80,11 @@ func setup(port string) (*webgo.Router, error) {
8080
CertFile: "tests/ssl/server.crt",
8181
KeyFile: "tests/ssl/server.key",
8282
}
83-
router := webgo.NewRouter(cfg, []*webgo.Route{
84-
{
85-
Name: "hello",
86-
Pattern: "/hello",
87-
Method: http.MethodGet,
88-
Handlers: []http.HandlerFunc{handler},
89-
},
83+
router := webgo.NewRouter(cfg, &webgo.Route{
84+
Name: "hello",
85+
Pattern: "/hello",
86+
Method: http.MethodGet,
87+
Handlers: []http.HandlerFunc{handler},
9088
})
9189
return router, nil
9290
}

Diff for: middleware/cors/cors.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"sort"
1717
"strings"
1818

19-
"github.com/bnkamalesh/webgo/v5"
19+
"github.com/bnkamalesh/webgo/v6"
2020
)
2121

2222
const (

Diff for: middleware/cors/cors_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
"testing"
1919
"time"
2020

21-
"github.com/bnkamalesh/webgo/v5"
21+
"github.com/bnkamalesh/webgo/v6"
2222
)
2323

2424
func TestCORSEmptyconfig(t *testing.T) {
@@ -229,6 +229,6 @@ func setup(port string, routes []*webgo.Route) (*webgo.Router, error) {
229229
CertFile: "tests/ssl/server.crt",
230230
KeyFile: "tests/ssl/server.key",
231231
}
232-
router := webgo.NewRouter(cfg, routes)
232+
router := webgo.NewRouter(cfg, routes...)
233233
return router, nil
234234
}

Diff for: responses_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
)
1212

1313
func TestSendHeader(t *testing.T) {
14+
t.Parallel()
15+
1416
w := httptest.NewRecorder()
1517
SendHeader(w, http.StatusNoContent)
1618
if w.Result().StatusCode != http.StatusNoContent {
@@ -19,6 +21,7 @@ func TestSendHeader(t *testing.T) {
1921
}
2022

2123
func TestSendError(t *testing.T) {
24+
t.Parallel()
2225
w := httptest.NewRecorder()
2326
payload := map[string]string{"message": "hello world"}
2427
SendError(w, payload, http.StatusBadRequest)
@@ -95,6 +98,7 @@ func TestSendError(t *testing.T) {
9598
}
9699

97100
func TestSendResponse(t *testing.T) {
101+
t.Parallel()
98102
w := httptest.NewRecorder()
99103
payload := map[string]string{"hello": "world"}
100104

@@ -170,6 +174,7 @@ func TestSendResponse(t *testing.T) {
170174
}
171175

172176
func TestSend(t *testing.T) {
177+
t.Parallel()
173178
w := httptest.NewRecorder()
174179
payload := map[string]string{"hello": "world"}
175180
reqBody, _ := json.Marshal(payload)
@@ -208,6 +213,7 @@ func TestSend(t *testing.T) {
208213
}
209214

210215
func TestRender(t *testing.T) {
216+
t.Parallel()
211217
w := httptest.NewRecorder()
212218
data := struct {
213219
Hello string
@@ -274,6 +280,7 @@ func TestRender(t *testing.T) {
274280
}
275281

276282
func TestResponsehelpers(t *testing.T) {
283+
t.Parallel()
277284
w := httptest.NewRecorder()
278285
want := "hello world"
279286
resp := struct {

Diff for: route.go

+60
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ type Route struct {
3333
// uriPattern is the compiled regex to match the URI pattern
3434
uriPattern *regexp.Regexp
3535

36+
// skipMiddleware if true, middleware added using `router` will not be applied to this Route.
37+
// This is used only when a Route is set using the RouteGroup, which can have its own set of middleware
38+
skipMiddleware bool
39+
40+
initialized bool
41+
3642
serve http.HandlerFunc
3743
}
3844

@@ -112,6 +118,10 @@ func (r *Route) parseURIWithParams(patternString string) (string, error) {
112118

113119
// init prepares the URIKeys, compile regex for the provided pattern
114120
func (r *Route) init() error {
121+
if r.initialized {
122+
return nil
123+
}
124+
115125
patternString := r.Pattern
116126

117127
patternString, err := r.parseURIWithParams(patternString)
@@ -135,6 +145,7 @@ func (r *Route) init() error {
135145
r.uriPatternString = patternString
136146
r.serve = defaultRouteServe(r)
137147

148+
r.initialized = true
138149
return nil
139150
}
140151

@@ -159,6 +170,16 @@ func (r *Route) params(requestURI string) map[string]string {
159170
return uriValues
160171
}
161172

173+
func (r *Route) use(mm ...Middleware) {
174+
for idx := range mm {
175+
m := mm[idx]
176+
srv := r.serve
177+
r.serve = func(rw http.ResponseWriter, req *http.Request) {
178+
m(rw, req, srv)
179+
}
180+
}
181+
}
182+
162183
func routeServeChainedHandlers(r *Route) http.HandlerFunc {
163184
return func(rw http.ResponseWriter, req *http.Request) {
164185

@@ -185,3 +206,42 @@ func defaultRouteServe(r *Route) http.HandlerFunc {
185206
// is already written or fallthrough is enabled
186207
return r.Handlers[0]
187208
}
209+
210+
type RouteGroup struct {
211+
routes []*Route
212+
// skipRouterMiddleware if set to true, middleware applied to the router will not be applied
213+
// to this route group.
214+
skipRouterMiddleware bool
215+
// PathPrefix is the URI prefix for all routes in this group
216+
PathPrefix string
217+
}
218+
219+
func (rg *RouteGroup) Add(rr ...Route) {
220+
for idx := range rr {
221+
route := rr[idx]
222+
route.skipMiddleware = rg.skipRouterMiddleware
223+
route.Pattern = fmt.Sprintf("%s%s", rg.PathPrefix, route.Pattern)
224+
_ = route.init()
225+
rg.routes = append(rg.routes, &route)
226+
}
227+
}
228+
229+
func (rg *RouteGroup) Use(mm ...Middleware) {
230+
for idx := range rg.routes {
231+
route := rg.routes[idx]
232+
route.use(mm...)
233+
}
234+
}
235+
236+
func (rg *RouteGroup) Routes() []*Route {
237+
return rg.routes
238+
}
239+
240+
func NewRouteGroup(pathPrefix string, skipRouterMiddleware bool, rr ...Route) *RouteGroup {
241+
rg := RouteGroup{
242+
PathPrefix: pathPrefix,
243+
skipRouterMiddleware: skipRouterMiddleware,
244+
}
245+
rg.Add(rr...)
246+
return &rg
247+
}

0 commit comments

Comments
 (0)