What the hell middleware!
If you are programmer you have probably heard one of the two terms - "middleware" or "decorator"
They both achieve the same thing!
A pattern that allows adding new behaviors to objects/functions dynamically by placing them inside special wrapper objects/functions. This allows wrapping countless number of times since both input and output follow the same interface.
Requirement:
Create two middlewares:
- logMw: for logging req and response
- loggedInMw: for verifying that a user is logged in
Create one handler:
- "/me" shows basic user information if not logged in. But shows more info if user is logged in
Thinking:
I should be able to use the middleware like this:
mux.HandleFunc("/", logMw(loggedInMw(userInfo)))
where userInfo is my http.HandlerFunc
and lowMw and loggedInMw are the middlewares
More thinking:
A middleware then is a function that accepts and returns a HandlerFunc, thus enabling chaining
func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//middleware logic
.... // can operate on w and r
//call next in the end
next(w, r)
}
}
Code it!
From now on see my comments inline.
Note: The pattern to add a header called "loggedIn" is not the right thing to do . We will look at how to make it right, but for this example bear with me.
package main
import (
"fmt"
"log"
"math/rand"
"net/http"
"time"
)
var mux *http.ServeMux = http.NewServeMux()
func main() {
mux.HandleFunc("/", lowMw(loggedInMw(userInfo)))
fmt.Println("server running on 8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
// check header "loggedIn"
func userInfo(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(w.Header().Get("loggedIn")))
}
// log the req and response after the final handler has been called
func lowMw(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// start middleware logic
t := time.Now()
next(w, r)
fmt.Printf(`[%v] {"r.Method": "%s", "r.URL.Path": "%s", "status": "%v", "timeTakenµs":"%v", "loggedIn": "%s"}
`, t.UTC(), r.Method, r.URL.Path, 200, time.Since(t).Nanoseconds(), w.Header().Get("loggedIn"))
}
}
// check if user is logged or not and set a header called "loggedIn" for next handlerFunc to read from
func loggedInMw(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// start middleware logic
//
// dummy process of setting loggedIn = true
rand.Seed(time.Now().UnixNano())
temp := rand.Intn(2)
var loggedIn string = "no"
if temp == 0 {
loggedIn = "yes"
}
// end of dummy process
//
w.Header().Set("loggedIn", loggedIn)
// end of middleware logic
next(w, r)
}
}
Output
Checkout the "loggedIn" in the log below! We did!
[2020-01-14 03:50:39.7792098 +0000 UTC] {"r.Method": "GET", "r.URL.Path": "/", "status": "200", "timeTakenµs":"0", "loggedIn": "yes"}
[2020-01-14 03:50:39.9446553 +0000 UTC] {"r.Method": "GET", "r.URL.Path": "/", "status": "200", "timeTakenµs":"0", "loggedIn": "no"}
[2020-01-14 03:50:40.1101016 +0000 UTC] {"r.Method": "GET", "r.URL.Path": "/", "status": "200", "timeTakenµs":"0", "loggedIn": "yes"}
[2020-01-14 03:50:40.2615089 +0000 UTC] {"r.Method": "GET", "r.URL.Path": "/", "status": "200", "timeTakenµs":"0", "loggedIn": "yes"}
[2020-01-14 03:50:40.4339753 +0000 UTC] {"r.Method": "GET", "r.URL.Path": "/", "status": "200", "timeTakenµs":"0", "loggedIn": "yes"}
[2020-01-14 03:50:40.5863852 +0000 UTC] {"r.Method": "GET", "r.URL.Path": "/", "status": "200", "timeTakenµs":"0", "loggedIn": "yes"}
You may like to read the series:
# To the point - http middleware in go with net/http Part - 1
# To the point - http middleware in go with net/http Part - 2
Top comments (0)