diff --git a/web/middleware/envinit.go b/web/middleware/envinit.go index aefefb7..ae3b683 100644 --- a/web/middleware/envinit.go +++ b/web/middleware/envinit.go @@ -6,21 +6,22 @@ import ( "github.com/zenazn/goji/web" ) -// EnvInit is a middleware that allocates an environment map if one does not -// already exist. This is necessary because Goji does not guarantee that Env is -// present when running middleware (it avoids forcing the map allocation). Note -// that other middleware should check Env for nil in order to maximize -// compatibility (when EnvInit is not used, or when another middleware layer -// blanks out Env), but for situations in which the user controls the middleware -// stack and knows EnvInit is present, this middleware can eliminate a lot of -// boilerplate. -func EnvInit(c *web.C, h http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - if c.Env == nil { - c.Env = make(map[string]interface{}) - } - h.ServeHTTP(w, r) +type envInit struct { + c *web.C + h http.Handler +} + +func (e envInit) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if e.c.Env == nil { + e.c.Env = make(map[interface{}]interface{}) } + e.h.ServeHTTP(w, r) +} - return http.HandlerFunc(fn) +// EnvInit is a middleware that allocates an environment map if it is nil. While +// it's impossible in general to ensure that Env is never nil in a middleware +// stack, in most common cases placing this middleware at the top of the stack +// will eliminate the need for repetative nil checks. +func EnvInit(c *web.C, h http.Handler) http.Handler { + return envInit{c, h} } diff --git a/web/middleware/options_test.go b/web/middleware/options_test.go index 74f5481..e962145 100644 --- a/web/middleware/options_test.go +++ b/web/middleware/options_test.go @@ -21,7 +21,7 @@ func testOptions(r *http.Request, f func(*web.C, http.ResponseWriter, *http.Requ return w } -var optionsTestEnv = map[string]interface{}{ +var optionsTestEnv = map[interface{}]interface{}{ web.ValidMethodsKey: []string{ "hello", "world", diff --git a/web/middleware/realip.go b/web/middleware/realip.go index 99cd311..ae5599f 100644 --- a/web/middleware/realip.go +++ b/web/middleware/realip.go @@ -3,20 +3,14 @@ package middleware import ( "net/http" "strings" - - "github.com/zenazn/goji/web" ) -// Key the original value of RemoteAddr is stored under. -const OriginalRemoteAddrKey = "originalRemoteAddr" - var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") var xRealIP = http.CanonicalHeaderKey("X-Real-IP") // RealIP is a middleware that sets a http.Request's RemoteAddr to the results // of parsing either the X-Forwarded-For header or the X-Real-IP header (in that -// order). It places the original value of RemoteAddr in a context environment -// variable. +// order). // // This middleware should be inserted fairly early in the middleware stack to // ensure that subsequent layers (e.g., request loggers) which examine the @@ -29,13 +23,9 @@ var xRealIP = http.CanonicalHeaderKey("X-Real-IP") // values from the client, or if you use this middleware without a reverse // proxy, malicious clients will be able to make you very sad (or, depending on // how you're using RemoteAddr, vulnerable to an attack of some sort). -func RealIP(c *web.C, h http.Handler) http.Handler { +func RealIP(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { if rip := realIP(r); rip != "" { - if c.Env == nil { - c.Env = make(map[string]interface{}) - } - c.Env[OriginalRemoteAddrKey] = r.RemoteAddr r.RemoteAddr = rip } h.ServeHTTP(w, r) diff --git a/web/middleware/request_id.go b/web/middleware/request_id.go index 27e2313..834d8e3 100644 --- a/web/middleware/request_id.go +++ b/web/middleware/request_id.go @@ -60,7 +60,7 @@ func init() { func RequestID(c *web.C, h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { if c.Env == nil { - c.Env = make(map[string]interface{}) + c.Env = make(map[interface{}]interface{}) } myid := atomic.AddUint64(&reqid, 1) c.Env[RequestIDKey] = fmt.Sprintf("%s-%06d", prefix, myid) diff --git a/web/middleware_test.go b/web/middleware_test.go index b262b07..678ccaa 100644 --- a/web/middleware_test.go +++ b/web/middleware_test.go @@ -179,7 +179,7 @@ func TestContext(t *testing.T) { if c.Env != nil || c.URLParams != nil { t.Error("Expected a clean context") } - c.Env = make(map[string]interface{}) + c.Env = make(map[interface{}]interface{}) c.Env["reqID"] = 1 h.ServeHTTP(w, r) diff --git a/web/mux_test.go b/web/mux_test.go index 1854524..f461452 100644 --- a/web/mux_test.go +++ b/web/mux_test.go @@ -36,7 +36,7 @@ func TestIfItWorks(t *testing.T) { } r, _ = http.NewRequest("GET", "/hello/bob", nil) - env := map[string]interface{}{"greeting": "Yo "} + env := map[interface{}]interface{}{"greeting": "Yo "} m.ServeHTTPC(C{Env: env}, httptest.NewRecorder(), r) out = <-ch if out != "Yo bob" { diff --git a/web/pattern.go b/web/pattern.go index 99ca59e..c277ae1 100644 --- a/web/pattern.go +++ b/web/pattern.go @@ -56,7 +56,9 @@ of three types: that is anchored on the left (i.e., the beginning of the string). If your regular expression is not anchored on the left, a hopefully-identical left-anchored regular expression will be created - and used instead. + and used instead. Named capturing groups will bind URLParams of the + same name; unnamed capturing groups will be bound to the variables + "$1", "$2", etc. ParsePattern fatally exits (using log.Fatalf) if it is passed a value of an unexpected type. It is the caller's responsibility to ensure that ParsePattern diff --git a/web/router.go b/web/router.go index c291294..4659d85 100644 --- a/web/router.go +++ b/web/router.go @@ -129,7 +129,7 @@ func (rt *router) route(c *C, w http.ResponseWriter, r *http.Request) { sort.Strings(methodsList) if c.Env == nil { - c.Env = map[string]interface{}{ + c.Env = map[interface{}]interface{}{ ValidMethodsKey: methodsList, } } else { diff --git a/web/router_test.go b/web/router_test.go index 44dabf5..1d2c27e 100644 --- a/web/router_test.go +++ b/web/router_test.go @@ -310,7 +310,7 @@ func TestValidMethods(t *testing.T) { // This should also work when c.Env has already been initalized m.Use(func(c *C, h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - c.Env = make(map[string]interface{}) + c.Env = make(map[interface{}]interface{}) h.ServeHTTP(w, r) }) }) diff --git a/web/web.go b/web/web.go index 480f670..81a2bae 100644 --- a/web/web.go +++ b/web/web.go @@ -1,26 +1,18 @@ /* -Package web implements a fast and flexible middleware stack and mux. - -The underlying philosophy behind this package is that net/http is a very good -HTTP library which is only missing a few features. If you disagree with this -statement (e.g., you think that the interfaces it exposes are not especially -good, or if you're looking for a comprehensive "batteries included" feature -list), you're likely not going to have a good time using this library. In that -spirit, we have attempted wherever possible to be compatible with net/http. You -should be able to insert any net/http compliant handler into this library, or -use this library with any other net/http compliant mux. +Package web provides a fast and flexible middleware stack and mux. This package attempts to solve three problems that net/http does not. First, it -allows you to specify URL patterns with Sinatra-like named wildcards and -regexps. Second, it allows you to write reconfigurable middleware stacks. And -finally, it allows you to attach additional context to requests, in a manner -that can be manipulated by both compliant middleware and handlers. +allows you to specify flexible patterns, including routes with named parameters +and regular expressions. Second, it allows you to write reconfigurable +middleware stacks. And finally, it allows you to attach additional context to +requests, in a manner that can be manipulated by both compliant middleware and +handlers. A usage example: m := web.New() -Use your favorite HTTP verbs: +Use your favorite HTTP verbs and the interfaces you know and love from net/http: var legacyFooHttpHandler http.Handler // From elsewhere m.Get("/foo", legacyFooHttpHandler) @@ -28,7 +20,7 @@ Use your favorite HTTP verbs: w.Write([]byte("Hello world!")) }) -Bind parameters using either Sinatra-like patterns or regular expressions: +Bind parameters using either named captures or regular expressions: m.Get("/hello/:name", func(c web.C, w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %s!", c.URLParams["name"]) @@ -41,33 +33,32 @@ Bind parameters using either Sinatra-like patterns or regular expressions: Middleware are functions that wrap http.Handlers, just like you'd use with raw net/http. Middleware functions can optionally take a context parameter, which will be threaded throughout the middleware stack and to the final handler, even -if not all of these things support contexts. Middleware are encouraged to use -the Env parameter to pass data to other middleware and to the final handler: +if not all of the intervening middleware or handlers support contexts. +Middleware are encouraged to use the Env parameter to pass request-scoped data +to other middleware and to the final handler: - m.Use(func(h http.Handler) http.Handler { + func LoggerMiddleware(h http.Handler) http.Handler { handler := func(w http.ResponseWriter, r *http.Request) { log.Println("Before request") h.ServeHTTP(w, r) log.Println("After request") } return http.HandlerFunc(handler) - }) - m.Use(func(c *web.C, h http.Handler) http.Handler { + } + func AuthMiddleware(c *web.C, h http.Handler) http.Handler { handler := func(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie("user") - if err == nil { - // Consider using the middleware EnvInit instead - // of repeating the below check - if c.Env == nil { - c.Env = make(map[string]interface{}) - } + if cookie, err := r.Cookie("user"); err == nil { c.Env["user"] = cookie.Value } h.ServeHTTP(w, r) } return http.HandlerFunc(handler) - }) + } + // This makes the AuthMiddleware above a little cleaner + m.Use(middleware.EnvInit) + m.Use(AuthMiddleware) + m.Use(LoggerMiddleware) m.Get("/baz", func(c web.C, w http.ResponseWriter, r *http.Request) { if user, ok := c.Env["user"].(string); ok { w.Write([]byte("Hello " + user)) @@ -83,46 +74,41 @@ import ( ) /* -C is a per-request context object which is threaded through all compliant middleware -layers and to the final request handler. - -As an implementation detail, references to these structs are reused between -requests to reduce allocation churn, but the maps they contain are created fresh -on every request. If you are closing over a context (especially relevant for -middleware), you should not close over either the URLParams or Env objects, -instead accessing them through the context whenever they are required. +C is a request-local context object which is threaded through all compliant +middleware layers and given to the final request handler. */ type C struct { - // The parameters parsed by the mux from the URL itself. In most cases, - // will contain a map from programmer-specified identifiers to the - // strings that matched those identifiers, but if a unnamed regex - // capture is used, it will be assigned to the special identifiers "$1", - // "$2", etc. + // URLParams is a map of variables extracted from the URL (typically + // from the path portion) during routing. See the documentation for the + // URL Pattern you are using (or the documentation for ParsePattern for + // the case of standard pattern types) for more information about how + // variables are extracted and named. URLParams map[string]string - // A free-form environment, similar to Rack or PEP 333's environments. - // Middleware layers are encouraged to pass data to downstream layers - // and other handlers using this map, and are even more strongly - // encouraged to document and maybe namespace the keys they use. - Env map[string]interface{} + // Env is a free-form environment for storing request-local data. Keys + // may be arbitrary types that support equality, however package-private + // types with type-safe accessors provide a convenient way for packages + // to mediate access to their request-local data. + Env map[interface{}]interface{} } -// Handler is a superset of net/http's http.Handler, which also includes a -// mechanism for serving requests with a context. If your handler does not -// support the use of contexts, we encourage you to use http.Handler instead. +// Handler is similar to net/http's http.Handler, but also accepts a Goji +// context object. type Handler interface { - http.Handler ServeHTTPC(C, http.ResponseWriter, *http.Request) } -// HandlerFunc is like net/http's http.HandlerFunc, but supports a context -// object. Implements both http.Handler and web.Handler free of charge. +// HandlerFunc is similar to net/http's http.HandlerFunc, but supports a context +// object. Implements both http.Handler and web.Handler. type HandlerFunc func(C, http.ResponseWriter, *http.Request) +// ServeHTTP implements http.Handler, allowing HandlerFunc's to be used with +// net/http and other compliant routers. When used in this way, the underlying +// function will be passed an empty context. func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { h(C{}, w, r) } -// ServeHTTPC wraps ServeHTTP with a context parameter. +// ServeHTTPC implements Handler. func (h HandlerFunc) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { h(c, w, r) }