需要先把要写入的数据放在Buffer中,放内存缓存着,但是我需要写入大量的数据,如果都放内存里肯定要 OOM 了,http client 并没有提供流式写入的方法,我这么大的数据量直接用Buffer肯定是不行的,最后在 google 了一番之后找到了解决办法。
使用 io.pipe
调用io.pipe()方法会返回Reader和Writer接口实现对象,通过Writer写数据,Reader就可以读到,利用这个特性就可以实现流式的写入,开一个协程来写,然后把Reader传递到方法中,就可以实现 http client body 的流式写入了。
代码示例:
1 2 3 4 5 6 7 8 9 10
pr, pw := io.Pipe() // 开协程写入大量数据 gofunc(){ for i := 0; i < 100000; i++ { pw.Write([]byte(fmt.Sprintf("line:%d\r\n", i))) } pw.Close() }() // 传递Reader http.Post("localhost:8099/report","text/pain",pr)
源码阅读
目的
了解 go 中 http client 对于 body 的传输是如何处理的。
开始
在构建 Request 的时候,会断言 body 参数的类型,当类型为*bytes.Buffer、*bytes.Reader、*strings.Reader的时候,可以直接通过Len()方法取出长度,用于Content-Length请求头,相关代码net/http/request.go#L872-L914:
count := 100 line := []byte("line\r\n") pr, rw := io.Pipe() gofunc() { for i := 0; i < count; i++ { rw.Write(line) } rw.Close() }() // 构造request对象 request, err := http.NewRequest("POST", "http://localhost:8099/report", pr) if err != nil { log.Fatal(err) } // 提前计算出ContentLength request.ContentLength = int64(len(line) * count) // 发起请求 http.DefaultClient.Do(request)
抓包结果:
可以看到确实直接使用的Content-Length进行传输,没有进行chunked编码了。
总结
本文的目的主要是记录 go 语言中http client如何进行流式的写入,并通过阅读源码了解http client内部对 body 的写入是如何进行处理的,通过两个验证可以得知,如果能提前计算出ContentLength并且对性能要求比较苛刻的情况下,可以通过手动设置ContentLength来优化性能。