http handlers in go

Go servers using the standard library tend to use the Handler interfaces and HandlerFunc type:

type Handler interface { 
    ServeHTTP(ResponseWriter, *Request)
}
type HandlerFunc func(ResponseWriter, *Request) 

Context

The standard http HandlerFunc accepts a ResponseWriter interface, and a pointer to a Request type. This means if you nest handlers, they have no way of communicating with each other, and there is no simple way to store and pass other information to your handlers, for example a logged in user,  information on the matched route and parsed parameters from the url, the state of the app config, etc. Various suggestions have been made to handle this, from a global context to request-specific contexts. Other approaches (as in Martini) eschew this handler, and use reflection to allow you to use any function signature for handlers - unfortunately this throws out most of the benefits of the go type system, and makes your app more complex instead of using a simple defined handler type.

Errors

The standard HandleFunc doesn't have any return value and you are expected to handle errors within each handler as they come up. In middleware handlers you have no way of signalling that a request has been handled, and no further handlers should tackle it.

The treatment of errors in web development is often very similar, so you'll find yourself either repeating calls to http.Error then return or repeating calls to render an error template. Often you'll want to present errors in a standardised format, and send more information like the http status code, a stack trace, etc., which requires a bit more information than http.Error or the error interface provide. For this reason I think it's better to return an error type from HandlerFuncs so that the Handler can recognise that a given request has been handled successfully, or an error has occurred.

Another approach

Fragmenta offers another approach to both of these problems by creating a context for each request and passing it to a defined Handler function type, and requiring handlers to return an error value. Handlers in fragmenta are of the form:

type Handler func(router.Context) error

Where router.Context is an interface which stores the Writer and Request values for the request, and error is an error conforming to the error interface. The error may in fact be a more complex error of type router. StatusError, which stores a status and about the callsite for debug, and means we can display more detail for errors in development mode, and a friendlier message to users in production. This lets us serve requests with the minimum of boilerplate around fetching parameters etc, and return specific http errors with a status code and other details to be rendered by a user-defined error function which is common to all handlers.

    func HandleShow(context router.Context) error {

      // Find the tag 
      tag, err := tags.Find(context.ParamInt("id"))
      if err != nil {
        return router.NotFoundError(err)
      }

      // Authorize access to this tag 
      err = authorise.Resource(context, tag)
      if err != nil {
        return router.NotAuthorizedError(err)
      }

      // Serve template 
      view := view.New(context)
      view.AddKey("tag", tag)
      return view.Render()
    }

Discuss at Golang News