Golang读取请求正文

时间:2017-03-25 19:36:32

标签: http go server

我正在编写自己的logginMiddleware。基本上,我需要记录请求的主体和响应。我遇到的问题是,当我读到身体时,它会变空,我无法读取它两次。 我知道它发生是因为它是ReadCloser类型。有没有办法将身体倒回到开头?

2 个答案:

答案 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)