Middleware

Pada bab ini kita akan membuat middleware. Kasus yang digunakan adalah handling auth.

  • Buat file baru libraries/api/middleware.go

package api

// Middleware is a function designed to run some code before and/or after
// another Handler. It is designed to remove boilerplate or other concerns not
// direct to any given Handler.
type Middleware func(Handler) Handler

// wrapMiddleware creates a new handler by wrapping middleware around a final
// handler. The middlewares' Handlers will be executed by requests in the order
// they are provided.
func wrapMiddleware(mw []Middleware, handler Handler) Handler {

    // Loop backwards through the middleware invoking each one. Replace the
    // handler with the new wrapped handler. Looping backwards ensures that the
    // first middleware of the slice is the first to be executed by requests.
    for i := len(mw) - 1; i >= 0; i-- {
        h := mw[i]
        if h != nil {
            handler = h(handler)
        }
    }

    return handler
}
  • list semua middleware yang diperlukan pada routing/route.go

package routing

import (
    "database/sql"
    "essentials/controllers"
    "essentials/libraries/api"
    "essentials/middlewares"
    "log"
    "net/http"
)

func mid(db *sql.DB, log *log.Logger) []api.Middleware {
    var mw []api.Middleware
    mw = append(mw, middlewares.Auths(db, log, []string{"/login"}))

    return mw
}

// API handling routing
func API(db *sql.DB, log *log.Logger) http.Handler {
    app := api.NewApp(log, mid(db, log)...)
    app.HandleCors()

    // Users routing
    {
        users := controllers.Users{Db: db, Log: log}
        app.Handle(http.MethodGet, "/users", users.List)
        app.Handle(http.MethodPost, "/users", users.Create)
        app.Handle(http.MethodGet, "/users/:id", users.View)
        app.Handle(http.MethodPut, "/users/:id", users.Update)
        app.Handle(http.MethodDelete, "/users/:id", users.Delete)
    }

    return app
}
  • Tambahkan field middleware di type App libraries/api/app.go

package api

import (
    "context"
    "log"
    "net/http"
    "time"

    "github.com/julienschmidt/httprouter"
)

// App struct for new api
type App struct {
    log *log.Logger
    mux *httprouter.Router
    mw  []Middleware
}

// Handler type as standard http.Handle
type Handler func(http.ResponseWriter, *http.Request)

// Ctx type for encapsulated context key
type Ctx string

// Handle associates a httprouter Handle function with an HTTP Method and URL pattern.
func (a *App) Handle(method, url string, h Handler) {
    h = wrapMiddleware(a.mw, h)

    fn := func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
        ctx := context.WithValue(r.Context(), Ctx("ps"), ps)
        //const timeout = 1 * time.Second
        //ctx2, cancel := context.WithTimeout(ctx, timeout)
        //defer cancel()

        header := w.Header()
        header.Add("Access-Control-Allow-Origin", "*")
        header.Add("Access-Control-Allow-Methods", "DELETE, POST, GET, OPTIONS, PUT")
        header.Add("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With, Token")
        header.Add("Content-Type", "application/json; charset=utf-8")

        h(w, r.WithContext(ctx))
    }

    a.mux.Handle(method, url, fn)
}

// HandleCors and OPTIONS response
func (a *App) HandleCors() {
    a.mux.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Header.Get("Access-Control-Request-Method") != "" {
            // Set CORS headers
            header := w.Header()
            header.Add("Access-Control-Allow-Origin", "*")
            header.Add("Access-Control-Allow-Methods", "DELETE, POST, GET, OPTIONS, PUT")
            header.Add("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With, Token")
            header.Add("Content-Type", "application/json; charset=utf-8")
        }

        // Adjust status code to 204
        w.WriteHeader(http.StatusNoContent)
    })
}

// ServeHTTP implements the http.Handler interface
func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    a.mux.ServeHTTP(w, r)
}

// NewApp for create new api
func NewApp(log *log.Logger, mw ...Middleware) *App {
    return &App{
        log: log,
        mux: httprouter.New(),
        mw:  mw,
    }
}
  • Buat middleware untuk handling authorization ( middlewares/auth.go )

package middlewares

import (
    "database/sql"
    "errors"
    "log"
    "net/http"

    "essentials/libraries/api"
)

// Auths middleware
func Auths(db *sql.DB, log *log.Logger, allow []string) api.Middleware {
    fn := func(before api.Handler) api.Handler {
        h := func(w http.ResponseWriter, r *http.Request) {
            var isAuth bool

            // hardcode athorization for true.
            // upcoming chapter, this line will execute RBAC checking
            isAuth = true

            if !isAuth {
                api.ResponseError(w, api.ErrForbidden(errors.New("Forbidden"), ""))
            } else {
                before(w, r)
            }
        }

        return h
    }

    return fn
}

Last updated