Back | Home
الـ Path الحالي: /home/picotech/domains/instantly.picotech.app/public_html/vendor/voku/.././async-aws/core/src
الملفات الموجودة في هذا الـ Path:
.
..
AbstractApi.php
AwsClientFactory.php
AwsError
Configuration.php
Credentials
EndpointDiscovery
EnvVar.php
Exception
HttpClient
Input.php
Request.php
RequestContext.php
Response.php
Result.php
Signer
Stream
Sts
Test
Waiter.php
مشاهدة ملف: 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>
*/
final class Response
{
/**
* @var ResponseInterface
*/
private $httpResponse;
/**
* @var HttpClientInterface
*/
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(): (HttpException|NetworkException))|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.
*
* @var bool
*/
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, class-string<HttpException>>
*/
private $exceptionMapping;
/**
* @param array<string, class-string<HttpException>> $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;
}
/**
* @return array<string, list<string>>
*
* @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;
}
}
/**
* @return array<string, mixed>
*
* @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;
$this->resolveResult = static function () use ($exceptionClass, $httpResponse, $awsError): HttpException {
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)) {
$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');
}
}