下面演示及源码基于go1.18
我们先看下http协议的Transfer-Encoding
https://developer.mozilla.org...
我们这里只讨论什么时候返回http server返回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
}
如果想了解相关源码
具体什么自动生成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()
这两个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)