2

I am trying to customize request pipeline through middleware pattern, the code as follow:

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Hello, middleware!")
}
func middleware1(next http.HandlerFunc) func(w http.ResponseWriter, r *http.Request) {
    return func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("[START] middleware1")
        ctx := r.Context()
        ctx = context.WithValue(ctx, middleware1Key, middleware1Value)
        r = r.WithContext(ctx)
        next(w, r)
        fmt.Println("[END] middleware1")
        ctx = r.Context()
        if val, ok := ctx.Value(middleware2Key).(string); ok {
            fmt.Printf("Value from middleware2 %s \n", val)
        }

    }
}
func middleware2(next http.HandlerFunc) func(w http.ResponseWriter, r *http.Request) {
    return func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("[START] middleware2")
        ctx := r.Context()
        if val, ok := ctx.Value(middleware1Key).(string); ok {
            fmt.Printf("Value from middleware1 %s \n", val)
        }
        ctx = context.WithValue(ctx, middleware2Key, middleware2Value)
        r = r.WithContext(ctx)
        next(w, r)
        fmt.Println("[END] middleware2")

    }
}
func main() {
    mux := http.NewServeMux()
    middlewares := newMws(middleware1, middleware2)
    mux.HandleFunc("/hello", middlewares.then(helloHandler))
    if err := http.ListenAndServe(":8080", mux); err != nil {
        panic(err)
    }

}

and the output is :

[START] middleware1
[START] middleware2
Value from middleware1 middleware1Value
Hello, middleware!
[END] middleware2
[END] middleware1

According to the output, the value could pass from parent to the child , while, if the child add something to the context, it is invisible to the parent

How can I propagate value from child middleware to parent?

LoremIpsum
  • 1,652
  • 18
  • 27

1 Answers1

1

What you're doing is creating a new pointer to the modified http.Request via WithContext method. So if you're passing it to next middleware in the chain everything works as expected since you're passing this new pointer as an argument. If you want to modify the request and make it visible for those who hold the pointer to it, you need to dereference the pointer and set the modified value.

So in your 'child' middleware instead of:

r = r.WithContext(ctx)

Just do the following:

*r = *r.WithContext(ctx)

Good exercise to understand pointers in Go but you SHOULD NOT do similar operations in your production code. The docs are clear about it. See https://pkg.go.dev/net/http#Handler.

Another possible solution (without messing with the request itself) is to pass a map inside a context and read/write from/to map instead. So in your first middleware:

ctx := r.Context()
m := make(map[string]string)
m[middleware1Key] = middleware1Value
ctx = context.WithValue(ctx, dummyStoreKey, m)
r = r.WithContext(ctx)
...
if val, ok := m[middleware2Key]; ok {
    fmt.Printf("Value from middleware2 %s \n", val)
}

And in the second one:

ctx := r.Context()
if store, ok := ctx.Value(dummyStoreKey).(map[string]string); ok {
    if val, ok := store[middleware1Key]; ok {
        fmt.Printf("Value from middleware1 %s \n", val)
    }
    store[middleware2Key] = middleware2Value
}

You could add a AddStoreMiddleware as the first one in the pipeline and then use it in each successor if needed. Remember, maps in Go are not concurrently safe so in some subtle cases you should serialize access.

erni27
  • 151
  • 7
  • 1
    Please don't. The net/http module explicitly warns against modifying the request object. See for more https://stackoverflow.com/questions/13255907/in-go-http-handlers-why-is-the-responsewriter-a-value-but-the-request-a-pointer/56875204#56875204 – Ivan Velichko Jul 06 '22 at 09:39