首页 > 其他分享 >Go语言: 如何让 request.Body 可以多次读取

Go语言: 如何让 request.Body 可以多次读取

时间:2023-03-08 12:02:39浏览次数:54  
标签:Body body request Request io Go Close

起因: 困惑

使用了go的http服务后, 发现 request.Body 居然只能读取一次,第二次读取数据为nil.

比如我在gin的服务器中, 先加入了accessLog,需要进行parseForm() 但是后续居然读不到数据.

所以打算深入分析一下,然后简单的解决下这个问题,再优化一下.

分析下源代码

request.Body的类型为: Body io.ReadCloser

	// Body is the request's body.
	//
	// For client requests, a nil body means the request has no                  	对于客户端请求,nil 正文表示请求没有
	// body, such as a GET request. The HTTP Client's Transport                  	正文,例如 GET 请求。HTTP 客户端的传输
	// is responsible for calling the Close method.                              	负责调用 Close 方法。
	//                                                                           	//
	// For server requests, the Request Body is always non-nil                   	对于服务器请求,请求正文始终为非 nil
	// but will return EOF immediately when no body is present.                  	但会在body没有提供数据时立即返回EOF。
	// The Server will close the request body. The ServeHTTP                     	服务器将关闭请求正文。The ServeHTTP
	// Handler does not need to.                                                 	处理程序不需要。
	//                                                                           	//
	// Body must allow Read to be called concurrently with Close.                	正文必须允许读取与关闭同时调用。
	// In particular, calling Close should unblock a Read waiting                	特别是,调用 Close 应取消阻止读取等待
	// for input.                                                                	用于输入。
	Body io.ReadCloser

可以看到 这就是一个 reader+closer 接口,真实的数据可以打印出来是:

log.Printf("c.Request.Body is %T", c.Request.Body)

结果为:
   c.Request.Body is *http.body

http.body是个未导出的类型

// body turns a Reader into a ReadCloser.
// Close ensures that the body has been fully read
// and then reads the trailer if necessary.
type body struct {
	src          io.Reader
	hdr          any           // non-nil (Response or Request) value means read trailer
	r            *bufio.Reader // underlying wire-format reader for the trailer
	closing      bool          // is the connection to be closed after reading body?
	doEarlyClose bool          // whether Close should stop early

	mu         sync.Mutex // guards following, and calls to Read and Close
	sawEOF     bool
	closed     bool
	earlyClose bool   // Close called and we didn't read to the end of src
	onHitEOF   func() // if non-nil, func to call when EOF is Read
}

好家伙 , 里面 src 又是一个 io.Reader 大部分又是 io.LimitReader ,算了不分析这个了, 无法直接进行 reset 的, 那就写一个新的reader 就好了.

纯Reader 是没有Reset或Seek接口的,这个真是应该把 request.Body 设置为 ReadSeekCloser 这种接口 ,那样就不会有此文问题了.

解决方案 (重点)

简单点的就是 读出一个 bytes 然后再覆盖掉 request.Body即可

func readBodyAndSetBodyRepeatRead(c *gin.Context, cb func()) {
	//logger.WARN("c.Request.Body is %T", c.Request.Body) // *http.body
	if s, ok := c.Request.Body.(io.Seeker); ok {
		//logger.WARN("c.Request.Body is io.Seeker:%v", s)
		//执行读取Body的操作
		cb()
		//再次设置可读状态
		_, err := s.Seek(0, 0)
		if err == nil {
			return
		}
	}

	bs, _ := io.ReadAll(c.Request.Body)
	//_ = c.Request.Body.Close()// NOTE 原始的 Body 无需手动关闭,会在 response.reqBody中自动关闭的.
	//设置可读状态
	r := bytes.NewReader(bs)
	c.Request.Body = io.NopCloser(r)
	//执行读取Body的操作
	cb()
	//再次设置可读状态
	_, _ = r.Seek(0, 0)
	//logger.WARN("c.Request.Body is %T", c.Request.Body) //io.nopCloserWriterTo
}

bytes.NewReader 支持进行 Seek 设置,也就是可以重置读取指针(游标)位置,如果下次再次运行,就可以直接设置了

本想着 c.Request.Body 是不是要Close()呢? 查了下,发现可以不管,因为再 Response 结束后,会关闭的. 而且,这种写法也是安全的,可以看参考资料2

至于要不要用 pool 再次优化高并发下的性能,以减少 GC, 可以参考 参考资料2,我这里 就够用了.

用法

我是在gin下测试的,换做其他库简单修改下即可,反正request 都是原生 http.* 包下的.

	readBodyAndSetBodyRepeatRead(c, func() {
		_ = c.Request.ParseForm()
	})

参考资料:
1 本人学识
2 掘金网: 如何让 gin 正确多次读取 http request body 内容

标签:Body,body,request,Request,io,Go,Close
From: https://www.cnblogs.com/ayanmw/p/17191530.html

相关文章

  • RequestContextHolder获取得到Request
    RequestContextHolder获取得到Request目录RequestContextHolder获取得到Request一、问题二、使用三、RequestContextHolder初始化四、特殊情况:子线程获取得到request子线......
  • requests库
    1.requests库是用来发送http请求,接收http响应的一个python库requests库经常被用来爬取网站信息用它发起http请求到网站,从响应消息中提取信息例:pipinstallrequestsim......
  • 改变容器存储位置后启动mongo失败,报错Failed to unlink socket file tmpmongodb-27017
    一.改变容器存储位置默认存储位置是/var/lib/docker1.停止dockersystemctlstopdocker有时候会报错Warning:Stoppingdocker.service,butitcanstillbeactiva......
  • (转)Golang中log日志包的使用
    原文:https://juejin.cn/post/69872042995330580781.前言作为后端开发人员,日志文件记录了发生在操作系统或其他软件运行时的事件或状态。技术人员可以通过日志记录进而......
  • Go err handle
    Go错误处理代码//Go语言中没有trycatch这样麻烦的异常捕获方法//如果需要捕获异常的话需要使用deferpanicrecover()来实现funcmain(){fmt.Println("c"......
  • TenantClientHttpRequestInterceptor implements ClientHttpRequestInterceptor
      ClientHttpRequestInterceptor是对RestTemplate的请求进行拦截的,在项目中直接使用restTemplate.getForObject的时候,会对这种请求进行拦截,经常被称为:RestTempalte拦截......
  • Go语言学习第一天
    packagemainimport"fmt"funcmain(){/*这是我的第一个简单的程序*/fmt.Println("Hello,World!")}第一行代码packagemain定义了包名。你必须在源......
  • 一次goroutine 泄漏排查案例
    一次goroutine泄漏排查案例服务监控系列文章服务监控系列视频背景这是一个比较经典的golang协程泄漏案例。背景是这样,今天看到监控大盘数据发现协程的数量监控很奇怪......
  • 记- django通过celery beat results实现定时任务
    1.实验环境python版本:3.7.8django版本:3.2.15celery版本:5.2.7django-celery版本:3.2.1django-celery-beat版本:2.4.0django-celery-results版本:2.4.0django-redis版本......
  • (转)Golang使用系列---- Go Net 协议层
    原文:https://kingjcy.github.io/post/golang/go-net/Golang使用系列----GoNet协议层网络编程是go语言使用的一个核心模块。golang的网络封装使用对于底层socket或者......