HTTP middleware to support net/context, for Golang
Go
Pull request Compare This branch is 16 commits ahead, 151 commits behind urfave:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
.gitignore
.travis.yml
LICENSE
README.md
bundle.go
doc.go
hax.go
hax_test.go
logger.go
logger_test.go
recovery.go
recovery_test.go
response_writer.go
response_writer_test.go
static.go
static_test.go

README.md

Update: After more reading up, it looks like net/context will be attached to net/http.Request! Yes :D https://github.com/golang/go/issues/14660 https://go-review.googlesource.com/#/c21496

So, I think its probably better to stick to negroni + gorilla for development. And wait for Go1.7 to be released.


what other options are out there:

  • negroni + gorilla/mux - could not share auth information downstream. one solution was gorilla/context. That's akin to managing a global singleton through the lifecycle of webapp.
  • goji/goji (aka gojiv2) - tried using it but there was a noticeable lag in the request even using curl -i http://localhost/helloworld
  • zenazn/goji - has a context structure that I really like but its not using net/context in the signature, but its own webcontext.

  • other ideas - to hook into responsewriter (dont' like this), or hook to request. prefer to keep context within the signature to be explicit. save into datastore and extract (i/o based)

considerations:

  • compatibility - moving forward to (c, rw, req) signatures. easy to perform backward compatibility by explicitly using an adapter.
  • decoupled middleware engine vs multiplexer/router.

limitations:

  • might be racy if context is passed over to goroutines directly - when goroutine outlives the request. but i believe developers who use spawn additional goroutines within requests should be experienced enough to handle this by passing the contextual params by-value, rather than by-reference to a goroutine.

Hax GoDoc Build Status

Hax (based on Negroni) is tiny, non-intrusive, and encourages use of net/http Handlers.

It uses an Express-like (NodeJS) approach to web middleware in Go. If you like the idea of Martini, but you think it contains too much magic, then Hax is a great fit.

Hax is a direct port of Negroni:

  • Negroni lightweight and solid to use as an app engine (how many lines of code). you know what is in your code.
  • builds a middleware stack
  • keeps the router separate, so you can use any router easily according to your needs. keeps the development modular.
  • Provides a shared bundle that is embedded in net/context to allow bundle values to be shared across middleware per request. this is useful for authentication/authorization processes. or pre-request handling.

Getting Started

After installing Go and setting up your GOPATH, create your first .go file. We'll call it server.go.

package main

import (
  "net/http"
  "fmt"

  "golang.org/x/net/context"
  "github.com/haxgo/hax"
  "github.com/haxgo/muxc"
)

func main() {
  mux := muxc.New()
  mux.HandleFunc("/hello", hello)
  mux.HandleFunc("/helloinline", func(c context.Context, w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, "Hello inline!")
  })

  hax := hax.Classic()
  hax.UseHandlerFunc(middlewareA)
  hax.UseHandlerFunc(middlewareB)
  hax.UseHandler(mux)
  hax.Run(":3000")
}

func hello(c context.Context, w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello!")
    bun := hax.GetBundle(c)

    if value := bun.Get("valueA"); value != nil {
        logger.Printy("from helloHandlerFunc, valueA is " + value.(string))
    }
    if value := bun.Get("valueB"); value != nil {
        logger.Printy("from helloHandlerFunc, valueB is " + value.(string))
    }
}

func middlewareA(c context.Context, w http.ResponseWriter, r *http.Request, next httpc.HandlerFunc) {
    fmt.Println("[hax] I am middlewareA")
    bun := hax.GetBundle(c)
    bun.Set("valueA", ": from middlewareA")
}

func middlewareB(c context.Context, w http.ResponseWriter, r *http.Request, next httpc.HandlerFunc) {
    fmt.Println("[hax] I am middlewareB")
    bun := hax.GetBundle(c)
    bun.Set("valueB", ": from middlewareB")
}

Then install the hax package (go 1.1 and greater is required):

go get github.com/haxgo/hax

Then run your server:

go run server.go

You will now have a Go net/http webserver running on localhost:3000.

Need Help?

The GitHub issues for Hax will be used exclusively for bug reports and pull requests.

Is Hax a Framework?

Hax is not a framework. It is a no-frills engine to manage the middleware stack with contextual flow per request.

Routing net/http?

Hax is BYOR (Bring your own Router). While the Go community already has a number of great http routers available, they need to handle a signature that serves Golang's net/context. This is usually straightforward.

The signature used is based on haxgo/httpc: (context.Context, http.ResponseWriter, *http.Request) For comparison, the signature of net/http is : (http.ResponseWriter, *http.Request)

See haxgo/httpcrouter and haxgo/muxc for ports of julienschmidt/httprouter and gorill/mux respectively. For instance, integrating with Haxgo/Muxc looks like so:

  mux := muxc.NewRouter()
  mux.HandleFunc("/helloinline", func(c context.Context, w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, "Hello inline!")
  })

  hax := hax.Classic()
  hax.UseHandlerFunc(middlewareA) // see getting started above 
  hax.UseHandlerFunc(middlewareB) // see getting started above 
  hax.UseHandler(mux)
  hax.Run(":3000")

Golang net/context + haxgo/bundle

Hax shares a per-request bundle that is encapsulated by net/context. Middlewares in Hax carry a (context.Context, http.ResponseWriter, *http.Request) signature. If you do not need need context information, use Negroni instead.

Note: The bundle is meant to be a lightweight carrier. For example, it can be used to store bearer tokens from the authentication layer in middleware. The tokens can then be accessed downstream handlers.

import (
    //  ...
    "net/http"
    "golang.org/x/net/context"
    "github.com/haxgo/hax"
)

func someFunction(c context.Context, w http.ResponseWriter, r *http.Request) {
    // Get request context
    bun := hax.GetBundle(c)
    if value := bun.Get("key"); value != nil {
        fmt.Println("The request value is " + value.(string))
    }

    // Set request context
    bun.Set("key", "some value")
}

However, if you need flowthrough between your middlewares, but just need minor support for net/http for a couple of functions, its trival to transform the handlers. The context information should still flowthrough the stack via Hax as needed.

func someHandlerFunc(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello!")
}

// http.HandlerFunc aka func(w http.ResponseWriter, r *http.Request)
handlerFunc := someHandlerFunc 

// Transform to http.handler aka ServeHttp(w http.ResponseWriter, r *http.Request)
handler := http.HandlerFunc(someHandlerFunc)

// Transform to httpc.handler aka ServeHTTPC(c context.Context, w http.ResponseWriter, r *http.Request)
handlerC := httpc.Handler(http.HandlerFunc(someHandlerFunc))

hax.Classic()

hax.Classic() provides some default middleware that is useful for most applications:

  • hax.Recovery - Panic Recovery Middleware.
  • hax.Logging - Request/Response Logging Middleware.
  • hax.Static - Static File serving under the "public" directory.

This makes it really easy to get started with some useful features from Hax.

Handlers

Hax has built-in adapters to handle middleware flow. This is done through the hax.Handler interface:

type Handler interface {
  ServeHTTPC(c context.Context, rw http.ResponseWriter, r *http.Request, next httpc.HandlerFunc)
}

If a middleware hasn't already written to the ResponseWriter, it should call the next httpc.HandlerFunc in the chain to yield to the next middleware handler. This can be used for great good:

func middlewareC(c context.Context, rw http.ResponseWriter, r *http.Request, next httpc.HandlerFunc) {
  // do some stuff before
  next(c, rw, r)
  // do some stuff after
}

And you can map it to the handler chain with the Use... functions:

// middleware is of type hax.Handler ServeHttpC(context.Context, http.ResponseWriter, *http.Request, next httpc.HandlerFunc)
hax.Use(middleware)     

// middleware is of type hax.HandlerFunc func(context.Context, http.ResponseWriter, http.Request, next httpc.HandlerFunc)
hax.UseFunc(middleware) 

// middleware is of type httpc.Handler ServeHttpC(context.Context, http.ResponseWriter, *http.Request)
hax.UseHandler(middleware)

// middleware is of type httpc.HandlerFunc func(context.Context, http.ResponseWriter, *http.Request)
hax.UseHandlerFunc(middleware)

// middleware is of type http.Handler ServeHttp(http.ResponseWriter, *http.Request)
hax.UseHttpHandler(middleware)

// middleware is of type http.HandlerFunc func(http.ResponseWriter, *http.Request)
hax.UseHttpHandlerFunc(middleware)

Run()

Hax has a convenience function called Run. Run takes an addr string identical to http.ListenAndServe.

hax := hax.Classic()
// ...
log.Fatal(http.ListenAndServe(":8080", hax))

Route Specific Middleware

If you have a route group of routes that need specific middleware to be executed, you can simply create a new Hax instance and use it as your route handler.

router := mux.NewRouter()
adminRoutes := mux.NewRouter()
// add admin routes here
// ...

// Create a new hax instance for the admin middleware
router.Handle("/admin", hax.New(
  Middleware1,
  Middleware2,
  hax.Wrap(adminRoutes),
))

Essential Reading for Beginners of Go & Hax

About

Hax is based on Negroni, which is obsessively designed by none other than the Code Gangsta