我正在编写自己的日志中间件。基本上,我需要记录请求和响应的正文内容。我遇到的问题是当我读取正文内容时它变为空,并且我无法再次读取它。我理解这是因为它是ReadCloser类型。有没有一种方法将正文内容倒回开头?
当您首次读取请求体时,必须将其存储,以便在完成后,您可以设置一个由原始数据构建的新的 io.ReadCloser
作为请求体。因此,当您在链中前进时,下一个处理程序可以读取相同的请求体。
一种选择是使用 ioutil.ReadAll()
读取整个请求体,它会将请求体作为字节切片返回。
您可以使用 bytes.NewBuffer()
从字节切片获取一个 io.Reader
。
最后缺少的一点是将 io.Reader
变成 io.ReadCloser
,因为 bytes.Buffer
没有 Close()
方法。为此,您可以使用 ioutil.NopCloser()
将一个 io.Reader
包装起来,并返回一个 io.ReadCloser
,其添加的 Close()
方法将是一个空操作(不执行任何操作)。
请注意,您甚至可以修改用于创建“新”请求体的字节切片的内容。您完全控制它。
但是必须小心,因为可能会有其他 HTTP 字段(如 content-length 和校验和),如果仅修改数据,则这些字段可能会变得无效。如果后续处理程序检查这些字段,则还需要修改这些字段!
如果您还想读取响应体,则必须包装您获得的 http.ResponseWriter
并将包装器传递给链。此包装器可以缓存发送的数据,您可以在之后或在实时(随着后续处理程序对其进行写入)检查它。
这是一个简单的 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
中。您甚至可以修改数据。您有完全的控制权。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)
}
})
}
GetBody func() (io.ReadCloser, error)
这样就可以获取请求的Body而不使其为空。getBody := request.GetBody
copyBody, err := getBody()
if err != nil {
// Do something return err
}
http.DefaultClient.Do(request)
runtime error: invalid memory address or nil pointer dereference
。 - seanlook
ioutil.ReadAll
不建议用于处理 HTTP 请求体,尤其是高负载的服务器。详情请参见:https://haisum.github.io/2017/09/11/golang-ioutil-readall/ - zakaria amine