回答

收藏

Golang 多次读取请求正文

技术问答 技术问答 236 人阅读 | 0 人回复 | 2023-09-12

我正在编写自己的登录中间件。基本上,我需要记录请求和响应的正文。我面临的问题是,当我阅读正文时,它变空了,我无法阅读两次。我知道它发生是因为它是 ReadCloser 类型。有没有办法把身体回到开始?! r! `+ r2 y* v8 J. N
                                                                9 t3 H" h7 `' }: y* @7 |9 B
    解决方案:                                                                * d: C& i% l# l) \
                                                                检查和模拟请求文本当你第一次阅读文本时,你必须储存它,所以一旦你完成它,你可以设置一个新的io.ReadCloser作为从原始数据构造的请求正文。因此,当您在链中前进时,下一个处理程序可以读取相同的主体。
: z2 {, A8 M3 Q- G. ~一种选择是 阅读整个主体ioutil.ReadAll(),它将主体作为字节切片。
+ e9 ]! p6 B4 e3 K6 n可以从字节切片中使用bytes.NewBuffer()获取io.Reader。
& _4 a! J, V# D# j最后缺少的部分是生产io.Readeran io.ReadCloser,因为bytes.Buffer没有Close()方法ioutil.NopCloser()which 包装 an io.Reader,并返回 an io.ReadCloser,其添加的Close()方法将是无操作(什么都不做)。
8 M( B. ]+ u% k: b- K请注意,您甚至可以修改用于创建新主体的字节切片。您可以完全控制它。: V: B4 R& \7 o# r$ y' X
但是要小心,因为可能还有其他 HTTP 字段,如内容长度和验证,如果只修改数据,可能无效。如果检查后续处理程序,您还需要修改它们!5 `7 N5 \; W+ x3 J2 P6 l& |
检查/修改响应文本如果您还想阅读响应文本,则必须包装http.ResponseWriter您得到的,并将包装器传输到链上。该包装器可以缓存发送的数据,并在运行后检查数据(当后续处理程序写入时)。
6 t- G& v5 y( a这很简单ResponseWriter包装器只缓存数据,只能在后续处理程序返回后使用:) [; {! f, i6 c* X9 a
    type MyResponseWriter struct {    http.ResponseWriter    buf *bytes.Buffer}func (mrw *MyResponseWriter) Write(p []byte) (int,error) {    return mrw.buf.Write(p)}
    , v( r" w4 J) ?- @
请注意,MyResponseWriter.Write()只将数据写入缓冲区。您也可以选择立即检查它(在Write()方法中)并立即将数据写入包装/嵌入ResponseWriter. 你甚至可以修改数据。你有完全的控制权。: W* u6 X2 x# Q  o$ d
但必须再次小心,因为后续处理程序也可能发送与响应数据相关的 HTTP 响应标头-如长度或验证-如果您更改响应数据,这些标头也可能无效。
+ R2 r) t& z( M* c完整示例把这些部分放在一起是一个完整的工作示例:
9 }1 F0 t. f& S4 W! ?" j* q[code]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)       }code]
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则