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()是加锁的。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~