我正在编写自己的logginMiddleware。基本上,我需要记录请求的主体和响应。我遇到的问题是,当我读到身体时,它会变空,我无法读取它两次。 我知道它发生是因为它是ReadCloser类型。有没有办法将身体倒回到开头?
答案 0 :(得分:65)
当您第一次阅读正文时,您必须将其存储,所以一旦您完成它,您可以设置一个新的io.ReadCloser
作为从原始数据构造的请求正文。因此,当您在链中前进时,下一个处理程序可以读取相同的主体。
一个选项是使用ioutil.ReadAll()
读取整个身体,它将身体作为字节切片。
您可以使用bytes.NewBuffer()
从字节切片中获取io.Reader
。
最后一个缺失的部分是让io.Reader
成为io.ReadCloser
,因为bytes.Buffer
没有Close()
方法。为此,您可以使用包裹io.Reader
的{{3}},并返回io.ReadCloser
,其添加的Close()
方法将为无操作(无效)。
请注意,您甚至可以修改用于创建" new"的字节切片的内容。身体。你可以完全控制它。
但必须小心,因为如果仅修改数据,可能会有其他HTTP字段,如内容长度和校验和,这些字段可能会变为无效。如果后续处理程序检查这些,您也需要修改它们!
如果你还想阅读响应主体,那么你必须包装你得到的ioutil.NopCloser()
,然后传递链上的包装器。这个包装器可以缓存发送出来的数据,你可以在运行之后(随后的处理程序写入它)检查它。
这是一个简单的ResponseWriter
包装器,它只是缓存数据,因此在后续处理程序返回后它可用:
type MyResponseWriter struct {
http.ResponseWriter
buf *bytes.Buffer
}
func (mrw *MyResponseWriter) Write(p []byte) (int, error) {
return mrw.buf.Write(p)
}
请注意MyResponseWriter.Write()
只是将数据写入缓冲区。您也可以选择即时检查(在Write()
方法中)并立即将数据写入包装/嵌入ResponseWriter
。您甚至可以修改数据。你完全可以控制。
但必须小心,因为后续处理程序也可能发送与响应数据相关的HTTP响应头 - 例如长度或校验和 - 如果您更改响应数据,这些头也可能无效。
将各个部分放在一起,这是一个完整的工作示例:
func loginmw(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading body: %v", err)
http.Error(w, "can't read body", http.StatusBadRequest)
return
}
// Work / inspect body. You may even modify it!
// And now set a new body, which will simulate the same data we read:
r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
// Create a response wrapper:
mrw := &MyResponseWriter{
ResponseWriter: w,
buf: &bytes.Buffer{},
}
// Call next handler, passing the response wrapper:
handler.ServeHTTP(mrw, r)
// Now inspect response, and finally send it out:
// (You can also modify it before sending it out!)
if _, err := io.Copy(w, mrw.buf); err != nil {
log.Printf("Failed to send out response: %v", err)
}
})
}
答案 1 :(得分:1)
我可以使用Request包中的GetBody
。
在net / http中的request.go的源代码中查找此注释:
GetBody定义了一个可选的func,以返回的新副本 身体。当需要重定向时,它用于客户端请求 阅读身体不止一次。仍使用GetBody 需要设置主体。 对于服务器请求,它是未使用的。“
GetBody func() (io.ReadCloser, error)
这样,您可以在不将其保留为空的情况下获取正文请求。
示例:
getBody := request.GetBody
copyBody, err := getBody()
if err != nil {
// Do something return err
}
http.DefaultClient.Do(request)