/** * Intercept the exception and inject a response * * @param ResponseInterface $response Response to set */ public function intercept(ResponseInterface $response) { $this->stopPropagation(); $this->getTransaction()->setResponse($response); $this->exception->setThrowImmediately(false); RequestEvents::emitComplete($this->getTransaction()); }
/** * Receive a response header from curl * * @param resource $curl Curl handle * @param string $header Received header * * @return int */ public function receiveResponseHeader($curl, $header) { static $normalize = array("\r", "\n"); $length = strlen($header); $header = str_replace($normalize, '', $header); if (strpos($header, 'HTTP/') === 0) { $startLine = explode(' ', $header, 3); // Only download the body to a target body when a successful // response is received. if ($startLine[1][0] != '2') { $this->body = null; } $this->statusCode = $startLine[1]; $this->reasonPhrase = isset($startLine[2]) ? $startLine[2] : null; $this->protocolVersion = substr($startLine[0], -3); $this->headers = array(); } elseif ($pos = strpos($header, ':')) { $this->headers[substr($header, 0, $pos)][] = substr($header, $pos + 1); } elseif ($header == '' && $this->statusCode >= 200) { $response = $this->messageFactory->createResponse($this->statusCode, $this->headers, $this->body, array('protocol_version' => $this->protocolVersion, 'reason_phrase' => $this->reasonPhrase)); $this->headers = $this->body = null; $this->transaction->setResponse($response); // Allows events to react before downloading any of the body RequestEvents::emitHeaders($this->transaction); } return $length; }
private function createResponseObject(RequestInterface $request, array $headers, TransactionInterface $transaction, StreamInterface $stream) { $parts = explode(' ', array_shift($headers), 3); $options = ['protocol_version' => substr($parts[0], -3)]; if (isset($parts[2])) { $options['reason_phrase'] = $parts[2]; } $response = $this->messageFactory->createResponse($parts[1], $this->headersFromLines($headers), null, $options); // Automatically decode responses when instructed. if ($request->getConfig()->get('decode_content')) { switch ($response->getHeader('Content-Encoding')) { case 'gzip': case 'deflate': $stream = new InflateStream($stream); break; } } // Drain the stream immediately if 'stream' was not enabled. if (!$request->getConfig()['stream']) { $stream = $this->getSaveToBody($request, $stream); } $response->setBody($stream); $transaction->setResponse($response); RequestEvents::emitHeaders($transaction); return $response; }
/** * Transfers an HTTP request and populates a response * * @param TransactionInterface $transaction Transaction abject to populate * * @return ResponseInterface */ public function send(TransactionInterface $transaction) { // HTTP/1.1 streams using the PHP stream wrapper require a // Connection: close header. Setting here so that it is added before // emitting the request.before_send event. $request = $transaction->getRequest(); if ($request->getProtocolVersion() == '1.1' && !$request->hasHeader('Connection')) { $transaction->getRequest()->setHeader('Connection', 'close'); } try { RequestEvents::emitBefore($transaction); if (!$transaction->getResponse()) { $this->createResponse($transaction); RequestEvents::emitComplete($transaction); } return $transaction->getResponse(); } catch (RequestException $e) { if ($e->hasResponse() && $e->getResponse()->getBody()) { $message = trim($e->getResponse()->getBody()->__toString()); if (empty($message)) { $message = $e->getMessage(); } throw new APIException($message, $e->getRequest(), $e->getResponse(), $e); } throw $e; } }
private function createResponseObject(array $headers, TransactionInterface $transaction, $stream) { $parts = explode(' ', array_shift($headers), 3); $options = ['protocol_version' => substr($parts[0], -3)]; if (isset($parts[2])) { $options['reason_phrase'] = $parts[2]; } $response = $this->messageFactory->createResponse($parts[1], $this->headersFromLines($headers), $stream, $options); $transaction->setResponse($response); RequestEvents::emitHeaders($transaction); return $response; }
/** * Sends multiple commands concurrently and returns a hash map of commands * mapped to their corresponding result or exception. * * Note: This method keeps every command and command and result in memory, * and as such is NOT recommended when sending a large number or an * indeterminable number of commands concurrently. Instead, you should use * executeAll() and utilize the event system to work with results. * * @param ServiceClientInterface $client * @param array|\Iterator $commands Commands to send. * @param array $options Passes through the options available * in {@see ServiceClientInterface::createPool()} * * @return BatchResults * @throws \InvalidArgumentException if the event format is incorrect. */ public static function batch(ServiceClientInterface $client, $commands, array $options = []) { $hash = new \SplObjectStorage(); foreach ($commands as $command) { $hash->attach($command); } $client->executeAll($commands, RequestEvents::convertEventArray($options, ['process'], ['priority' => RequestEvents::LATE, 'fn' => function (ProcessEvent $e) use($hash) { if ($e->getException()) { $hash[$e->getCommand()] = $e->getException(); } else { $hash[$e->getCommand()] = $e->getResult(); } }])); return new BatchResults($hash); }
/** * Transfers an HTTP request and populates a response * * @param TransactionInterface $transaction Transaction abject to populate * * @return ResponseInterface */ public function send(TransactionInterface $transaction) { // HTTP/1.1 streams using the PHP stream wrapper require a // Connection: close header. Setting here so that it is added before // emitting the request.before_send event. $request = $transaction->getRequest(); if ($request->getProtocolVersion() == '1.1' && !$request->hasHeader('Connection')) { $transaction->getRequest()->setHeader('Connection', 'close'); } RequestEvents::emitBefore($transaction); if (!$transaction->getResponse()) { $this->createResponse($transaction); RequestEvents::emitComplete($transaction); } return $transaction->getResponse(); }
public function send(TransactionInterface $transaction) { RequestEvents::emitBefore($transaction); if (!$transaction->getResponse()) { $response = is_callable($this->response) ? call_user_func($this->response, $transaction) : $this->response; if (!$response instanceof ResponseInterface) { throw new \RuntimeException('Invalid mocked response'); } // Read the request body if it is present if ($transaction->getRequest()->getBody()) { $transaction->getRequest()->getBody()->__toString(); } $transaction->setResponse($response); RequestEvents::emitHeaders($transaction); RequestEvents::emitComplete($transaction); } return $transaction->getResponse(); }
/** * Sends multiple requests in parallel and returns an array of responses * and exceptions that uses the same ordering as the provided requests. * * IMPORTANT: This method keeps every request and response in memory, and * as such, is NOT recommended when sending a large number or an * indeterminate number of requests concurrently. * * @param ClientInterface $client Client used to send the requests * @param array|\Iterator $requests Requests to send in parallel * @param array $options Passes through the options available in * {@see GuzzleHttp\Pool::__construct} * * @return BatchResults Returns a container for the results. * @throws \InvalidArgumentException if the event format is incorrect. */ public static function batch(ClientInterface $client, $requests, array $options = []) { $hash = new \SplObjectStorage(); foreach ($requests as $request) { $hash->attach($request); } // In addition to the normally run events when requests complete, add // and event to continuously track the results of transfers in the hash. (new self($client, $requests, RequestEvents::convertEventArray($options, ['end'], ['priority' => RequestEvents::LATE, 'fn' => function (EndEvent $e) use($hash) { $hash[$e->getRequest()] = $e->getException() ? $e->getException() : $e->getResponse(); }])))->wait(); return new BatchResults($hash); }
/** * Convenience method for sending multiple requests in parallel and * retrieving a hash map of requests to response objects or * RequestException objects. * * Note: This method keeps every request and response in memory, and as * such is NOT recommended when sending a large number or an indeterminable * number of requests in parallel. * * @param ClientInterface $client Client used to send the requests * @param array|\Iterator $requests Requests to send in parallel * @param array $options Passes through the options available in * {@see GuzzleHttp\ClientInterface::sendAll()} * * @return \SplObjectStorage Requests are the key and each value is a * {@see GuzzleHttp\Message\ResponseInterface} if the request succeeded * or a {@see GuzzleHttp\Exception\RequestException} if it failed. * @throws \InvalidArgumentException if the event format is incorrect. */ function batch(ClientInterface $client, $requests, array $options = array()) { $hash = new \SplObjectStorage(); foreach ($requests as $request) { $hash->attach($request); } // Merge the necessary complete and error events to the event listeners // so that as each request succeeds or fails, it is added to the result // hash. $options = RequestEvents::convertEventArray($options, array('complete', 'error'), array('priority' => RequestEvents::EARLY, 'once' => true, 'fn' => function ($e) use($hash) { /** @noinspection PhpUndefinedMethodInspection */ $hash[$e->getRequest()] = $e; })); // Send the requests in parallel and aggregate the results. $client->sendAll($requests, $options); // Update the received value for any of the intercepted requests. foreach ($hash as $request) { /** @var \GuzzleHttp\Event\ErrorEvent[] $hash */ if ($hash[$request] instanceof CompleteEvent) { $hash[$request] = $hash[$request]->getResponse(); } elseif ($hash[$request] instanceof ErrorEvent) { $hash[$request] = $hash[$request]->getException(); } } return $hash; }
private function preventCommandExceptions(array $options) { // Prevent CommandExceptions from being thrown return RequestEvents::convertEventArray($options, array('error'), array('priority' => RequestEvents::LATE, 'fn' => function (CommandErrorEvent $e) { $e->stopPropagation(); })); }
private function handleError(TransactionInterface $transaction, $info, $handle) { $error = curl_error($handle); $this->releaseEasyHandle($handle); RequestEvents::emitError($transaction, new AdapterException("cURL error {$info['curl_result']}: {$error}"), $info); }
private function createResponseObject(array $headers, TransactionInterface $transaction, $stream) { $parts = explode(' ', array_shift($headers), 3); $options = ['protocol_version' => substr($parts[0], -3)]; if (isset($parts[2])) { $options['reason_phrase'] = $parts[2]; } // Set the size on the stream if it was returned in the response $responseHeaders = []; foreach ($headers as $header) { $headerParts = explode(':', $header, 2); $responseHeaders[$headerParts[0]] = isset($headerParts[1]) ? $headerParts[1] : ''; } $response = $this->messageFactory->createResponse($parts[1], $responseHeaders, $stream, $options); $transaction->setResponse($response); RequestEvents::emitHeaders($transaction); return $response; }
/** * This function ensures that a response was set on a transaction. If one * was not set, then the request is retried if possible. This error * typically means you are sending a payload, curl encountered a * "Connection died, retrying a fresh connect" error, tried to rewind the * stream, and then encountered a "necessary data rewind wasn't possible" * error, causing the request to be sent through curl_multi_info_read() * without an error status. * * @param TransactionInterface $transaction * @param BatchContext $context * * @return bool Returns true if it's OK, and false if it failed. * @throws \GuzzleHttp\Exception\RequestException If it failed and cannot * recover. */ private function validateResponseWasSet(TransactionInterface $transaction, BatchContext $context) { if ($transaction->getResponse()) { return true; } $body = $transaction->getRequest()->getBody(); if (!$body) { // This is weird and should probably never happen. RequestEvents::emitError($transaction, new RequestException('No response was received for a request with no body. This' . ' could mean that you are saturating your network.', $transaction->getRequest())); } elseif (!$body->isSeekable() || !$body->seek(0)) { // Nothing we can do with this. Sorry! RequestEvents::emitError($transaction, new RequestException('The connection was unexpectedly closed. The request would' . ' have been retried, but attempting to rewind the' . ' request body failed. Consider wrapping your request' . ' body in a CachingStream decorator to work around this' . ' issue if necessary.', $transaction->getRequest())); } else { $this->retryFailedConnection($transaction, $context); } return false; }
public function testThrowsUnInterceptedErrors() { $ex = new \Exception('Foo'); $client = new Client(); $request = new Request('GET', '/'); $t = new Transaction($client, $request); $errCalled = 0; $request->getEmitter()->on('before', function (BeforeEvent $e) use($ex) { throw $ex; }); $request->getEmitter()->on('error', function (ErrorEvent $e) use(&$errCalled) { $errCalled++; }); try { RequestEvents::emitBefore($t); $this->fail('Did not throw'); } catch (RequestException $e) { $this->assertEquals(1, $errCalled); } }
/** * @dataProvider prepareEventProvider */ public function testConvertsEventArrays(array $in, array $events, $add, array $out) { $result = RequestEvents::convertEventArray($in, $events, $add); $this->assertEquals($out, $result); }
private function isCurlException(TransactionInterface $transaction, array $curl, BatchContext $context, array $info) { if (CURLM_OK == $curl['result'] || CURLM_CALL_MULTI_PERFORM == $curl['result']) { return false; } $request = $transaction->getRequest(); try { // Send curl stats along if they are available $stats = ['curl_result' => $curl['result']] + $info; RequestEvents::emitError($transaction, new RequestException(sprintf('[curl] (#%s) %s [url] %s', $curl['result'], function_exists('curl_strerror') ? curl_strerror($curl['result']) : self::ERROR_STR, $request->getUrl()), $request), $stats); } catch (RequestException $e) { $this->throwException($e, $context); } return true; }
/** * @expectedException \InvalidArgumentException */ public function testValidatesEventFormat() { RequestEvents::convertEventArray(['foo' => false], ['foo'], []); }