如何让 PHP 生成 Chunked 响应

Posted

技术标签:

【中文标题】如何让 PHP 生成 Chunked 响应【英文标题】:How to make PHP generate Chunked response 【发布时间】:2011-01-29 17:24:44 【问题描述】:

我搜索了这个问题,但没有答案。

我希望我的 php 脚本生成分块的 HTTP 响应(http://en.wikipedia.org/wiki/Chunked_transfer_encoding)。怎么做?

更新: 我想到了。我必须指定 Transfer-encoding 标头并刷新它。

header("Transfer-encoding: chunked");
flush(); 

冲洗是必要的。否则会产生Content-Length头。

而且,我必须自己制作块。有了辅助函数,就不难了。

function dump_chunk($chunk)

    echo sprintf("%x\r\n", strlen($chunk));
    echo $chunk;
    echo "\r\n";

【问题讨论】:

你可以在每个块之后刷新。 您能解释一下这些块是如何从文件中获取的吗?请? ^_º 小心:设置header("Transfer-encoding: chunked");会显式破坏PHP的原生分块编码,它不会再计算和发送分块长度! 【参考方案1】:

输出缓冲区在满之前不会发送到浏览器。默认大小为4096 字节。因此,您要么需要更改缓冲区大小,要么需要填充块。此外,在页面显示之前,浏览器可能有它自己的最小缓冲区。请注意,根据Wikipedia Article 关于分块编码,发送0\r\n\r\n 块会终止响应。

如果你想改变输出缓冲大小设置,我读到你不能使用ini_set('output_buffering', $value)。相反,通过将以下内容添加到您的 php.ini 文件来更改输出缓冲设置。

php_value output_buffering 1024

这是填充块的示例

header("Transfer-Encoding: chunked");
header("Content-Encoding: none");

// Send chunk to browser
function send_chunk($chunk)

    // The chunk must fill the output buffer or php won't send it
    $chunk = str_pad($chunk, 4096);

    printf("%x\r\n%s\r\n", strlen($chunk), $chunk);
    flush();


// Send your content in chunks
for($i=0; $i<10; $i++)

    send_chunk("This is Chunk #$i.<br>\r\n");
    usleep(500000);


// note that if you send an empty chunk
// the browser won't display additional output
echo "0\r\n\r\n";
flush();

这是一个简短的版本,演示 0\r\n\r\n\ 终止输出:

$output = "hello world";
header("Transfer-Encoding: chunked");
header('Content-Encoding: none');
printf("%x\r\n%s\r\n", strlen($output), $output);
ob_flush();
print("0\r\n\r\n");
ob_flush();
flush();
sleep(10);
print('POST PROCESSING');

【讨论】:

【参考方案2】:

如果大小足够大,Apache 会为您完成。 如果您使用 ob_gzhandler,我认为这没有任何意义。最好尽快让缓冲区出来。如果 PHP 确实没有自动发送 content-length 标头或者 content-length 来自未压缩的内容:

ob_start();
ob_start("ob_gzhandler");
...
ob_end_flush();  
header('Content-Length: '.ob_get_length()); 
ob_end_flush();

您可以使用apache 的 mod_buffer(可能使用 PHP 的 ini_set)来减小缓冲区大小。

PS:如果size足够大,content-length未知,内容会分块发送。

【讨论】:

【参考方案3】:

Andriy F.'s solution 为我工作。这是他的回答的简化版本:

function dump_chunk($chunk)

    echo $chunk;
    flush();
    ob_flush();


header('Content-Type: text/html; charset=UTF-8');

flush();

for ($i = 0; $i < 3; $i++) 
    dump_chunk('Sending data chunk ' . ($i + 1) . ' of 1000 <br />');
    sleep(1);

虽然我不明白为什么需要ob_flush() 调用。如果有人知道,请发表评论。

【讨论】:

【参考方案4】:

截至 2013 年 2 月 16 日的工作示例

<?php

function dump_chunk($chunk) 
    //echo sprintf("%x\r\n", strlen($chunk));
    echo sprintf("\r\n");
    echo $chunk;
    echo "\r\n";


ob_start();
?>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Title</title>
    </head>
    <body>
        <?php
        ob_end_flush();
        flush();
        ob_flush();
        for ($i = 0; $i < 5; $i++) 
            sleep(1);
            dump_chunk('Sending data chunk ' . ($i + 1) . ' of 1000 <br />');
            flush();
            ob_flush();
        
        sleep(1); // needed for last animation
        ?>
    </body>
</html>

【讨论】:

由于某种原因,页面仅在整个页面呈现而不是增加时显示。你知道可能是什么问题吗? 你使用的是什么配置? (Apache版本、模块、php版本)? PHP 5.3.26, apache 2.2.24 + nginx hosting24.com/blog/…由hosting24的银包hosting24.com/features.php提供 在apachefriends.org/en/xampp.html 上尝试代码。如果它适用于 XAMPP,那么您的问题可能是由于 nginx(请参阅 ***.com/questions/3222007/… 或 nginx.org/en/docs/faq/chunked_encoding_from_backend.html 以获取线索) 为什么要使用输出缓冲?您似乎不需要它,除非您在输出开始后更改标头,将函数应用于输出,或通过 ob_get_clean() 将缓冲区保存到变量。【参考方案5】:

我需要做两件额外的事情才能让它发挥作用。如果对象尚未启动,则调用 ob_start。并且还回显出一个长的空字符串。浏览器需要在它出现的第一个块中发送一定数量的内容。

header('Content-Encoding', 'chunked');
header('Transfer-Encoding', 'chunked');
header('Content-Type', 'text/html');
header('Connection', 'keep-alive');

if (ob_get_level() == 0) ob_start();
echo str_pad('',4096)."\n";
ob_flush();
flush();

【讨论】:

【参考方案6】:

这有点模糊...如果您不介意大块(0x1000 字节左右),那么是的,PHP 会制作它们。

<?php

while (true) 
    # output data
    flush()
    usleep(pow(2,18));

?>

PHP 会生成编号部分等

如果你想发送很小的块,就像你可能对 AJAX 客户端所做的那样......好吧,我已经将 OPs 问题与对 PHP.NET 的一些研究结合起来,看起来他确实在好东西。

$ echo -en "GET /chunked/ HTTP/1.1\r\n主机: ec\r\n\r\n" |数控本地主机 80

HTTP/1.1 200 OK
Date: Wed, 23 May 2012 13:03:01 GMT
Server: Apache/2.2.9 (Debian) PHP/5.3.5-1 with Suhosin-Patch mod_ssl/2.2.9 OpenSSL/0.9.8o
X-Powered-By: PHP/5.3.5-1
Transfer-encoding: chunked
Content-Type: text/html

14
Teachers have class.
50
We secure our friends not by accepting favors but by doing them.
            -- Thucydides
48
Vulcans never bluff.
            -- Spock, "The Doomsday Machine", stardate 4202.1
31
All kings is mostly rapscallions.
            -- Mark Twain
41
Reappraisal, n.:
    An abrupt change of mind after being found out.
49
He who knows, does not speak.  He who speaks, does not know.
            -- Lao Tsu

它是否最终会挤出自己的(不正确的)块数,还有待观察......但我没有看到任何迹象。

<?php
header("Transfer-encoding: chunked");
@apache_setenv('no-gzip', 1);
@ini_set('zlib.output_compression', 0);
@ini_set('implicit_flush', 1);
for ($i = 0; $i < ob_get_level(); $i++)  ob_end_flush();
ob_implicit_flush(1); flush();

function dump_chunk($chunk)

  printf("%x\r\n%s\r\n", strlen($chunk), $chunk);
  flush();


for (;;) 
  $output = array();
  exec("/usr/games/fortune", $output);
  dump_chunk(implode("\n", $output));
  usleep(pow(2,18));

?>

【讨论】:

如果您的意图是摆脱每个输出缓冲区,这种方式只会删除其中的一半(四舍五入)。请改用while(ob_get_level())ob_end_flush();。如果您想避免重复调用该函数:for($i=ob_get_level(); $i; --$i)ob_end_flush();$i=ob_get_level()+1;while(--$i)ob_end_flush();(如果 +1 看起来很奇怪,请使用后递减)。感谢 netcat 的示例,HTTP 比我预期的要简单得多,这很有趣,因为我已经看到了 &lt;method&gt; &lt;path&gt; &lt;HTTP version&gt;、标头和正文,但是当我查看它们时它们通常会单独显示。【参考方案7】:

如果您没有指定内容长度标头,PHP 响应将始终被分块,并且会发生刷新。 (这会在 x 字节后自动发生,只是不知道具体多少)。

这是一件奇怪的事情。这是某种学术/学习练习,还是您正在尝试解决现实世界的问题?

【讨论】:

我想强制分块传输编码,因为我正在测试一个应该能够读取它的 ruby​​ 脚本。因此,如果我理解正确,我只需要粘贴一些 flush();定期运行,这将迫使 php 在块传输编码中响应。不需要显式输出任何块大小的数字? Apache 应该自己为您处理。基本上,如果 apache 事先不知道长度,它别无选择,只能使用 'chunked' Apache 在发送前缓冲一定数量的输出。如果你的整个输出都适合缓冲区,那么它不会被分块,除非你强制它。【参考方案8】:

一种更简单的方法(无需创建自己的数据块)是只使用flush():

<?php 
$fp = fopen('data.bin', "rb");
flush();
fpassthru($fp);
?>

这对我有用,如以下 HTTP 请求和响应所示。

请求:

00000000  47 45 54 20 2f 67 65 74  2d 63 68 75 6e 6b 2e 70 GET /get -chunk.p
00000010  68 70 20 48 54 54 50 2f  31 2e 31 0d 0a 55 73 65 hp HTTP/ 1.1..Use
00000020  72 2d 41 67 65 6e 74 3a  20 63 75 72 6c 2f 37 2e r-Agent:  curl/7.
00000030  32 31 2e 37 20 28 69 36  38 36 2d 70 63 2d 6c 69 21.7 (i6 86-pc-li
00000040  6e 75 78 2d 67 6e 75 29  20 6c 69 62 63 75 72 6c nux-gnu)  libcurl
00000050  2f 37 2e 32 31 2e 37 20  4f 70 65 6e 53 53 4c 2f /7.21.7  OpenSSL/
00000060  31 2e 30 2e 30 64 20 7a  6c 69 62 2f 31 2e 32 2e 1.0.0d z lib/1.2.
00000070  35 20 6c 69 62 73 73 68  32 2f 31 2e 32 2e 37 0d 5 libssh 2/1.2.7.
00000080  0a 48 6f 73 74 3a 20 67  61 69 61 0d 0a 41 63 63 .Host: g aia..Acc
00000090  65 70 74 3a 20 2a 2f 2a  0d 0a 0d 0a             ept: */* ....

回复:

00000000  48 54 54 50 2f 31 2e 31  20 32 30 30 20 4f 4b 0d HTTP/1.1  200 OK.
00000010  0a 44 61 74 65 3a 20 54  68 75 2c 20 32 32 20 53 .Date: T hu, 22 S
00000020  65 70 20 32 30 31 31 20  32 33 3a 35 33 3a 30 32 ep 2011  23:53:02
00000030  20 47 4d 54 0d 0a 53 65  72 76 65 72 3a 20 41 70  GMT..Se rver: Ap
00000040  61 63 68 65 2f 32 2e 32  2e 38 20 28 46 65 64 6f ache/2.2 .8 (Fedo
00000050  72 61 29 0d 0a 58 2d 50  6f 77 65 72 65 64 2d 42 ra)..X-P owered-B
00000060  79 3a 20 50 48 50 2f 35  2e 32 2e 36 0d 0a 54 72 y: PHP/5 .2.6..Tr
00000070  61 6e 73 66 65 72 2d 45  6e 63 6f 64 69 6e 67 3a ansfer-E ncoding:
00000080  20 63 68 75 6e 6b 65 64  0d 0a 43 6f 6e 74 65 6e  chunked ..Conten
00000090  74 2d 54 79 70 65 3a 20  74 65 78 74 2f 68 74 6d t-Type:  text/htm
000000A0  6c 3b 20 63 68 61 72 73  65 74 3d 55 54 46 2d 38 l; chars et=UTF-8
000000B0  0d 0a 0d 0a                                      ....
000000B4  61 0d 0a 54 45 53 54 20  44 41 54 41 0a 0d 0a 30 a..TEST  DATA...0
000000C4  0d 0a 0d 0a                                      ....

【讨论】:

【参考方案9】:

你应该可以使用:

<?php header("Transfer-Encoding: chunked");

但您必须确保自己的输出符合规范。

【讨论】:

好像还不够。我必须在最后一个 header() 语句之后立即执行 flush() 以确保未生成“Content-Length”标头。 有时您必须在服务器上禁用 gz-compress 并发送header("Content-Encoding: identity");

以上是关于如何让 PHP 生成 Chunked 响应的主要内容,如果未能解决你的问题,请参考以下文章

HTTP协议扫盲(九 )HTTP1.1响应的Transfer-Encoding=chunked方式

如何禁用传输编码:chunked websphere

Chunked Stream响应不正确

net::ERR_INCOMPLETE_CHUNKED_ENCODING 是啥意思,我该如何解决?

在 PHP 中读取“分块”的 POST 数据

PHP CURL请求自定义标头[重复]