In the last post, we created an http middleware and propagated a value to signify if the user is logged in or not. The value was propagated using ResponseWriter.Header, that isn't really what we should do.
Instead, we should make use of the http.Request.Context
Note
- We will not be using the "context" package directly
- Since go v1.7, Context is part of the incoming http.Request object)
Very basics of http.Request.Context:
// Given a request "r"
// context values are immutable,
// so, creating a new context from existing context:
newContext := context.WithValue(r.Context(), key, <someValue>)
// a context can be attached to the request only upon request creation,
// so, create a new request using the next context:
r = r.WithContext(newContext)
// reading the value off the context in a downstream middleware
readValue := r.Context().Value(key).(string)
// some extra stuff to note: (ref https://golang.org/pkg/context/#WithValue)
// so, create out own type for the context key and assign name for the key
type contextKey string
var key contextKey = "loggedIn"
Note: The way contexts work, they are only propagated downstream. i.e. A middleware sitting before another middleware creating a context wont be able to access the context
Code it!
With the above knowledge we can make the changes!
The changes are all marked with the comment prefixed with "CHANGE: "
package main
import (
"context"
"fmt"
"log"
"math/rand"
"net/http"
"time"
)
var mux *http.ServeMux = http.NewServeMux()
func main() {
mux.HandleFunc("/", loggedInMw(lowMw(userInfo))) //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"))) // CHANGE: replace this line with the following
w.Write([]byte(r.Context().Value(key).(string)))
}
// 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(), r.Context().Value(key)) // CHANGE: replaced 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) // CHANGE: replace this line with the following lines
newContext := context.WithValue(r.Context(), key, loggedIn)
r = r.WithContext(newContext) // even this works: r = r.Clone(newContext); Q. exercise: explore the difference!
// end of middleware logic
next(w, r)
}
}
// CHANGE: add the following lines
// create out own type for the context key and assign name for the key
type contextKey string
var key contextKey = "loggedIn"
Output
Checkout the "loggedIn" in the log below! We did!
[2020-01-15 00:17:20.1845556 +0000 UTC] {"r.Method": "GET", "r.URL.Path": "/", "status": "200", "timeTakenµs":"0", "loggedIn": "yes"}
[2020-01-15 00:17:20.8007946 +0000 UTC] {"r.Method": "GET", "r.URL.Path": "/", "status": "200", "timeTakenµs":"0", "loggedIn": "no"}
[2020-01-15 00:17:21.320177 +0000 UTC] {"r.Method": "GET", "r.URL.Path": "/", "status": "200", "timeTakenµs":"0", "loggedIn": "yes"}
[2020-01-15 00:17:23.6705213 +0000 UTC] {"r.Method": "GET", "r.URL.Path": "/", "status": "200", "timeTakenµs":"0", "loggedIn": "no"}
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
Further reading:
- https://medium.com/@cep21/how-to-correctly-use-context-context-in-go-1-7-8f2c0fafdf39
- https://drstearns.github.io/tutorials/gomiddleware/
Top comments (0)