This is a best-practice question that probably doesn't have one correct answer. It seems that most of my handlers need to perform a number of common initialisation jobs before starting on work specific to the handler. Examples would be user auth, detecting locale and loading translated strings, checking memcached values, and so on.
It would seem reasonable to take care of some of these tasks within init
, but most require the Http.Request
or the appengine.Context
. As far as I can see this leaves three choices:
Implement
ServeHTTP
and add the ability to execute a custom init function at the end. Problem being, I wouldn't be able to use Gorilla mux which implements its ownServeHTTP
.Use a forked version of mux (less than ideal).
Put a
startHandler
function at the beginning of each and every handler throughout the app. Seems cumbersome, although I suppose it makes it plain exactly what's happening as opposed to 'hiding' common code inServeHTTP
.
What's the preferred way to take care of jobs common to all handlers? Am I missing another approach?
Here's a complete App Engine example of the approach outlined in minikomi's answer. It's also well-worth visiting Jeff Wendling's tutorial.
package app
import (
"fmt"
"log"
"net/http"
"appengine"
"appengine/datastore"
"github.com/gorilla/context"
"github.com/gorilla/mux"
)
type Config struct {
DefaultLocale string
DefaultTimezone string
}
type ContextKey int
const (
SiteConfig ContextKey = iota
// ...
)
type InitHandler func(http.ResponseWriter, *http.Request, appengine.Context)
func (h InitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// All handler initialisation tasks go here
c := appengine.NewContext(r)
k := datastore.NewKey(c, "Config", "site:config", 0, nil)
config := new(Config)
if err := datastore.Get(c, k, config); err != nil {
log.Fatal("Couldn't read config from datastore: %s\n", err.Error())
}
context.Set(r, SiteConfig, config)
// Finally, call the handler itself
h(w, r, c)
}
func init () {
r := mux.NewRouter()
r.Handle("/", InitHandler(home)) // Note: NOT r.HandleFunc!
http.Handle("/", r)
}
func home(w http.ResponseWriter, r *http.Request, c appengine.Context) {
site := context.Get(r, SiteConfig).(*Config)
fmt.Fprintf(w, "Locale: %s, timezone: %s.", site.DefaultLocale, site.DefaultTimezone)
}
What threw me for a little while is the need to use router.Handle
and not router.HandleFunc
. Assuming an appropriate entity in the datastore, output looks like:
Locale: en_US, timezone: UTC.