Golang 多次读取请求体 [英] Golang read request body multiple times

查看:53
本文介绍了Golang 多次读取请求体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写自己的登录中间件.基本上,我需要记录请求和响应的正文.我面临的问题是,当我阅读正文时,它变空了,我无法阅读两次.我知道它发生是因为它是 ReadCloser 类型.有没有办法将身体倒回到开头?

I am writing my own logginMiddleware. Basically, I need to log body of the request and the response. The problem that I faced is that when I read body, it becomes empty and I cannot read it twice. I understand that it happens because it is of type ReadCloser. Is there a way to rewind body to the beginning?

推荐答案

检查和模拟请求正文

当你第一次阅读正文时,你必须存储它,所以一旦你完成它,你就可以设置一个新的 io.ReadCloser 作为从原始数据构造的请求体.所以当你在链中前进时,下一个处理程序可以读取相同的主体.

Inspecting and mocking request body

When you first read the body, you have to store it so once you're done with it, you can set a new io.ReadCloser as the request body constructed from the original data. So when you advance in the chain, the next handler can read the same body.

一种选择是使用ioutil.ReadAll(),它以字节片的形式为您提供正文.

One option is to read the whole body using ioutil.ReadAll(), which gives you the body as a byte slice.

您可以使用 bytes.NewBuffer() 来获取来自字节切片的 io.Reader.

You may use bytes.NewBuffer() to obtain an io.Reader from a byte slice.

最后缺少的部分是使 io.Reader 成为 io.ReadCloser,因为 bytes.Buffer 没有 >Close() 方法.为此,您可以使用 ioutil.NopCloser() 其中包装一个 io.Reader,并返回一个 io.ReadCloser,其添加的 Close() 方法将是一个空操作(什么都不做).

The last missing piece is to make the io.Reader an io.ReadCloser, because bytes.Buffer does not have a Close() method. For this you may use ioutil.NopCloser() which wraps an io.Reader, and returns an io.ReadCloser, whose added Close() method will be a no-op (does nothing).

请注意,您甚至可以修改用于创建新"主体的字节切片的内容.您可以完全控制它.

Note that you may even modify the contents of the byte slice you use to create the "new" body. You have full control over it.

但必须小心,因为可能还有其他 HTTP 字段,如内容长度和校验和,如果您只修改数据,这些字段可能会变得无效.如果后续处理程序检查这些,您也需要修改它们!

如果您还想读取响应正文,那么您必须包装http.ResponseWriter 你得到,并在链上传递包装器.这个包装器可以缓存发送的数据,你可以在运行之后检查这些数据(当后续处理程序写入它时).

If you also want to read the response body, then you have to wrap the http.ResponseWriter you get, and pass the wrapper on the chain. This wrapper may cache the data sent out, which you can inspect either after, on on-the-fly (as the subsequent handlers write to it).

这是一个简单的 ResponseWriter 包装器,它只缓存数据,以便在后续处理程序返回后可用:

Here's a simple ResponseWriter wrapper, which just caches the data, so it'll be available after the subsequent handler returns:

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.您甚至可以修改数据.您拥有完全控制权.

Note that MyResponseWriter.Write() just writes the data to a buffer. You may also choose to inspect it on-the-fly (in the Write() method) and write the data immediately to the wrapped / embedded ResponseWriter. You may even modify the data. You have full control.

但必须再次小心,因为后续处理程序还可能发送与响应数据相关的 HTTP 响应标头 - 例如长度或校验和 - 如果您更改响应数据,这些标头也可能变得无效.

将各个部分放在一起,这是一个完整的工作示例:

Putting the pieces together, here's a full working example:

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)
        }
    })
}

这篇关于Golang 多次读取请求体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆