Moon is a simple middleware chaining system with requests context handled by /x/net/context context.Context
No magic GO code is required to use Moon.
Middlewares use this signature
func (context.Context, moon.Next) http.Handlerand final handler
func handler(context.Context) http.HandlerMiddlewares are chained with moon.New
middlewares := moon.New(middleware1, middleware2, middleware3, ...)moon.New returns a Moon struct.
The final handler is appended by passing moon.Handler to Moon.Then
r.Handle("/api", middlewares.Then(handler))or by passing a function to Moon.ThenFunc
r.Handle("/api", middlewares.ThenFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
...
}))Inside a middleware you can advance the chain by calling next(ctx)
func Middleware(ctx context.Context, next moon.Next) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ...
next(ctx)
// ...
})
}By default a context.Context is created for each request and is an instance of
ctx := context.TODO()this behaviour can be modified by setting a callback for moon.Context
ie. if you run you app in appengine:
func init() {
...
// just setup it once; then this function is called for every request
moon.Context = func(r *http.Request) {
return appengine.NewContext(r)
}
...
}Recover from panics with status code http.StatusInternalServerError and a dump of the goroutine stack trace
middlewares := moon.New(moon.Panic, ...).Then(...)Compatibility is provided with all 3rd party middlewares using the following signature
func (http.Handler) http.Handlerjust wrap the function with
moon.Adapt
ie. GOJI SimpleBasicAuth
goji_middleware := moon.Adapt(httpauth.SimpleBasicAuth("user", "pass"))
middlewares := moon.New(goji_middlware, ...).Then(...)// Appengine test app
package app
import (
"fmt"
"net/http"
"google.golang.org/appengine"
"golang.org/x/net/context"
"github.com/gorilla/mux"
"github.com/nferruzzi/moon"
)
func MWRequireJSON(ctx context.Context, next moon.Next) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ct := r.Header.Get("Content-Type")
if ct == "application/json" {
ctx = context.WithValue(ctx, "Content-Type", ct)
next(ctx)
} else {
w.WriteHeader(http.StatusBadRequest)
}
})
}
func MWRequireUser(ctx context.Context, next moon.Next) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// let's pretend some checks are made
ctx = context.WithValue(ctx, "User", "user")
next(ctx)
})
}
func handler(ctx context.Context) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello %v! thanks for your %v", ctx.Value("User"), ctx.Value("Content-Type"))
})
}
func init() {
// Appengine only: configure moon to get the context from the request
// Common default is context.TODO()
moon.Context = func(r *http.Request) context.Context {
return appengine.NewContext(r)
}
// Middlewares chain
middlewares := moon.New(MWRequireJSON, MWRequireUser)
// Use gorilla mux to route and handle the request
r := mux.NewRouter()
r.Handle("/api", middlewares.Then(handler))
http.Handle("/", r)
}
// curl -H Content-Type:application/json http://localhost:8080/api
// Hello user! thanks for your application/json✔ ~API is under development.
Thanks to the authors of the package below, I got a lot of idea from your code
Using contex.Context inspired by kami and appengine
https://github.com/guregu/kami
Middleware chaining inspired by Alice
https://github.com/justinas/alice
and by Stack
https://github.com/alexedwards/stack
Bidirectional middleware flow inspired by Negroni
https://github.com/codegangsta/negroni
Tested with GOJI middlewares