PHP fgets无限期运行

I have built myself a simple HTTP request object, very basic

class Request {

    protected $hostname;
    protected $port = 80;
    protected $method = 'GET';
    protected $uri = '/';
    protected $headers = array();
    protected $body;
    protected $handle;
    protected $response;

    public function __construct() {
        $cookies = null;
        foreach (\Cookie::getAll() as $k => $v) {
            $cookies .= urlencode($k) . '=' . urlencode($v) . '; ';
        }
        $this->setHeader('Cookie', substr($cookies, 0, -2));
    }

    public function setHostname($name, $ssl = false) {
        if (filter_var(gethostbyname($name), FILTER_VALIDATE_IP)) {
            $this->hostname = ($ssl ? 'ssl://' : null) . $name;
            $this->setHeader('Host', $name);

            return true;
        }
        return false;
    }

    public function setPort($port) {
        if ($port >= 1 && $port <= 65535) {
            $this->port = (int)$port;
            return true;
        }
        return false;
    }

    public function setMethod($method) {
        $methods = [
            'GET' => true,
            'POST' => true,
            'PUT' => true,
            'DELETE' => true
        ];

        if (isset($methods[$method])) {
            $this->method = strtoupper($method);
            return true;
        }
        return false;
    }

    public function setUri($uri) {
        $this->uri = $uri;
        return true;
    }

    public function setHeader($key, $value) {
        $this->headers[$key] = $value;
        return true;
    }

    public function removeHeader($key) {
        unset($this->headers[$key]);
        return true;
    }

    public function setBody($body) {
        $this->body = $body;
        return true;
    }

    public function send($async = false, $timeout = 30) {
        $errno = null;
        $errstr = null;

        $this->setHeader('Content-Length', strlen($this->body));
        if($async){
            $this->setHeader('Connection', 'Close');
        }

        $this->response = null;
        $this->handle = fsockopen($this->hostname, $this->port, $errno, $errstr, $timeout);
        if (!$this->handle) {
            throw new \Exception($errstr, $errno);
        }

        $headers = null;
        foreach ($this->headers as $key => $value) {
            $headers .= $key . ': ' . $value . "
";
        }

        $request = "{$this->method} {$this->uri} HTTP/1.1
{$headers}
" . $this->body;

        fwrite($this->handle, $request);

        if ($async) {
            fclose($this->handle);
            return;
        }

        while (!feof($this->handle)) {
            echo $this->response . ' dgd<br/><br/><br/>';
            $this->response .= fgets($this->handle, 8192);
        }
        fclose($this->handle);
    }

    public function getResponse() {
        return $this->response;
    }
}

When I send an "asynchronous" there's no problems but when I try to get the response I get maximum execution time exceeded error. I added an echo into the response-reading loop to debug and it only runs once. What may the problem be?

$r = new \Request();
$r->setHostname('localhost');
$r->setUri('/test.php');
$r->send();

echo "<pre>" . print_r($r, true) . "</pre>";

test.php

echo 'Test complete.';

Response source of the initial request

 dgd<br/><br/><br/><br />
<b>Fatal error</b>:  Maximum execution time of 30 seconds exceeded in <b>....\Request.php</b> on line <b>98</b><br />

Where line 98 is

$this->response .= fgets($this->handle, 8192);

The fgets() will read data from the socket and complete the read under several conditions such as end of file, the length - 1 number of characters read, or socket close.

The first thing you could try is to change your length to a much smaller value to see if you can get multiple reads. This will tell you whether the loop is working and the fgets() is not seeing the proper read termination behavior from the client that is sending data through the socket which is being read by the fgets().

What I suspect is that the client which is sending data is not closing the socket after sending the data.

The HTTP protocol is really designed to be stateless. The basic procedure is that the client should open a socket, send an HTTP request, get an HTTP response from the server, and then close the socket. However you can modify this procedure with the proper changes in the client and the server so as to maintain a connection sending and receiving data such as JSON or XML.

Since a TCP connection is a simple byte stream, any coordination between client and server where a connection is maintained must be implemented with some agreement by the client and the server as to the structure of the data. Using JSON or XML is pretty straightforward as those have messages have a structure where you know the beginning and the ending of a message.

For simple HTTP using the standard HTTP procedure, the end of the message is communicated by the closing of the socket by the client after it receives the HTTP response from the server. This tells the server that the communication is done.

I am trying to test your code, but you are using some sort of Cookie class? I commented that stuff out of the constructor ran the code and everything seems to go.

I get back:

Request Object
(
[hostname:protected] => test
[port:protected] => 80
[method:protected] => GET
[uri:protected] => /test.php
[headers:protected] => Array
    (
        [Host] => test
        [Content-Length] => 0
    )

[body:protected] => 
[handle:protected] => Resource id #3
[response:protected] => HTTP/1.1 200 OK
Date: Wed, 04 Feb 2015 13:51:23 GMT
Server: Apache/2.4.9 (Unix) PHP/5.4.37
X-Powered-By: PHP/5.4.37
Content-Length: 11
Content-Type: text/html

Hello World
)

However, I would highly urge you to do the same as others have suggested, and just use cURL or at the very least a simple file_get_contents() call. Here is a simple cURL function that I use repeatedly and never had an issue.

function getUrl( $u ){

    // is cURL installed yet?
    if (!function_exists('curl_init')){
        die('Sorry cURL is not installed!');
    }

    // OK cool - then let's create a new cURL resource handle
    $ch = curl_init();

    // Set URL to download
    curl_setopt( $ch, CURLOPT_URL, $u );


    // User agent
    curl_setopt($ch, CURLOPT_USERAGENT, "MozillaXYZ/1.0");

    // Include header in result? (0 = yes, 1 = no)
    curl_setopt($ch, CURLOPT_HEADER, 0);

    // Should cURL return or print out the data?
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    // Timeout in seconds
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);

    // Download the given URL, and return output
    $output = curl_exec($ch);

    // Close the cURL resource, and free system resources
    curl_close($ch);

    return $output;
}