简体   繁体   中英

Prevent PHP HTTP wrapper from waiting for close of persistent connection

I'm using PHP code to retrieve a resource from an HTTP server via PHP HTTP wrapper , like:

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

While the PHP sends HTTP/1.0 request, the server responds with HTTP/1.1 response with Connection: Keep-Alive header:

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

Although the PHP has no way to use the persistent connection, it seems to pointlessly wait for the HTTP connection to close. The server closes it only after 60 seconds of inactivity.


Is there any way to prevent PHP HTTP wrapper from waiting for the server to close the connection, but close it itself after receiving the data announced by Content-Length header?

Or is it at least safe to use the fopen and the fread loop, reading until I get the Content-Length bytes?


The only workaround I've found is to set the timeout HTTP context option to reduce the wait time. But obviously I need to set the timeout high enough to be sure it does not interrupt reading of the response, so it's still far from ideal.


Other approaches I've tried without success:

  • use the HTTP/1.1 (using the protocol_version context option), hoping that it will make PHP be aware of persisted connection
  • use the Connection: close header (the server ignores it)
$context = stream_context_create(array('http' => array('header'=>"Connection: close\r\n")));
file_get_contents("http://...",false,$context);

You say you've done this in which case a better answer may be to manually write/read the response using fsockopen until fgets returns false (don't use feof it'll hang until the connection is closed).

Reference: http://php.net/manual/en/function.fsockopen.php & http://php.net/manual/en/function.fgets.php

EDIT: As stated previously and after further testing I've confirmed this indeed hangs as well so here's a revised solution that works correctly

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

This is clearly very simple and missing quite a bit of error checking but provides the intended behavior.

As there seems to be no way to make get_file_contents follow Content-Length header, I ended up using own implementation:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM