To the point - http middleware in go with net/http Part - 2
Tags: go, beginners, webdev, tutorial
Published: 2020-01-15 11:01:45

<br/>

> 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:

```go
// 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: "

```go
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/
```