/** * Perform a request to the server and return the result * * Perform a request to the server and return the result converted into a * phpillowResponse object. If you do not expect a JSON structure, which * could be converted in such a response object, set the forth parameter to * true, and you get a response object retuerned, containing the raw body. * * @param string $method * @param string $path * @param string $data * @return phpillowResponse */ protected function request($method, $path, $data, $raw = false) { $basicAuth = ''; if ($this->options['username']) { $basicAuth .= "{$this->options['username']}:{$this->options['password']}@"; } $url = 'http://' . $basicAuth . $this->options['host'] . ':' . $this->options['port'] . $path; $httpFilePointer = @fopen($url = 'http://' . $this->options['host'] . ':' . $this->options['port'] . $path, 'r', false, stream_context_create(array('http' => array('method' => $method, 'content' => $data, 'ignore_errors' => true, 'user_agent' => 'PHPillow arbit-0.5-alpha', 'timeout' => $this->options['timeout'], 'header' => 'Content-type: application/json')))); // Check if connection has been established successfully if ($httpFilePointer === false) { $error = error_get_last(); throw new phpillowConnectionException("Could not connect to server at %ip:%port: %error", array('ip' => $this->options['ip'], 'port' => $this->options['port'], 'error' => $error['message'])); } // Read request body $body = ''; while (!feof($httpFilePointer)) { $body .= fgets($httpFilePointer); } $metaData = stream_get_meta_data($httpFilePointer); // @TODO: This seems to have changed in last CVS versions of PHP 5.3, // should be removeable, once there is a next release of PHP 5.3 $rawHeaders = isset($metaData['wrapper_data']['headers']) ? $metaData['wrapper_data']['headers'] : $metaData['wrapper_data']; $headers = array(); foreach ($rawHeaders as $lineContent) { // Extract header values if (preg_match('(^HTTP/(?P<version>\\d+\\.\\d+)\\s+(?P<status>\\d+))S', $lineContent, $match)) { $headers['version'] = $match['version']; $headers['status'] = (int) $match['status']; } else { list($key, $value) = explode(':', $lineContent, 2); $headers[strtolower($key)] = ltrim($value); } } // If requested log response information to http log if ($this->options['http-log'] !== false) { file_put_contents($this->options['http-log'], sprintf("Requested: %s\n\n%s\n\n%s\n\n", $url, implode("\n", $rawHeaders), $body)); } // Create repsonse object from couch db response return phpillowResponseFactory::parse($headers, $body, $raw); }
/** * Perform a request to the server and return the result * * Perform a request to the server and return the result converted into a * phpillowResponse object. If you do not expect a JSON structure, which * could be converted in such a response object, set the forth parameter to * true, and you get a response object returned, containing the raw body. * * @param string $method * @param string $path * @param string $data * @param bool $raw * @return phpillowResponse */ protected function request($method, $path, $data, $raw = false) { // Try establishing the connection to the server $this->checkConnection(); // Send the build request to the server if (fwrite($this->connection, $request = $this->buildRequest($method, $path, $data)) === false) { // Reestablish which seems to have been aborted // // The recursion in this method might be problematic if the // connection establishing mechanism does not correctly throw an // exception on failure. $this->connection = null; return $this->request($method, $path, $data, $raw); } // If requested log request information to http log if ($this->options['http-log'] !== false) { $fp = fopen($this->options['http-log'], 'a'); fwrite($fp, "\n\n" . $request); } // Read server response headers $rawHeaders = ''; $headers = array('connection' => $this->options['keep-alive'] ? 'Keep-Alive' : 'Close'); // Remove leading newlines, should not occur at all, actually. while (($line = fgets($this->connection)) !== false && ($lineContent = rtrim($line)) === '') { } // Throw exception, if connection has been aborted by the server, and // leave handling to the user for now. if ($line === false) { // Reestablish which seems to have been aborted // // The recursion in this method might be problematic if the // connection establishing mechanism does not correctly throw an // exception on failure. // // An aborted connection seems to happen here on long running // requests, which cause a connection timeout at server side. $this->connection = null; return $this->request($method, $path, $data, $raw); } do { // Also store raw headers for later logging $rawHeaders .= $lineContent . "\n"; // Extract header values if (preg_match('(^HTTP/(?P<version>\\d+\\.\\d+)\\s+(?P<status>\\d+))S', $lineContent, $match)) { $headers['version'] = $match['version']; $headers['status'] = (int) $match['status']; } else { list($key, $value) = explode(':', $lineContent, 2); $headers[strtolower($key)] = ltrim($value); } } while (($line = fgets($this->connection)) !== false && ($lineContent = rtrim($line)) !== ''); // Read response body $body = ''; if (!isset($headers['transfer-encoding']) || $headers['transfer-encoding'] !== 'chunked') { // HTTP 1.1 supports chunked transfer encoding, if the according // header is not set, just read the specified amount of bytes. $bytesToRead = (int) (isset($headers['content-length']) ? $headers['content-length'] : 0); // Read body only as specified by chunk sizes, everything else // are just footnotes, which are not relevant for us. while ($bytesToRead > 0) { $body .= $read = fgets($this->connection, $bytesToRead + 1); $bytesToRead -= strlen($read); } } else { // When transfer-encoding=chunked has been specified in the // response headers, read all chunks and sum them up to the body, // until the server has finished. Ignore all additional HTTP // options after that. do { $line = rtrim(fgets($this->connection)); // Get bytes to read, with option appending comment if (preg_match('(^([0-9a-f]+)(?:;.*)?$)', $line, $match)) { $bytesToRead = hexdec($match[1]); // Read body only as specified by chunk sizes, everything else // are just footnotes, which are not relevant for us. $bytesLeft = $bytesToRead; while ($bytesLeft > 0) { $body .= $read = fread($this->connection, $bytesLeft + 2); $bytesLeft -= strlen($read); } } } while ($bytesToRead > 0); // Chop off \r\n from the end. $body = substr($body, 0, -2); } // Reset the connection if the server asks for it. if ($headers['connection'] !== 'Keep-Alive') { fclose($this->connection); $this->connection = null; } // If requested log response information to http log if ($this->options['http-log'] !== false) { fwrite($fp, "\n" . $rawHeaders . "\n" . $body . "\n"); fclose($fp); } // Handle some response state as special cases switch ($headers['status']) { case 301: case 302: case 303: case 307: $path = parse_url($headers['location'], PHP_URL_PATH); return $this->request('GET', $path, $data, $raw); } // Create response object from couch db response return phpillowResponseFactory::parse($headers, $body, $raw); }