Browse Source

Make C.Env a map[interface{}]interface{}

This is a major breaking change to web.C, the Goji context object. The
Env key has been changed from a map[string]interface{} to a
map[interface{}]interface{} in order to better support package-private
environment values and to make the context value more compatible with
golang.org/x/net/context's Context.

Since strings support equality, most existing uses of web.C should
continue to function. Users who construct Env by hand (i.e., by calling
"make") will need to update their code as instructed by their compiler.
Users who iterate over the environment will need to update their code to
take into account the fact that keys may no longer be strings.
Carl Jackson 11 years ago
parent
commit
8459820305
10 changed files with 67 additions and 88 deletions
  1. +16
    -15
      web/middleware/envinit.go
  2. +1
    -1
      web/middleware/options_test.go
  3. +2
    -12
      web/middleware/realip.go
  4. +1
    -1
      web/middleware/request_id.go
  5. +1
    -1
      web/middleware_test.go
  6. +1
    -1
      web/mux_test.go
  7. +3
    -1
      web/pattern.go
  8. +1
    -1
      web/router.go
  9. +1
    -1
      web/router_test.go
  10. +40
    -54
      web/web.go

+ 16
- 15
web/middleware/envinit.go View File

@ -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}
}

+ 1
- 1
web/middleware/options_test.go View File

@ -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",


+ 2
- 12
web/middleware/realip.go View File

@ -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)


+ 1
- 1
web/middleware/request_id.go View File

@ -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)


+ 1
- 1
web/middleware_test.go View File

@ -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)


+ 1
- 1
web/mux_test.go View File

@ -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" {


+ 3
- 1
web/pattern.go View File

@ -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


+ 1
- 1
web/router.go View File

@ -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 {


+ 1
- 1
web/router_test.go View File

@ -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)
})
})


+ 40
- 54
web/web.go View File

@ -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)
}

Loading…
Cancel
Save