【发布时间】:2017-11-29 17:12:04
【问题描述】:
我想多次访问http.Request 的Body。第一次发生在我的身份验证中间件中,它使用它来重新创建 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.Reader或bytes.Buffer,您实际上只是保留了相同的缓存字节片。您是否试图避免使用ReadAll复制它? -
@JimB
ReadAll是我发现从Body阅读所有内容的最简单方法 -
我在问为什么你认为将字节切片放回正文很昂贵?你是想避免复制,还是别的什么?简单地将字节片包装在
Reader和Closer中并不比您的上下文方法更昂贵。如果您想避免复制,请创建一个简单的类型,让您可以直接访问正文中的字节。 -
@JimB 我认为从
io.Reader读取会涉及某种转换,或者至少比从Context访问值更昂贵。这不正确吗? -
是的,从正文中读取会将字节复制到目标切片中(没有转换)。但是,如果您需要副本,那么无论如何您都会这样做。如果您不需要副本,我仍然会将其存储在 Body 中以保持与任何其他不知道上下文中的 body 的代码兼容,并从 Body 接口而不是上下文中提取它。跨度>