Phirehose::connect PHP Method

connect() protected method

Connects to the stream URL using the configured method.
protected connect ( )
    protected function connect()
    {
        // Init state
        $connectFailures = 0;
        $tcpRetry = $this->tcpBackoff / 2;
        $httpRetry = $this->httpBackoff / 2;
        // Keep trying until connected (or max connect failures exceeded)
        do {
            // Check filter predicates for every connect (for filter method)
            if ($this->method == self::METHOD_FILTER) {
                $this->checkFilterPredicates();
            }
            // Construct URL/HTTP bits
            $url = $this->URL_BASE . $this->method . '.' . $this->format;
            $urlParts = parse_url($url);
            // Setup params appropriately
            $requestParams = array();
            //$requestParams['delimited'] = 'length';    //No, we don't want this any more
            // Setup the language of the stream
            if ($this->lang) {
                $requestParams['language'] = $this->lang;
            }
            // Filter takes additional parameters
            if (($this->method == self::METHOD_FILTER || $this->method == self::METHOD_USER) && count($this->trackWords) > 0) {
                $requestParams['track'] = implode(',', $this->trackWords);
            }
            if (($this->method == self::METHOD_FILTER || $this->method == self::METHOD_SITE) && count($this->followIds) > 0) {
                $requestParams['follow'] = implode(',', $this->followIds);
            }
            if ($this->method == self::METHOD_FILTER && count($this->locationBoxes) > 0) {
                $requestParams['locations'] = implode(',', $this->locationBoxes);
            }
            if ($this->count != 0) {
                $requestParams['count'] = $this->count;
            }
            // Debugging is useful
            $this->log('Connecting to twitter stream: ' . $url . ' with params: ' . str_replace("\n", '', var_export($requestParams, TRUE)));
            /**
             * Open socket connection to make POST request. It'd be nice to use stream_context_create with the native
             * HTTP transport but it hides/abstracts too many required bits (like HTTP error responses).
             */
            $errNo = $errStr = NULL;
            $scheme = $urlParts['scheme'] == 'https' ? 'ssl://' : 'tcp://';
            $port = $urlParts['scheme'] == 'https' ? $this->secureHostPort : $this->hostPort;
            $this->log("Connecting to {$scheme}{$urlParts['host']}, port={$port}, connectTimeout={$this->connectTimeout}");
            @($this->conn = fsockopen($scheme . $urlParts['host'], $port, $errNo, $errStr, $this->connectTimeout));
            // No go - handle errors/backoff
            if (!$this->conn || !is_resource($this->conn)) {
                $this->lastErrorMsg = $errStr;
                $this->lastErrorNo = $errNo;
                $connectFailures++;
                if ($connectFailures > $this->connectFailuresMax) {
                    $msg = 'TCP failure limit exceeded with ' . $connectFailures . ' failures. Last error: ' . $errStr;
                    $this->log($msg, 'error');
                    throw new PhirehoseConnectLimitExceeded($msg, $errNo);
                    // Throw an exception for other code to handle
                }
                // Increase retry/backoff up to max
                $tcpRetry = $tcpRetry < $this->tcpBackoffMax ? $tcpRetry * 2 : $this->tcpBackoffMax;
                $this->log('TCP failure ' . $connectFailures . ' of ' . $this->connectFailuresMax . ' connecting to stream: ' . $errStr . ' (' . $errNo . '). Sleeping for ' . $tcpRetry . ' seconds.', 'info');
                sleep($tcpRetry);
                continue;
            }
            // TCP connect OK, clear last error (if present)
            $this->log('Connection established to ' . $urlParts['host']);
            $this->lastErrorMsg = NULL;
            $this->lastErrorNo = NULL;
            // If we have a socket connection, we can attempt a HTTP request - Ensure blocking read for the moment
            stream_set_blocking($this->conn, 1);
            // Encode request data
            $postData = http_build_query($requestParams, NULL, '&');
            $postData = str_replace('+', '%20', $postData);
            //Change it from RFC1738 to RFC3986 (see
            //enc_type parameter in http://php.net/http_build_query and note that enc_type is
            //not available as of php 5.3)
            $authCredentials = $this->getAuthorizationHeader($url, $requestParams);
            // Do it
            $s = "POST " . $urlParts['path'] . " HTTP/1.1\r\n";
            $s .= "Host: " . $urlParts['host'] . ':' . $port . "\r\n";
            $s .= "Connection: Close\r\n";
            $s .= "Content-type: application/x-www-form-urlencoded\r\n";
            $s .= "Content-length: " . strlen($postData) . "\r\n";
            $s .= "Accept: */*\r\n";
            $s .= 'Authorization: ' . $authCredentials . "\r\n";
            $s .= 'User-Agent: ' . $this->userAgent . "\r\n";
            $s .= "\r\n";
            $s .= $postData . "\r\n";
            $s .= "\r\n";
            fwrite($this->conn, $s);
            $this->log($s);
            // First line is response
            list($httpVer, $httpCode, $httpMessage) = preg_split('/\\s+/', trim(fgets($this->conn, 1024)), 3);
            // Response buffers
            $respHeaders = $respBody = '';
            $isChunking = false;
            // Consume each header response line until we get to body
            while ($hLine = trim(fgets($this->conn, 4096))) {
                $respHeaders .= $hLine . "\n";
                if (strtolower($hLine) == 'transfer-encoding: chunked') {
                    $isChunking = true;
                }
            }
            // If we got a non-200 response, we need to backoff and retry
            if ($httpCode != 200) {
                $connectFailures++;
                // Twitter will disconnect on error, but we want to consume the rest of the response body (which is useful)
                //TODO: this might be chunked too? In which case this contains some bad characters??
                while ($bLine = trim(fgets($this->conn, 4096))) {
                    $respBody .= $bLine;
                }
                // Construct error
                $errStr = 'HTTP ERROR ' . $httpCode . ': ' . $httpMessage . ' (' . $respBody . ')';
                // Set last error state
                $this->lastErrorMsg = $errStr;
                $this->lastErrorNo = $httpCode;
                // Have we exceeded maximum failures?
                if ($connectFailures > $this->connectFailuresMax) {
                    $msg = 'Connection failure limit exceeded with ' . $connectFailures . ' failures. Last error: ' . $errStr;
                    $this->log($msg, 'error');
                    throw new PhirehoseConnectLimitExceeded($msg, $httpCode);
                    // We eventually throw an exception for other code to handle
                }
                // Increase retry/backoff up to max
                $httpRetry = $httpRetry < $this->httpBackoffMax ? $httpRetry * 2 : $this->httpBackoffMax;
                $this->log('HTTP failure ' . $connectFailures . ' of ' . $this->connectFailuresMax . ' connecting to stream: ' . $errStr . '. Sleeping for ' . $httpRetry . ' seconds.', 'info');
                sleep($httpRetry);
                continue;
            } else {
                if (!$isChunking) {
                    throw new Exception("Twitter did not send a chunking header. Is this really HTTP/1.1? Here are headers:\n{$respHeaders}");
                }
                //TODO: rather crude!
            }
            // Loop until connected OK
        } while (!is_resource($this->conn) || $httpCode != 200);
        // Connected OK, reset connect failures
        $connectFailures = 0;
        $this->lastErrorMsg = NULL;
        $this->lastErrorNo = NULL;
        // Switch to non-blocking to consume the stream (important)
        stream_set_blocking($this->conn, 0);
        // Connect always causes the filterChanged status to be cleared
        $this->filterChanged = FALSE;
        // Flush stream buffer & (re)assign fdrPool (for reconnect)
        $this->fdrPool = array($this->conn);
        $this->buff = '';
    }