【问题标题】:Prevent PHP HTTP wrapper from waiting for close of persistent connection防止 PHP HTTP 包装器等待关闭持久连接
【发布时间】:2019-04-12 16:00:49
【问题描述】:

我正在使用 PHP 代码通过 PHP HTTP wrapper 从 HTTP 服务器检索资源,例如:

file_get_contents("http://...");

当 PHP 发送 HTTP/1.0 请求时,服务器以带有 Connection: Keep-Alive 标头的 HTTP/1.1 响应响应:

HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Length: ...

虽然 PHP 没有办法使用长连接,但它似乎毫无意义地等待 HTTP 连接关闭。服务器仅在不活动 60 秒后关闭它。


有什么办法可以防止PHP HTTP wrapper 等待服务器关闭连接,而是在收到Content-Length header 宣布的数据后自行关闭?

或者至少使用fopenfread 循环读取直到我得到Content-Length 字节是否安全?


我发现的唯一解决方法是设置 timeout HTTP context option 以减少等待时间。但显然我需要将超时设置得足够高,以确保它不会中断响应的读取,所以它仍然远非理想。


我尝试过的其他方法都没有成功:

  • 使用 HTTP/1.1(使用 protocol_version 上下文选项),希望它能让 PHP 意识到持久连接
  • 使用Connection: close 标头(服务器忽略它)

【问题讨论】:

  • 我建议卷曲。如果服务器打得不好,file_get_contents 不使用 Content-Length,怎么办?
  • @SH- 我希望有一个(上下文)选项可以让它工作。或者另一个简单的实现,可能使用 fopen + fread 并明确使用 Content-Length 标头。
  • 是的,我看了看文档,什么也没看到。除非源代码中有隐藏的参数...至于简单,文档建议套接字 I/O...
  • 没关系,fclose 不适用于 file_get_contents:stackoverflow.com/questions/19650817/…

标签: php http http-headers keep-alive


【解决方案1】:
$context = stream_context_create(array('http' => array('header'=>"Connection: close\r\n")));
file_get_contents("http://...",false,$context);

您说您已经这样做了,在这种情况下,更好的答案可能是使用 fsockopen 手动写入/读取响应,直到 fgets 返回 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;
}
?>

这显然非常简单,缺少很多错误检查,但提供了预期的行为。

【讨论】:

  • 但是在这种情况下fgets 真的会返回 false 吗?我怀疑是这样。我假设file_get_contents 在内部执行此操作(可能使用类似fread 的调用而不是fgets)。
  • 看来你是正确的,更好的方法是阅读内容长度并在那时关闭。给我一点,我会举一个例子。
  • 谢谢。我会测试它。虽然使用fopen$http_response_headerfread 循环会更容易实现吗?正如我假设您的解决方案必须处理 Content-Encoding (例如分块)。虽然fopen/fread 应该给你解码数据。
  • 这是另一种可能的解决方案,自从我不得不使用 fopen 处理这种情况已经有一段时间了,我似乎记得与分块请求类似的问题。编辑:但它似乎已从 PHP 5.3 bugs.php.net/bug.php?id=42445 解决
【解决方案2】:

由于似乎没有办法让get_file_contents 跟随Content-Length 标头,我最终使用了自己的实现:

// 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;
}

【讨论】:

    猜你喜欢
    • 2014-09-20
    • 1970-01-01
    • 1970-01-01
    • 2017-05-28
    • 1970-01-01
    • 1970-01-01
    • 2014-01-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多