<?php 
namespace Aws\Handler\GuzzleV5; 
 
use Aws\Sdk; 
use Exception; 
use GuzzleHttp\Client; 
use GuzzleHttp\ClientInterface; 
use GuzzleHttp\Exception\ConnectException; 
use GuzzleHttp\Exception\RequestException; 
use GuzzleHttp\Message\ResponseInterface as GuzzleResponse; 
use GuzzleHttp\Promise; 
use GuzzleHttp\Psr7\Response as Psr7Response; 
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, 
        '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 
     */ 
    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) { 
                // Reject with information about the error. 
                return new Promise\RejectedPromise($this->prepareErrorData($exception)); 
            } 
        ); 
    } 
 
    private function createGuzzleRequest(Psr7Request $psrRequest, array $options) 
    { 
        $ringConfig = []; 
 
        // 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 
        ); 
 
        // 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; 
    } 
} 
 
 |