3

I have the following simple API in Go:

package main

import (
    "context"
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
)

func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Call the handler
        next.ServeHTTP(w, r)

        // Retrieve custom data from the request object after the request is served
        customData := r.Context().Value("custom_data")
        fmt.Println("Custom data:", customData)
    })
}

func handler(w http.ResponseWriter, reqIn *http.Request) {
    reqIn = reqIn.WithContext(context.WithValue(reqIn.Context(), "custom_data", true))
}

func main() {
    r := mux.NewRouter()
    // Attach the middleware to the router
    r.Use(middleware)
    // Attach the handler to the router
    r.HandleFunc("/", handler).Methods("GET")
    http.ListenAndServe(":8080", r)
}

I expected the context in the middleware to be able to access the value of "custom_data", but it is not able to, returning for that context value. This happens even if I use Clone instead of WithContext for adding a value in the context of the request.

Looking around, specifically this post, if I instead use this as the handler:

func handler(w http.ResponseWriter, reqIn *http.Request) {
    req := reqIn.WithContext(context.WithValue(reqIn.Context(), "custom_data", true))
    *reqIn = *req
}

It works as expected. But modifying the *http.Request is not the norm.

My real question that I am trying to solve is; how can I pass information from the handler to the middleware?

  • Adding a value to the context of the *http.Request would be able to be accessed in the middleware.
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
  • 1
    But middleware is processed before the handler. – naneri Feb 16 '23 at 15:52
  • 1
    Context cannot be modified, only wrapped. This means that as you go _down_ the middleware chain, you can add to the context, but that's unwrapped as you climb back up the call chain. This means that handlers can see values stored in context by middlewares, but not the other way around. – Jonathan Hall Feb 16 '23 at 16:37

1 Answers1

1

You can do the following:

func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        custom_data := make(map[string]any)
        r = r.WithContext(context.WithValue(r.Context(), "custom_data", custom_data))

        // Call the handler
        next.ServeHTTP(w, r)

        // Retrieve custom data from the request object after the request is served
        v := r.Context().Value("custom_data")
        fmt.Printf("Custom data(%T): %v\n", v, v)

        // or use the above defined map directly
        fmt.Printf("Custom data(%T): %v\n", custom_data, custom_data)
    })
}

func handler(w http.ResponseWriter, r *http.Request) {
    m, ok := r.Context().Value("custom_data").(map[string]any)
    if ok && m != nil {
        m["value"] = true
    }
}

mkopriva
  • 35,176
  • 4
  • 57
  • 71
  • Thats perfect, thank you so much! Testing that, If I wanted `custom_data` to just be a boolean, it does not work. e.g. `custom_data := bool` And then of course updating the other parts to use custom_data as a boolean. Do you happen to know why this doesn't work? – Kyle Joerres Feb 16 '23 at 16:29
  • @KyleJoerres Because a `map` under the hood has a pointer and can therefore be used to share state. A plain boolean doesn't have that. You can use `*bool` however, if you want. https://go.dev/play/p/Dpc45oNspAu (note that to read/write the bool value you need to dereference the pointer) – mkopriva Feb 16 '23 at 16:38