This change exposes a new type, Match, which represents a matched route. When present in the Goji environment (bound to the key MatchKey), routing will be skipped and the bound Match will be dispatched to instead. In addition, the Goji router has been exposed as a middleware using the Match mechanism above. This allows middleware inserted after the router access to the Match object and any bound URLParams. Fixes #76. See also #32.
| @ -0,0 +1,42 @@ | |||
| package web | |||
| import ( | |||
| "log" | |||
| "net/http" | |||
| ) | |||
| const unknownHandler = `Unknown handler type %T. See http://godoc.org/github.com/zenazn/goji/web#HandlerType for a list of acceptable types.` | |||
| type netHTTPHandlerWrap struct{ http.Handler } | |||
| type netHTTPHandlerFuncWrap struct { | |||
| fn func(http.ResponseWriter, *http.Request) | |||
| } | |||
| type handlerFuncWrap struct { | |||
| fn func(C, http.ResponseWriter, *http.Request) | |||
| } | |||
| func (h netHTTPHandlerWrap) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { | |||
| h.Handler.ServeHTTP(w, r) | |||
| } | |||
| func (h netHTTPHandlerFuncWrap) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { | |||
| h.fn(w, r) | |||
| } | |||
| func (h handlerFuncWrap) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { | |||
| h.fn(c, w, r) | |||
| } | |||
| func parseHandler(h HandlerType) Handler { | |||
| switch f := h.(type) { | |||
| case func(c C, w http.ResponseWriter, r *http.Request): | |||
| return handlerFuncWrap{f} | |||
| case func(w http.ResponseWriter, r *http.Request): | |||
| return netHTTPHandlerFuncWrap{f} | |||
| case Handler: | |||
| return f | |||
| case http.Handler: | |||
| return netHTTPHandlerWrap{f} | |||
| default: | |||
| log.Fatalf(unknownHandler, h) | |||
| panic("log.Fatalf does not return") | |||
| } | |||
| } | |||
| @ -0,0 +1,66 @@ | |||
| package web | |||
| // The key used to store route Matches in the Goji environment. If this key is | |||
| // present in the environment and contains a value of type Match, routing will | |||
| // not be performed, and the Match's Handler will be used instead. | |||
| const MatchKey = "goji.web.Match" | |||
| // Match is the type of routing matches. It is inserted into C.Env under | |||
| // MatchKey when the Mux.Router middleware is invoked. If MatchKey is present at | |||
| // route dispatch time, the Handler of the corresponding Match will be called | |||
| // instead of performing routing as usual. | |||
| // | |||
| // By computing a Match and inserting it into the Goji environment as part of a | |||
| // middleware stack (see Mux.Router, for instance), it is possible to customize | |||
| // Goji's routing behavior or replace it entirely. | |||
| type Match struct { | |||
| // Pattern is the Pattern that matched during routing. Will be nil if no | |||
| // route matched (Handler will be set to the Mux's NotFound handler) | |||
| Pattern Pattern | |||
| // The Handler corresponding to the matched pattern. | |||
| Handler Handler | |||
| } | |||
| // GetMatch returns the Match stored in the Goji environment, or an empty Match | |||
| // if none exists (valid Matches always have a Handler property). | |||
| func GetMatch(c C) Match { | |||
| if c.Env == nil { | |||
| return Match{} | |||
| } | |||
| mi, ok := c.Env[MatchKey] | |||
| if !ok { | |||
| return Match{} | |||
| } | |||
| if m, ok := mi.(Match); ok { | |||
| return m | |||
| } | |||
| return Match{} | |||
| } | |||
| // RawPattern returns the PatternType that was originally passed to ParsePattern | |||
| // or any of the HTTP method functions (Get, Post, etc.). | |||
| func (m Match) RawPattern() PatternType { | |||
| switch v := m.Pattern.(type) { | |||
| case regexpPattern: | |||
| return v.re | |||
| case stringPattern: | |||
| return v.raw | |||
| default: | |||
| return v | |||
| } | |||
| } | |||
| // RawHandler returns the HandlerType that was originally passed to the HTTP | |||
| // method functions (Get, Post, etc.). | |||
| func (m Match) RawHandler() HandlerType { | |||
| switch v := m.Handler.(type) { | |||
| case netHTTPHandlerWrap: | |||
| return v.Handler | |||
| case handlerFuncWrap: | |||
| return v.fn | |||
| case netHTTPHandlerFuncWrap: | |||
| return v.fn | |||
| default: | |||
| return v | |||
| } | |||
| } | |||
| @ -0,0 +1,50 @@ | |||
| package web | |||
| import ( | |||
| "net/http" | |||
| "regexp" | |||
| "testing" | |||
| ) | |||
| var rawPatterns = []PatternType{ | |||
| "/hello/:name", | |||
| regexp.MustCompile("^/hello/(?P<name>[^/]+)$"), | |||
| testPattern{}, | |||
| } | |||
| func TestRawPattern(t *testing.T) { | |||
| t.Parallel() | |||
| for _, p := range rawPatterns { | |||
| m := Match{Pattern: ParsePattern(p)} | |||
| if rp := m.RawPattern(); rp != p { | |||
| t.Errorf("got %#v, expected %#v", rp, p) | |||
| } | |||
| } | |||
| } | |||
| type httpHandlerOnly struct{} | |||
| func (httpHandlerOnly) ServeHTTP(w http.ResponseWriter, r *http.Request) {} | |||
| type handlerOnly struct{} | |||
| func (handlerOnly) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) {} | |||
| var rawHandlers = []HandlerType{ | |||
| func(w http.ResponseWriter, r *http.Request) {}, | |||
| func(c C, w http.ResponseWriter, r *http.Request) {}, | |||
| httpHandlerOnly{}, | |||
| handlerOnly{}, | |||
| } | |||
| func TestRawHandler(t *testing.T) { | |||
| t.Parallel() | |||
| for _, h := range rawHandlers { | |||
| m := Match{Handler: parseHandler(h)} | |||
| if rh := m.RawHandler(); !funcEqual(rh, h) { | |||
| t.Errorf("got %#v, expected %#v", rh, h) | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,35 @@ | |||
| package web | |||
| import ( | |||
| "net/http" | |||
| "net/http/httptest" | |||
| "testing" | |||
| ) | |||
| func TestRouterMiddleware(t *testing.T) { | |||
| t.Parallel() | |||
| m := New() | |||
| ch := make(chan string, 1) | |||
| m.Get("/a", chHandler(ch, "a")) | |||
| m.Get("/b", chHandler(ch, "b")) | |||
| m.Use(m.Router) | |||
| m.Use(func(c *C, h http.Handler) http.Handler { | |||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||
| m := GetMatch(*c) | |||
| if rp := m.RawPattern(); rp != "/a" { | |||
| t.Fatalf("RawPattern was not /a: %v", rp) | |||
| } | |||
| r.URL.Path = "/b" | |||
| h.ServeHTTP(w, r) | |||
| } | |||
| return http.HandlerFunc(fn) | |||
| }) | |||
| r, _ := http.NewRequest("GET", "/a", nil) | |||
| w := httptest.NewRecorder() | |||
| m.ServeHTTP(w, r) | |||
| if v := <-ch; v != "a" { | |||
| t.Error("Routing was not frozen! %s", v) | |||
| } | |||
| } | |||