What are the different approaches we might use for middleware? list approach: - the request runs a _list_ of handlers - some may be before the "primary" one, some after - ex: routes.GET("/foo", handler1, handler2, handler3, handler4) - ex auth middleware (before): func authHandler(ctx *Context) (ok, Response) { token := ctx.GetHeader("Authorization") if token.IsGood() { ctx.User = GetUserByToken(token) return true } else { return false, New401Response("bad token") } } - ex error logging middleware (after): func errorLoggingHandler(ctx *Context, res Response) Response { for _, err := ctx.Errors { log.Print(err) } return res } - ex timing middleware (both??): // This must be done with a pair of middlewares. func makeTimingHandlers() (BeforeHandler, AfterHandler) { var start time.Time before := func(ctx *Context) (bool, Response) { start = time.Now() return true, nil } after := func(ctx *Context, res Response) Response { end := time.Now() log.Print(end.Sub(start)) return res } return before, after } before, after := makeTimingHandlers() // Pretty sure this doesn't actually work because these functions can't be cleanly shared between multiple handlers. // ... - ex route group: type RouteGroup struct { BeforeHandlers []func(ctx *Context) (ok bool, res Response) AfterHandlers []func(ctx *Context, res Response) Response } rg := RouteGroup{ BeforeHandlers: []BeforeHandler{timingBeforeHandler, authHandler}, AfterHandlers: []AfterHandler{errorLoggingHandler, timingAfterHandler}, } composition approach: - the request runs _one_ handler, but that handler can call others - ex: routes.GET("/foo", timingHandler(errorLoggingHandler(authHandler(FooHandler)))) - ex middleware: func authHandler(next Handler) Handler { return func(ctx *Context) Response { token := ctx.GetHeader("Authorization") if token.IsGood() { ctx.User = GetUserByToken(token) return next(ctx) } else { return New401Response("bad token") } } } - ex error logging middleware: func errorLoggingHandler(previous Handler) Handler { return func(ctx *Context) Response { res := prev(ctx) for _, err := ctx.Errors { log.Print(err) } return res } } - ex timing middleware: func timingHandler(next Handler) Handler { return func(ctx *Context) Response { start := time.Now() res := next(ctx) end := time.Now() log.Print(end.Sub(start)) return res } } - ex route group: type RouteGroup struct { Middleware func(primaryHandler Handler) Handler } rg := RouteGroup { Middleware: func(h Handler) Handler { return timingHandler(errorLoggingHandler(authHandler(h))) } } or what if we did it with less composition (I like this approach): - ex route group: type RouteGroup struct { Middleware func(*Context, Handler) Response } rg := RouteGroup { Middleware: func(ctx *Context, h Handler) Response { start := time.Now() token := ctx.GetHeader("Authorization") if token.IsGood() { ctx.User = GetUserByToken(token) } else { return New401Response("bad token") } res := h(ctx) for _, err := ctx.Errors { log.Print(err) } end := time.Now() log.Print(end.Sub(start)) return res } } - same but more concise: type RouteGroup struct { Middleware func(*Context, Handler) Response } func authMiddleware(ctx *Context) (ok, Response) { token := ctx.GetHeader("Authorization") if token.IsGood() { ctx.User = GetUserByToken(token) return true, nil } else { return false, New401Response("bad token") } } func logContextErrors(ctx *Context) { for _, err := ctx.Errors { log.Print(err) } } func logDuration(start time.Time) { end := time.Now() log.Print(end.Sub(start)) } rg := RouteGroup { Middleware: func(ctx *Context, h Handler) Response { defer logDuration(time.Now()) authOk, errRes := authMiddleware(ctx) if !authOk { return errRes } res := h(ctx) logContextErrors(ctx) return res } }