Somewhat inspired by this article last week, I\'m toying with refactoring an application I have to more explicitly pass context (DB pools, session stores, etc) to my handlers.
I would use a closure and do something like this:
func IndexHandler(a *appContext) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *httpRequest) {
// ... do stuff
fmt.Fprintf(w, "db is %q and store is %q\n", a.db, a.store)
})
}
And just use the returned http.Handler.
You'll just have to make sure your appContext is goroutine-safe.
After some discussion with a couple of helpful Gophers on #go-nuts, the method above is about "as good as it gets" from what I can discern.
ServeHTTP can access it as well. Note that we can't define our handlers as methods on appHandler i.e. func (ah *appHandler) IndexHandler(...) because we need to call the handler in ServeHTTP (i.e. ah.h(w,r)).
type appContext struct {
db *sqlx.DB
store *sessions.CookieStore
templates map[string]*template.Template
}
type appHandler struct {
handler func(w http.ResponseWriter, r *http.Request) (int, error)
*appContext // Embedded so we can just call app.db or app.store in our handlers.
}
// In main() ...
context := &appContext{db: nil, store: nil}
r.Get("/", appHandler{context.IndexHandler, context})
...
This is also, most importantly, fully compatible with http.Handler so we can still wrap our handler struct with generic middleware like so: gzipHandler(appHandler{context.IndexHandler, context}).
(I'm still open to other suggestions however!)
Update
Thanks to this great reply on Reddit I was able to find a better solution that didn't require passing two references to my context instance per-request.
We instead just create a struct that accepts an embedded context and our handler type, and we still satisfy the http.Handler interface thanks to ServeHTTP. Handlers are no longer methods on our appContext type but instead just accept it as a parameter, which leads to a slightly longer function signature but is still "obvious" and easy to read. If we were concerned about 'typing' we're breaking even because we no longer have a method receiver to worry about.
type appContext struct {
db *sqlx.DB
store *sessions.CookieStore
templates map[string]*template.Template
type appHandler struct {
*appContext
h func(a *appContext, w http.ResponseWriter, r *http.Request) (int, error)
}
func (ah appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// We can now access our context in here.
status, err := ah.h(ah.appContext, w, r)
log.Printf("Hello! DB: %v", ah.db)
if err != nil {
log.Printf("HTTP %d: %q", status, err)
switch status {
case http.StatusNotFound:
// err := ah.renderTemplate(w, "http_404.tmpl", nil)
http.NotFound(w, r)
case http.StatusInternalServerError:
// err := ah.renderTemplate(w, "http_500.tmpl", nil)
http.Error(w, http.StatusText(status), status)
default:
// err := ah.renderTemplate(w, "http_error.tmpl", nil)
http.Error(w, http.StatusText(status), status)
}
}
}
func main() {
context := &appContext{
db: nil,
store: nil,
templates: nil,
}
r := web.New()
// We pass a reference to context *once* per request, and it looks simpler
r.Get("/", appHandler{context, IndexHandler})
graceful.ListenAndServe(":8000", r)
}
func IndexHandler(a *appContext, w http.ResponseWriter, r *http.Request) (int, error) {
fmt.Fprintf(w, "db is %q and store is %q\n", a.db, a.store)
return 200, nil
}