Passing context in gorilla mux - go idioms

I am fairly new to golang and I try to make a better way to do it idiomatically.

I have an array of routes that I statically define and move on to gorilla/mux . I wrap each function of the handler with something like a request time and handle the panic (mainly to understand how packaging works).

I want each of them to have access to a "context" - a structure that will have a one-on-http server that can have things like database descriptors, config, etc. What I do not want for this is to use a static global variable.

As I do this, I can give the shells access to the context structure, but I cannot figure out how to do this in the actual handler, since it wants it to be http.HandlerFunc . I thought that I could do the conversion of http.HandlerFunc to my own type, which was the receiver for Context (and do it the same for wrappers, but (after long games) I could not get Handler() until Accept this.

I cannot help but think that I am missing something obvious here. The code is 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, like 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) for _, route := range routes { // the context object is 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) { // I want this function to be able to have access to '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 is a way to do it, but it looks pretty awful. I can't help but think that there should be a better way to do this - perhaps for a 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) for _, 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 is %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)) } 
+7
go gorilla
source share
1 answer

I am learning Go and is currently in the middle of an almost identical problem, and that is how I dealt with it:


First, I think you missed an important detail: there are no global variables in Go. the widest area you can have for a variable is the package area. The only true globals in Go are predefined identifiers such as true and false (and you cannot change them or make them yourself).

So, great to set a variable with package main to keep the context for your program. Based on the background of C / C ++, it took me a little time to get used to. Because variables are packet-covered, they do not suffer from global variable problems . If something in another package needs such a variable, you will have to pass it explicitly.

Don't be afraid to use package variables when it makes sense. This can help you reduce the complexity of your program and in many cases make your custom handlers a lot easier (where calling http.HandlerFunc() and going through the closure will suffice).

Such a simple handler might look like this:

 func simpleHandler(c Context, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // FIXME Do something with our context next.ServeHTTP(w, r) }) } 

and will be used:

 r = mux.NewRouter() http.Handle("/", simpleHandler(c, r)) 

If your needs are more complex, you may need to implement your own http.Handler . Remember that http.Handler is just an interface that implements ServeHTTP(w http.ResponseWriter, r *http.Request) .

This is untested, but you should get about 95% of the way:

 package main import ( "net/http" ) type complicatedHandler struct { h http.Handler opts ComplicatedOptions } type ComplicatedOptions struct { // FIXME All of the variables you want to set for this handler } func (m complicatedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // FIXME Do stuff before serving page // Call the next handler mhServeHTTP(w, r) // FIXME Do 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 to the mux opts := ComplicatedOptions{/* FIXME */} myHandler := ComplicatedHandler(opts) http.Handle("/", myHandler(r)) 

For a more developed handler example, see basicAuth in goji / httpauth , from which this example was shamelessly ripped off.


Further reading:

+3
source share

All Articles