【问题标题】:What is the proper way to allow multiple reads of http.Request.Body允许多次读取 http.Request.Body 的正确方法是什么
【发布时间】:2017-11-29 17:12:04
【问题描述】:

我想多次访问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
}

我觉得保留一个单字节数组比每次读取一个新数组要便宜。这是准确的吗?这个解决方案有我看不到的陷阱吗?

【问题讨论】:

  • 如果您将正文放入bytes.Readerbytes.Buffer,您实际上只是保留了相同的缓存字节片。您是否试图避免使用ReadAll 复制它?
  • @JimB ReadAll 是我发现从Body阅读所有内容的最简单方法
  • 我在问为什么你认为将字节切片放回正文很昂贵?你是想避免复制,还是别的什么?简单地将字节片包装在 ReaderCloser 中并不比您的上下文方法更昂贵。如果您想避免复制,请创建一个简单的类型,让您可以直接访问正文中的字节。
  • @JimB 我认为从io.Reader 读取会涉及某种转换,或者至少比从Context 访问值更昂贵。这不正确吗?
  • 是的,从正文中读取会将字节复制到目标切片中(没有转换)。但是,如果您需要副本,那么无论如何您都会这样做。如果您不需要副本,我仍然会将其存储在 Body 中以保持与任何其他不知道上下文中的 body 的代码兼容,并从 Body 接口而不是上下文中提取它。跨度>

标签: parsing go http-post


【解决方案1】:

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

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

【讨论】:

    猜你喜欢
    • 2012-12-12
    • 1970-01-01
    • 1970-01-01
    • 2010-10-15
    • 1970-01-01
    • 1970-01-01
    • 2014-11-14
    • 2011-05-30
    相关资源
    最近更新 更多