下面演示及源码基于go1.18
我们先看下http协议的Transfer-Encoding
https://developer.mozilla.org...
图片.png
图片.png
我们这里只讨论什么时候返回http响应头返回Transfer-Encoding: chunked,什么时候返回Content-Length

func main() {

    //手动flush,如果头不带content-length就一定会返回Transfer-Encoding:chunked
    http.HandleFunc("/1", func(writer http.ResponseWriter, request *http.Request) {
        flusher, ok := writer.(http.Flusher)
        if !ok {
            panic("expected http.ResponseWriter to be an http.Flusher")
        }
        for i := 0; i < 10; i++ {
            fmt.Fprintf(writer, "[%02d]: %v\n", i, time.Now())
            flusher.Flush()
            time.Sleep(1 * time.Second)
        }
    })

    //这种情况下Transfer-Encoding不是chunk,会返回content-length
    http.HandleFunc("/2", func(writer http.ResponseWriter, request *http.Request) {
        for i := 0; i < 10; i++ {
            str := fmt.Sprintf("[%02d]: %v\n", i, time.Now())
            _, err := writer.Write([]byte(str))
            if err != nil {
                fmt.Println("err", err)
            }
            time.Sleep(1 * time.Second)
        }
    })

    //这种情况下Transfer-Encoding是chunk,因为太长了,自动转换了
    //具体超过多少字节转换成Transfer-Encoding:chunk,为2048
    //可参考src/net/http/server.go bufferBeforeChunkingSize = 2048
    //我们发现当前长度超过4096时我们的的客户端终端会收到部分数据,代表已经刷入tcp链接
    http.HandleFunc("/3", func(writer http.ResponseWriter, request *http.Request) {
        longStr := getStr(512)
        lenStr := 0
        for i := 0; i < 10; i++ {
            str := fmt.Sprintf("[%02d]: %v %s\n", i, time.Now(), longStr)
            lenStr += len(longStr)
            fmt.Println("当前长度:", lenStr)
            _, err := writer.Write([]byte(str))
            if err != nil {
                fmt.Println("err", err)
            }
            time.Sleep(2 * time.Second)
        }
    })

    http.ListenAndServe(":9080", nil)
}

func getStr(n int) string {
    s := ""
    for i := 0; i < n; i++ {
        s += "1"
    }
    return s
}

响应头返回如下:
图片.png
图片.png
图片.png

如果想了解相关源码可以关注下面这几部分:

具体什么自动生成Content-Length什么时候生成Transfer-Encoding可以去看相关源码:

//src/http/server.go
func (cw *chunkWriter) writeHeader(p []byte) {
    if cw.wroteHeader {
        return
    }
    cw.wroteHeader = true

    w := cw.res
    keepAlivesEnabled := w.conn.server.doKeepAlives()
    isHEAD := w.req.Method == "HEAD"
    ....
}

想了解Write什么时候Flush可以去看:

// src/bufio/bufio.go
func (b *Writer) Write(p []byte) (nn int, err error) {
    for len(p) > b.Available() && b.err == nil {
        var n int
        if b.Buffered() == 0 {
            // Large write, empty buffer.
            // Write directly from p to avoid copy.
            n, b.err = b.wr.Write(p)
        } else {
            n = copy(b.buf[b.n:], p)
            b.n += n
            b.Flush()
        }
        nn += n
        p = p[n:]
    }
    if b.err != nil {
        return nn, b.err
    }
    n := copy(b.buf[b.n:], p)
    b.n += n
    nn += n
    return nn, nil
}

//src/net/http/server.go
func (cw *chunkWriter) Write(p []byte) (n int, err error) {
    if !cw.wroteHeader {
        cw.writeHeader(p)
    }
    if cw.res.req.Method == "HEAD" {
        // Eat writes.
        return len(p), nil
    }
    if cw.chunking {
        _, err = fmt.Fprintf(cw.res.conn.bufw, "%x\r\n", len(p))
        if err != nil {
            cw.res.conn.rwc.Close()
            return
        }
    }
    n, err = cw.res.conn.bufw.Write(p)
    if cw.chunking && err == nil {
        _, err = cw.res.conn.bufw.Write(crlf)
    }
    if err != nil {
        cw.res.conn.rwc.Close()
    }
    return
}

顺带提一下有两个地方调用了newBufioWriterSize()
图片.png
这两个buffer初始化的大小代表了不同含义,如下:

// Read next request from connection.
func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
    ...

    w = &response{
        conn:          c,
        cancelCtx:     cancelCtx,
        req:           req,
        reqBody:       req.Body,
        handlerHeader: make(Header),
        contentLength: -1,
        closeNotifyCh: make(chan bool, 1),

        // We populate these ahead of time so we're not
        // reading from req.Header after their Handler starts
        // and maybe mutates it (Issue 14940)
        wants10KeepAlive: req.wantsHttp10KeepAlive(),
        wantsClose:       req.wantsClose(),
    }
    if isH2Upgrade {
        w.closeAfterReply = true
    }
    w.cw.res = w
    //这里的bufferBeforeChunkingSize为2048,代表超过2048转成chunk
    w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize)
    return w, nil
}

// 这里是超过4096刷入tcp conn
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

AVOli
6 声望0 粉丝