router: auto wrapping of http.Handler and http.HandlerFunc
diff --git a/router.go b/router.go index f18a678..1d70c66 100644 --- a/router.go +++ b/router.go
@@ -85,6 +85,32 @@ // wildcards (variables). type Handle func(http.ResponseWriter, *http.Request, Params) +// Wrap is the default wrap function to wrap handle types to Handle. +// Currently Handle, http.Handler and http.HandlerFunc are supported. +// Opther handle types can be used by setting a custom wrap function. +func Wrap(handle interface{}) Handle { + switch h := handle.(type) { + case Handle: + return h + case func(http.ResponseWriter, *http.Request, Params): + return h + case http.HandlerFunc: + return func(w http.ResponseWriter, req *http.Request, _ Params) { + h(w, req) + } + case func(http.ResponseWriter, *http.Request): + return func(w http.ResponseWriter, req *http.Request, _ Params) { + h(w, req) + } + case http.Handler: + return func(w http.ResponseWriter, req *http.Request, _ Params) { + h.ServeHTTP(w, req) + } + default: + panic("unknown handle type") + } +} + // Param is a single URL parameter, consisting of a key and a value. type Param struct { Key string @@ -159,6 +185,11 @@ // The handler can be used to keep your server from crashing because of // unrecovered panics. PanicHandler func(http.ResponseWriter, *http.Request, interface{}) + + // Function to wrap handle types to Handle. The default function is Wrap. + // Custom functions can be used to allow other handle types or apply pre- / + // postprocessing of the request. + Wrap func(interface{}) Handle } // Make sure the Router conforms with the http.Handler interface @@ -172,41 +203,42 @@ RedirectFixedPath: true, HandleMethodNotAllowed: true, HandleOPTIONS: true, + Wrap: Wrap, } } // GET is a shortcut for router.Handle("GET", path, handle) -func (r *Router) GET(path string, handle Handle) { +func (r *Router) GET(path string, handle interface{}) { r.Handle("GET", path, handle) } // HEAD is a shortcut for router.Handle("HEAD", path, handle) -func (r *Router) HEAD(path string, handle Handle) { +func (r *Router) HEAD(path string, handle interface{}) { r.Handle("HEAD", path, handle) } // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle) -func (r *Router) OPTIONS(path string, handle Handle) { +func (r *Router) OPTIONS(path string, handle interface{}) { r.Handle("OPTIONS", path, handle) } // POST is a shortcut for router.Handle("POST", path, handle) -func (r *Router) POST(path string, handle Handle) { +func (r *Router) POST(path string, handle interface{}) { r.Handle("POST", path, handle) } // PUT is a shortcut for router.Handle("PUT", path, handle) -func (r *Router) PUT(path string, handle Handle) { +func (r *Router) PUT(path string, handle interface{}) { r.Handle("PUT", path, handle) } // PATCH is a shortcut for router.Handle("PATCH", path, handle) -func (r *Router) PATCH(path string, handle Handle) { +func (r *Router) PATCH(path string, handle interface{}) { r.Handle("PATCH", path, handle) } // DELETE is a shortcut for router.Handle("DELETE", path, handle) -func (r *Router) DELETE(path string, handle Handle) { +func (r *Router) DELETE(path string, handle interface{}) { r.Handle("DELETE", path, handle) } @@ -218,11 +250,16 @@ // This function is intended for bulk loading and to allow the usage of less // frequently used, non-standardized or custom methods (e.g. for internal // communication with a proxy). -func (r *Router) Handle(method, path string, handle Handle) { +func (r *Router) Handle(method, path string, handle interface{}) { if path[0] != '/' { panic("path must begin with '/' in path '" + path + "'") } + if r.Wrap == nil { + r.Wrap = Wrap + } + h := r.Wrap(handle) + if r.trees == nil { r.trees = make(map[string]*node) } @@ -233,23 +270,7 @@ r.trees[method] = root } - 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) { - r.Handler(method, path, handler) + root.addRoute(path, h) } // ServeFiles serves files from the given file system root.
diff --git a/router_test.go b/router_test.go index db57740..300fbd1 100644 --- a/router_test.go +++ b/router_test.go
@@ -76,7 +76,8 @@ } func TestRouterAPI(t *testing.T) { - var get, head, options, post, put, patch, delete, handler, handlerFunc bool + var get, head, options, post, put, patch, delete bool + var handle, handler, handlerFunc1, handlerFunc2 bool httpHandler := handlerStruct{&handler} @@ -102,10 +103,16 @@ router.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ Params) { delete = true }) - router.Handler("GET", "/Handler", httpHandler) - router.HandlerFunc("GET", "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) { - handlerFunc = true + router.Handle("GET", "/Handle", Handle(func(w http.ResponseWriter, r *http.Request, _ Params) { + handle = true + })) + router.Handle("GET", "/Handler", httpHandler) + router.Handle("GET", "/HandlerFunc1", func(w http.ResponseWriter, r *http.Request) { + handlerFunc1 = true }) + router.Handle("GET", "/HandlerFunc2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handlerFunc2 = true + })) w := new(mockResponseWriter) @@ -151,16 +158,57 @@ t.Error("routing DELETE failed") } + r, _ = http.NewRequest("GET", "/Handle", nil) + router.ServeHTTP(w, r) + if !handle { + t.Error("routing Handle failed") + } + r, _ = http.NewRequest("GET", "/Handler", nil) router.ServeHTTP(w, r) if !handler { t.Error("routing Handler failed") } - r, _ = http.NewRequest("GET", "/HandlerFunc", nil) + r, _ = http.NewRequest("GET", "/HandlerFunc1", nil) router.ServeHTTP(w, r) - if !handlerFunc { - t.Error("routing HandlerFunc failed") + if !handlerFunc1 { + t.Error("routing HandlerFunc1 failed") + } + + r, _ = http.NewRequest("GET", "/HandlerFunc2", nil) + router.ServeHTTP(w, r) + if !handlerFunc2 { + t.Error("routing HandlerFunc2 failed") + } +} + +func TestRouterInvalidWrap(t *testing.T) { + r := New() + + // nil handle + recv := catchPanic(func() { + r.Handle("GET", "/nil", nil) + }) + if recv == nil { + t.Errorf("no panic when inserting nil handle") + } + + // handle which can not be wrapped + recv = catchPanic(func() { + r.Handle("GET", "/unknown", mockResponseWriter{}) + }) + if recv == nil { + t.Errorf("no panic when inserting unknown handle") + } + + // no wrap func. Router should fall back to default + r.Wrap = nil + recv = catchPanic(func() { + r.Handle("GET", "/wrap", func(w http.ResponseWriter, r *http.Request, _ Params) {}) + }) + if recv != nil { + t.Errorf("panic when inserting without wrap func") } }