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 own ServeHTTP
.
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 in ServeHTTP
.
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.
You can create a (func) type which has a ServeHTTP
that does everything you need, and then internally calls the original function, and then convert your handlers to that type:
package main
import (
"fmt"
"log"
"net/http"
)
type wrappedHandler func(w http.ResponseWriter, r *http.Request)
func (h wrappedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Println("Do Other GAE Stuff")
h(w, r)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi!")
}
func main() {
http.Handle("/", wrappedHandler(handler))
http.ListenAndServe(":8080", nil)
}
If you want to pass something in to the handler()
func, you can add it to the signature eg:
type wrappedHandler func(w http.ResponseWriter, r *http.Request, conn *db.Connection)
func (h wrappedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
conn := db.CreateConnection();
h(w, r, conn)
}
func handler(w http.ResponseWriter, r *http.Request, conn *db.Connection) {
data := conn.AllTheData()
fmt.Fprintf(w, data)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With