-
-
Save elithrar/5aef354a54ba71a32e23 to your computer and use it in GitHub Desktop.
package main | |
import ( | |
"fmt" | |
"log" | |
"net/http" | |
"html/template" | |
"github.com/gorilla/sessions" | |
"github.com/jmoiron/sqlx" | |
"github.com/zenazn/goji/graceful" | |
"github.com/zenazn/goji/web" | |
) | |
// appContext contains our local context; our database pool, session store, template | |
// registry and anything else our handlers need to access. We'll create an instance of it | |
// in our main() function and then explicitly pass a reference to it for our handlers to access. | |
type appContext struct { | |
db *sqlx.DB | |
store *sessions.CookieStore | |
templates map[string]*template.Template | |
decoder *schema.Decoder | |
// ... and the rest of our globals. | |
} | |
// We've turned our original appHandler into a struct with two fields: | |
// - A function type similar to our original handler type (but that now takes an *appContext) | |
// - An embedded field of type *appContext | |
type appHandler struct { | |
*appContext | |
h func(*appContext, http.ResponseWriter, *http.Request) (int, error) | |
} | |
// Our ServeHTTP method is mostly the same, and also has the ability to | |
// access our *appContext's fields (templates, loggers, etc.) as well. | |
func (ah appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
// Updated to pass ah.appContext as a parameter to our handler type. | |
status, err := ah.h(ah.appContext, w, r) | |
if err != nil { | |
log.Printf("HTTP %d: %q", status, err) | |
switch status { | |
case http.StatusNotFound: | |
http.NotFound(w, r) | |
// And if we wanted a friendlier error page: | |
// err := ah.renderTemplate(w, "http_404.tmpl", nil) | |
case http.StatusInternalServerError: | |
http.Error(w, http.StatusText(status), status) | |
default: | |
http.Error(w, http.StatusText(status), status) | |
} | |
} | |
} | |
func main() { | |
// These are 'nil' for our example, but we'd either assign | |
// the values as below or use a constructor function like | |
// (NewAppContext(conf config) *appContext) that initialises | |
// it for us based on our application's configuration file. | |
context := &appContext{db: nil, store: nil} // Simplified for this example | |
r := web.New() | |
// We pass an instance to our context pointer, and our handler. | |
r.Get("/", appHandler{context, IndexHandler}) | |
graceful.ListenAndServe(":8000", r) | |
} | |
func IndexHandler(a *appContext, w http.ResponseWriter, r *http.Request) (int, error) { | |
// Our handlers now have access to the members of our context struct. | |
// e.g. we can call methods on our DB type via err := a.db.GetPosts() | |
fmt.Fprintf(w, "IndexHandler: db is %q and store is %q", a.db, a.store) | |
return 200, nil | |
} |
Just wanted to drop by to thank you for this lovely little snippet! I'm leveraging it well with gorilla/mux right now.
Won't you have a race condition with concurrent users when initializing appContext inside the main?
@vim345 Only if the members of appContext
aren't thread safe, but that applies to any object in Go. All of the objects in my example are safe for use.
Hello there, I'm just reacting here, I find it more simple & powerfull to use http funcs like this :
package main
import "net/http"
type Context int
type ServiceA struct {
ctx Context
a int
}
func (sa *ServiceA) Foo(w http.ResponseWriter, req *http.Request) {
sa.a = 1
sa.ctx = sa.ctx
}
func (sa *ServiceA) Bar(w http.ResponseWriter, req *http.Request) {
sa.a = 2
}
func main() {
a := &ServiceA{ctx: 42, a: 28}
http.HandleFunc("/a/foo", a.Foo)
http.HandleFunc("/a/bar", a.Bar)
}
And you could even chain them with alice if you really need to !!
@azr What happens if you want to define a http func in another package?
@azr your solution isn't friendly to middleware as well btw. Just tried to implement it on a reasonably simple app, and the fact that I had to both instantiate the ServiceA
completely breaks the usual middleware chain.
@sb89 - you can still do that. Make the struct public and satisfy its Handler type.
My middleware functions live in a separate package similar to the goji example: https://github.com/zenazn/goji/blob/master/example/middleware.go
In order to make the application context available to my middleware functions it seems like I need to create wrapper functions around my current middleware functions. Do you think this is a reasonable approach?