什么是允许多次读取http.Request.Body

时间:2017-11-29 17:12:04

标签: parsing go http-post

我想多次访问http.RequestBody。第一次发生在我的身份验证中间件中,它使用它来重新创建sha256签名。第二次发生,我将其解析为JSON以便在我的数据库中使用。

我意识到您不能多次从io.Reader(或本例中的io.ReadCloser)读取。我发现answer to another question有一个解决方案:

  

当您第一次阅读正文时,您必须将其存储,所以一旦完成它,您可以设置一个新的io.ReadCloser作为从原始数据构造的请求正文。因此,当您在链中前进时,下一个处理程序可以读取相同的主体。

然后在示例中,他们将http.Request.Body设置为新的io.ReadCloser

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

Body读取,然后在我的中间件的每一步设置一个新的io.ReadCloser似乎很昂贵。这准确吗?

为了减少繁琐和昂贵,我使用a solution described here将已解析的字节数组存储在请求的Context()值中。每当我想要它时,它就像字节数组一样等待我:

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
}

我觉得保持一个单字节数组比每次读一个新数组便宜。这准确吗?这个解决方案是否存在我无法看到的陷阱?

1 个答案:

答案 0 :(得分:0)

它“更便宜”吗?可能,取决于您正在查看的资源,但您应该对您的特定应用程序进行基准测试和比较以确定。有陷阱吗?一切都有陷阱,但这对我来说似乎并不特别“冒险”。由于编译时类型检查的丢失以及复杂性的普遍增加和可读性的丧失,上下文值对于任何问题都是一种糟糕的解决方案。你必须决定在你的特定情况下做出什么样的权衡。

如果你不需要在处理程序启动之前完成哈希,你也可以将正文阅读器包装在另一个阅读器中(例如io.TeeReader),这样当你解组JSON时,包装器就可以看了读取的字节数并计算签名哈希值。那“更便宜”吗?你需要基准和&amp;比较知道。好点吗?完全取决于您的情况。这是一个值得考虑的选择。