3
\$\begingroup\$

I have two ways (middleware) of setting a specific header (if not set) to request but want to know if one is better/beneficial than the other and the reasons why.

Note: At some point I will need to log X-Request-Id value in every single application log I have. If this plan affects your answer, I don't know. Just saying!

Context version

package middleware

import (
    "context"
    "github.com/google/uuid"
    "net/http"
)

type CtxKey string
const CtxReqIdKey CtxKey = "X-Request-Id"

func RequestId(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
        CtxKey := req.Header.Get("X-Request-Id")
        if CtxKey == "" {
            CtxKey = uuid.New().String()
        }

        ctx1 := context.WithValue(req.Context(), CtxReqIdKey, CtxKey)
        ctx2 := req.WithContext(ctx1)

        handler.ServeHTTP(res, ctx2)
    })
}

# Elsewhere: request.Context().Value(middleware.CtxReqIdKey)

Without context version

package middleware

import (
    "github.com/google/uuid"
    "net/http"
)

func RequestId(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
        if req.Header.Get("X-Request-Id") == "" {
            req.Header.Set("X-Request-Id", uuid.New().String())
        }

        handler.ServeHTTP(res, req)
    })
}

Usage

err := http.ListenAndServe(":8080", middleware.RequestId(Router))
\$\endgroup\$
  • 2
    \$\begingroup\$ passing the req-id to the context will let you track it within sub layer of your application like db requests by giving them the updated context. \$\endgroup\$ – mh-cbon Sep 15 at 10:27
3
\$\begingroup\$

Long form comment: Yes, the context version. I'd consider it a bad pattern to modify incoming data inline (because it's harder to track later what came in and what the application did with it). Especially in applications which do not only handle HTTP traffic, but also, say, gRPC calls, it's even more important that this data isn't stored for one type of handler only, but for all of them in a unified fashion. Plus, context is (for better and worse) a kitchen sink for all layers of the code, even in the deepest layers you'll still be able to get that ID for various purposes, maybe auditing, or so.

Maybe it doesn't even need to go into the context, simply add it to the logger data (if there's such a thing), or however else that's being populated. Doing that in a wrapper like this will again allow you to keep it out of the rest of the application - problem solved and no need to mess with the context object. In any case it's not difficult to change that later.

\$\endgroup\$
  • 2
    \$\begingroup\$ It will be a field in JSON formatted logs for error/access tracking purposes and passed to other microservices through HTTP etc. calls. So the whole thing is just for e2e footprint. Souns like the Context is the way to Go. Ta. \$\endgroup\$ – BentCoder Sep 17 at 22:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.