/** * Check for server connection * * Checks if the connection already has been established, or tries to * establish the connection, if not done yet. * * @return void */ protected function checkConnection() { // If the connection could not be established, fsockopen sadly does not // only return false (as documented), but also always issues a warning. if ($this->connection === null && ($this->connection = @fsockopen($this->options['ip'], $this->options['port'], $errno, $errstr)) === false) { // This is a bit hackisch... $this->connection = null; throw HTTPException::connectionFailure($this->options['ip'], $this->options['port'], $errstr, $errno); } }
/** * Perform a request to the server and return the result * * Perform a request to the server and return the result converted into a * Response 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 * @param bool $raw * @return Response * @throws HTTPException */ public function request($method, $path, $data = null, $raw = false) { $basicAuth = ''; if ($this->options['username']) { $basicAuth .= "{$this->options['username']}:{$this->options['password']}@"; } // TODO SSL support? $httpFilePointer = @fopen('http://' . $basicAuth . $this->options['host'] . ':' . $this->options['port'] . $path, 'r', false, stream_context_create(array('http' => array('method' => $method, 'content' => $data, 'ignore_errors' => true, 'max_redirects' => 0, 'user_agent' => 'Doctrine CouchDB ODM $Revision$', 'timeout' => $this->options['timeout'], 'header' => 'Content-type: application/json')))); // Check if connection has been established successfully if ($httpFilePointer === false) { $error = error_get_last(); throw HTTPException::connectionFailure($this->options['ip'], $this->options['port'], $error['message'], 0); } // Read request body $body = ''; while (!feof($httpFilePointer)) { $body .= fgets($httpFilePointer); } $metaData = stream_get_meta_data($httpFilePointer); // The structure of this array differs depending on PHP compiled with // --enable-curlwrappers or not. Both cases are normally required. $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 (empty($headers['status'])) { throw HTTPException::readFailure($this->options['ip'], $this->options['port'], 'Received an empty response or not status code', 0); } // Create repsonse object from couch db response if ($headers['status'] >= 400) { return new ErrorResponse($headers['status'], $headers, $body); } return new Response($headers['status'], $headers, $body, $raw); }
/** * Check for server connection * * Checks if the connection already has been established, or tries to * establish the connection, if not done yet. * * @return void * @throws HTTPException */ protected function checkConnection() { // Setting Connection scheme according ssl support if ($this->options['ssl']) { if (!extension_loaded('openssl')) { // no openssl extension loaded. // This is a bit hackisch... $this->connection = null; throw HTTPException::connectionFailure($this->options['ip'], $this->options['port'], "ssl activated without openssl extension loaded", 0); } $host = 'ssl://' . $this->options['host']; } else { $host = $this->options['ip']; } // If the connection could not be established, fsockopen sadly does not // only return false (as documented), but also always issues a warning. if ($this->connection === null && ($this->connection = @fsockopen($host, $this->options['port'], $errno, $errstr)) === false) { // This is a bit hackisch... $this->connection = null; throw HTTPException::connectionFailure($this->options['ip'], $this->options['port'], $errstr, $errno); } }
/** * Sets up the stream connection. * * @param $method * @param $path * @param $data * @param $headers * @throws HTTPException */ protected function checkConnection($method, $path, $data, $headers) { $basicAuth = ''; if ($this->options['username']) { $basicAuth .= "{$this->options['username']}:{$this->options['password']}@"; } if (!isset($headers['Content-Type'])) { $headers['Content-Type'] = 'application/json'; } $stringHeader = ''; if ($headers != null) { foreach ($headers as $key => $val) { $stringHeader .= $key . ": " . $val . "\r\n"; } } if ($this->httpFilePointer == null) { // TODO SSL support? $this->httpFilePointer = @fopen('http://' . $basicAuth . $this->options['host'] . ':' . $this->options['port'] . $path, 'r', false, stream_context_create(array('http' => array('method' => $method, 'content' => $data, 'ignore_errors' => true, 'max_redirects' => 0, 'user_agent' => 'Doctrine CouchDB ODM $Revision$', 'timeout' => $this->options['timeout'], 'header' => $stringHeader)))); } // Check if connection has been established successfully. if ($this->httpFilePointer === false) { $error = error_get_last(); throw HTTPException::connectionFailure($this->options['ip'], $this->options['port'], $error['message'], 0); } }
/** * Reads multipart data from sourceConnection and streams it to the * targetConnection.Returns the body of the request or the status code in * case there is no body. * * @param $method * @param $path * @param $streamEnd * @param array $requestHeaders * @return mixed|string * @throws \Exception * @throws \HTTPException */ protected function sendStream($method, $path, $streamEnd, $requestHeaders = array()) { $dataStream = $this->sourceConnection; // Read the json doc. Use _attachments field to find the total // Content-Length and create the request header with initial doc data. // At present CouchDB can't handle chunked data and needs // Content-Length header. $str = ''; $jsonFlag = 0; $attachmentCount = 0; $totalAttachmentLength = 0; $streamLine = $this->getNextLineFromSourceConnection(); while ($jsonFlag == 0 || $jsonFlag == 1 && trim($streamLine) == '') { $str .= $streamLine; if (strpos($streamLine, 'Content-Type: application/json') !== false) { $jsonFlag = 1; } $streamLine = $this->getNextLineFromSourceConnection(); } $docBoundaryLength = strlen(explode('=', $requestHeaders['Content-Type'], 2)[1]) + 2; $json = json_decode($streamLine, true); foreach ($json['_attachments'] as $docName => $metaData) { // Quotes and a "/r/n" $totalAttachmentLength += strlen('Content-Disposition: attachment; filename=') + strlen($docName) + 4; $totalAttachmentLength += strlen('Content-Type: ') + strlen($metaData['content_type']) + 2; $totalAttachmentLength += strlen('Content-Length: '); if (isset($metaData['encoding'])) { $totalAttachmentLength += $metaData['encoded_length'] + strlen($metaData['encoded_length']) + 2; $totalAttachmentLength += strlen('Content-Encoding: ') + strlen($metaData['encoding']) + 2; } else { $totalAttachmentLength += $metaData['length'] + strlen($metaData['length']) + 2; } $totalAttachmentLength += 2; $attachmentCount++; } // Add Content-Length to the headers. $requestHeaders['Content-Length'] = strlen($str) + strlen($streamLine) + $totalAttachmentLength + $attachmentCount * (2 + $docBoundaryLength) + $docBoundaryLength + 2; if ($this->targetConnection == null) { $this->targetConnection = $this->targetClient->getConnection($method, $path, null, $requestHeaders); } // Write the initial body data. fwrite($this->targetConnection, $str); // Write the rest of the data including attachments line by line or in // chunks. while (!feof($dataStream) && ($streamEnd === null || strpos($streamLine, $streamEnd) === false)) { $totalSent = 0; $length = strlen($streamLine); while ($totalSent != $length) { $sent = fwrite($this->targetConnection, substr($streamLine, $totalSent)); if ($sent === false) { throw new \HTTPException('Stream write error.'); } else { $totalSent += $sent; } } // Use maxLength while reading the data as there may be no newlines // in the binary and compressed attachments, or the lines may be // very long. $streamLine = $this->getNextLineFromSourceConnection(100000); } // Read response headers $rawHeaders = ''; $headers = array('connection' => $this->targetClient->getOptions()['keep-alive'] ? 'Keep-Alive' : 'Close'); // Remove leading newlines, should not occur at all, actually. while (($line = fgets($this->targetConnection)) !== 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) { // sendStream can't be called in recursion as the source stream can be // read only once. $error = error_get_last(); throw HTTPException::connectionFailure($this->targetClient->getOptions()['ip'], $this->targetClient->getOptions()['port'], $error['message'], 0); } 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->targetConnection)) !== false && ($lineContent = rtrim($line)) !== ''); // Read response body $body = ''; // 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->targetConnection, $bytesToRead + 1); $bytesToRead -= strlen($read); } // Reset the connection if the server asks for it. if ($headers['connection'] !== 'Keep-Alive') { fclose($this->targetConnection); $this->targetConnection = null; } // Handle some response state as special cases switch ($headers['status']) { case 301: case 302: case 303: case 307: // Temporary redirect. // sendStream can't be called in recursion as the source stream can be // read only once. throw HTTPException::fromResponse($path, new Response($headers['status'], $headers, $body)); } return $body != '' ? json_decode($body, true) : array("status" => $headers['status']); }