1

我有一个 php 网站。由于我使用的是模板引擎并且我总是在“一次性”中执行 html,因此我预先设置了 html 文档的大小。所以我决定设置 Content-Length 标头以获得更好的性能。如果我没有设置它,则使用分块编码传输文档。

html 输出的 php 代码如下所示:

header('Accept-Ranges: none');
header('Content-Length: '.strlen($content));

echo $content;

我在 Chrome、IE、Firefox 和 Safari 的 windows 下对其进行了测试——它可以工作文件。但是微软必应机器人(使用必应网站管理员工具)表示该网站没有响应。我决定进行调查,这就是我发现的:

  • wget 在 CentOS 5.x 和 CentOS 6.x 上运行良好
  • CentOS 6.x 上的 elinks 工作正常
  • CentOS 5.x 上的 elinks停顿(版本 elinks-0.11.1-6.el5_4.1)

因此,Centos 5 上的 elinks 是我发现的唯一一个在访问该站点时遇到问题的 http 客户端。但是我不知道如何从中获取调试信息。

问题:

  1. 有人可以告诉我如何从 elinks 中获取调试信息。是否有可能拥有 http+headers 的原始副本?或某种错误日志
  2. 知道为什么停顿发生在一个客户身上而没有发生在另一个客户身上吗?
  3. 嗯,很可能是导致问题的不正确的标题“Content-Length”,因为当我删除它时,它在 elinks 和 Bing 中工作正常。什么可能导致内容长度差异
  4. 还有其他要测试的http客户端吗?

所有测试都在同一个 Web 服务器、相同的 php 版本、相同的网页和相同的内容上完成。我能想到的是 UTF-8 文本文件标识符(一些浏览器放置的文本文件前面的几个字节)

这是带有 wget 的标头转储:

wget dev.site.com/ --server-response -O /dev/null
--2013-11-09 01:32:37--  http://dev.site.com/
Resolving dev.site.com... 127.0.0.1
Connecting to dev.site.com|127.0.0.1|:80... connected.
HTTP request sent, awaiting response...
  HTTP/1.1 200 OK
  Date: Fri, 08 Nov 2013 23:32:37 GMT
  Server: Apache
  Set-Cookie: lng=en; expires=Wed, 07-May-2014 23:32:37 GMT; path=/; domain=dev.site.com
  Last-Modified: Fri, 08 Nov 2013 23:32:37 GMT
  Cache-Control: must-revalidate, post-check=0, pre-check=0
  Pragma: no-cache
  Expires: 0
  Set-Cookie: PHPSESSID=8a1e9b871474b882e1eef4ca0dfea0fc; expires=Thu, 06-Feb-2014 23:32:37 GMT; path=/
  Content-Language: en
  Set-Cookie: hc=1518952; expires=Mon, 17-Nov-2036 00:38:00 GMT; path=/; domain=dev.site.com
  Accept-Ranges: none
  Content-Length: 16970
  Keep-Alive: timeout=15, max=100
  Connection: Keep-Alive
  Content-Type: text/html; charset=UTF-8
Length: 16970 (17K) [text/html]
Saving to: “/dev/null”

100%[===================================================================================================================================================================================================>] 16,970      --.-K/s   in 0.1s

2013-11-09 01:32:37 (152 KB/s) - “/dev/null” saved [16970/16970]

更新:

我能够重现该问题,但仅在生产服务器上。我注意到工作和非工作 elink 之间的一个区别是非工作发送此标头:Accept-Encoding: gzip

当然,如果它是 gzip 压缩的,大小会有所不同。zlib.output_compression 在 php.ini 上打开。我想这可能是问题所在。输出缓冲也是 4096。这很奇怪,因为大多数浏览器在可用时都使用压缩。我会在网络浏览器中再试一次。

是的,浏览器(chrome)也要求压缩,并且 gzip 存在于响应标头中:

Content-Length: 15916
Content-Encoding: gzip

查看源代码显示正好 15916 字节。Chrome 可以选择显示原始标题以及解析。可能发生的情况是 Chrome 在计数之前实际上会解压缩数据。听起来很奇怪,但这是 GUI Web 浏览器工作而一些较低级别的客户端不能工作的唯一解释

4

3 回答 3

1

没有既好又干净的解决方案。我希望能够设置 zlib 缓冲区大小:

zlib.output_compression = 131072

如果我确定页面不会超过 128k(未压缩),但是没有办法获得缓冲区的压缩大小。

所以有两种解决方案:

  1. 关闭输出压缩或不设置 Content-Length ...这不是一个解决方案,但它可以工作
  2. 将 zlib 压缩处理程序替换为:

ob_start(); // start normal buffer
ob_start("ob_gzhandler"); // start gzip buffer
echo $content;
ob_end_flush(); // output gzipped content

$gzippedContent = ob_get_contents(); // store gzipped content to get size
header('Content-Length: '.strlen($gzippedContent));
ob_end_flush(); // flush gzipped content

但请确保 zlib.output_compression 处于关闭状态。

尽管 php 手册认为 zlib.output_compression 是首选,但我怀疑使用 ob_gzhandler 会显着降低性能。

您可以通过以下方式设置压缩级别

ini_set('zlib.output_compression_level', 4);

我对其进行了测试,它适用于在客户端/浏览器中启用 gzip 和禁用 gzip。

wget --header='Accept-Encoding: gzip,deflate' -O ./page.html.gz http://www.site.com/ && gunzip page.html.gz
wget -O ./page.html http://www.site.com/
于 2013-11-09T02:12:10.583 回答
1

答案已经存在。Content-Length必须是实际发送的大小,即“$content”压缩后的大小。您在 view-source 上看到的内容的大小自然是解压缩后的大小。

连接不会停止。您的浏览器正在等待更多数据到来,但压缩后的数据大小小于浏览器等待的数据。如果您的服务器最终使连接超时,您的浏览器将假定它已获取所有数据并显示它。它适用于 wget 等,因为它们不发送接受压缩标头并且服务器不发送压缩响应。

如果必须,您可以禁用压缩、手动压缩和发送$content以及适当Content-Encoding的标头。

另一种选择是下载未压缩的页面(Accept-Encoding: gzip用wget发送,我猜它不会被解压缩,但即使默认情况下没有启用它wget可能毕竟支持压缩,我不知道。我知道cURL不支持它,您可以使用它)并获取响应减去标头的大小(这意味着只有标头结束序列之后 的数据大小)并在发送时使用该大小。但是当然,改变压缩级别或实现(不同的 Web 服务器/模块或相同 Web 服务器/模块的不同版本)会改变生成的压缩数据的大小,所以这是一种非常脆弱的方法。\r\n\r\nContent-Length

你为什么还要修改Content-Length?PHP 或 Web 服务器应该处理这个问题。

于 2013-11-09T01:30:10.290 回答
0

我遇到了同样的问题——我试图设置Content-Length标题,却没有意识到我在缓冲区内测量的长度会大于实际的 GZip 输出(是的,看起来浏览器被挂起)。在我已经解决了我的问题(下面的解决方案)之后,我偶然发现了这个问答。

@Etherealone 有一点很到位:

连接不会停止。您的浏览器正在等待更多数据到来,但压缩后的数据大小小于浏览器等待的数据。

@Etherealone 和 @NickSoft 都暗示了这一点,但实际上并没有说出来:Content-Length动态生成内容的标头不是必需的,服务器应该发送Transfer-Encoding: chunked标头。这告诉浏览器保持连接打开,直到它收到一个零长度的块,这表示内容的结束。

但是,对传输进行分块确实会增加一些开销,因此想要指定 aContent-Length肯定不会受到伤害。@NickSoft 有正确的想法,但它不必那么复杂。

因此,如果您坚持使用Content-Length标头而不是让服务器将内容分块,那么您所要做的就是缓冲两次;一次用于压缩,然后再一次,以便您可以测量大小并发送Content-Length标头:

<?php

// "Outer" buffer to capture content and size of "inner" buffer and send content length header
ob_start();

// "Inner" buffer for compression
ob_start('ob_gzhandler');

// Do stuff...
echo $content;

// Flush the inner buffer, the contents of which is GZip'd
ob_end_flush();

// Measure the inner buffer size and set the header
header('Content-Length: ' . ob_get_length());  

// Send the outer buffer
ob_end_flush();

?>

在我实现这个之后,我看到了新的Content-Length标题;Transfer-Encoding: chunked标题消失了;并且“挂起”的浏览器症状消失了(浏览器获取了所有内容并关闭了连接)。

于 2016-09-11T20:53:17.623 回答