Browse Source

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.
Carl Jackson 11 years ago
parent
commit
f292697223
3 changed files with 155 additions and 164 deletions
  1. +108
    -3
      web/mux.go
  2. +0
    -107
      web/router.go
  3. +47
    -54
      web/router_test.go

+ 108
- 3
web/mux.go View File

@ -53,7 +53,7 @@ Handler must be one of the following types:
*/ */
type Mux struct { type Mux struct {
ms mStack ms mStack
router
rt router
} }
// New creates a new Mux without any routes or middleware. // New creates a new Mux without any routes or middleware.
@ -63,12 +63,12 @@ func New() *Mux {
stack: make([]mLayer, 0), stack: make([]mLayer, 0),
pool: makeCPool(), pool: makeCPool(),
}, },
router: router{
rt: router{
routes: make([]route, 0), routes: make([]route, 0),
notFound: parseHandler(http.NotFound), notFound: parseHandler(http.NotFound),
}, },
} }
mux.ms.router = &mux.router
mux.ms.router = &mux.rt
return &mux return &mux
} }
@ -118,3 +118,108 @@ func (m *Mux) Insert(middleware, before interface{}) error {
func (m *Mux) Abandon(middleware interface{}) error { func (m *Mux) Abandon(middleware interface{}) error {
return m.ms.Abandon(middleware) 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()
}

+ 0
- 107
web/router.go View File

@ -226,14 +226,6 @@ func (rm routeMachine) route(c *C, w http.ResponseWriter, r *http.Request) (meth
return methods, false 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 { func (rt *router) compile() *routeMachine {
rt.lock.Lock() rt.lock.Lock()
defer rt.lock.Unlock() defer rt.lock.Unlock()
@ -313,102 +305,3 @@ func (rt *router) handle(p Pattern, m method, h Handler) {
rt.setMachine(nil) rt.setMachine(nil)
rt.routes = newRoutes 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)
}

+ 47
- 54
web/router_test.go View File

@ -11,13 +11,6 @@ import (
// These tests can probably be DRY'd up a bunch // 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 { func chHandler(ch chan string, s string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ch <- s ch <- s
@ -29,24 +22,24 @@ var methods = []string{"CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH",
func TestMethods(t *testing.T) { func TestMethods(t *testing.T) {
t.Parallel() t.Parallel()
rt := makeRouter()
m := New()
ch := make(chan string, 1) 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 { for _, method := range methods {
r, _ := http.NewRequest(method, "/", nil) r, _ := http.NewRequest(method, "/", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
rt.route(&C{}, w, r)
m.ServeHTTP(w, r)
select { select {
case val := <-ch: case val := <-ch:
if val != method { if val != method {
@ -74,12 +67,12 @@ var _ Pattern = testPattern{}
func TestPatternTypes(t *testing.T) { func TestPatternTypes(t *testing.T) {
t.Parallel() t.Parallel()
rt := makeRouter()
m := New()
rt.Get("/hello/carl", http.NotFound)
rt.Get("/hello/:name", http.NotFound)
rt.Get(regexp.MustCompile(`^/hello/(?P<name>.+)$`), 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<name>.+)$`), http.NotFound)
m.Get(testPattern{}, http.NotFound)
} }
type testHandler chan string type testHandler chan string
@ -101,27 +94,27 @@ var testHandlerTable = map[string]string{
func TestHandlerTypes(t *testing.T) { func TestHandlerTypes(t *testing.T) {
t.Parallel() t.Parallel()
rt := makeRouter()
m := New()
ch := make(chan string, 1) 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" 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" 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" 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" ch <- "web handler"
})) }))
rt.Get("/e", testHandler(ch))
m.Get("/e", testHandler(ch))
for route, response := range testHandlerTable { for route, response := range testHandlerTable {
r, _ := http.NewRequest("GET", route, nil) r, _ := http.NewRequest("GET", route, nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
rt.route(&C{}, w, r)
m.ServeHTTP(w, r)
select { select {
case resp := <-ch: case resp := <-ch:
if resp != response { if resp != response {
@ -191,10 +184,10 @@ var _ http.Handler = rsPattern{}
func TestRouteSelection(t *testing.T) { func TestRouteSelection(t *testing.T) {
t.Parallel() t.Parallel()
rt := makeRouter()
m := New()
counter := 0 counter := 0
ichan := make(chan int, 1) 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 ichan <- -1
}) })
@ -205,7 +198,7 @@ func TestRouteSelection(t *testing.T) {
prefix: s, prefix: s,
ichan: ichan, ichan: ichan,
} }
rt.Get(pat, pat)
m.Get(pat, pat)
} }
for _, test := range rsTests { for _, test := range rsTests {
@ -213,7 +206,7 @@ func TestRouteSelection(t *testing.T) {
for counter, n = range test.results { for counter, n = range test.results {
r, _ := http.NewRequest("GET", test.key, nil) r, _ := http.NewRequest("GET", test.key, nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
rt.route(&C{}, w, r)
m.ServeHTTP(w, r)
actual := <-ichan actual := <-ichan
if n != actual { if n != actual {
t.Errorf("Expected %q @ %d to be %d, got %d", t.Errorf("Expected %q @ %d to be %d, got %d",
@ -225,22 +218,22 @@ func TestRouteSelection(t *testing.T) {
func TestNotFound(t *testing.T) { func TestNotFound(t *testing.T) {
t.Parallel() t.Parallel()
rt := makeRouter()
m := New()
r, _ := http.NewRequest("post", "/", nil) r, _ := http.NewRequest("post", "/", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
rt.route(&C{}, w, r)
m.ServeHTTP(w, r)
if w.Code != 404 { if w.Code != 404 {
t.Errorf("Expected 404, got %d", w.Code) 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) http.Error(w, "I'm a teapot!", http.StatusTeapot)
}) })
r, _ = http.NewRequest("POST", "/", nil) r, _ = http.NewRequest("POST", "/", nil)
w = httptest.NewRecorder() w = httptest.NewRecorder()
rt.route(&C{}, w, r)
m.ServeHTTP(w, r)
if w.Code != http.StatusTeapot { if w.Code != http.StatusTeapot {
t.Errorf("Expected a teapot, got %d", w.Code) t.Errorf("Expected a teapot, got %d", w.Code)
} }
@ -248,16 +241,16 @@ func TestNotFound(t *testing.T) {
func TestPrefix(t *testing.T) { func TestPrefix(t *testing.T) {
t.Parallel() t.Parallel()
rt := makeRouter()
m := New()
ch := make(chan string, 1) 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 ch <- r.URL.Path
}) })
r, _ := http.NewRequest("GET", "/hello/world", nil) r, _ := http.NewRequest("GET", "/hello/world", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
rt.route(&C{}, w, r)
m.ServeHTTP(w, r)
select { select {
case val := <-ch: case val := <-ch:
if val != "/hello/world" { if val != "/hello/world" {
@ -278,10 +271,10 @@ var validMethodsTable = map[string][]string{
func TestValidMethods(t *testing.T) { func TestValidMethods(t *testing.T) {
t.Parallel() t.Parallel()
rt := makeRouter()
m := New()
ch := make(chan []string, 1) 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 { if c.Env == nil {
ch <- []string{} ch <- []string{}
return return
@ -294,19 +287,19 @@ func TestValidMethods(t *testing.T) {
ch <- methods.([]string) 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 { for path, eMethods := range validMethodsTable {
r, _ := http.NewRequest("BOGUS", path, nil) r, _ := http.NewRequest("BOGUS", path, nil)
rt.route(&C{}, httptest.NewRecorder(), r)
m.ServeHTTP(httptest.NewRecorder(), r)
aMethods := <-ch aMethods := <-ch
if !reflect.DeepEqual(eMethods, aMethods) { if !reflect.DeepEqual(eMethods, aMethods) {
t.Errorf("For %q, expected %v, got %v", path, eMethods, t.Errorf("For %q, expected %v, got %v", path, eMethods,


Loading…
Cancel
Save