123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- <?php
- namespace Aws\Handler\GuzzleV5;
- use Exception;
- use GuzzleHttp\Client;
- use GuzzleHttp\ClientInterface;
- use GuzzleHttp\Event\EndEvent;
- use GuzzleHttp\Exception\ConnectException;
- use GuzzleHttp\Exception\RequestException;
- use GuzzleHttp\Message\ResponseInterface as GuzzleResponse;
- use GuzzleHttp\Promise;
- use GuzzleHttp\Psr7\Response as Psr7Response;
- use GuzzleHttp\Stream\Stream;
- use Psr\Http\Message\RequestInterface as Psr7Request;
- use Psr\Http\Message\StreamInterface as Psr7StreamInterface;
- /**
- * A request handler that sends PSR-7-compatible requests with Guzzle 5.
- *
- * The handler accepts a PSR-7 Request object and an array of transfer options
- * and returns a Guzzle 6 Promise. The promise is either resolved with a
- * PSR-7 Response object or rejected with an array of error data.
- *
- * @codeCoverageIgnore
- */
- class GuzzleHandler
- {
- private static $validOptions = [
- 'proxy' => 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;
- }
- }
|