Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Zend_Http_Client - Read from Stream?

I want to access the new Twitter Stream API using Zend_Http_Client. The problem is, that the HTTP request to that web page (http://stream.twitter.com/1/statuses/sample.json) is never finished but keeps loading.

So even when I set Zend_Http_Client to setStream(), I can't get the information it sends out.

This is what my logic currently looks like:

    $httpClient = new Zend_Http_Client("http://stream.twitter.com/1/statuses/sample.json");
    $httpClient->setAuth("username", "password");
    $httpClient->setStream("/tmp/twitter_stream");

    flush();
    ob_flush();

    $response = $httpClient->request("GET");

    // Never get here... :(
    Zend_Debug::dump($response);
    flush();
    ob_flush();

    while(true)
    {
        echo fgets($response->getStream());
        flush();
        ob_flush();
    }

Now, once I start the request, I never get to the while loop. What the Zend Framework does is, it writes into a file.

The reason why I want to use Zend_Http_Client is, because I later have to access that Stream API using OAuth, and Zend_Oauth relies on Zend_Http_Client.

Any help would be greatly appreciated.

like image 915
Sebastian Hoitz Avatar asked Nov 05 '22 10:11

Sebastian Hoitz


1 Answers

Seems like the only possible way right now as of my understanding is, to extend the Zend_Http_Client_Adapter_Socket, copy the write method from the old Zend_Client_Http_Adapter_Socket and insert your custom logic in the do...while loop (See the // CUSTOM LOGIC HERE comment at the near end of the method):

<?php

class App_Http_Client_Adapter_Socket extends Zend_Http_Client_Adapter_Socket
{

    /**
     * Read response from server
     *
     * @return string
     */
    public function read()
    {
        // First, read headers only
        $response = '';
        $gotStatus = false;
        $stream = !empty($this->config['stream']);

        while (($line = @fgets($this->socket)) !== false)
        {
            $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
            if ($gotStatus)
            {
                $response .= $line;
                if (rtrim($line) === '')
                    break;
            }
        }

        $this->_checkSocketReadTimeout();

        $statusCode = Zend_Http_Response::extractCode($response);

        // Handle 100 and 101 responses internally by restarting the read again
        if ($statusCode == 100 || $statusCode == 101)
            return $this->read();

        // Check headers to see what kind of connection / transfer encoding we have
        $headers = Zend_Http_Response::extractHeaders($response);

        /**
         * Responses to HEAD requests and 204 or 304 responses are not expected
         * to have a body - stop reading here
         */
        if ($statusCode == 304 || $statusCode == 204 ||
                        $this->method == Zend_Http_Client::HEAD)
        {

            // Close the connection if requested to do so by the server
            if (isset($headers['connection']) && $headers['connection'] == 'close')
            {
                $this->close();
            }
            return $response;
        }

        // If we got a 'transfer-encoding: chunked' header
        if (isset($headers['transfer-encoding']))
        {

            if (strtolower($headers['transfer-encoding']) == 'chunked')
            {

                do
                {
                    $line = @fgets($this->socket);
                    $this->_checkSocketReadTimeout();

                    $chunk = $line;

                    // Figure out the next chunk size
                    $chunksize = trim($line);
                    if (!ctype_xdigit($chunksize))
                    {
                        $this->close();
                        require_once 'Zend/Http/Client/Adapter/Exception.php';
                        throw new Zend_Http_Client_Adapter_Exception('Invalid chunk size "' .
                                        $chunksize . '" unable to read chunked body');
                    }

                    // Convert the hexadecimal value to plain integer
                    $chunksize = hexdec($chunksize);

                    // Read next chunk
                    $read_to = ftell($this->socket) + $chunksize;

                    do
                    {
                        $current_pos = ftell($this->socket);
                        if ($current_pos >= $read_to)
                            break;

                        if ($this->out_stream)
                        {
                            if (stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0)
                            {
                                $this->_checkSocketReadTimeout();
                                break;
                            }
                        }
                        else
                        {
                            $line = @fread($this->socket, $read_to - $current_pos);
                            if ($line === false || strlen($line) === 0)
                            {
                                $this->_checkSocketReadTimeout();
                                break;
                            }
                            $chunk .= $line;
                        }
                    }
                    while (!feof($this->socket));

                    $chunk .= @ fgets($this->socket);
                    $this->_checkSocketReadTimeout();

                    if (!$this->out_stream)
                    {
                        $response .= $chunk;
                    }
                }
                while ($chunksize > 0);
            }
            else
            {
                $this->close();
                require_once 'Zend/Http/Client/Adapter/Exception.php';
                throw new Zend_Http_Client_Adapter_Exception('Cannot handle "' .
                                $headers['transfer-encoding'] . '" transfer encoding');
            }

            // We automatically decode chunked-messages when writing to a stream
            // this means we have to disallow the Zend_Http_Response to do it again
            if ($this->out_stream)
            {
                $response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $response);
            }
            // Else, if we got the content-length header, read this number of bytes
        }
        elseif (isset($headers['content-length']))
        {

            // If we got more than one Content-Length header (see ZF-9404) use
            // the last value sent
            if (is_array($headers['content-length']))
            {
                $contentLength = $headers['content-length'][count($headers['content-length']) - 1];
            }
            else
            {
                $contentLength = $headers['content-length'];
            }

            $current_pos = ftell($this->socket);
            $chunk = '';

            for ($read_to = $current_pos + $contentLength; $read_to > $current_pos; $current_pos = ftell($this->socket))
            {

                if ($this->out_stream)
                {
                    if (@stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0)
                    {
                        $this->_checkSocketReadTimeout();
                        break;
                    }
                }
                else
                {
                    $chunk = @fread($this->socket, $read_to - $current_pos);
                    if ($chunk === false || strlen($chunk) === 0)
                    {
                        $this->_checkSocketReadTimeout();
                        break;
                    }

                    $response .= $chunk;
                }

                // Break if the connection ended prematurely
                if (feof($this->socket))
                    break;
            }

            // Fallback: just read the response until EOF
        } else
        {

            do
            {
                if ($this->out_stream)
                {
                    if (@stream_copy_to_stream($this->socket, $this->out_stream) == 0)
                    {
                        $this->_checkSocketReadTimeout();
                        break;
                    }
                }
                else
                {
                    $buff = @fread($this->socket, 8192);
                    if ($buff === false || strlen($buff) === 0)
                    {
                        $this->_checkSocketReadTimeout();
                        break;
                    }
                    else
                    {
                        $response .= $buff;
                    }

                    // CUSTOM LOGIC HERE!!    
                    echo $response;
                    flush();
                    ob_flush();
                }
            }
            while (feof($this->socket) === false);

            $this->close();
        }

        // Close the connection if requested to do so by the server
        if (isset($headers['connection']) && $headers['connection'] == 'close')
        {
            $this->close();
        }

        return $response;
    }
}

Any other suggestions are still very welcome, as I feel this is more of a hack than a valid solution.

like image 145
Sebastian Hoitz Avatar answered Nov 11 '22 07:11

Sebastian Hoitz