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:


Comments

Popular posts from this blog

python - No exponential form of the z-axis in matplotlib-3D-plots -

php - Best Light server (Linux + Web server + Database) for Raspberry Pi -

c# - "Newtonsoft.Json.JsonSerializationException unable to find constructor to use for types" error when deserializing class -