gin源码-io.LimitedReader
先来看一个demo如下:
func main() {
r := gin.Default()
r.POST("/", func(c *gin.Context) {
c.GetRawData()
c.String(200, c.PostForm("phone"))
})
_ = r.Run(":8000")
}
发送post请求,发现接口获取不到phone参数,注释掉c.GetRawData()
后就可以了,c.GetRawData()
代码很简单,就是读取c.Request.Body的内容,c.Request.Body只能被读取一次,它是如何实现和使用的呢?跟踪上面demo的调用链路在这里。post请求的处理流程如下:
|-main.main.func1
|-github.com/gin-gonic/gin.(*Context).GetRawData
|-io.ReadAll(c.Request.Body) // c.Request.Body是在net/http.readTransfer()设置的
|-net/http.(*body).Read
|-github.com/gin-gonic/gin.(*Context).GetPostForm
|-github.com/gin-gonic/gin.(*Context).initFormCache
|-net/http.(*Request).ParseMultipartForm
|-net/http.(*Request).ParseForm
|-net/http.parsePostForm
|-io.ReadAll // 由于c.Request.Body已经读过一次,这里读不到了
|-io.(*LimitedReader).Read
|-net/http.(*body).Read
把关注点放在net/http.readTransfer()
上,看看c.Request.Body是如何设置的:
func readTransfer(msg any, r *bufio.Reader) (err error) {
t := &transferReader{RequestMethod: "GET"}
// 请求头没有Transfer-Encoding,所以t.Chunked=false
if err := t.parseTransferEncoding(); err != nil {
return err
}
// 请求头有Content-Length,realLength>0
realLength, err := fixLength(isResponse, t.StatusCode, t.RequestMethod, t.Header, t.Chunked)
if err != nil {
return err
}
switch {
case t.Chunked:
if isResponse && (noResponseBodyExpected(t.RequestMethod) || !bodyAllowedForStatus(t.StatusCode)) {
t.Body = NoBody
} else {
t.Body = &body{src: internal.NewChunkedReader(r), hdr: msg, r: r, closing: t.Close}
}
case realLength == 0:
t.Body = NoBody
case realLength > 0:
// 设置t.Body
t.Body = &body{src: io.LimitReader(r, realLength), closing: t.Close}
default:
}
// 设置req.Body
rr.Body = t.Body
// 省略...
}
可以看到req.Body其实是io.LimitReader产生的,它返回io.LimitedReader实例,这个实例的Read方法如下:
type LimitedReader struct {
R Reader // 读实例
N int64 // 剩余可读字节
}
func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }
func (l *LimitedReader) Read(p []byte) (n int, err error) {
if l.N <= 0 { // 没有剩余可读字节,返回io.EOF错误
return 0, EOF
}
if int64(len(p)) > l.N {
p = p[0:l.N]
}
n, err = l.R.Read(p)
l.N -= int64(n) // 每次读了多少,就减一下
return
}
看到这里也就明白了为什么c.Request.Body读了一次后就不能再读了。有个小问题是io.LimitedReader不是协程安全的,不过它是在net/http.body里面,net/http.body.Read()是加锁的。