What is the proper way to allow multiple reads of

2019-09-22 10:00发布

I want to access a http.Request's Body multiple times. The first time happens in my authentication middleware, it uses it to recreate a sha256 signature. The second time happens later, I parse it into JSON for use in my database.

I realize that you can't read from an io.Reader (or an io.ReadCloser in this case) more than once. I found an answer to another question with a solution:

When you first read the body, you have to store it so once you're done with it, you can set a new io.ReadCloser as the request body constructed from the original data. So when you advance in the chain, the next handler can read the same body.

Then in the example they set http.Request.Body to a new io.ReadCloser:

// And now set a new body, which will simulate the same data we read:
r.Body = ioutil.NopCloser(bytes.NewBuffer(body))

Reading from Body and then setting a new io.ReadCloser at each step in my middleware seems expensive. Is this accurate?

In an effort to make this less tedious and expensive I use a solution described here to stash the parsed byte array in the Context() value of the request. Whenever I want it, its waiting for me already as byte array:

type bodyKey int
const bodyAsBytesKey bodyKey = 0

func newContextWithParsedBody(ctx context.Context, req *http.Request) context.Context {
    if req.Body == nil || req.ContentLength <= 0 {
        return ctx
    }

    if _, ok := ctx.Value(bodyAsBytesKey).([]byte); ok {
        return ctx
    }

    body, err := ioutil.ReadAll(req.Body)
    if err != nil {
        return ctx
    }

    return context.WithValue(ctx, bodyAsBytesKey, body)
}

func parsedBodyFromContext(ctx context.Context) []byte {
    if body, ok := ctx.Value(bodyAsBytesKey).([]byte); ok {
        return body
    }

    return nil
}

I feel like keeping a single byte array around is cheaper than reading a new one each time. Is this accurate? Are there pitfalls to this solution that I can't see?

1条回答
男人必须洒脱
2楼-- · 2019-09-22 10:41

Is it "cheaper"? Probably, depending on what resource(s) you're looking at, but you should benchmark and compare your specific application to know for sure. Are there pitfalls? Everything has pitfalls, this doesn't seem particularly "risky" to me, though. Context values are kind of a lousy solution to any problem due to loss of compile-time type checking and the general increase in complexity and loss of readability. You'll have to decide what trade-offs to make in your particular situation.

If you don't need the hash to be completed before the handler starts, you could also wrap the body reader in another reader (e.g. io.TeeReader), so that when you unmarshall the JSON, the wrapper can watch the bytes that are read and compute the signature hash. Is that "cheaper"? You'd have to benchmark & compare to know. Is it better? Depends entirely on your situation. It is an option worth considering.

查看更多
登录 后发表回答