cURL 缓冲对下载流式数据的影响

陪她去流浪 桃子 2021年09月05日 编辑 阅读次数:2416

之前用 cURL 访问一个 gRPC 的流式(Stream)接口(已转换成 HTTP)时,出现了一个非常奇怪的现象:

  • 当直接 cuRL 时,总是能显示完整的内容,并且连接不会断开,因为是流式接口,完全符合预期的行为;
  • 而当 cURL | ...cURL > file 时,始终得不到完整的内容,而且无论等多久都不行;

这是为什么呢?这个问题困扰了我很长一段时间。直到昨天灵机一现突然想到了原因:

大概率是因为 cURL 的 缓冲(buffer) 导致的。

于是读了 cURL 的 manual,还真的有一个是跟 buffer 相关的:

1
2
3
4
5
6
7
-N, --no-buffer

  Disables  the buffering of the output stream. In normal work situations, curl will use a standard buffered output stream that will have the effect that it will output the data
 
  in chunks, not necessarily exactly when the data arrives.  Using this option will disable that buffering.
 
  Note that this is the negated option name documented. You can thus use --buffer to enforce the buffering.

然后尝试开启,果然问题解决。

但是,仍然有一个问题没有解决,为什么直接 cURL 的时候总是能显示完整的内容?不应该也 buffer 起来吗?未找到答案。盲猜是版本原因。

我写了一小段代码来尝试复现这个问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
	"net/http"
	"strings"
	"time"
)

var long = []byte(strings.Repeat(`0123456789`, (16<<10)/10+1))

func stream(w http.ResponseWriter, r *http.Request) {
	w.Header().Set(`transfer-encoding`, `identity`)
	w.Write(long)
	time.Sleep(time.Second * 10)
}

func main() {
	http.HandleFunc(`/`, stream)
	http.ListenAndServe(`:5626`, nil)
}

注:加 Transfer-Encoding 的原因是 go 语言默认会 chunked 传输,也不知道怎么简单关闭。

由于 cURL 的 缓冲(Buffer) 大小是 16KB,于是我的代码中构造了 16KB 多一点点数据。

现在来尝试请求这个接口:

1
2
3
4
$ curl localhost:5626
0123456789...
...
0123

果然停在了这里,后面的 456789 没有了。只要代码里面 sleep 得够久,这里就会一直等。

但是灵异的是,无论我是重定向到文件,还是通过管道给其它命令,都未能完全复现上面的问题(我懒得写 grpc 例子了)。

另外,cURL 可以指定在多长时间内如果速度持续低于某个值,则停止下载(返回错误码 28:Operation timeout):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
-Y, --speed-limit <speed>
  If a download is slower than this given speed (in bytes per second) for speed-
  time seconds it gets aborted. speed-time is set with -y, --speed-time  and  is
  30 if not set.

  If this option is used several times, the last one will be used.

-y, --speed-time <seconds>
  If  a download is slower than speed-limit bytes per second during a speed-time
  period, the download gets aborted. If speed-time is used, the  default  speed-
  limit will be 1 unless set with -Y, --speed-limit.

  This  option controls transfers and thus will not affect slow connects etc. If
  this is a concern for you, try the --connect-timeout option.

  If this option is used several times, the last one will be used.

我前面提到的服务是一个流式订阅服务,启动时会订阅全量,后续订阅到增量数据(较少),所以订阅完一般就会速度变为零, 这对于我来说,这几个参数变得很好使。最终我的命令如下:

1
2
3
$ curl -Ny1 ...
$ echo $?
28

至于最开始的问题:输出到终端行,到文件或管道就不行的问题,仍无答案。

标签:cURL · gRPC