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)) }