true, 'expect' => true, 'cert' => true, 'verify' => true, 'timeout' => true, 'debug' => true, 'connect_timeout' => true, 'stream' => true, 'delay' => true, 'sink' => true, ]; /** @var ClientInterface */ private $client; /** * @param ClientInterface $client */ public function __construct(ClientInterface $client = null) { $this->client = $client ?: new Client(); } /** * @param Psr7Request $request * @param array $options * @return Promise\Promise|Promise\PromiseInterface * @throws \GuzzleHttp\Exception\GuzzleException */ public function __invoke(Psr7Request $request, array $options = []) { // Create and send a Guzzle 5 request $guzzlePromise = $this->client->send( $this->createGuzzleRequest($request, $options) ); $promise = new Promise\Promise( function () use ($guzzlePromise) { try { $guzzlePromise->wait(); } catch (\Exception $e) { // The promise is already delivered when the exception is // thrown, so don't rethrow it. } }, [$guzzlePromise, 'cancel'] ); $guzzlePromise->then([$promise, 'resolve'], [$promise, 'reject']); return $promise->then( function (GuzzleResponse $response) { // Adapt the Guzzle 5 Future to a Guzzle 6 ResponsePromise. return $this->createPsr7Response($response); }, function (Exception $exception) use ($options) { // If we got a 'sink' that's a path, set the response body to // the contents of the file. This will build the resulting // exception with more information. if ($exception instanceof RequestException) { if (isset($options['sink'])) { if (!($options['sink'] instanceof Psr7StreamInterface)) { $exception->getResponse()->setBody( Stream::factory( file_get_contents($options['sink']) ) ); } } } // Reject with information about the error. return new Promise\RejectedPromise($this->prepareErrorData($exception)); } ); } private function createGuzzleRequest(Psr7Request $psrRequest, array $options) { $ringConfig = []; $statsCallback = isset($options['http_stats_receiver']) ? $options['http_stats_receiver'] : null; unset($options['http_stats_receiver']); // Remove unsupported options. foreach (array_keys($options) as $key) { if (!isset(self::$validOptions[$key])) { unset($options[$key]); } } // Handle delay option. if (isset($options['delay'])) { $ringConfig['delay'] = $options['delay']; unset($options['delay']); } // Prepare sink option. if (isset($options['sink'])) { $ringConfig['save_to'] = ($options['sink'] instanceof Psr7StreamInterface) ? new GuzzleStream($options['sink']) : $options['sink']; unset($options['sink']); } // Ensure that all requests are async and lazy like Guzzle 6. $options['future'] = 'lazy'; // Create the Guzzle 5 request from the provided PSR7 request. $request = $this->client->createRequest( $psrRequest->getMethod(), $psrRequest->getUri(), $options ); if (is_callable($statsCallback)) { $request->getEmitter()->on( 'end', function (EndEvent $event) use ($statsCallback) { $statsCallback($event->getTransferInfo()); } ); } // For the request body, adapt the PSR stream to a Guzzle stream. $body = $psrRequest->getBody(); if ($body->getSize() === 0) { $request->setBody(null); } else { $request->setBody(new GuzzleStream($body)); } $request->setHeaders($psrRequest->getHeaders()); $request->setHeader( 'User-Agent', $request->getHeader('User-Agent') . ' ' . Client::getDefaultUserAgent() ); // Make sure the delay is configured, if provided. if ($ringConfig) { foreach ($ringConfig as $k => $v) { $request->getConfig()->set($k, $v); } } return $request; } private function createPsr7Response(GuzzleResponse $response) { if ($body = $response->getBody()) { $body = new PsrStream($body); } return new Psr7Response( $response->getStatusCode(), $response->getHeaders(), $body, $response->getReasonPhrase() ); } private function prepareErrorData(Exception $e) { $error = [ 'exception' => $e, 'connection_error' => false, 'response' => null, ]; if ($e instanceof ConnectException) { $error['connection_error'] = true; } if ($e instanceof RequestException && $e->getResponse()) { $error['response'] = $this->createPsr7Response($e->getResponse()); } return $error; } }