/** * Read data from the request body and send it to curl * * @param resource $ch Curl handle * @param resource $fd File descriptor * @param int $length Amount of data to read * * @return string */ public function readRequestBody($ch, $fd, $length) { if (!($body = $this->request->getBody())) { return ''; } $read = (string) $body->read($length); if ($this->emitIo) { $this->request->dispatch('curl.callback.read', array('request' => $this->request, 'read' => $read)); } return $read; }
/** * Get the payload part of a signature from a request. * * @param RequestInterface $request * * @return string */ protected function getPayload(RequestInterface $request) { // Calculate the request signature payload if ($request->hasHeader('x-amz-content-sha256')) { // Handle streaming operations (e.g. Glacier.UploadArchive) return (string) $request->getHeader('x-amz-content-sha256'); } if ($request instanceof EntityEnclosingRequestInterface) { return hash('sha256', $request->getMethod() == 'POST' && count($request->getPostFields()) ? (string) $request->getPostFields() : (string) $request->getBody()); } return self::DEFAULT_PAYLOAD; }
/** * Factory method to create a new curl handle based on an HTTP request. * * There are some helpful options you can set to enable specific behavior: * - debug: Set to true to enable cURL debug functionality to track the actual headers sent over the wire. * - progress: Set to true to enable progress function callbacks. * * @param RequestInterface $request Request * * @return CurlHandle * @throws RuntimeException */ public static function factory(RequestInterface $request) { $requestCurlOptions = $request->getCurlOptions(); $mediator = new RequestMediator($request, $requestCurlOptions->get('emit_io')); $tempContentLength = null; $method = $request->getMethod(); $bodyAsString = $requestCurlOptions->get(self::BODY_AS_STRING); // Prepare url $url = (string) $request->getUrl(); if (($pos = strpos($url, '#')) !== false) { // strip fragment from url $url = substr($url, 0, $pos); } // Array of default cURL options. $curlOptions = array(CURLOPT_URL => $url, CURLOPT_CONNECTTIMEOUT => 150, CURLOPT_RETURNTRANSFER => false, CURLOPT_HEADER => false, CURLOPT_PORT => $request->getPort(), CURLOPT_HTTPHEADER => array(), CURLOPT_WRITEFUNCTION => array($mediator, 'writeResponseBody'), CURLOPT_HEADERFUNCTION => array($mediator, 'receiveResponseHeader'), CURLOPT_HTTP_VERSION => $request->getProtocolVersion() === '1.0' ? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1, CURLOPT_SSL_VERIFYPEER => 1, CURLOPT_SSL_VERIFYHOST => 2); if (defined('CURLOPT_PROTOCOLS')) { // Allow only HTTP and HTTPS protocols $curlOptions[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; } // Add CURLOPT_ENCODING if Accept-Encoding header is provided if ($acceptEncodingHeader = $request->getHeader('Accept-Encoding')) { $curlOptions[CURLOPT_ENCODING] = (string) $acceptEncodingHeader; // Let cURL set the Accept-Encoding header, prevents duplicate values $request->removeHeader('Accept-Encoding'); } // Enable curl debug information if the 'debug' param was set if ($requestCurlOptions->get('debug')) { $curlOptions[CURLOPT_STDERR] = fopen('php://temp', 'r+'); // @codeCoverageIgnoreStart if (false === $curlOptions[CURLOPT_STDERR]) { throw new RuntimeException('Unable to create a stream for CURLOPT_STDERR'); } // @codeCoverageIgnoreEnd $curlOptions[CURLOPT_VERBOSE] = true; } // Specify settings according to the HTTP method if ($method == 'GET') { $curlOptions[CURLOPT_HTTPGET] = true; } elseif ($method == 'HEAD') { $curlOptions[CURLOPT_NOBODY] = true; // HEAD requests do not use a write function unset($curlOptions[CURLOPT_WRITEFUNCTION]); } elseif (!$request instanceof EntityEnclosingRequest) { $curlOptions[CURLOPT_CUSTOMREQUEST] = $method; } else { $curlOptions[CURLOPT_CUSTOMREQUEST] = $method; // Handle sending raw bodies in a request if ($request->getBody()) { // You can send the body as a string using curl's CURLOPT_POSTFIELDS if ($bodyAsString) { $curlOptions[CURLOPT_POSTFIELDS] = (string) $request->getBody(); // Allow curl to add the Content-Length for us to account for the times when // POST redirects are followed by GET requests if ($tempContentLength = $request->getHeader('Content-Length')) { $tempContentLength = (int) (string) $tempContentLength; } // Remove the curl generated Content-Type header if none was set manually if (!$request->hasHeader('Content-Type')) { $curlOptions[CURLOPT_HTTPHEADER][] = 'Content-Type:'; } } else { $curlOptions[CURLOPT_UPLOAD] = true; // Let cURL handle setting the Content-Length header if ($tempContentLength = $request->getHeader('Content-Length')) { $tempContentLength = (int) (string) $tempContentLength; $curlOptions[CURLOPT_INFILESIZE] = $tempContentLength; } // Add a callback for curl to read data to send with the request only if a body was specified $curlOptions[CURLOPT_READFUNCTION] = array($mediator, 'readRequestBody'); // Attempt to seek to the start of the stream $request->getBody()->seek(0); } } else { // Special handling for POST specific fields and files $postFields = false; if (count($request->getPostFiles())) { $postFields = $request->getPostFields()->useUrlEncoding(false)->urlEncode(); foreach ($request->getPostFiles() as $key => $data) { $prefixKeys = count($data) > 1; foreach ($data as $index => $file) { // Allow multiple files in the same key $fieldKey = $prefixKeys ? "{$key}[{$index}]" : $key; $postFields[$fieldKey] = $file->getCurlValue(); } } } elseif (count($request->getPostFields())) { $postFields = (string) $request->getPostFields()->useUrlEncoding(true); } if ($postFields !== false) { if ($method == 'POST') { unset($curlOptions[CURLOPT_CUSTOMREQUEST]); $curlOptions[CURLOPT_POST] = true; } $curlOptions[CURLOPT_POSTFIELDS] = $postFields; $request->removeHeader('Content-Length'); } } // If the Expect header is not present, prevent curl from adding it if (!$request->hasHeader('Expect')) { $curlOptions[CURLOPT_HTTPHEADER][] = 'Expect:'; } } // If a Content-Length header was specified but we want to allow curl to set one for us if (null !== $tempContentLength) { $request->removeHeader('Content-Length'); } // Set custom cURL options foreach ($requestCurlOptions->toArray() as $key => $value) { if (is_numeric($key)) { $curlOptions[$key] = $value; } } // Do not set an Accept header by default if (!isset($curlOptions[CURLOPT_ENCODING])) { $curlOptions[CURLOPT_HTTPHEADER][] = 'Accept:'; } // Add any custom headers to the request. Empty headers will cause curl to not send the header at all. foreach ($request->getHeaderLines() as $line) { $curlOptions[CURLOPT_HTTPHEADER][] = $line; } // Add the content-length header back if it was temporarily removed if ($tempContentLength) { $request->setHeader('Content-Length', $tempContentLength); } // Apply the options to a new cURL handle. $handle = curl_init(); // Enable the progress function if the 'progress' param was set if ($requestCurlOptions->get('progress')) { // Wrap the function in a function that provides the curl handle to the mediator's progress function // Using this rather than injecting the handle into the mediator prevents a circular reference $curlOptions[CURLOPT_PROGRESSFUNCTION] = function () use($mediator, $handle) { $args = func_get_args(); $args[] = $handle; // PHP 5.5 pushed the handle onto the start of the args if (is_resource($args[0])) { array_shift($args); } call_user_func_array(array($mediator, 'progress'), $args); }; $curlOptions[CURLOPT_NOPROGRESS] = false; } curl_setopt_array($handle, $curlOptions); return new static($handle, $curlOptions); }
/** * Add body (content) specific options to the context options * * @param RequestInterface $request */ protected function addBodyOptions(RequestInterface $request) { // Add the content for the request if needed if (!$request instanceof EntityEnclosingRequestInterface) { return; } if (count($request->getPostFields())) { $this->setContextValue('http', 'content', (string) $request->getPostFields(), true); } elseif ($request->getBody()) { $this->setContextValue('http', 'content', (string) $request->getBody(), true); } // Always ensure a content-length header is sent if (isset($this->contextOptions['http']['content'])) { $headers = isset($this->contextOptions['http']['header']) ? $this->contextOptions['http']['header'] : array(); $headers[] = 'Content-Length: ' . strlen($this->contextOptions['http']['content']); $this->setContextValue('http', 'header', $headers, true); } }
/** * Returns a formatted message * * @param RequestInterface $request Request that was sent * @param Response $response Response that was received * @param CurlHandle $handle Curl handle associated with the message * @param array $customData Associative array of custom template data * * @return string */ public function format(RequestInterface $request, Response $response = null, CurlHandle $handle = null, array $customData = array()) { $cache = $customData; return preg_replace_callback('/{\\s*([A-Za-z_\\-\\.0-9]+)\\s*}/', function (array $matches) use($request, $response, $handle, &$cache) { if (array_key_exists($matches[1], $cache)) { return $cache[$matches[1]]; } $result = ''; switch ($matches[1]) { case 'request': $result = (string) $request; break; case 'response': $result = (string) $response; break; case 'req_body': $result = $request instanceof EntityEnclosingRequestInterface ? (string) $request->getBody() : ''; break; case 'res_body': $result = $response ? $response->getBody(true) : ''; break; case 'ts': $result = gmdate('c'); break; case 'method': $result = $request->getMethod(); break; case 'url': $result = (string) $request->getUrl(); break; case 'resource': $result = $request->getResource(); break; case 'protocol': $result = 'HTTP'; break; case 'version': $result = $request->getProtocolVersion(); break; case 'host': $result = $request->getHost(); break; case 'hostname': $result = gethostname(); break; case 'port': $result = $request->getPort(); break; case 'code': $result = $response ? $response->getStatusCode() : ''; break; case 'phrase': $result = $response ? $response->getReasonPhrase() : ''; break; case 'connect_time': $result = $handle && $handle->getInfo(CURLINFO_CONNECT_TIME) ? $handle->getInfo(CURLINFO_CONNECT_TIME) : ($response ? $response->getInfo('connect_time') : ''); break; case 'total_time': $result = $handle && $handle->getInfo(CURLINFO_TOTAL_TIME) ? $handle->getInfo(CURLINFO_TOTAL_TIME) : ($response ? $response->getInfo('total_time') : ''); break; case 'curl_error': $result = $handle ? $handle->getError() : ''; break; case 'curl_code': $result = $handle ? $handle->getErrorNo() : ''; break; case 'curl_stderr': $result = $handle ? $handle->getStderr() : ''; break; default: if (strpos($matches[1], 'req_header_') === 0) { $result = $request->getHeader(substr($matches[1], 11)); } elseif ($response && strpos($matches[1], 'res_header_') === 0) { $result = $response->getHeader(substr($matches[1], 11)); } } $cache[$matches[1]] = $result; return $result; }, $this->template); }
/** * Clone a request while changing the method. Emulates the behavior of * {@see Guzzle\Http\Message\Request::clone}, but can change the HTTP method. * * @param RequestInterface $request Request to clone * @param string $method Method to set * * @return RequestInterface */ public function cloneRequestWithMethod(RequestInterface $request, $method) { // Create the request with the same client if possible if ($request->getClient()) { $cloned = $request->getClient()->createRequest($method, $request->getUrl(), $request->getHeaders()); } else { $cloned = $this->create($method, $request->getUrl(), $request->getHeaders()); } $cloned->getCurlOptions()->replace($request->getCurlOptions()->toArray()); $cloned->setEventDispatcher(clone $request->getEventDispatcher()); // Ensure that that the Content-Length header is not copied if changing to GET or HEAD if (!$cloned instanceof EntityEnclosingRequestInterface) { $cloned->removeHeader('Content-Length'); } elseif ($request instanceof EntityEnclosingRequestInterface) { $cloned->setBody($request->getBody()); } $cloned->getParams()->replace($request->getParams()->toArray()); $cloned->dispatch('request.clone', array('request' => $cloned)); return $cloned; }
/** * @link https://github.com/guzzle/guzzle/issues/710 */ private function validateResponseWasSet(RequestInterface $request) { if ($request->getResponse()) { return true; } $body = $request instanceof EntityEnclosingRequestInterface ? $request->getBody() : null; if (!$body) { $rex = new RequestException('No response was received for a request with no body. This' . ' could mean that you are saturating your network.'); $rex->setRequest($request); $this->removeErroredRequest($request, $rex); } elseif (!$body->isSeekable() || !$body->seek(0)) { // Nothing we can do with this. Sorry! $rex = new RequestException('The connection was unexpectedly closed. The request would' . ' have been retried, but attempting to rewind the' . ' request body failed.'); $rex->setRequest($request); $this->removeErroredRequest($request, $rex); } else { $this->remove($request); // Add the request back to the batch to retry automatically. $this->requests[] = $request; $this->addHandle($request); } return false; }