阻止 PHP HTTP 包装器等待持久连接的关闭
Prevent PHP HTTP wrapper from waiting for close of persistent connection
我正在使用 PHP 代码通过 PHP HTTP wrapper 从 HTTP 服务器检索资源,例如:
file_get_contents("http://...");
当 PHP 发送 HTTP/1.0 请求时,服务器以 [=46=].1 响应 Connection: Keep-Alive
header:
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Length: ...
虽然PHP没有办法使用持久连接,但似乎毫无意义地等待HTTP连接关闭。服务器只有在 60 秒不活动后才会关闭它。
有什么办法可以防止PHP HTTP wrapper等待服务器关闭连接,而是在收到Content-Length
header通告的数据后自己关闭?
或者至少使用 fopen
和 fread
循环是安全的,一直读到 Content-Length
字节?
我发现的唯一解决方法是设置 timeout
HTTP context option 以减少等待时间。但显然我需要将超时设置得足够高以确保它不会中断响应的读取,所以它仍然远非理想。
我尝试过但没有成功的其他方法:
- 使用 HTTP/1.1(使用
protocol_version
上下文选项),希望它能让 PHP 意识到持续连接
- 使用
Connection: close
header(服务器忽略它)
$context = stream_context_create(array('http' => array('header'=>"Connection: close\r\n")));
file_get_contents("http://...",false,$context);
你说你已经这样做了,在这种情况下,更好的答案可能是手动 write/read 使用 fsockopen 的响应直到 fgets returns false(不要使用 feof 它会挂起直到连接已关闭)。
参考:http://php.net/manual/en/function.fsockopen.php & http://php.net/manual/en/function.fgets.php
编辑:
如前所述,经过进一步测试后,我已经确认这确实也挂起,所以这里有一个可以正常工作的修改后的解决方案
<?php
$handle = fsockopen("google.com", 80);
if ($handle) {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: google.com\r\n";
$out .= "Connection: Keep-Alive\r\n\r\n";
fwrite($handle, $out);
$bytesread = 0;
while (($buffer = fgets($handle, 4096)) !== false) {
if(strpos($buffer, "Content-Length:") !== false) {
list(, $len) = explode("Content-Length: ", $buffer);
}
if($buffer === "\r\n") {
break;
}
}
$data = '';
while($bytesread != intval($len)) {
$buf = fgets($handle, 1024);
$bytesread += strlen($buf);
$data .= $buf;
}
fclose($handle);
print $data;
}
?>
这显然非常简单,缺少很多错误检查,但提供了预期的行为。
因为似乎没有办法让 get_file_contents
跟随 Content-Length
header,我最终使用了自己的实现:
// parse $http_response_header auto variable to associative array
function get_http_response_headers($http_response_header)
{
$response_headers = array();
if (isset($http_response_header))
{
foreach ($http_response_header as $header)
{
$i = strpos($header, ":");
if ($i !== false)
{
$name = trim(substr($header, 0, $i));
$value = trim(substr($header, $i+1));
}
else
{
$name = trim($header);
$value = NULL;
}
$name = strtolower($name);
$response_headers[$name] = $value;
}
}
else
{
$response_headers = array();
}
return $response_headers;
}
// Replacement for file_get_contents that follows Content-Length header
function http_get_contents($url, $context)
{
$h = fopen($url, "r", false, $context);
$result = ($h !== false);
if ($result)
{
$response_headers = get_http_response_headers($http_response_header);
$len = null;
// If it is not persistent connection, just read to the end,
// as file_get_contents does
if (isset($response_headers["connection"]) &&
(strcasecmp($response_headers["connection"], "close") == 0))
{
$len = false;
}
// If it is not persistent connection, follow Content-Length
else if (isset($response_headers["content-length"]))
{
$len = intval($response_headers["content-length"]);
}
else
{
trigger_error("No Content-Length or Connection:close header");
$result = false;
}
if ($result)
{
$result = null;
$total = 0;
while (true)
{
$toread = ($len === false) ? 16384 : ($len - $total);
$buf = fread($h, $toread);
if ($buf === false)
{
trigger_error("Reading HTTP failed");
$result = false;
break;
}
else
{
$read = strlen($buf);
$total += $read;
$result .= $buf;
if ($len !== false)
{
if ($read > $len)
{
trigger_error("Read too much data $read > $len");
break;
}
else if ($read == $len)
{
// done
break;
}
}
else
{
if ($read == 0)
{
// done
break;
}
}
}
}
}
fclose($h);
}
return $result;
}
我正在使用 PHP 代码通过 PHP HTTP wrapper 从 HTTP 服务器检索资源,例如:
file_get_contents("http://...");
当 PHP 发送 HTTP/1.0 请求时,服务器以 [=46=].1 响应 Connection: Keep-Alive
header:
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Length: ...
虽然PHP没有办法使用持久连接,但似乎毫无意义地等待HTTP连接关闭。服务器只有在 60 秒不活动后才会关闭它。
有什么办法可以防止PHP HTTP wrapper等待服务器关闭连接,而是在收到Content-Length
header通告的数据后自己关闭?
或者至少使用 fopen
和 fread
循环是安全的,一直读到 Content-Length
字节?
我发现的唯一解决方法是设置 timeout
HTTP context option 以减少等待时间。但显然我需要将超时设置得足够高以确保它不会中断响应的读取,所以它仍然远非理想。
我尝试过但没有成功的其他方法:
- 使用 HTTP/1.1(使用
protocol_version
上下文选项),希望它能让 PHP 意识到持续连接 - 使用
Connection: close
header(服务器忽略它)
$context = stream_context_create(array('http' => array('header'=>"Connection: close\r\n")));
file_get_contents("http://...",false,$context);
你说你已经这样做了,在这种情况下,更好的答案可能是手动 write/read 使用 fsockopen 的响应直到 fgets returns false(不要使用 feof 它会挂起直到连接已关闭)。
参考:http://php.net/manual/en/function.fsockopen.php & http://php.net/manual/en/function.fgets.php
编辑: 如前所述,经过进一步测试后,我已经确认这确实也挂起,所以这里有一个可以正常工作的修改后的解决方案
<?php
$handle = fsockopen("google.com", 80);
if ($handle) {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: google.com\r\n";
$out .= "Connection: Keep-Alive\r\n\r\n";
fwrite($handle, $out);
$bytesread = 0;
while (($buffer = fgets($handle, 4096)) !== false) {
if(strpos($buffer, "Content-Length:") !== false) {
list(, $len) = explode("Content-Length: ", $buffer);
}
if($buffer === "\r\n") {
break;
}
}
$data = '';
while($bytesread != intval($len)) {
$buf = fgets($handle, 1024);
$bytesread += strlen($buf);
$data .= $buf;
}
fclose($handle);
print $data;
}
?>
这显然非常简单,缺少很多错误检查,但提供了预期的行为。
因为似乎没有办法让 get_file_contents
跟随 Content-Length
header,我最终使用了自己的实现:
// parse $http_response_header auto variable to associative array
function get_http_response_headers($http_response_header)
{
$response_headers = array();
if (isset($http_response_header))
{
foreach ($http_response_header as $header)
{
$i = strpos($header, ":");
if ($i !== false)
{
$name = trim(substr($header, 0, $i));
$value = trim(substr($header, $i+1));
}
else
{
$name = trim($header);
$value = NULL;
}
$name = strtolower($name);
$response_headers[$name] = $value;
}
}
else
{
$response_headers = array();
}
return $response_headers;
}
// Replacement for file_get_contents that follows Content-Length header
function http_get_contents($url, $context)
{
$h = fopen($url, "r", false, $context);
$result = ($h !== false);
if ($result)
{
$response_headers = get_http_response_headers($http_response_header);
$len = null;
// If it is not persistent connection, just read to the end,
// as file_get_contents does
if (isset($response_headers["connection"]) &&
(strcasecmp($response_headers["connection"], "close") == 0))
{
$len = false;
}
// If it is not persistent connection, follow Content-Length
else if (isset($response_headers["content-length"]))
{
$len = intval($response_headers["content-length"]);
}
else
{
trigger_error("No Content-Length or Connection:close header");
$result = false;
}
if ($result)
{
$result = null;
$total = 0;
while (true)
{
$toread = ($len === false) ? 16384 : ($len - $total);
$buf = fread($h, $toread);
if ($buf === false)
{
trigger_error("Reading HTTP failed");
$result = false;
break;
}
else
{
$read = strlen($buf);
$total += $read;
$result .= $buf;
if ($len !== false)
{
if ($read > $len)
{
trigger_error("Read too much data $read > $len");
break;
}
else if ($read == $len)
{
// done
break;
}
}
else
{
if ($read == 0)
{
// done
break;
}
}
}
}
}
fclose($h);
}
return $result;
}