Skip to content

Commit

Permalink
Route grouping #27 (#34)
Browse files Browse the repository at this point in the history
[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
  • Loading branch information
bnkamalesh authored Oct 12, 2021
1 parent ba2999f commit e91a3a5
Show file tree
Hide file tree
Showing 18 changed files with 236 additions and 59 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: go
go:
# webgo is still compatible with 1.8, but the tests are importing versioned
# modules which fails for older Go versions. And using `errors.Is` which was introduced in Go 1.13
# modules which fails for older Go versions and using `errors.Is` which was introduced in Go 1.13
# - "1.8"
- "1.13"
- master
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![](https://godoc.org/github.com/nathany/looper?status.svg)](http://godoc.org/github.com/bnkamalesh/webgo)
[![](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go#web-frameworks)

# WebGo v5.3.1
# WebGo v6.2.0

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).

Expand Down Expand Up @@ -118,7 +118,7 @@ cfg := &webgo.Config{
CertFile: "/path/to/certfile",
KeyFile: "/path/to/keyfile",
}
router := webgo.NewRouter(cfg, routes())
router := webgo.NewRouter(cfg, routes()...)
router.StartHTTPS()
```

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

router := webgo.NewRouter(cfg, routes())
router := webgo.NewRouter(cfg, routes()...)
go router.StartHTTPS()
router.Start()
```
Expand All @@ -154,7 +154,7 @@ func main() {
WriteTimeout: 60 * time.Second,
ShutdownTimeout: 15 * time.Second,
}
router := webgo.NewRouter(cfg, routes())
router := webgo.NewRouter(cfg, routes()...)

go func() {
<-osSig
Expand Down
10 changes: 8 additions & 2 deletions cmd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,17 @@ You can try the following API calls with the sample app. It also uses all the fe
- http://localhost:8080/matchall/hello
- http://localhost:8080/matchall/hello/world
- http://localhost:8080/matchall/hello/world/user
3. `http://localhost:8080/api/<param>
3. `http://localhost:8080/api/<param>`
- Route with a named 'param' configured
- It will match all requests which match `/api/<single parameter>`
- e.g.
- http://localhost:8080/api/hello
- http://localhost:8080/api/world
4. `http://localhost:8080/error-setter`
- Route which sets an error and sets response status 500
- Route which sets an error and sets response status 500
5. `http://localhost:8080/v5.4/api/<param>`
- Route with a named 'param' configured
- It will match all requests which match `/v5.4/api/<single parameter>`
- e.g.
- http://localhost:8080/v5.4/api/hello
- http://localhost:8080/v5.4/api/world
31 changes: 24 additions & 7 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (
"strings"
"time"

"github.com/bnkamalesh/webgo/v5"
"github.com/bnkamalesh/webgo/v5/middleware/accesslog"
"github.com/bnkamalesh/webgo/v5/middleware/cors"
"github.com/bnkamalesh/webgo/v6"
"github.com/bnkamalesh/webgo/v6/middleware/accesslog"
"github.com/bnkamalesh/webgo/v6/middleware/cors"
)

var (
Expand Down Expand Up @@ -77,6 +77,11 @@ func errLogger(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
}
}

func routegroupMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
w.Header().Add("routegroup", "true")
next(w, r)
}

// StaticFiles is used to serve static files
func StaticFiles(rw http.ResponseWriter, r *http.Request) {
wctx := webgo.Context(r)
Expand Down Expand Up @@ -154,14 +159,26 @@ func main() {
WriteTimeout: 60 * time.Second,
}

router := webgo.NewRouter(cfg, getRoutes())
router.UseOnSpecialHandlers(accesslog.AccessLog)
router.Use(errLogger, accesslog.AccessLog, cors.CORS(nil))

webgo.GlobalLoggerConfig(
nil, nil,
webgo.LogCfgDisableDebug,
)

routeGroup := webgo.NewRouteGroup("/v6.2", false)
routeGroup.Add(webgo.Route{
Name: "router-group-prefix-v6.2_api",
Method: http.MethodGet,
Pattern: "/api/:param",
Handlers: []http.HandlerFunc{chain, helloWorld},
})
routeGroup.Use(routegroupMiddleware)

routes := getRoutes()
routes = append(routes, routeGroup.Routes()...)

router := webgo.NewRouter(cfg, routes...)
router.UseOnSpecialHandlers(accesslog.AccessLog)
router.Use(errLogger, accesslog.AccessLog, cors.CORS(nil))

router.Start()
}
3 changes: 3 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
)

func TestConfig_LoadInvalid(t *testing.T) {
t.Parallel()
tl := &testLogger{
out: bytes.Buffer{},
}
Expand All @@ -28,6 +29,7 @@ func TestConfig_LoadInvalid(t *testing.T) {
}

func TestConfig_LoadValid(t *testing.T) {
t.Parallel()
cfg := Config{}
cfg.Load("tests/config.json")

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

func TestConfig_Validate(t *testing.T) {
t.Parallel()
type fields struct {
Host string
Port string
Expand Down
1 change: 1 addition & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
)

func Test_loggerWithCfg(t *testing.T) {
t.Parallel()
cfgs := []logCfg{
LogCfgDisableDebug,
LogCfgDisableInfo,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/bnkamalesh/webgo/v5
module github.com/bnkamalesh/webgo/v6

go 1.14

2 changes: 1 addition & 1 deletion middleware/accesslog/accesslog.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"net/http"
"time"

"github.com/bnkamalesh/webgo/v5"
"github.com/bnkamalesh/webgo/v6"
)

// AccessLog is a middleware which prints access log to stdout
Expand Down
14 changes: 6 additions & 8 deletions middleware/accesslog/accesslog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"testing"
"time"

"github.com/bnkamalesh/webgo/v5"
"github.com/bnkamalesh/webgo/v6"
)

func TestAccessLog(t *testing.T) {
Expand Down Expand Up @@ -80,13 +80,11 @@ func setup(port string) (*webgo.Router, error) {
CertFile: "tests/ssl/server.crt",
KeyFile: "tests/ssl/server.key",
}
router := webgo.NewRouter(cfg, []*webgo.Route{
{
Name: "hello",
Pattern: "/hello",
Method: http.MethodGet,
Handlers: []http.HandlerFunc{handler},
},
router := webgo.NewRouter(cfg, &webgo.Route{
Name: "hello",
Pattern: "/hello",
Method: http.MethodGet,
Handlers: []http.HandlerFunc{handler},
})
return router, nil
}
2 changes: 1 addition & 1 deletion middleware/cors/cors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"sort"
"strings"

"github.com/bnkamalesh/webgo/v5"
"github.com/bnkamalesh/webgo/v6"
)

const (
Expand Down
4 changes: 2 additions & 2 deletions middleware/cors/cors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"testing"
"time"

"github.com/bnkamalesh/webgo/v5"
"github.com/bnkamalesh/webgo/v6"
)

func TestCORSEmptyconfig(t *testing.T) {
Expand Down Expand Up @@ -229,6 +229,6 @@ func setup(port string, routes []*webgo.Route) (*webgo.Router, error) {
CertFile: "tests/ssl/server.crt",
KeyFile: "tests/ssl/server.key",
}
router := webgo.NewRouter(cfg, routes)
router := webgo.NewRouter(cfg, routes...)
return router, nil
}
7 changes: 7 additions & 0 deletions responses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
)

func TestSendHeader(t *testing.T) {
t.Parallel()

w := httptest.NewRecorder()
SendHeader(w, http.StatusNoContent)
if w.Result().StatusCode != http.StatusNoContent {
Expand All @@ -19,6 +21,7 @@ func TestSendHeader(t *testing.T) {
}

func TestSendError(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
payload := map[string]string{"message": "hello world"}
SendError(w, payload, http.StatusBadRequest)
Expand Down Expand Up @@ -95,6 +98,7 @@ func TestSendError(t *testing.T) {
}

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

Expand Down Expand Up @@ -170,6 +174,7 @@ func TestSendResponse(t *testing.T) {
}

func TestSend(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
payload := map[string]string{"hello": "world"}
reqBody, _ := json.Marshal(payload)
Expand Down Expand Up @@ -208,6 +213,7 @@ func TestSend(t *testing.T) {
}

func TestRender(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
data := struct {
Hello string
Expand Down Expand Up @@ -274,6 +280,7 @@ func TestRender(t *testing.T) {
}

func TestResponsehelpers(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
want := "hello world"
resp := struct {
Expand Down
60 changes: 60 additions & 0 deletions route.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ type Route struct {
// uriPattern is the compiled regex to match the URI pattern
uriPattern *regexp.Regexp

// skipMiddleware if true, middleware added using `router` will not be applied to this Route.
// This is used only when a Route is set using the RouteGroup, which can have its own set of middleware
skipMiddleware bool

initialized bool

serve http.HandlerFunc
}

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

// init prepares the URIKeys, compile regex for the provided pattern
func (r *Route) init() error {
if r.initialized {
return nil
}

patternString := r.Pattern

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

r.initialized = true
return nil
}

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

func (r *Route) use(mm ...Middleware) {
for idx := range mm {
m := mm[idx]
srv := r.serve
r.serve = func(rw http.ResponseWriter, req *http.Request) {
m(rw, req, srv)
}
}
}

func routeServeChainedHandlers(r *Route) http.HandlerFunc {
return func(rw http.ResponseWriter, req *http.Request) {

Expand All @@ -185,3 +206,42 @@ func defaultRouteServe(r *Route) http.HandlerFunc {
// is already written or fallthrough is enabled
return r.Handlers[0]
}

type RouteGroup struct {
routes []*Route
// skipRouterMiddleware if set to true, middleware applied to the router will not be applied
// to this route group.
skipRouterMiddleware bool
// PathPrefix is the URI prefix for all routes in this group
PathPrefix string
}

func (rg *RouteGroup) Add(rr ...Route) {
for idx := range rr {
route := rr[idx]
route.skipMiddleware = rg.skipRouterMiddleware
route.Pattern = fmt.Sprintf("%s%s", rg.PathPrefix, route.Pattern)
_ = route.init()
rg.routes = append(rg.routes, &route)
}
}

func (rg *RouteGroup) Use(mm ...Middleware) {
for idx := range rg.routes {
route := rg.routes[idx]
route.use(mm...)
}
}

func (rg *RouteGroup) Routes() []*Route {
return rg.routes
}

func NewRouteGroup(pathPrefix string, skipRouterMiddleware bool, rr ...Route) *RouteGroup {
rg := RouteGroup{
PathPrefix: pathPrefix,
skipRouterMiddleware: skipRouterMiddleware,
}
rg.Add(rr...)
return &rg
}
Loading

0 comments on commit e91a3a5

Please sign in to comment.