如何在 Golang 中间件中读取请求体两次?

25

在一个中间件中,我想读取请求正文来执行一些检查。然后,将请求传递给下一个中间件,在那里正文将再次被读取。

以下是我的做法:

bodyBytes, _ := ioutil.ReadAll(req.Body)
req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
// use bodyBytes
// pass to next middleware

现在,req.Body.Close将不会执行任何操作。由于先前的req.Body.Close实现处理了一些连接,它是否会出现问题?


可能是相关/重复的问题:Golang读取请求体 - icza
我已经阅读了它。在答案中,原始正文没有关闭,这很令人困惑。 - Sergey
2
在服务器端,您不需要关闭请求正文。Request.Body: "服务器将关闭请求正文。ServeHTTP处理程序不需要这样做。" - icza
但是一旦应用了ioutil.NopCloser,原始关闭从原始主体中丢失。 - Sergey
1
不,原来的“close”并没有丢失。你只是给导出的Request.Body字段分配了一个新值。这不是唯一需要关闭的原始body reader的引用。 - icza
2
标题有误,应该是读取请求体而不是读取请求。 - wilsont
2个回答

54

由于先前的req.Body.Close实现进行了一些连接处理,因此会出现错误吗?

不会。

但是您的代码存在缺陷:在读取完整个req.Body后,您应该关闭它。然后,像之前一样构建一个新的ReadCloser并将其传递给下一个中间件(这个中间件本身或更深层次的内容负责关闭它)。

bodyBytes, _ := ioutil.ReadAll(req.Body)
req.Body.Close()  //  must close
req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))

在你的代码中,只有当 err 为 nil 时才应该关闭 body,是吗? - Sergey
15
在服务器端,您不需要关闭请求正文。Request.Body“服务器将关闭请求正文。ServeHTTP处理程序无需进行此操作。” - icza
1
@Sergey,我不明白。即使读取req.Body时出现错误,您也应该关闭它。这与此无关。您是否认为如果请求本身产生错误,则不需要关闭它? - Volker
1
在查看服务器代码时,服务器保留了对Body的自己指针,因此它将始终自行关闭原始body,至少看起来是这样。因此,在这里关闭它是否重要? - Luka Govedič
根据我的观察,似乎不需要@Volker the Close。即使你覆盖了req.Body,服务器也会关闭“真正”的body。我通过将req的body传递过一个chan并在请求完成后进行检查来运行测试,发现它被服务器关闭了。 - masebase
显示剩余3条评论

0
不需要在HTTP处理程序中关闭请求体。Stdlib会自动处理。
我建议在中间件中使用这个非阻塞版本的代码:
var b bytes.Buffer
r.Body = io.NopCloser(io.TeeReader(r.Body, &b))
  • 当HTTP处理程序读取请求体时,将其复制到缓冲区中
  • 不会因为较大的请求体而减慢处理程序的速度

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接