Skip to content

Instantly share code, notes, and snippets.

@larryfox
Last active August 29, 2015 14:03
Show Gist options
  • Save larryfox/cfa0911c3633ed25f1f7 to your computer and use it in GitHub Desktop.
Save larryfox/cfa0911c3633ed25f1f7 to your computer and use it in GitHub Desktop.
Functional middleware chaining in Go
package middleware
import "net/http"
// Middleware are just functions that accept an
// http.Handler and return an http.Handler
type Middleware func(http.Handler) http.Handler
// Chain composes any number of middleware to another.
// Middleware(a).Chain(b, c)
// is equvilant to
// Chain(a, b, c)
func (m Middleware) Chain(wares ...Middleware) Middleware {
return compose(m, Chain(wares...))
}
// Apply calls the Middleware with the supplied Handler.
// Middleware(myMiddleware).Apply(myHandler)
// is equivlant to
// Middleware(myMiddleware)(myHandler)
// is equivlant to
// myMiddleware(myHandler)
func (m Middleware) Apply(x http.Handler) http.Handler {
return m(x)
}
// Chain composes any number of middleware together.
// Chain(a, b, c, d)
func Chain(wares ...Middleware) Middleware {
return foldr(compose, noop, wares...)
}
type combinator func(a, b Middleware) Middleware
func compose(a, b Middleware) Middleware {
return func(x http.Handler) http.Handler {
return a(b(x))
}
}
func foldr(cmb combinator, acc Middleware, wares ...Middleware) Middleware {
if len(wares) == 0 {
return acc
}
return cmb(wares[0], foldr(cmb, acc, wares[1:]...))
}
// noop Middleware. Used as the initial value for foldr
func noop(h http.Handler) http.Handler {
return h
}
@larryfox
Copy link
Author

larryfox commented Jul 8, 2014

Usage…

package main

import (
    "fmt"
    "net/http"

    . "gist.github.com/cfa0911c3633ed25f1f7.git"
)

func main() {
    fmt.Printf("Listening on localhost%s (ctrl+C to exit)\n", ":9292")
    mux := http.NewServeMux()

    // Use multiple middleware chains
    middlewareOne := Middleware(a).
        Chain(b).
        Chain(c, d)

    // Or just one
    middlewareTwo := Chain(a, b, c, d)

    // Return an http.Handler like this
    mux.Handle("/one", middlewareOne.Apply(y))

    // or this
    mux.Handle("/two", middlewareTwo(z))

    // which is just sugar for this
    mux.Handle("/sugar", a(b(c(d(z)))))

    http.ListenAndServe(":9292", mux)
}

func a(h http.Handler) http.Handler {
    handler := func(w http.ResponseWriter, req *http.Request) {
        fmt.Println("Before a")
        h.ServeHTTP(w, req)
        fmt.Println("After a")
    }
    return http.HandlerFunc(handler)
}

func b(h http.Handler) http.Handler {
    handler := func(w http.ResponseWriter, req *http.Request) {
        fmt.Println("Before b")
        h.ServeHTTP(w, req)
        fmt.Println("After b")
    }
    return http.HandlerFunc(handler)
}

func c(h http.Handler) http.Handler {
    handler := func(w http.ResponseWriter, req *http.Request) {
        fmt.Println("Before c")
        h.ServeHTTP(w, req)
        fmt.Println("After c")
    }
    return http.HandlerFunc(handler)
}

func d(h http.Handler) http.Handler {
    handler := func(w http.ResponseWriter, req *http.Request) {
        fmt.Println("Before d")
        h.ServeHTTP(w, req)
        fmt.Println("After d")
    }
    return http.HandlerFunc(handler)
}

var y = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    fmt.Println("In y")
    fmt.Fprint(w, "Hello from y")
})

var z = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    fmt.Println("In z")
    fmt.Fprint(w, "Hello from z")
})

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