DEV Community

Cover image for To the point - http middleware in go with net/http Part - 2
HM
HM

Posted on

To the point - http middleware in go with net/http Part - 2

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

  1. We will not be using the "context" package directly
  2. 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"
Enter fullscreen mode Exit fullscreen mode

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"


Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Further reading:

- https://medium.com/@cep21/how-to-correctly-use-context-context-in-go-1-7-8f2c0fafdf39
- https://drstearns.github.io/tutorials/gomiddleware/
Enter fullscreen mode Exit fullscreen mode

Top comments (0)