使用 Unix 管道将数据流式传输到 REST 服务

使用 Unix 管道将数据流式传输到 REST 服务

基于对另一个问题的回答我正在使用curl 将stdout一个进程的流作为请求的实体POST

myDataGeneratingApp \
| curl -H "Content-Type: application/json" -H "Transfer-Encoding: chunked" -X POST -d @- http://localhost:12000

EOF不幸的是,curl在开始发送数据之前正在等待标准输出。我知道这一点是因为我可以独立运行我的应用程序,并且数据会立即输出到控制台,但是当我通过管道传输到curl 时,在服务开始接收数据之前会出现明显的延迟。

当数据从应用程序的标准中可用时,如何使用curl立即流式传输数据?如果在curl中不可能,那么还有其他解决方案(例如wget)吗?

答案1

浏览curl代码传输.c看起来该程序能够使用分块协议重新打包请求数据(从curl到服务器),其中每个数据块都以ascii十六进制的块长度为前缀,并以\r\n.

连接到服务器后,似乎可以使用流式传输方式使用它-T -。考虑这个例子:

for i in $(seq 5)
do date
   sleep 1
done | 
dd conv=block cbs=512 |
strace -t -e sendto,read -o /tmp/e \
 curl --trace-ascii - \
 -H "Transfer-Encoding: chunked" \
 -H "Content-Type: application/json" \
 -X POST -T -  http://localhost/...

此脚本将 5 个数据块发送到管道,每个数据块均以日期开头,并通过dd, 填充到 512 字节,并在其中strace运行curl -T -以读取管道。在终端中我们可以看到

== Info: Connected to localhost (::1) port 80 (#0)
=> Send header, 169 bytes (0xa9)
0000: POST /... HTTP/1.1
001e: Host: localhost
002f: User-Agent: curl/7.47.1
0048: Accept: */*
0055: Transfer-Encoding: chunked
0071: Content-Type: application/json
0091: Expect: 100-continue
00a7: 
<= Recv header, 23 bytes (0x17)
0000: HTTP/1.1 100 Continue

它显示连接和发送的标头。特别curl是没有提供Content-length:标头,而是Expect:服务器(apache)已回复的标头Continue。紧接着是前 512 字节(十六进制 200)数据:

=> Send data, 519 bytes (0x207)
0000: 200
0005: Fri Sep 14 15:58:15 CEST 2018                                   
0045:                                                                 
0085:                                                                 
00c5:                                                                 
0105:                                                                 
0145:                                                                 
0185:                                                                 
01c5:                                                                 
=> Send data, 519 bytes (0x207)

在输出文件中,我们看到来自管道的strace每个时间戳,并写入连接:readsendto

16:00:00 read(0, "Fri Sep 14 16:00:00 CEST 2018   "..., 16372) = 512
16:00:00 sendto(3, "200\r\nFri Sep 14 16:00:00 CEST 20"..., 519, ...) = 519
16:00:00 read(0, "Fri Sep 14 16:00:01 CEST 2018   "..., 16372) = 512
16:00:01 sendto(3, "200\r\nFri Sep 14 16:00:01 CEST 20"..., 519, ...) = 519
16:00:01 read(0, "Fri Sep 14 16:00:02 CEST 2018   "..., 16372) = 512
16:00:02 sendto(3, "200\r\nFri Sep 14 16:00:02 CEST 20"..., 519, ...) = 519
16:00:02 read(0, "Fri Sep 14 16:00:03 CEST 2018   "..., 16372) = 512
16:00:03 sendto(3, "200\r\nFri Sep 14 16:00:03 CEST 20"..., 519, ...) = 519
16:00:03 read(0, "Fri Sep 14 16:00:04 CEST 2018   "..., 16372) = 512
16:00:04 sendto(3, "200\r\nFri Sep 14 16:00:04 CEST 20"..., 519, ...) = 519
16:00:04 read(0, "", 16372)             = 0
16:00:05 sendto(3, "0\r\n\r\n", 5, ...) = 5

正如您所看到的,它们之间的间隔为 1 秒,表明数据在接收的同时也在发送。您必须至少有 512 个字节要发送,因为 正在读取数据fread()

答案2

请参阅下面的编辑

你想要的东西是不可能的。要发送 POST 数据,必须知道长度,因此curl必须首先读取整个数据以确定长度。

Transfer-Encoding: chunked是绕过该限制的一种方法,但仅适用于服务器的响应。

原因是chunked仅在HTTP/1.1中支持,但在发送请求时,客户端无法知道服务器是否理解HTTP/1.1。该信息附带答案,但发送请求为时已晚。

编辑

从 wget 手册来看,这似乎是 wget 的限制:

请注意,Wget 需要提前知道 POST 数据的大小。因此 --post-file 的参数必须是常规文件;指定 FIFO 或 /dev/stdin 之类的东西是行不通的。目前还不太清楚如何解决 HTTP/1.0 固有的限制。尽管 HTTP/1.1 引入了不需要提前知道请求长度的分块传输,但客户端不能使用分块,除非它知道它正在与 HTTP/1.1 服务器通信。在收到响应之前它无法知道这一点,而响应又要求请求已完成——这是一个先有鸡还是先有蛋的问题。

尽管问题确实存在,但人们已经认识到这一点RFC 7230:

客户端不得发送包含 Transfer-Encoding 的请求,除非它知道服务器将处理 HTTP/1.1(或更高版本)请求;此类知识可能以特定用户配置的形式或通过记住先前收到的响应的版本来实现。

因此,发送分块 POST 数据是可能的,正如另一个答案所示,curl 已经支持它。

相关内容