Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
}
@egonelbre

This comment has been minimized.

Copy link

@egonelbre egonelbre commented Jul 16, 2014

Can be made simpler: see http://play.golang.org/p/xF5N4GjL1T

@elithrar

This comment has been minimized.

Copy link
Owner Author

@elithrar elithrar commented Jul 16, 2014

@egonelbre That's not the same thing unfortunately. In your example, it's not possible to access our instance of appContext inside ServeHTTP (or any other method we use to satisfy an interface). We wouldn't be able to access our (e.g.) custom logger or template map in that case.

This is why I left the err := ah.renderTemplate... comment in there and highlighted it in the comment above ServeHTTP.

@nmerouze

This comment has been minimized.

Copy link

@nmerouze nmerouze commented Jul 18, 2014

If I have parameters in the URL, they are not passed to the handler because you didn't use Goji context. If you want to keep appHandler as a http.Handler how would you pass URL parameters?

@elithrar

This comment has been minimized.

Copy link
Owner Author

@elithrar elithrar commented Aug 1, 2014

@nmerouze I've kept it simple here—so others wouldn't get confused by the extra Goji stuff—but you would just extend the signature to match and add a ServeHTTPC method:

type appHandler struct {
    *appContext
    h func(*appContext, web.C, http.ResponseWriter, *http.Request) (int, error) // Add web.C
}

// Satisfy http.Handler, which Goji's web.Handler extends.
func (ah appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    ah.ServeHTTPC(web.C{}, w, r)
}

// Satisfy Goji's web.Handler interface
func (ah appHandler) ServeHTTPC(c web.C, 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)
        }
    }
}

// And now your handlers can access Goji's context.
func AnotherHandler(a *appContext, c web.C, w http.ResponseWriter, r *http.Request) (int, error) {
    fmt.Fprintf(w, "AnotherHandler %v", c.URLParams["id"])
    return 200, nil
}

This is exactly how I do it in my own applications.

@hasryan

This comment has been minimized.

Copy link

@hasryan hasryan commented Aug 22, 2014

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?

package main

import(
  ...
  "myproj/mymiddleware"
  "myproj/mycontext"
)

func main() {
  ...
  appContext := initializeAppContext()
  goji.Use(mymiddleware.SuperSecure(appContext))
}
package mymiddleware

import "myproj/mycontext"

func SuperSecure(appContext *mycontext.AppContext) (func(c *web.C, h http.Handler) http.Handler) {
  return func(c *web.C, h http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
     result := appContext.Db.Query("select * from foo")
    }
    return http.HandlerFunc(fn)
  }
}
@jagger27

This comment has been minimized.

Copy link

@jagger27 jagger27 commented Nov 17, 2014

Just wanted to drop by to thank you for this lovely little snippet! I'm leveraging it well with gorilla/mux right now.

@vim345

This comment has been minimized.

Copy link

@vim345 vim345 commented Mar 26, 2015

Won't you have a race condition with concurrent users when initializing appContext inside the main?

@elithrar

This comment has been minimized.

Copy link
Owner Author

@elithrar elithrar commented Apr 28, 2015

@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.

@azr

This comment has been minimized.

Copy link

@azr azr commented Oct 28, 2015

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 !!

@sb89

This comment has been minimized.

Copy link

@sb89 sb89 commented Jan 13, 2016

@azr What happens if you want to define a http func in another package?

@jayd3e

This comment has been minimized.

Copy link

@jayd3e jayd3e commented Jun 19, 2016

@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.

@elithrar

This comment has been minimized.

Copy link
Owner Author

@elithrar elithrar commented Aug 7, 2016

@sb89 - you can still do that. Make the struct public and satisfy its Handler type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment