<?php 
namespace Aws; 
 
use Aws\Api\Parser\Exception\ParserException; 
use GuzzleHttp\Promise; 
use Psr\Http\Message\RequestInterface; 
use Psr\Http\Message\ResponseInterface; 
 
/** 
 * Converts an HTTP handler into a Command HTTP handler. 
 * 
 * HTTP handlers have the following signature: 
 *     function(RequestInterface $request, array $options) : PromiseInterface 
 * 
 * The promise returned form an HTTP handler must resolve to a PSR-7 response 
 * object when fulfilled or an error array when rejected. The error array 
 * can contain the following data: 
 * 
 * - exception: (required, Exception) Exception that was encountered. 
 * - response: (ResponseInterface) PSR-7 response that was received (if a 
 *   response) was received. 
 * - connection_error: (bool) True if the error is the result of failing to 
 *   connect. 
 */ 
class WrappedHttpHandler 
{ 
    private $httpHandler; 
    private $parser; 
    private $errorParser; 
    private $exceptionClass; 
 
    /** 
     * @param callable $httpHandler    Function that accepts a request and array 
     *                                 of request options and returns a promise 
     *                                 that fulfills with a response or rejects 
     *                                 with an error array. 
     * @param callable $parser         Function that accepts a response object 
     *                                 and returns an AWS result object. 
     * @param callable $errorParser    Function that parses a response object 
     *                                 into AWS error data. 
     * @param string   $exceptionClass Exception class to throw. 
     */ 
    public function __construct( 
        callable $httpHandler, 
        callable $parser, 
        callable $errorParser, 
        $exceptionClass = 'Aws\Exception\AwsException' 
    ) { 
        $this->httpHandler = $httpHandler; 
        $this->parser = $parser; 
        $this->errorParser = $errorParser; 
        $this->exceptionClass = $exceptionClass; 
    } 
 
    /** 
     * Calls the simpler HTTP specific handler and wraps the returned promise 
     * with AWS specific values (e.g., a result object or AWS exception). 
     * 
     * @param CommandInterface $command Command being executed. 
     * @param RequestInterface $request Request to send. 
     * 
     * @return Promise\PromiseInterface 
     */ 
    public function __invoke( 
        CommandInterface $command, 
        RequestInterface $request 
    ) { 
        $fn = $this->httpHandler; 
 
        return Promise\promise_for($fn($request, $command['@http'] ?: [])) 
            ->then( 
                function (ResponseInterface $res) use ($command, $request) { 
                    return $this->parseResponse($command, $request, $res); 
                }, 
                function ($err) use ($request, $command) { 
                    if (is_array($err)) { 
                        $exception = $this->parseError($err, $request, $command); 
                        return new Promise\RejectedPromise($exception); 
                    } 
                    return new Promise\RejectedPromise($err); 
                } 
            ); 
    } 
 
    /** 
     * @param CommandInterface  $command 
     * @param RequestInterface  $request 
     * @param ResponseInterface $response 
     * 
     * @return ResultInterface 
     */ 
    private function parseResponse( 
        CommandInterface $command, 
        RequestInterface $request, 
        ResponseInterface $response 
    ) { 
        $parser = $this->parser; 
        $status = $response->getStatusCode(); 
        $result = $status < 300 
            ? $parser($command, $response) 
            : new Result(); 
 
        $metadata = [ 
            'statusCode'   => $status, 
            'effectiveUri' => (string) $request->getUri(), 
            'headers'      => [] 
         ]; 
 
        // Bring headers into the metadata array. 
        foreach ($response->getHeaders() as $name => $values) { 
            $metadata['headers'][strtolower($name)] = $values[0]; 
        } 
 
        $result['@metadata'] = $metadata; 
 
        return $result; 
    } 
 
    /** 
     * Parses a rejection into an AWS error. 
     * 
     * @param array            $err     Rejection error array. 
     * @param RequestInterface $request Request that was sent. 
     * @param CommandInterface $command Command being sent. 
     * 
     * @return \Exception 
     */ 
    private function parseError( 
        array $err, 
        RequestInterface $request, 
        CommandInterface $command 
    ) { 
        if (!isset($err['exception'])) { 
            throw new \RuntimeException('The HTTP handler was rejected without an "exception" key value pair.'); 
        } 
 
        $serviceError = "AWS HTTP error: " . $err['exception']->getMessage(); 
 
        if (!isset($err['response'])) { 
            $parts = ['response' => null]; 
        } else { 
            try { 
                $parts = call_user_func($this->errorParser, $err['response']); 
                $serviceError .= " {$parts['code']} ({$parts['type']}): " 
                    . "{$parts['message']} - " . $err['response']->getBody(); 
            } catch (ParserException $e) { 
                $parts = []; 
                $serviceError .= ' Unable to parse error information from ' 
                    . "response - {$e->getMessage()}"; 
            } 
 
            $parts['response'] = $err['response']; 
        } 
 
        $parts['exception'] = $err['exception']; 
        $parts['request'] = $request; 
        $parts['connection_error'] = !empty($err['connection_error']); 
 
        return new $this->exceptionClass( 
            sprintf( 
                'Error executing "%s" on "%s"; %s', 
                $command->getName(), 
                $request->getUri(), 
                $serviceError 
            ), 
            $command, 
            $parts, 
            $err['exception'] 
        ); 
    } 
} 
 
 |