%PDF- %PDF-
Direktori : /home/vacivi36/intranet.vacivitta.com.br/protected/vendor/async-aws/core/src/ |
Current File : /home/vacivi36/intranet.vacivitta.com.br/protected/vendor/async-aws/core/src/Response.php |
<?php declare(strict_types=1); namespace AsyncAws\Core; use AsyncAws\Core\AwsError\AwsErrorFactoryInterface; use AsyncAws\Core\AwsError\ChainAwsErrorFactory; use AsyncAws\Core\EndpointDiscovery\EndpointCache; use AsyncAws\Core\Exception\Exception; use AsyncAws\Core\Exception\Http\ClientException; use AsyncAws\Core\Exception\Http\HttpException; use AsyncAws\Core\Exception\Http\NetworkException; use AsyncAws\Core\Exception\Http\RedirectionException; use AsyncAws\Core\Exception\Http\ServerException; use AsyncAws\Core\Exception\InvalidArgument; use AsyncAws\Core\Exception\LogicException; use AsyncAws\Core\Exception\RuntimeException; use AsyncAws\Core\Exception\UnparsableResponse; use AsyncAws\Core\Stream\ResponseBodyResourceStream; use AsyncAws\Core\Stream\ResponseBodyStream; use AsyncAws\Core\Stream\ResultStream; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; /** * The response provides a facade to manipulate HttpResponses. * * @author Jérémy Derussé <jeremy@derusse.com> * * @internal */ class Response { /** * @var ResponseInterface */ private $httpResponse; private $httpClient; /** * A Result can be resolved many times. This variable contains the last resolve result. * Null means that the result has never been resolved. Array contains material to create an exception. * * @var bool|HttpException|NetworkException|callable|null */ private $resolveResult; /** * A flag that indicated that the body have been downloaded. * * @var bool */ private $bodyDownloaded = false; /** * A flag that indicated that the body started being downloaded. * * @var bool */ private $streamStarted = false; /** * A flag that indicated that an exception has been thrown to the user. */ private $didThrow = false; /** * @var LoggerInterface */ private $logger; /** * @var AwsErrorFactoryInterface */ private $awsErrorFactory; /** * @var ?EndpointCache */ private $endpointCache; /** * @var ?Request */ private $request; /** * @var bool */ private $debug; /** * @var array<string, string> */ private $exceptionMapping; public function __construct(ResponseInterface $response, HttpClientInterface $httpClient, LoggerInterface $logger, AwsErrorFactoryInterface $awsErrorFactory = null, EndpointCache $endpointCache = null, Request $request = null, bool $debug = false, array $exceptionMapping = []) { $this->httpResponse = $response; $this->httpClient = $httpClient; $this->logger = $logger; $this->awsErrorFactory = $awsErrorFactory ?? new ChainAwsErrorFactory(); $this->endpointCache = $endpointCache; $this->request = $request; $this->debug = $debug; $this->exceptionMapping = $exceptionMapping; } public function __destruct() { if (null === $this->resolveResult || !$this->didThrow) { $this->resolve(); } } /** * Make sure the actual request is executed. * * @param float|null $timeout Duration in seconds before aborting. When null wait * until the end of execution. Using 0 means non-blocking * * @return bool whether the request is executed or not * * @throws NetworkException * @throws HttpException */ public function resolve(?float $timeout = null): bool { if (null !== $this->resolveResult) { return $this->getResolveStatus(); } try { if (null === $timeout) { $this->httpResponse->getStatusCode(); } else { foreach ($this->httpClient->stream($this->httpResponse, $timeout) as $chunk) { if ($chunk->isTimeout()) { return false; } if ($chunk->isFirst()) { break; } } } $this->defineResolveStatus(); } catch (TransportExceptionInterface $e) { $this->resolveResult = new NetworkException('Could not contact remote server.', 0, $e); } if (true === $this->debug) { $httpStatusCode = $this->httpResponse->getInfo('http_code'); if (0 === $httpStatusCode) { // Network exception $this->logger->debug('AsyncAws HTTP request could not be sent due network issues'); } else { $this->logger->debug('AsyncAws HTTP response received with status code {status_code}', [ 'status_code' => $httpStatusCode, 'headers' => json_encode($this->httpResponse->getHeaders(false)), 'body' => $this->httpResponse->getContent(false), ]); $this->bodyDownloaded = true; } } return $this->getResolveStatus(); } /** * Make sure all provided requests are executed. * * @param self[] $responses * @param float|null $timeout Duration in seconds before aborting. When null wait * until the end of execution. Using 0 means non-blocking * @param bool $downloadBody Wait until receiving the entire response body or only the first bytes * * @return iterable<self> * * @throws NetworkException * @throws HttpException */ final public static function wait(iterable $responses, float $timeout = null, bool $downloadBody = false): iterable { /** @var self[] $responseMap */ $responseMap = []; $indexMap = []; $httpResponses = []; $httpClient = null; foreach ($responses as $index => $response) { if (null !== $response->resolveResult && (true !== $response->resolveResult || !$downloadBody || $response->bodyDownloaded)) { yield $index => $response; continue; } if (null === $httpClient) { $httpClient = $response->httpClient; } elseif ($httpClient !== $response->httpClient) { throw new LogicException('Unable to wait for the given results, they all have to be created with the same HttpClient'); } $httpResponses[] = $response->httpResponse; $indexMap[$hash = spl_object_id($response->httpResponse)] = $index; $responseMap[$hash] = $response; } // no response provided (or all responses already resolved) if (empty($httpResponses)) { return; } if (null === $httpClient) { throw new InvalidArgument('At least one response should have contain an Http Client'); } foreach ($httpClient->stream($httpResponses, $timeout) as $httpResponse => $chunk) { $hash = spl_object_id($httpResponse); $response = $responseMap[$hash] ?? null; // Check if null, just in case symfony yield an unexpected response. if (null === $response) { continue; } // index could be null if already yield $index = $indexMap[$hash] ?? null; try { if ($chunk->isTimeout()) { // Receiving a timeout mean all responses are inactive. break; } } catch (TransportExceptionInterface $e) { // Exception is stored as an array, because storing an instance of \Exception will create a circular // reference and prevent `__destruct` being called. $response->resolveResult = new NetworkException('Could not contact remote server.', 0, $e); if (null !== $index) { unset($indexMap[$hash]); yield $index => $response; if (empty($indexMap)) { // early exit if all statusCode are known. We don't have to wait for all responses return; } } } if (!$response->streamStarted && '' !== $chunk->getContent()) { $response->streamStarted = true; } if ($chunk->isLast()) { $response->bodyDownloaded = true; if (null !== $index && $downloadBody) { unset($indexMap[$hash]); yield $index => $response; } } if ($chunk->isFirst()) { $response->defineResolveStatus(); if (null !== $index && !$downloadBody) { unset($indexMap[$hash]); yield $index => $response; } } if (empty($indexMap)) { // early exit if all statusCode are known. We don't have to wait for all responses return; } } } /** * Returns info on the current request. * * @return array{ * resolved: bool, * body_downloaded: bool, * response: \Symfony\Contracts\HttpClient\ResponseInterface, * status: int, * } */ public function info(): array { return [ 'resolved' => null !== $this->resolveResult, 'body_downloaded' => $this->bodyDownloaded, 'response' => $this->httpResponse, 'status' => (int) $this->httpResponse->getInfo('http_code'), ]; } public function cancel(): void { $this->httpResponse->cancel(); $this->resolveResult = false; } /** * @throws NetworkException * @throws HttpException */ public function getHeaders(): array { $this->resolve(); return $this->httpResponse->getHeaders(false); } /** * @throws NetworkException * @throws HttpException */ public function getContent(): string { $this->resolve(); try { return $this->httpResponse->getContent(false); } finally { $this->bodyDownloaded = true; } } /** * @throws NetworkException * @throws UnparsableResponse * @throws HttpException */ public function toArray(): array { $this->resolve(); try { return $this->httpResponse->toArray(false); } catch (DecodingExceptionInterface $e) { throw new UnparsableResponse('Could not parse response as array', 0, $e); } finally { $this->bodyDownloaded = true; } } public function getStatusCode(): int { return $this->httpResponse->getStatusCode(); } /** * @throws NetworkException * @throws HttpException */ public function toStream(): ResultStream { $this->resolve(); if (\is_callable([$this->httpResponse, 'toStream'])) { return new ResponseBodyResourceStream($this->httpResponse->toStream()); } if ($this->streamStarted) { throw new RuntimeException('Can not create a ResultStream because the body started being downloaded. The body was started to be downloaded in Response::wait()'); } try { return new ResponseBodyStream($this->httpClient->stream($this->httpResponse)); } finally { $this->bodyDownloaded = true; } } /** * In PHP < 7.4, a reference to the arguments is present in the stackTrace of the exception. * This creates a Circular reference: Response -> resolveResult -> Exception -> stackTrace -> Response. * This mean, that calling `unset($response)` does not call the `__destruct` method and does not throw the * remaining exception present in `resolveResult`. The `__destruct` method will be called once the garbage collector * will detect the loop. * That's why this method does not creates exception here, but creates closure instead that will be resolved right * before throwing the exception. */ private function defineResolveStatus(): void { try { $statusCode = $this->httpResponse->getStatusCode(); } catch (TransportExceptionInterface $e) { $this->resolveResult = static function () use ($e): NetworkException { return new NetworkException('Could not contact remote server.', 0, $e); }; return; } if (300 <= $statusCode) { try { $awsError = $this->awsErrorFactory->createFromResponse($this->httpResponse); if ($this->request && $this->endpointCache && (400 === $statusCode || 'InvalidEndpointException' === $awsError->getCode())) { $this->endpointCache->removeEndpoint($this->request->getEndpoint()); } } catch (UnparsableResponse $e) { $awsError = null; } if ((null !== $awsCode = ($awsError ? $awsError->getCode() : null)) && isset($this->exceptionMapping[$awsCode])) { $exceptionClass = $this->exceptionMapping[$awsCode]; } elseif (500 <= $statusCode) { $exceptionClass = ServerException::class; } elseif (400 <= $statusCode) { $exceptionClass = ClientException::class; } else { $exceptionClass = RedirectionException::class; } $httpResponse = $this->httpResponse; /** @psalm-suppress MoreSpecificReturnType */ $this->resolveResult = static function () use ($exceptionClass, $httpResponse, $awsError): HttpException { /** @psalm-suppress LessSpecificReturnStatement */ return new $exceptionClass($httpResponse, $awsError); }; return; } $this->resolveResult = true; } private function getResolveStatus(): bool { if (\is_bool($this->resolveResult)) { return $this->resolveResult; } if (\is_callable($this->resolveResult)) { /** @psalm-suppress PropertyTypeCoercion */ $this->resolveResult = ($this->resolveResult)(); } $code = null; $message = null; $context = ['exception' => $this->resolveResult]; if ($this->resolveResult instanceof HttpException) { /** @var int $code */ $code = $this->httpResponse->getInfo('http_code'); /** @var string $url */ $url = $this->httpResponse->getInfo('url'); $context['aws_code'] = $this->resolveResult->getAwsCode(); $context['aws_message'] = $this->resolveResult->getAwsMessage(); $context['aws_type'] = $this->resolveResult->getAwsType(); $context['aws_detail'] = $this->resolveResult->getAwsDetail(); $message = sprintf('HTTP %d returned for "%s".', $code, $url); } if ($this->resolveResult instanceof Exception) { $this->logger->log( 404 === $code ? LogLevel::INFO : LogLevel::ERROR, $message ?? $this->resolveResult->getMessage(), $context ); $this->didThrow = true; throw $this->resolveResult; } throw new RuntimeException('Unexpected resolve state'); } }