如何在没有 Content-Length 标头的情况下流式传输 HTTP 文件上传?

Posted

技术标签:

【中文标题】如何在没有 Content-Length 标头的情况下流式传输 HTTP 文件上传?【英文标题】:How to stream an HTTP file upload without the Content-Length header? 【发布时间】:2012-08-23 10:33:56 【问题描述】:

是否可以在不包含 content-length 标头的情况下将文件上传到 apache php 服务器?

我正在尝试将我正在动态创建的文件作为文件上传进行流式传输。当我不使用内容长度标头时,我得到了 apache“501 方法未实现”。

$sock = fsockopen($host,80,$errno, $error);
fwrite($sock, "POST $resource HTTP/1.1\r\n" .
                     "Host: $host\r\n\r\n");
fwrite($sock,fread($readHandle,filesize($file)));

如果我包含内容长度,它可以正常工作。

服务器正在从 php://input 读取

【问题讨论】:

如果缺少长度标头会怎样?有错误信息吗? 无关:sweet john snow gravatar -- asoiaf FTW。 【参考方案1】:

根据 HTTP 规范,您在技术上不需要指定 Content-Length 标头。来自RFC 2616 14.13:

应用程序应该使用这个字段来指示传输长度 消息正文,除非本节中的规则禁止这样做 4.4.

但是,对于大多数服务器来说,这是一个相当标准的要求,如果 Content-Length 标头丢失或指定不正确,它们通常会发回错误响应。出于所有意图和目的,在这种情况下,应该等同于必须

问题在于(尤其是在保持连接的情况下),服务器不知道您的请求消息何时在没有Content-Length 标头的情况下实际结束。如果您正在流式传输请求实体正文,另一种选择是发送 Transfer-Encoding: chunked 标头并一次手动发送一个实体正文块。

总而言之,如果您想在消息中发送实体正文但不想发送 Content-Length 标头,您唯一真正的选择是发送分块的 HTTP 消息。如果您想流式传输该实体主体并且不提前知道其长度,这基本上是必需的。

如何对 HTTP 实体主体进行块编码以进行流式传输...

Transfer-Encoding: chunked 表示您正在根据RFC2616 Sec3.6.1 中规定的约束对 HTTP 消息的实体主体进行编码。这种编码格式可以应用于请求或响应(呃,它们都是 HTTP 消息)。这种格式非常有用,因为它允许您在知道实体主体的大小或什至确切地知道该实体主体将是什么之前立即开始发送 HTTP 消息。事实上,这正是 PHP 在您 echo 没有发送像 header('Content-Length: 42') 这样的长度标头的任何输出时透明地为您做的事情。

我不打算深入讨论分块编码的细节——这就是 HTTP 规范的用途——但如果你想流式传输请求实体主体,你需要做这样的事情:

<?php

$sock = fsockopen($host,80,$errno, $error);
$readStream = fopen('/some/file/path.txt', 'r+');

fwrite($sock, "POST /somePath HTTP/1.1\r\n" .
              "Host: www.somehost.com\r\n" .
              "Transfer-Encoding: chunked\r\n\r\n");

while (!feof($readStream)) 
    $chunkData = fread($readStream, $chunkSize);
    $chunkLength = strlen($chunkData);
    $chunkData = dechex($chunkLength) . "\r\n$chunkData\r\n";

    fwrite($sock, $chunkData);


fwrite($sock, "0\r\n\r\n");

【讨论】:

我会像这样清理标题代码:$headers = array(); $headers[] = 'POST /somePath HTTP/1.1'; $headers[] = 'Host: example.com'; $headers[] = 'Transfer-Encoding: chunked'; fwrite($sock, implode(PHP_EOL, $headers) . PHP_EOL); @ShaquinTrifonoff 不幸的是,您的代码无法在非 Windows 环境中运行。在这种情况下使用 PHP_EOL 会破坏事情,因为 HTTP 协议特别要求标头之间的 CRLF 换行符。 @rdlowrey 有趣,我不知道。无论如何,我的意思是将标题放入一个数组中,然后将其内爆(这样看起来更好)。 :-)

以上是关于如何在没有 Content-Length 标头的情况下流式传输 HTTP 文件上传?的主要内容,如果未能解决你的问题,请参考以下文章

如何从跨域 Ajax 请求访问 Content-Length 标头?

如何从使用 axios 发送的 POST 请求中获取 Content-Length 标头?

ios 目标 c:如何让 NSURLSession 从服务器返回 Content-Length(http 标头)

Apache HttpClient 未显示响应的 Content-Length 和 Content-Encoding 标头

Content-Length 标头已存在

在 node.js 中计算 Content-length 标头的正确方法是啥