Passing context to gorilla mux - go idioms -
i'm reasonably new golang , trying work out best way idiomatically.
i have array of routes statically defining , passing gorilla/mux
. wrapping each handler function time request , handle panics (mainly understand how wrapping worked).
i want them each able have access 'context' - struct that's going one-per-http-server, might have things database handles, config etc. don't want use static global variable.
the way i'm doing can give wrappers access context structure, can't see how actual handler, wants http.handlerfunc
. thought convert http.handlerfunc
type of own receiver context
(and wrappers, (after playing about) couldn't handler()
accept this.
i can't think i'm missing obvious here. code below.
package main import ( "fmt" "github.com/gorilla/mux" "html" "log" "net/http" "time" ) type route struct { name string method string pattern string handlerfunc http.handlerfunc } type context struct { route *route // imagine other stuff here, database handles, config etc. } type routes []route var routes = routes{ route{ "index", "get", "/", index, }, // imagine lots more routes here } func wraplogger(inner http.handler, context *context) http.handler { return http.handlerfunc(func(w http.responsewriter, r *http.request) { start := time.now() inner.servehttp(w, r) log.printf( "%s\t%s\t%s\t%s", r.method, r.requesturi, context.route.name, time.since(start), ) }) } func wrappanic(inner http.handler, context *context) http.handler { return http.handlerfunc(func(w http.responsewriter, r *http.request) { defer func() { if err := recover(); err != nil { log.printf("panic caught: %+v", err) http.error(w, http.statustext(500), 500) } }() inner.servehttp(w, r) }) } func newrouter() *mux.router { router := mux.newrouter().strictslash(true) _, route := range routes { // context object created here context := context { &route, // imagine more stuff here } router. methods(route.method). path(route.pattern). name(route.name). handler(wraplogger(wrappanic(route.handlerfunc, &context), &context)) } return router } func index(w http.responsewriter, r *http.request) { // want function able have access 'context' fmt.fprintf(w, "hello, %q", html.escapestring(r.url.path)) } func main() { fmt.print("starting\n"); router := newrouter() log.fatal(http.listenandserve("127.0.0.1:8080", router)) }
here's a way it, seems pretty horrible. can't think there must better way - perhaps subclass (?) http.handler
.
package main import ( "fmt" "github.com/gorilla/mux" "html" "log" "net/http" "time" ) type route struct { name string method string pattern string handlerfunc contexthandlerfunc } type context struct { route *route secret string } type contexthandlerfunc func(c *context, w http.responsewriter, r *http.request) type routes []route var routes = routes{ route{ "index", "get", "/", index, }, } func wraplogger(inner contexthandlerfunc) contexthandlerfunc { return func(c *context, w http.responsewriter, r *http.request) { start := time.now() inner(c, w, r) log.printf( "%s\t%s\t%s\t%s", r.method, r.requesturi, c.route.name, time.since(start), ) } } func wrappanic(inner contexthandlerfunc) contexthandlerfunc { return func(c *context, w http.responsewriter, r *http.request) { defer func() { if err := recover(); err != nil { log.printf("panic caught: %+v", err) http.error(w, http.statustext(500), 500) } }() inner(c, w, r) } } func newrouter() *mux.router { router := mux.newrouter().strictslash(true) _, route := range routes { context := context{ &route, "test", } router.methods(route.method). path(route.pattern). name(route.name). handlerfunc(func(w http.responsewriter, r *http.request) { wraplogger(wrappanic(route.handlerfunc))(&context, w, r) }) } return router } func index(c *context, w http.responsewriter, r *http.request) { fmt.fprintf(w, "hello, %q secret %s\n", html.escapestring(r.url.path), c.secret) } func main() { fmt.print("starting\n") router := newrouter() log.fatal(http.listenandserve("127.0.0.1:8080", router)) }
i learning go , in middle of identical problem, , how i've dealt it:
first, think missed important detail: there no global variables in go. widest scope can have variable package scope. true globals in go predeclared identifiers true
, false
(and can't change these or make own).
so, it's fine set variable scoped package main
hold context program. coming c/c++ background took me little time used to. since variables package scoped, not suffer the problems of global variables. if in package needs such variable, have pass explicitly.
don't afraid use package variables when makes sense. can reduce complexity in program, , in lot of cases make custom handlers simpler (where calling http.handlerfunc()
, passing closure suffice).
such simple handler might this:
func simplehandler(c context, next http.handler) http.handler { return http.handlerfunc(func(w http.responsewriter, r *http.request) { // fixme our context next.servehttp(w, r) }) }
and used by:
r = mux.newrouter() http.handle("/", simplehandler(c, r))
if needs more complex, may need implement own http.handler
. remember http.handler
interface implements servehttp(w http.responsewriter, r *http.request)
.
this untested should 95% of way there:
package main import ( "net/http" ) type complicatedhandler struct { h http.handler opts complicatedoptions } type complicatedoptions struct { // fixme of variables want set handler } func (m complicatedhandler) servehttp(w http.responsewriter, r *http.request) { // fixme stuff before serving page // call next handler m.h.servehttp(w, r) // fixme stuff after serving page } func complicatedhandler(o complicatedoptions) func(http.handler) http.handler { return func(h http.handler) http.handler { return complicatedhandler{h, o} } }
to use it:
r := mux.newrouter() // fixme: add routes mux opts := complicatedoptions{/* fixme */} myhandler := complicatedhandler(opts) http.handle("/", myhandler(r))
for more developed handler example see basicauth in goji/httpauth, example shamelessly ripped off.
some further reading:
- a recap of request handling
- making , using http middleware
- justinas/alice (for chaining lots of handlers)
Comments
Post a Comment