public function testEmitsEvents() { $request = new EntityEnclosingRequest('PUT', 'http://www.example.com'); $request->setBody('foo'); $request->setResponse(new Response(200)); // Ensure that IO events are emitted $request->getCurlOptions()->set('emit_io', true); // Attach listeners for each event type $request->getEventDispatcher()->addListener('curl.callback.progress', array($this, 'event')); $request->getEventDispatcher()->addListener('curl.callback.read', array($this, 'event')); $request->getEventDispatcher()->addListener('curl.callback.write', array($this, 'event')); $mediator = new RequestMediator($request, true); $mediator->progress('a', 'b', 'c', 'd'); $this->assertEquals(1, count($this->events)); $this->assertEquals('curl.callback.progress', $this->events[0]->getName()); $this->assertEquals(3, $mediator->writeResponseBody('foo', 'bar')); $this->assertEquals(2, count($this->events)); $this->assertEquals('curl.callback.write', $this->events[1]->getName()); $this->assertEquals('bar', $this->events[1]['write']); $this->assertSame($request, $this->events[1]['request']); $this->assertEquals('foo', $mediator->readRequestBody('a', 'b', 3)); $this->assertEquals(3, count($this->events)); $this->assertEquals('curl.callback.read', $this->events[2]->getName()); $this->assertEquals('foo', $this->events[2]['read']); $this->assertSame($request, $this->events[2]['request']); }
public function testSetsCurlHandleParameter() { $request = new EntityEnclosingRequest('PUT', 'http://www.example.com'); $mediator = new RequestMediator($request); $handle = $this->getMockBuilder('Guzzle\\Http\\Curl\\CurlHandle')->disableOriginalConstructor()->getMock(); $mediator->setCurlHandle($handle); $this->assertSame($handle, $request->getParams()->get('curl_handle')); }
/** * 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. The * - progress: Set to true to enable progress function callbacks. Most * users do not need this, so it has been disabled by default. * * @param RequestInterface $request Request * * @return CurlHandle */ public static function factory(RequestInterface $request) { $mediator = new RequestMediator($request); $requestCurlOptions = $request->getCurlOptions(); $tempContentLength = null; $method = $request->getMethod(); $client = $request->getClient(); // Array of default cURL options. $curlOptions = array(CURLOPT_URL => $request->getUrl(), CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_RETURNTRANSFER => false, CURLOPT_HEADER => false, CURLOPT_USERAGENT => (string) $request->getHeader('User-Agent'), CURLOPT_ENCODING => '', CURLOPT_PORT => $request->getPort(), CURLOPT_HTTPHEADER => array(), CURLOPT_HEADERFUNCTION => array($mediator, 'receiveResponseHeader'), CURLOPT_HTTP_VERSION => $request->getProtocolVersion() === '1.0' ? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1); // Enable the progress function if the 'progress' param was set if ($requestCurlOptions->get('progress')) { $curlOptions[CURLOPT_PROGRESSFUNCTION] = array($mediator, 'progress'); $curlOptions[CURLOPT_NOPROGRESS] = false; } // 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; } // HEAD requests need no response body, everything else might if ($method != 'HEAD') { $curlOptions[CURLOPT_WRITEFUNCTION] = array($mediator, 'writeResponseBody'); } // Account for PHP installations with safe_mode or open_basedir enabled // @codeCoverageIgnoreStart if (CurlVersion::getInstance()->get('follow_location')) { $curlOptions[CURLOPT_FOLLOWLOCATION] = true; $curlOptions[CURLOPT_MAXREDIRS] = 5; } // @codeCoverageIgnoreEnd // Specify settings according to the HTTP method switch ($method) { case 'GET': $curlOptions[CURLOPT_HTTPGET] = true; break; case 'HEAD': $curlOptions[CURLOPT_NOBODY] = true; break; case 'POST': $curlOptions[CURLOPT_POST] = true; // Special handling for POST specific fields and files if (count($request->getPostFiles())) { $fields = $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; $fields[$fieldKey] = $file->getCurlString(); } } $curlOptions[CURLOPT_POSTFIELDS] = $fields; $request->removeHeader('Content-Length'); } elseif (count($request->getPostFields())) { $curlOptions[CURLOPT_POSTFIELDS] = (string) $request->getPostFields()->useUrlEncoding(true); $request->removeHeader('Content-Length'); } elseif (!$request->getBody()) { // Need to remove CURLOPT_POST to prevent chunked encoding for an empty POST unset($curlOptions[CURLOPT_POST]); $curlOptions[CURLOPT_CUSTOMREQUEST] = 'POST'; } break; case 'PUT': case 'PATCH': case 'DELETE': $curlOptions[CURLOPT_UPLOAD] = true; if ($method != 'PUT') { $curlOptions[CURLOPT_CUSTOMREQUEST] = $method; } // Let cURL handle setting the Content-Length header $contentLength = $request->getHeader('Content-Length'); if ($contentLength !== null) { $contentLength = (int) (string) $contentLength; $curlOptions[CURLOPT_INFILESIZE] = $contentLength; $tempContentLength = $contentLength; $request->removeHeader('Content-Length'); } break; default: $curlOptions[CURLOPT_CUSTOMREQUEST] = $method; } // Special handling for requests sending raw data if ($request instanceof EntityEnclosingRequestInterface) { if ($request->getBody()) { // 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); } // If the Expect header is not present, prevent curl from adding it if (!$request->hasHeader('Expect')) { $curlOptions[CURLOPT_HTTPHEADER][] = 'Expect:'; } } // Set custom cURL options foreach ($requestCurlOptions as $key => $value) { if (is_numeric($key)) { $curlOptions[$key] = $value; } } // Check if any headers or cURL options are blacklisted if ($client && ($blacklist = $client->getConfig('curl.blacklist'))) { foreach ($blacklist as $value) { if (strpos($value, 'header.') !== 0) { unset($curlOptions[$value]); } else { // Remove headers that may have previously been set but are supposed to be blacklisted $key = substr($value, 7); $request->removeHeader($key); $curlOptions[CURLOPT_HTTPHEADER][] = $key . ':'; } } } // 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; } // Apply the options to a new cURL handle. $handle = curl_init(); curl_setopt_array($handle, $curlOptions); $request->getParams()->set('curl.last_options', $curlOptions); if ($tempContentLength) { $request->setHeader('Content-Length', $tempContentLength); } $handle = new static($handle, $curlOptions); $mediator->setCurlHandle($handle); return $handle; }
/** * 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); // Array of default cURL options. $curlOptions = array(CURLOPT_URL => $request->getUrl(), 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 the progress function if the 'progress' param was set if ($requestCurlOptions->get('progress')) { $curlOptions[CURLOPT_PROGRESSFUNCTION] = array($mediator, 'progress'); $curlOptions[CURLOPT_NOPROGRESS] = false; } // 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->getAll() 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:'; } // Check if any headers or cURL options are blacklisted if ($blacklist = $requestCurlOptions->get('blacklist')) { foreach ($blacklist as $value) { if (strpos($value, 'header.') !== 0) { unset($curlOptions[$value]); } else { // Remove headers that may have previously been set but are supposed to be blacklisted $key = substr($value, 7); $request->removeHeader($key); $curlOptions[CURLOPT_HTTPHEADER][] = $key . ':'; } } } // 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; } // Apply the options to a new cURL handle. $handle = curl_init(); curl_setopt_array($handle, $curlOptions); if ($tempContentLength) { $request->setHeader('Content-Length', $tempContentLength); } $handle = new static($handle, $curlOptions); $mediator->setCurlHandle($handle); return $handle; }