php和Content-Length标头连接停止

时间:2013-11-08 23:49:44

标签: php http header content-length

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

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

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

echo $content;

我在Chrome,IE,Firefox和Safari的Windows下进行了测试 - 它的工作文件。然而,微软Bing bot(使用bing网站管理员工具)表示该网站没有回应。我决定调查,这是我发现的:

  • wget在CentOS 5.x和CentOS 6.x上运行良好
  • elOSs在CentOS 6.x上工作正常
  • elinks on CentOS 5.x 摊位(版本elinks-0.11.1-6.el5_4.1)

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

问题:

  1. 有人可以告诉我如何从elinks中获取调试信息。是否有可能有原始的http +标头重复?或某种错误日志
  2. 任何想法为什么在一个客户端发生停顿并且不会在另一个客户端发生停顿?
  3. 这很可能是导致问题的错误标题“Content-Length”,因为当我删除它时,它在elinks和Bing中工作正常。什么可能导致内容长度差异
  4. 要测试的任何其他http客户端?
  5. 所有测试都在同一个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,那么尺寸会有所不同。 php.ini上的zlib.output_compression为On。我猜这可能是问题所在。输出缓冲也是4096.这很奇怪,因为大多数浏览器在可用时使用压缩。我将在网络浏览器中再试一次。

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

    Content-Length: 15916
    Content-Encoding: gzip
    

    view source显示15916个字节。 Chrome可以选择显示原始标题以及解析。可能发生的事情是Chrome实际上在计算之前解压缩数据。听起来很奇怪,但这是GUI web浏览器工作的唯一原因,而一些较低级别的客户端不能

3 个答案:

答案 0 :(得分:1)

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

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

如果必须,您可以停用压缩,手动压缩并发送$content以及相应的Content-Encoding标题。

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

为什么要修改Content-Length? Php或Web服务器应该处理它。

答案 1 :(得分:1)

没有干净整洁的解决方案。我希望能够用:

设置zlib缓冲区大小
zlib.output_compression = 131072

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

因此有两种解决方案:

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

  3. 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手册sais 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/
    

答案 2 :(得分:0)

我有同样的问题 - 我试图设置Content-Length标题而没有意识到我在缓冲区内测量的长度将大于实际的GZip输出(是的,它似乎是浏览器挂了)。在我已经解决了我的问题(下面的解决方案)之后,我偶然发现了这个Q& A。

@Etherealone点击一点:

  

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

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

但是,分块传输确实增加了一些开销,所以想要指定Transfer-Encoding: chunked肯定不会受到伤害。 @NickSoft有正确的想法,但它不一定非常复杂。

所以,如果你坚持使用Content-Length标题而不是让服务器对内容进行分块,那么你所要做的只是缓冲两次;一次用于压缩,然后再次,以便您可以测量大小并发送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标题消失了;并且“挂起”的浏览器症状消失了(浏览器获取了所有内容并关闭了连接)。