Skip to content
This repository has been archived by the owner on Mar 16, 2019. It is now read-only.

Commit

Permalink
Merge branch 'middleware'
Browse files Browse the repository at this point in the history
* middleware:
  Split everything into files by responsibility
  Add Router.chain() for middleware chaining
  Add middleware
  Remove json files
  Update README with features
  • Loading branch information
bahlo committed Aug 1, 2014
2 parents 416e564 + 0c2fefe commit 6a41dac
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 78 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
)

func notImplementedHandler(w http.ResponseWriter, r *http.Request, p goat.Params) {
goat.WriteError(w, "Not implemented")
goat.WriteError(w, 500, "Not implemented")
}

func helloHandler(w http.ResponseWriter, r *http.Request, p goat.Params) {
Expand All @@ -37,6 +37,23 @@ func main() {
}
```

## Features
### Groups
You can group the routes by a prefix. This can have a serious impact on the
readability of your code.

### Indices
Every route can have a description (like `user_login_url`). These can be used
to automagically generate an API index (like [this](https://api.github.com)).
If you want to hide specific methods, just provide an empty string.

**Note:** Indices are only supported for `GET` requests. Open an issue, if you
want them on other methods, too

### Helpers
You can quickly pretty print JSON to a `http.ResponseWriter` using
`goat.WriteJSON` or `goat.WriteError`.

## Roadmap
* [x] Subrouters or Grouping
* [ ] Middleware
Expand Down
11 changes: 10 additions & 1 deletion goat.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package goat

import "github.com/julienschmidt/httprouter"
import (
"net/http"

"github.com/julienschmidt/httprouter"
)

// New creates a new Router and returns it
func New() *Router {
Expand All @@ -12,3 +16,8 @@ func New() *Router {

return r
}

// Run starts the server
func (r *Router) Run(address string) error {
return http.ListenAndServe(address, r.chain())
}
38 changes: 38 additions & 0 deletions index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package goat

import (
"net/http"
"sort"
)

// IndexHandler writes the index of all GET methods to the ResponseWriter
func (r *Router) IndexHandler(w http.ResponseWriter, _ *http.Request, _ Params) {
WriteJSON(w, r.Index())
}

// Index returns a string map with the titles and urls of all GET routes
func (r *Router) Index() map[string]string {
index := r.index

// Recursion
for _, sr := range r.children {
si := sr.Index()

for k, v := range si {
index[k] = v
}
}

// Sort
sorted := make(map[string]string)
var keys []string
for k := range index {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
sorted[k] = index[k]
}

return sorted
}
23 changes: 23 additions & 0 deletions index_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package goat

import (
"reflect"
"testing"
)

func TestIndex(t *testing.T) {
r := New()

r.Get("/foo/bar", "foo_bar_url", emptyHandler)
r.Get("/bar", "bar_url", emptyHandler)
r.Post("/foo", "foo_url", emptyHandler)

out := r.Index()
expected := map[string]string{
"bar_url": "/bar",
"foo_bar_url": "/foo/bar",
}
if !reflect.DeepEqual(out, expected) {
t.Errorf("Index should regurn %v, but did return %v", expected, out)
}
}
26 changes: 26 additions & 0 deletions middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package goat

import "net/http"

// Middleware reprents a default middleware function
type Middleware func(http.Handler) http.Handler

// chain calls all middlewares and returns the final handler
func (r *Router) chain() http.Handler {
var final http.Handler
final = r.router

for i := len(r.middleware) - 1; i >= 0; i-- {
final = r.middleware[i](final)
}

return final
}

// Use adds middleware to the router
func (r *Router) Use(middleware ...Middleware) {
if r.parent != nil {
panic("subrouters can't use middleware!")
}
r.middleware = append(r.middleware, middleware...)
}
16 changes: 16 additions & 0 deletions middleware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package goat

import (
"net/http"
"testing"
)

func TestUse(t *testing.T) {
r := New()
mw := func(h http.Handler) http.Handler { return nil }
//exp := []Middleware{mw}

r.Use(mw)

// TODO: Test function equality
}
83 changes: 24 additions & 59 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package goat

import (
"net/http"
"sort"

"github.com/julienschmidt/httprouter"
)
Expand All @@ -21,31 +20,27 @@ type Router struct {

// The router
router *httprouter.Router

// Middleware
middleware []Middleware
}

// Handle describes the function that should be used by handlers
type Handle func(http.ResponseWriter, *http.Request, Params)

// notFoundHandler handles (as you already know) the 404 error
func (r *Router) notFoundHandler(w http.ResponseWriter, req *http.Request) {
WriteError(w, 404, "404 Not Found")
}

// ServeHTTP calls the same method on the router
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.router.ServeHTTP(w, req)
}

// subPath returns the prefix of the router + the given path and eliminates
// duplicate slashes
func (r *Router) subPath(p string) string {
pre := r.prefix

if (pre == "/" || pre[:len(pre)-1] == "/") && p[:1] == "/" {
pre = pre[:len(pre)-1]
// Subrouter creates and returns a subrouter
func (r *Router) Subrouter(path string) *Router {
sr := &Router{
index: make(map[string]string),
prefix: r.subPath(path),
router: r.router,
}

return pre + p
// Init relationships
r.children = append(r.children, sr)
sr.parent = r

return sr
}

// addRoute adds a route to the index and passes it over to the httprouter
Expand Down Expand Up @@ -87,49 +82,19 @@ func (r *Router) Put(path, title string, fn Handle) {
r.addRoute("PUT", path, title, fn)
}

// Subrouter creates and returns a subrouter
func (r *Router) Subrouter(path string) *Router {
sr := &Router{
index: make(map[string]string),
prefix: r.subPath(path),
router: r.router,
}

// Init relationships
r.children = append(r.children, sr)
sr.parent = r

return sr
}

// IndexHandler writes the index of all GET methods to the ResponseWriter
func (r *Router) IndexHandler(w http.ResponseWriter, _ *http.Request, _ Params) {
WriteJSON(w, r.Index())
// notFoundHandler handles (as you already know) the 404 error
func (r *Router) notFoundHandler(w http.ResponseWriter, req *http.Request) {
WriteError(w, 404, "404 Not Found")
}

// Index returns a string map with the titles and urls of all GET routes
func (r *Router) Index() map[string]string {
index := r.index

// Recursion
for _, sr := range r.children {
si := sr.Index()

for k, v := range si {
index[k] = v
}
}
// subPath returns the prefix of the router + the given path and eliminates
// duplicate slashes
func (r *Router) subPath(p string) string {
pre := r.prefix

// Sort
sorted := make(map[string]string)
var keys []string
for k := range index {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
sorted[k] = index[k]
if (pre == "/" || pre[:len(pre)-1] == "/") && p[:1] == "/" {
pre = pre[:len(pre)-1]
}

return sorted
return pre + p
}
17 changes: 0 additions & 17 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,6 @@ func TestAddRoute(t *testing.T) {
}
}

func TestIndex(t *testing.T) {
r := New()

r.Get("/foo/bar", "foo_bar_url", emptyHandler)
r.Get("/bar", "bar_url", emptyHandler)
r.Post("/foo", "foo_url", emptyHandler)

out := r.Index()
expected := map[string]string{
"bar_url": "/bar",
"foo_bar_url": "/foo/bar",
}
if !reflect.DeepEqual(out, expected) {
t.Errorf("Index should regurn %v, but did return %v", expected, out)
}
}

func TestSubrouter(t *testing.T) {
pre := "/user"
r := New()
Expand Down

0 comments on commit 6a41dac

Please # to comment.