From f2926972236e62bb85a77c9ed50514d23fd5b294 Mon Sep 17 00:00:00 2001 From: Carl Jackson Date: Sun, 26 Oct 2014 16:25:25 -0700 Subject: [PATCH] Put more methods on Mux This fixes the documentation wart where half of the methods were defined on "rt *Mux". It doesn't change the public API. --- web/mux.go | 111 +++++++++++++++++++++++++++++++++++++++++++-- web/router.go | 107 ------------------------------------------- web/router_test.go | 101 +++++++++++++++++++---------------------- 3 files changed, 155 insertions(+), 164 deletions(-) diff --git a/web/mux.go b/web/mux.go index 8c38551..7fafa8e 100644 --- a/web/mux.go +++ b/web/mux.go @@ -53,7 +53,7 @@ Handler must be one of the following types: */ type Mux struct { ms mStack - router + rt router } // New creates a new Mux without any routes or middleware. @@ -63,12 +63,12 @@ func New() *Mux { stack: make([]mLayer, 0), pool: makeCPool(), }, - router: router{ + rt: router{ routes: make([]route, 0), notFound: parseHandler(http.NotFound), }, } - mux.ms.router = &mux.router + mux.ms.router = &mux.rt return &mux } @@ -118,3 +118,108 @@ func (m *Mux) Insert(middleware, before interface{}) error { func (m *Mux) Abandon(middleware interface{}) error { return m.ms.Abandon(middleware) } + +// Router functions + +/* +Dispatch to the given handler when the pattern matches, regardless of HTTP +method. See the documentation for type Mux for a description of what types are +accepted for pattern and handler. + +This method is commonly used to implement sub-routing: an admin application, for +instance, can expose a single handler that is attached to the main Mux by +calling Handle("/admin*", adminHandler) or similar. Note that this function +doesn't strip this prefix from the path before forwarding it on (e.g., the +handler will see the full path, including the "/admin" part), but this +functionality can easily be performed by an extra middleware layer. +*/ +func (m *Mux) Handle(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mALL, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// CONNECT. See the documentation for type Mux for a description of what types +// are accepted for pattern and handler. +func (m *Mux) Connect(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mCONNECT, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// DELETE. See the documentation for type Mux for a description of what types +// are accepted for pattern and handler. +func (m *Mux) Delete(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mDELETE, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// GET. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +// +// All GET handlers also transparently serve HEAD requests, since net/http will +// take care of all the fiddly bits for you. If you wish to provide an alternate +// implementation of HEAD, you should add a handler explicitly and place it +// above your GET handler. +func (m *Mux) Get(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mGET|mHEAD, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// HEAD. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *Mux) Head(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mHEAD, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// OPTIONS. See the documentation for type Mux for a description of what types +// are accepted for pattern and handler. +func (m *Mux) Options(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mOPTIONS, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// PATCH. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *Mux) Patch(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mPATCH, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// POST. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *Mux) Post(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mPOST, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// PUT. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *Mux) Put(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mPUT, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// TRACE. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *Mux) Trace(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mTRACE, handler) +} + +// Set the fallback (i.e., 404) handler for this mux. See the documentation for +// type Mux for a description of what types are accepted for handler. +// +// As a convenience, the context environment variable "goji.web.validMethods" +// (also available as the constant ValidMethodsKey) will be set to the list of +// HTTP methods that could have been routed had they been provided on an +// otherwise identical request. +func (m *Mux) NotFound(handler interface{}) { + m.rt.notFound = parseHandler(handler) +} + +// Compile the list of routes into bytecode. This only needs to be done once +// after all the routes have been added, and will be called automatically for +// you (at some performance cost on the first request) if you do not call it +// explicitly. +func (rt *router) Compile() { + rt.compile() +} diff --git a/web/router.go b/web/router.go index 952bf4d..f96f034 100644 --- a/web/router.go +++ b/web/router.go @@ -226,14 +226,6 @@ func (rm routeMachine) route(c *C, w http.ResponseWriter, r *http.Request) (meth return methods, false } -// Compile the list of routes into bytecode. This only needs to be done once -// after all the routes have been added, and will be called automatically for -// you (at some performance cost on the first request) if you do not call it -// explicitly. -func (rt *router) Compile() { - rt.compile() -} - func (rt *router) compile() *routeMachine { rt.lock.Lock() defer rt.lock.Unlock() @@ -313,102 +305,3 @@ func (rt *router) handle(p Pattern, m method, h Handler) { rt.setMachine(nil) rt.routes = newRoutes } - -// This is a bit silly, but I've renamed the method receivers in the public -// functions here "m" instead of the standard "rt", since they will eventually -// be shown on the documentation for the Mux that they are included in. - -/* -Dispatch to the given handler when the pattern matches, regardless of HTTP -method. See the documentation for type Mux for a description of what types are -accepted for pattern and handler. - -This method is commonly used to implement sub-routing: an admin application, for -instance, can expose a single handler that is attached to the main Mux by -calling Handle("/admin*", adminHandler) or similar. Note that this function -doesn't strip this prefix from the path before forwarding it on (e.g., the -handler will see the full path, including the "/admin" part), but this -functionality can easily be performed by an extra middleware layer. -*/ -func (rt *router) Handle(pattern interface{}, handler interface{}) { - rt.handleUntyped(pattern, mALL, handler) -} - -// Dispatch to the given handler when the pattern matches and the HTTP method is -// CONNECT. See the documentation for type Mux for a description of what types -// are accepted for pattern and handler. -func (rt *router) Connect(pattern interface{}, handler interface{}) { - rt.handleUntyped(pattern, mCONNECT, handler) -} - -// Dispatch to the given handler when the pattern matches and the HTTP method is -// DELETE. See the documentation for type Mux for a description of what types -// are accepted for pattern and handler. -func (rt *router) Delete(pattern interface{}, handler interface{}) { - rt.handleUntyped(pattern, mDELETE, handler) -} - -// Dispatch to the given handler when the pattern matches and the HTTP method is -// GET. See the documentation for type Mux for a description of what types are -// accepted for pattern and handler. -// -// All GET handlers also transparently serve HEAD requests, since net/http will -// take care of all the fiddly bits for you. If you wish to provide an alternate -// implementation of HEAD, you should add a handler explicitly and place it -// above your GET handler. -func (rt *router) Get(pattern interface{}, handler interface{}) { - rt.handleUntyped(pattern, mGET|mHEAD, handler) -} - -// Dispatch to the given handler when the pattern matches and the HTTP method is -// HEAD. See the documentation for type Mux for a description of what types are -// accepted for pattern and handler. -func (rt *router) Head(pattern interface{}, handler interface{}) { - rt.handleUntyped(pattern, mHEAD, handler) -} - -// Dispatch to the given handler when the pattern matches and the HTTP method is -// OPTIONS. See the documentation for type Mux for a description of what types -// are accepted for pattern and handler. -func (rt *router) Options(pattern interface{}, handler interface{}) { - rt.handleUntyped(pattern, mOPTIONS, handler) -} - -// Dispatch to the given handler when the pattern matches and the HTTP method is -// PATCH. See the documentation for type Mux for a description of what types are -// accepted for pattern and handler. -func (rt *router) Patch(pattern interface{}, handler interface{}) { - rt.handleUntyped(pattern, mPATCH, handler) -} - -// Dispatch to the given handler when the pattern matches and the HTTP method is -// POST. See the documentation for type Mux for a description of what types are -// accepted for pattern and handler. -func (rt *router) Post(pattern interface{}, handler interface{}) { - rt.handleUntyped(pattern, mPOST, handler) -} - -// Dispatch to the given handler when the pattern matches and the HTTP method is -// PUT. See the documentation for type Mux for a description of what types are -// accepted for pattern and handler. -func (rt *router) Put(pattern interface{}, handler interface{}) { - rt.handleUntyped(pattern, mPUT, handler) -} - -// Dispatch to the given handler when the pattern matches and the HTTP method is -// TRACE. See the documentation for type Mux for a description of what types are -// accepted for pattern and handler. -func (rt *router) Trace(pattern interface{}, handler interface{}) { - rt.handleUntyped(pattern, mTRACE, handler) -} - -// Set the fallback (i.e., 404) handler for this mux. See the documentation for -// type Mux for a description of what types are accepted for handler. -// -// As a convenience, the context environment variable "goji.web.validMethods" -// (also available as the constant ValidMethodsKey) will be set to the list of -// HTTP methods that could have been routed had they been provided on an -// otherwise identical request. -func (rt *router) NotFound(handler interface{}) { - rt.notFound = parseHandler(handler) -} diff --git a/web/router_test.go b/web/router_test.go index 59955cb..b4c9730 100644 --- a/web/router_test.go +++ b/web/router_test.go @@ -11,13 +11,6 @@ import ( // These tests can probably be DRY'd up a bunch -func makeRouter() *router { - return &router{ - routes: make([]route, 0), - notFound: parseHandler(http.NotFound), - } -} - func chHandler(ch chan string, s string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ch <- s @@ -29,24 +22,24 @@ var methods = []string{"CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", func TestMethods(t *testing.T) { t.Parallel() - rt := makeRouter() + m := New() ch := make(chan string, 1) - rt.Connect("/", chHandler(ch, "CONNECT")) - rt.Delete("/", chHandler(ch, "DELETE")) - rt.Head("/", chHandler(ch, "HEAD")) - rt.Get("/", chHandler(ch, "GET")) - rt.Options("/", chHandler(ch, "OPTIONS")) - rt.Patch("/", chHandler(ch, "PATCH")) - rt.Post("/", chHandler(ch, "POST")) - rt.Put("/", chHandler(ch, "PUT")) - rt.Trace("/", chHandler(ch, "TRACE")) - rt.Handle("/", chHandler(ch, "OTHER")) + m.Connect("/", chHandler(ch, "CONNECT")) + m.Delete("/", chHandler(ch, "DELETE")) + m.Head("/", chHandler(ch, "HEAD")) + m.Get("/", chHandler(ch, "GET")) + m.Options("/", chHandler(ch, "OPTIONS")) + m.Patch("/", chHandler(ch, "PATCH")) + m.Post("/", chHandler(ch, "POST")) + m.Put("/", chHandler(ch, "PUT")) + m.Trace("/", chHandler(ch, "TRACE")) + m.Handle("/", chHandler(ch, "OTHER")) for _, method := range methods { r, _ := http.NewRequest(method, "/", nil) w := httptest.NewRecorder() - rt.route(&C{}, w, r) + m.ServeHTTP(w, r) select { case val := <-ch: if val != method { @@ -74,12 +67,12 @@ var _ Pattern = testPattern{} func TestPatternTypes(t *testing.T) { t.Parallel() - rt := makeRouter() + m := New() - rt.Get("/hello/carl", http.NotFound) - rt.Get("/hello/:name", http.NotFound) - rt.Get(regexp.MustCompile(`^/hello/(?P.+)$`), http.NotFound) - rt.Get(testPattern{}, http.NotFound) + m.Get("/hello/carl", http.NotFound) + m.Get("/hello/:name", http.NotFound) + m.Get(regexp.MustCompile(`^/hello/(?P.+)$`), http.NotFound) + m.Get(testPattern{}, http.NotFound) } type testHandler chan string @@ -101,27 +94,27 @@ var testHandlerTable = map[string]string{ func TestHandlerTypes(t *testing.T) { t.Parallel() - rt := makeRouter() + m := New() ch := make(chan string, 1) - rt.Get("/a", func(w http.ResponseWriter, r *http.Request) { + m.Get("/a", func(w http.ResponseWriter, r *http.Request) { ch <- "http fn" }) - rt.Get("/b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + m.Get("/b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ch <- "http handler" })) - rt.Get("/c", func(c C, w http.ResponseWriter, r *http.Request) { + m.Get("/c", func(c C, w http.ResponseWriter, r *http.Request) { ch <- "web fn" }) - rt.Get("/d", HandlerFunc(func(c C, w http.ResponseWriter, r *http.Request) { + m.Get("/d", HandlerFunc(func(c C, w http.ResponseWriter, r *http.Request) { ch <- "web handler" })) - rt.Get("/e", testHandler(ch)) + m.Get("/e", testHandler(ch)) for route, response := range testHandlerTable { r, _ := http.NewRequest("GET", route, nil) w := httptest.NewRecorder() - rt.route(&C{}, w, r) + m.ServeHTTP(w, r) select { case resp := <-ch: if resp != response { @@ -191,10 +184,10 @@ var _ http.Handler = rsPattern{} func TestRouteSelection(t *testing.T) { t.Parallel() - rt := makeRouter() + m := New() counter := 0 ichan := make(chan int, 1) - rt.NotFound(func(w http.ResponseWriter, r *http.Request) { + m.NotFound(func(w http.ResponseWriter, r *http.Request) { ichan <- -1 }) @@ -205,7 +198,7 @@ func TestRouteSelection(t *testing.T) { prefix: s, ichan: ichan, } - rt.Get(pat, pat) + m.Get(pat, pat) } for _, test := range rsTests { @@ -213,7 +206,7 @@ func TestRouteSelection(t *testing.T) { for counter, n = range test.results { r, _ := http.NewRequest("GET", test.key, nil) w := httptest.NewRecorder() - rt.route(&C{}, w, r) + m.ServeHTTP(w, r) actual := <-ichan if n != actual { t.Errorf("Expected %q @ %d to be %d, got %d", @@ -225,22 +218,22 @@ func TestRouteSelection(t *testing.T) { func TestNotFound(t *testing.T) { t.Parallel() - rt := makeRouter() + m := New() r, _ := http.NewRequest("post", "/", nil) w := httptest.NewRecorder() - rt.route(&C{}, w, r) + m.ServeHTTP(w, r) if w.Code != 404 { t.Errorf("Expected 404, got %d", w.Code) } - rt.NotFound(func(w http.ResponseWriter, r *http.Request) { + m.NotFound(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "I'm a teapot!", http.StatusTeapot) }) r, _ = http.NewRequest("POST", "/", nil) w = httptest.NewRecorder() - rt.route(&C{}, w, r) + m.ServeHTTP(w, r) if w.Code != http.StatusTeapot { t.Errorf("Expected a teapot, got %d", w.Code) } @@ -248,16 +241,16 @@ func TestNotFound(t *testing.T) { func TestPrefix(t *testing.T) { t.Parallel() - rt := makeRouter() + m := New() ch := make(chan string, 1) - rt.Handle("/hello*", func(w http.ResponseWriter, r *http.Request) { + m.Handle("/hello*", func(w http.ResponseWriter, r *http.Request) { ch <- r.URL.Path }) r, _ := http.NewRequest("GET", "/hello/world", nil) w := httptest.NewRecorder() - rt.route(&C{}, w, r) + m.ServeHTTP(w, r) select { case val := <-ch: if val != "/hello/world" { @@ -278,10 +271,10 @@ var validMethodsTable = map[string][]string{ func TestValidMethods(t *testing.T) { t.Parallel() - rt := makeRouter() + m := New() ch := make(chan []string, 1) - rt.NotFound(func(c C, w http.ResponseWriter, r *http.Request) { + m.NotFound(func(c C, w http.ResponseWriter, r *http.Request) { if c.Env == nil { ch <- []string{} return @@ -294,19 +287,19 @@ func TestValidMethods(t *testing.T) { ch <- methods.([]string) }) - rt.Get("/hello/carl", http.NotFound) - rt.Post("/hello/carl", http.NotFound) - rt.Head("/hello/bob", http.NotFound) - rt.Get("/hello/:name", http.NotFound) - rt.Put("/hello/:name", http.NotFound) - rt.Patch("/hello/:name", http.NotFound) - rt.Get("/:greet/carl", http.NotFound) - rt.Put("/:greet/carl", http.NotFound) - rt.Delete("/:greet/:anyone", http.NotFound) + m.Get("/hello/carl", http.NotFound) + m.Post("/hello/carl", http.NotFound) + m.Head("/hello/bob", http.NotFound) + m.Get("/hello/:name", http.NotFound) + m.Put("/hello/:name", http.NotFound) + m.Patch("/hello/:name", http.NotFound) + m.Get("/:greet/carl", http.NotFound) + m.Put("/:greet/carl", http.NotFound) + m.Delete("/:greet/:anyone", http.NotFound) for path, eMethods := range validMethodsTable { r, _ := http.NewRequest("BOGUS", path, nil) - rt.route(&C{}, httptest.NewRecorder(), r) + m.ServeHTTP(httptest.NewRecorder(), r) aMethods := <-ch if !reflect.DeepEqual(eMethods, aMethods) { t.Errorf("For %q, expected %v, got %v", path, eMethods,