Go:disable HTTP chunk mode

本文介绍了如何在 Go 服务端禁用 HTTP Chunked 编码。通过设置 `Content-Length` 头部,即使内容长度超过默认缓冲区大小(2048B),Go 也会计算准确的 Content-Length 并避免使用 Chunked 编码。此外,文章讨论了修改源码设置缓冲区大小的不推荐做法及其原因。

Go:disable HTTP chunk mode


1.结论

应用层显式设置 Content-Length,可以 disable chunk mode。

	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("Content-Length", "2049")
		writer.Write(bytes.Repeat([]byte("."), 2049))
	})

2.跑起来

Go demo:

package main

import (
	"bytes"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		writer.Write(bytes.Repeat([]byte("."), 2049))
	})

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

运行:

$ go run main.go 
^Csignal: interrupt

测试:

$ curl -v http://127.0.0.1:9080/
...
> 
< HTTP/1.1 200 OK
< Date: Wed, 28 Apr 2021 10:39:50 GMT
< Content-Type: text/plain; charset=utf-8
< Transfer-Encoding: chunked
< 
...(2049B)

即,当Go服务端写入2049B时,将使用chunk模式传输(Transfer-Encoding: chunked)。


3.查源码

Go版本:

$ go version
go version go1.13.6 linux/amd64

源码定义:

// net/http/server.go
// This should be >= 512 bytes for DetectContentType,
// but otherwise it's somewhat arbitrary.
const bufferBeforeChunkingSize = 2048

// net/http/server.go
// 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,
		...
	}
	...
	w.cw.res = w
	w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize)
	return w, nil
}

在创建http.response时指定buffer的大小:

// A response represents the server side of an HTTP response.
type response struct {
	...
	w  *bufio.Writer // buffers output in chunks to chunkWriter
	cw chunkWriter
	...
}

4.disable HTTP chunk mode

源码注释:【重点】

// If the handler didn't declare a Content-Length up front, we either
// go into chunking mode or, if the handler finishes running before
// the chunking buffer size, we compute a Content-Length and send that
// in the header instead.

如果你已经显式声明设置Content-Length,则不会进入chunk mode,否则,受到chunk buffer size限制:

1.超过chunk buffer size,use chunk mode;
2.未超chunk buffer size,Go底层计算Content-Length并填入HTTP应答Header;

因此,如果应用层已知HTTP应答总长度,直接设置Content-Length即可disable chunk:

package main

import (
	"bytes"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("Content-Length", "2049")
		writer.Write(bytes.Repeat([]byte("."), 2049))
	})

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

此时,应用层写入2049B,超过chunk buffer size(2048),但是使用Content-Length而非chunk。

$ curl -v http://127.0.0.1:9080/
... 
< HTTP/1.1 200 OK
< Content-Length: 2049
< Date: Wed, 28 Apr 2021 11:00:00 GMT
< Content-Type: text/plain; charset=utf-8
< 
...(2049B)

5.不建议的方式

修改源码 src/net/http/server.go 中 bufferBeforeChunkingSize 值,例如:

// This should be >= 512 bytes for DetectContentType,
// but otherwise it's somewhat arbitrary.
const bufferBeforeChunkingSize = 1024(原2048

此时,应用层未设置Content-Length并写入1025B,重新编译运行,也将进入chunk mode:

func main() {
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		writer.Write(bytes.Repeat([]byte("."), 1025))
	})

	http.ListenAndServe(":9080", nil)
}
$ go build main.go
$ ./main
$ curl -v http://127.0.0.1:9080/
...
> 
< HTTP/1.1 200 OK
< Date: Wed, 28 Apr 2021 11:12:53 GMT
< Content-Type: text/plain; charset=utf-8
< Transfer-Encoding: chunked
< 
...(1025B)

由于没有API设置 bufferBeforeChunkingSize 值,因此,只能通过源码方式修改。

这种方法不利于后续运维,因此不建议。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值