This claim might be a little too optimistic. I'd like to challenge you (everyone) to associate a User object with an http.Request. Example code in Node.js/ExpressJS:
function auth(req, res, next) {
req.User = User("username");
next();
}
This is such a simple example of a useful piece of middleware, yet it is so difficult to implement in Go in a 'painless and fun' way.
I would prefer to avoid contention on a global mutex.
I would prefer to keep the compatibility with http.Handler and http.HandlerFunc.
Maybe I'm missing something about your question, but I think my solution to this problem is simply to use closures; if there's per-request state (a user, a session, a data store), I write a function that returns an anonymous HandlerFunc that wraps up the real handler, sets up its environment, and passes it down.
This is "compatible" with http.Handler and http.HandlerFunc; everything is wired together as if it was vanilla handlers. But my actual handlers (or "actions" or whatever you'd like to call them) get state, persistence, &c.
To be fair, Express is a web framework. Go's net/http package is a little lower level than that.
Still, this is possible if you make up for the lack of a framework by writing some code yourself. I'd use (and I do use) gorilla/context[1] to allow me to pass data in the request context between middleware layers (i.e. CSRF middleware passes the token along).
You could write a couple of little helpers using gorilla/context (SetUser/GetUser, or more generically, SetString/GetString) and SetUser(username, r) in your middleware. The helpers (see the context docs!) just wrap a type assertion on the Get side, and set a map value on the Set side.
// Compatible with http.HandlerFunc
func auth(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
SetUser(r, "dave")
h.ServeHTTP(w, r)
}
}
// Routes
r.HandleFunc("/admin", use(adminHandler, auth)
http.Handle("/", r)
// Allows you to chain your middleware rather than wrapping functions
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
for _, m := range middleware {
h = m(h)
}
return h
}
You can mod this to work with http.Handler if you want: replace HandlerFunc and then write r.Handle("/admin", use(http.HandlerFunc(adminHandler), auth) instead.
No, 'r' is request-specific; it's owned by the goroutine that's dispatching the current request.
If you had actual shared state that needed to be mutated by concurrent handlers, the idiomatic solution would be to park it behind a channel on its own goroutine; that's why goroutines are so cheap, is so you can allocate them to problems like this. If you want your solution to be general and unfussy, you'd have the channel be of closures; you'd just pass whatever mutating code you want to run to the goroutine.
var (
mutex sync.Mutex
data = make(map[*http.Request]map[interface{}]interface{})
datat = make(map[*http.Request]int64)
)
// Set stores a value for a given key in a given request.
func Set(r *http.Request, key, val interface{}) {
mutex.Lock()
defer mutex.Unlock()
if data[r] == nil {
data[r] = make(map[interface{}]interface{})
datat[r] = time.Now().Unix()
}
data[r][key] = val
}
The map is needed because 'r' is not actually (and cannot be) modified, so for a later handler (for the same request) to access the data it must be stored in this map, so 'Get' can get it back. And the map is global, so you need a mutex.
It's definitely not unidiomatic (heck, one of the Go team members inspired gorilla/context). It's not ideal, but at the same time a context map a) is simple b) does not impart significant complexity on middleware and c) should still perform well, even with a fair bit of contention. Map access is pretty fast, and most of the time you're only storing small things in it.
I'd be curious to see/benchmark the results of a more complex, less map-reliant solution vs. a context map: my gut feel is that the map route wouldn't have any problems hitting 10,000req/s on a small 2GB VM. Most of the time, the context map won't be your bottleneck.
func (c *YourContext) UserRequired(rw web.ResponseWriter, r *web.Request, next web.NextMiddlewareFunc) {
user := userFromSession(r) // Pretend like this is defined. It reads a session cookie and returns a *User or nil.
if user != nil {
c.User = user
next(rw, r)
} else {
rw.Header().Set("Location", "/")
rw.WriteHeader(http.StatusMovedPermanently)
// do NOT call next()
}
}
Compatibility with Handler/HandlerFunc is kept at the top level, but not at every step along the middleware chain.
This is pretty neat. Functions that read from or write to http.Request.Body should be able to accept a Context object, assuming they take a Reader/Writer. If they take an http.Request, you're going to pass in Context.req, similar to how custom.Request would contain a custom.req field.
I would prefer to avoid contention on a global mutex.
I would prefer to keep the compatibility with http.Handler and http.HandlerFunc.
Good luck!